From 75288dbc521a2515a9c3d011a9dcafe9f0b5f3e2 Mon Sep 17 00:00:00 2001 From: Paul Leroy Date: Wed, 14 Dec 2022 11:37:29 +0100 Subject: [PATCH] ConfusionMatrix --- confusionmatrix.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++ confusionmatrix.h | 29 +++++++++++++ confusionmatrix.ui | 34 ++++++++++++++++ q3DMASCClassifier.cpp | 25 ++++++++---- 4 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 confusionmatrix.cpp create mode 100644 confusionmatrix.h create mode 100644 confusionmatrix.ui diff --git a/confusionmatrix.cpp b/confusionmatrix.cpp new file mode 100644 index 0000000..b2a90e1 --- /dev/null +++ b/confusionmatrix.cpp @@ -0,0 +1,94 @@ +#include "confusionmatrix.h" +#include "ui_confusionmatrix.h" + +#include +#include +#include + +ConfusionMatrix::ConfusionMatrix(QWidget *parent) : + QWidget(parent), + ui(new Ui::ConfusionMatrix) +{ + ui->setupUi(this); +} + +ConfusionMatrix::~ConfusionMatrix() +{ + delete ui; +} + +void ConfusionMatrix::compute_precision_recall_f1_score(cv::Mat& confusion_matrix, cv::Mat& precision_recall_f1_score) +{ + +} + +void ConfusionMatrix::compute(std::vector &reality, std::vector &predicted) +{ + std::set classes(reality.begin(), reality.end()); + int idx_actual; + int idx_predicted; + int nbClasses = classes.size(); + int actual_class; + int predicted_class; + cv::Mat confusion_matrix(nbClasses, nbClasses, CV_32S, cv::Scalar(0)); + cv::Mat precision_recall_f1_score(nbClasses, 3, CV_32F, cv::Scalar(0)); + + for (int i = 0; i < reality.size(); i++) + { + actual_class = reality.at(i); + idx_actual = std::distance(classes.begin(), classes.find(actual_class)); + predicted_class = predicted.at(i); + idx_predicted = std::distance(classes.begin(), classes.find(predicted_class)); + confusion_matrix.at(idx_actual, idx_predicted)++; + } + // update the qTableWidget + this->ui->tableWidget->setColumnCount(nbClasses + 2); + this->ui->tableWidget->setRowCount(nbClasses + 2); + for (uint row = 0; row < nbClasses; row++) + for (uint column = 0; column < nbClasses; column++) + { + QTableWidgetItem *newItem = new QTableWidgetItem(QString::number(confusion_matrix.at(row, column))); + if (row == column) + newItem->setBackground(QColor(37, 190, 147, 1)); // green + else + newItem->setBackground(QColor(255, 129, 129, 1)); + this->ui->tableWidget->setItem(row + 2, column + 2, newItem); + } + + std::set::iterator itB = classes.begin(); + std::set::iterator itE = classes.end(); + std::vector vtr; + vtr.assign(itB, itE); + QTableWidgetItem *newItem = nullptr; + + // set the row andd column names + newItem = new QTableWidgetItem(QString::number(vtr[1])); + this->ui->tableWidget->setItem(3, 1, newItem); + this->ui->tableWidget->setSpan(0, 2, 1, 2); + this->ui->tableWidget->setSpan(2, 0, 2, 1); + newItem = new QTableWidgetItem("Predicted"); + QFont font(newItem->font()); + font.setBold(true); + newItem->setFont(font); + newItem->setBackground(Qt::lightGray); + this->ui->tableWidget->setItem(0, 2, newItem); + newItem = new QTableWidgetItem("True"); + newItem->setFont(font); + newItem->setBackground(Qt::lightGray); + this->ui->tableWidget->setItem(2, 0, newItem); + + // add data to the QTableWidget + for (int idx = 0; idx < vtr.size(); idx++) + { + QString str = QString::number(vtr[idx]); + newItem = new QTableWidgetItem(str); + newItem->setFont(font); + this->ui->tableWidget->setItem(1, 2 + idx, newItem); + newItem = new QTableWidgetItem(str); + newItem->setFont(font); + this->ui->tableWidget->setItem(2 + idx, 1, newItem); + } + + // compute precision recall F1-score + +} diff --git a/confusionmatrix.h b/confusionmatrix.h new file mode 100644 index 0000000..0de7330 --- /dev/null +++ b/confusionmatrix.h @@ -0,0 +1,29 @@ +#ifndef CONFUSIONMATRIX_H +#define CONFUSIONMATRIX_H + +#include + +#include "CCTypes.h" + +#include + +namespace Ui { +class ConfusionMatrix; +} + +class ConfusionMatrix : public QWidget +{ + Q_OBJECT + +public: + explicit ConfusionMatrix(QWidget *parent = nullptr); + ~ConfusionMatrix(); + + void compute_precision_recall_f1_score(cv::Mat& confusion_matrix, cv::Mat &precision_recall_f1_score); + void compute(std::vector& reality, std::vector& predicted); + +private: + Ui::ConfusionMatrix *ui; +}; + +#endif // CONFUSIONMATRIX_H diff --git a/confusionmatrix.ui b/confusionmatrix.ui new file mode 100644 index 0000000..700c9e4 --- /dev/null +++ b/confusionmatrix.ui @@ -0,0 +1,34 @@ + + + ConfusionMatrix + + + + 0 + 0 + 575 + 260 + + + + Form + + + + + + false + + + false + + + false + + + + + + + + diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index e2ec3fe..0ea4a69 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -38,6 +38,8 @@ #include #include +#include "confusionmatrix.h" + using namespace masc; Classifier::Classifier() @@ -123,13 +125,15 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, //look for the classification field CCCoreLib::ScalarField* classificationSF = Tools::GetClassificationSF(cloud); // add a ccConfidence value if needed - int cvConfidenceIdx = cloud->getScalarFieldIndexByName("cvConfidence"); + int cvConfidenceIdx = cloud->getScalarFieldIndexByName("Classification_confidence"); if (cvConfidenceIdx > 0) // if the scalar field exists, delete it cloud->deleteScalarField(cvConfidenceIdx); else - cvConfidenceIdx = cloud->addScalarField("cvConfidence"); + cvConfidenceIdx = cloud->addScalarField("Classification_confidence"); CCCoreLib::ScalarField* cvConfidenceSF = cloud->getScalarField(cvConfidenceIdx); + ccScalarField* classifSFBackup = nullptr; + if (classificationSF) { //save previous classification field (if any) @@ -139,7 +143,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, try { - ccScalarField* classifSFBackup = new ccScalarField(*static_cast(classificationSF)); + classifSFBackup = new ccScalarField(*static_cast(classificationSF)); classifSFBackup->setName("Classification_prev"); cloud->addScalarField(classifSFBackup); } @@ -200,8 +204,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, CCCoreLib::NormalizedProgress nProgress(pDlg.data(), cloud->size()); bool success = true; - cv::TermCriteria termCriteria = m_rtrees->getTermCriteria(); - int numberOfTrees = termCriteria.maxCount; + int numberOfTrees = m_rtrees->getRoots().size(); #ifndef _DEBUG #if defined(_OPENMP) #pragma omp parallel for @@ -235,13 +238,16 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, m_rtrees->getVotes(test_data, result, cv::ml::DTrees::PREDICT_MAX_VOTE); int classIndex = -1; for (int col = 0; col < result.cols; col++) - if (predictedClass == result.at(0, col)) + if (predictedClass == result.at(0, col)) { classIndex = col; break; } if (classIndex != -1) - cvConfidenceSF->setValue(i, static_cast(result.at(1, classIndex) / numberOfTrees)); + { + float nbVotes = result.at(1, classIndex); + cvConfidenceSF->setValue(i, static_cast(nbVotes / numberOfTrees)); + } else cvConfidenceSF->setValue(i, CCCoreLib::NAN_VALUE); @@ -253,6 +259,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, } } classificationSF->computeMinAndMax(); + cvConfidenceSF->computeMinAndMax(); //show the classification field by default { @@ -267,6 +274,10 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, QCoreApplication::processEvents(); } + ConfusionMatrix *confusionMatrix = new ConfusionMatrix(); + confusionMatrix->compute(*classifSFBackup, *classificationSF); + confusionMatrix->show(); + return success; }