This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,218 @@
#include "ovpCAlgorithmConfusionMatrix.h"
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
#ifdef DEBUG
static void dumpMatrix(Kernel::ILogManager& mng, const CMatrix& mat, const CString& desc)
{
mng << Kernel::LogLevel_Info << desc << "\n";
for (size_t i = 0; i < mat.getDimensionSize(0); i++)
{
mng << Kernel::LogLevel_Info << "Row " << i << ": ";
for (size_t j = 0; j < mat.getDimensionSize(1); j++) { mng << mat.getBuffer()[i * mat.getDimensionSize(1) + j] << " "; }
mng << "\n";
}
}
#endif
bool CAlgorithmConfusionMatrix::initialize()
{
ip_targetStimSet.initialize(getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_TargetStimulationSet));
ip_classifierStimSet.initialize(getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassifierStimulationSet));
ip_classesCodes.initialize(getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassCodes));
ip_usePercentages.initialize(getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Percentage));
ip_useSums.initialize(getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Sums));
op_confusionMatrix.initialize(getOutputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputParameterId_ConfusionMatrix));
return true;
}
bool CAlgorithmConfusionMatrix::uninitialize()
{
#ifdef DEBUG
dumpMatrix(this->getLogManager(), m_confusionMatrix, "Confusion matrix");
#endif
ip_targetStimSet.uninitialize();
ip_classifierStimSet.uninitialize();
ip_classesCodes.uninitialize();
ip_usePercentages.uninitialize();
ip_useSums.uninitialize();
op_confusionMatrix.uninitialize();
return true;
}
bool CAlgorithmConfusionMatrix::process()
{
const size_t nClass = size_t(ip_classesCodes->getStimulationCount());
if (this->isInputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetTarget))
{
for (size_t i = 0; i < ip_classesCodes->getStimulationCount(); ++i)
{
this->getLogManager() << Kernel::LogLevel_Trace << "class code " << i << ": " << ip_classesCodes->getStimulationIdentifier(i) << "\n";
}
m_nClassificationAttemptPerClass.clear();
for (size_t i = 0; i < ip_classesCodes->getStimulationCount(); ++i)
{
m_nClassificationAttemptPerClass.insert(std::make_pair(ip_classesCodes->getStimulationIdentifier(i), 0));
}
if (ip_useSums) { op_confusionMatrix->resize(nClass + 1, nClass + 1); }
else { op_confusionMatrix->resize(nClass, nClass); }
for (size_t i = 0; i < nClass; ++i)
{
const char* name = this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation,
ip_classesCodes->getStimulationIdentifier(i)).toASCIIString();
op_confusionMatrix->setDimensionLabel(0, i, (std::string("Target Class\n") + name));
op_confusionMatrix->setDimensionLabel(1, i, (std::string("Result Class\n") + name));
}
if (ip_useSums)
{
op_confusionMatrix->setDimensionLabel(0, nClass, "Sums");
op_confusionMatrix->setDimensionLabel(1, nClass, "Sums");
}
m_confusionMatrix.resize(nClass, nClass);
// initialization
for (size_t i = 0; i < op_confusionMatrix->getDimensionSize(0); ++i)
{
for (size_t j = 0; j < op_confusionMatrix->getDimensionSize(1); ++j)
{
op_confusionMatrix->getBuffer()[i * op_confusionMatrix->getDimensionSize(1) + j] = 0.0;
if (i < m_confusionMatrix.getDimensionSize(0) && j < m_confusionMatrix.getDimensionSize(1))
{
m_confusionMatrix.getBuffer()[i * m_confusionMatrix.getDimensionSize(1) + j] = 0.0;
}
}
}
}
if (this->isInputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetClassifier)) { }
if (this->isInputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedTarget))
{
for (size_t s = 0; s < ip_targetStimSet->getStimulationCount(); ++s)
{
uint64_t id = ip_targetStimSet->getStimulationIdentifier(s);
if (isClass(id))
{
uint64_t date = ip_targetStimSet->getStimulationDate(s);
m_targetsTimeLines.insert(std::pair<uint64_t, uint64_t>(date, id));
getLogManager() << Kernel::LogLevel_Trace << "Current target is " << m_targetsTimeLines.rbegin()->second << "\n";
}
else { getLogManager() << Kernel::LogLevel_Trace << "The target received is not a valid class: " << id << "\n"; }
}
}
if (this->isInputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedClassifier))
{
for (size_t s = 0; s < ip_classifierStimSet->getStimulationCount(); ++s)
{
//We need to locate the stimulation on the timeline
uint64_t id = ip_classifierStimSet->getStimulationIdentifier(s);
if (!isClass(id))//If we don't have
{
getLogManager() << Kernel::LogLevel_Trace << "The result received is not a valid class: " << id << "\n";
continue;
}
uint64_t targeted = 0;
const uint64_t date = ip_classifierStimSet->getStimulationDate(s);
bool found = false;
for (auto it = m_targetsTimeLines.begin(); it != m_targetsTimeLines.end() && !found; ++it)
{
auto nextTarget = it;
++nextTarget;
if ((nextTarget == m_targetsTimeLines.end() || date < nextTarget->first) && date > it->first)
{
targeted = it->second;
found = true;
}
}
if (found)
{
this->getLogManager() << Kernel::LogLevel_Trace << "Result received : " << id << ". Corresponding target : " << targeted << ".\n";
if (!op_confusionMatrix->getBuffer())
{
this->getLogManager() << Kernel::LogLevel_Error << "The confusion matrix buffer has not yet been initialized\n";
return false;
}
// now we found the target, let's update the confusion matrix
// we need to update the whole line vector for the targeted class
const size_t nOldAttempt = m_nClassificationAttemptPerClass[targeted];
m_nClassificationAttemptPerClass[targeted]++; // the confusion matrix can treat this result
size_t i = getClassIndex(targeted);// the good line index
const size_t resultIdx = getClassIndex(id);
for (size_t j = 0; j < nClass; ++j)
{
double newValue = 0.0;
const double oldValue = op_confusionMatrix->getBuffer()[i * op_confusionMatrix->getDimensionSize(0) + j];
if (j == resultIdx)
{
newValue = (oldValue * nOldAttempt + 1) / (m_nClassificationAttemptPerClass[targeted]);
m_confusionMatrix.getBuffer()[i * nClass + j]++;
}
else { newValue = (oldValue * nOldAttempt) / (m_nClassificationAttemptPerClass[targeted]); }
if (ip_usePercentages) { op_confusionMatrix->getBuffer()[i * op_confusionMatrix->getDimensionSize(0) + j] = newValue; }
else // the count value
{
op_confusionMatrix->getBuffer()[i * op_confusionMatrix->getDimensionSize(0) + j] = m_confusionMatrix.getBuffer()[i * nClass + j];
}
}
//we compute the sums if needed
if (ip_useSums)
{
const size_t size = op_confusionMatrix->getDimensionSize(0);
double total = 0.0;
for (i = 0; i < nClass; ++i)
{
double sumRow = 0.0;
double sumCol = 0.0;
for (size_t j = 0; j < nClass; ++j)
{
sumRow += op_confusionMatrix->getBuffer()[i * size + j];
sumCol += op_confusionMatrix->getBuffer()[j * size + i];
}
op_confusionMatrix->getBuffer()[i * size + size - 1] = sumRow;
op_confusionMatrix->getBuffer()[(size - 1) * size + i] = sumCol;
total += sumRow;
}
op_confusionMatrix->getBuffer()[(size - 1) * size + size - 1] =
total; // the lower-right entry, i.e. the last in the buffer
}
}
else { getLogManager() << Kernel::LogLevel_Warning << " No target available.\n"; }
}
this->activateOutputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputTriggerId_ConfusionPerformed, true);
}
return true;
}
bool CAlgorithmConfusionMatrix::isClass(const uint64_t id) const
{
for (size_t i = 0; i < ip_classesCodes->getStimulationCount(); ++i) { if (ip_classesCodes->getStimulationIdentifier(i) == id) { return true; } }
return false;
}
size_t CAlgorithmConfusionMatrix::getClassIndex(const uint64_t id) const
{
for (size_t i = 0; i < ip_classesCodes->getStimulationCount(); ++i) { if (ip_classesCodes->getStimulationIdentifier(i) == id) { return i; } }
return -1;
}
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,96 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <map>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
class CAlgorithmConfusionMatrix final : virtual public Toolkit::TAlgorithm<IAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_ConfusionMatrix)
protected:
Kernel::TParameterHandler<bool> ip_usePercentages;
Kernel::TParameterHandler<bool> ip_useSums;
// input TARGET
Kernel::TParameterHandler<IStimulationSet*> ip_targetStimSet;
// deduced timeline:
std::map<uint64_t, uint64_t> m_targetsTimeLines;
// input CLASSIFIER
Kernel::TParameterHandler<IStimulationSet*> ip_classifierStimSet;
//CONFUSION MATRIX computing
Kernel::TParameterHandler<IStimulationSet*> ip_classesCodes;
Kernel::TParameterHandler<CMatrix*> op_confusionMatrix;
CMatrix m_confusionMatrix; // the values, not percentage
std::map<uint64_t, size_t> m_nClassificationAttemptPerClass;
private:
bool isClass(const uint64_t id) const;
size_t getClassIndex(const uint64_t id) const;
};
class CAlgorithmConfusionMatrixDesc final : virtual public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Confusion Matrix Algorithm"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Make a confusion matrix out of classification results coming from one classifier."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Classification"); }
CString getVersion() const override { return CString("1.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ConfusionMatrix; }
IPluginObject* create() override { return new CAlgorithmConfusionMatrix; }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
prototype.addInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_TargetStimulationSet, "Targets",
Kernel::ParameterType_StimulationSet);
prototype.addInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassifierStimulationSet, "Classification results",
Kernel::ParameterType_StimulationSet);
prototype.addInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassCodes, "Class codes", Kernel::ParameterType_StimulationSet);
prototype.addInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Percentage, "Percentage", Kernel::ParameterType_Boolean);
prototype.addInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Sums, "Sums", Kernel::ParameterType_Boolean);
prototype.addOutputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputParameterId_ConfusionMatrix, "Confusion matrix",
Kernel::ParameterType_Matrix);
prototype.addInputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetTarget, "Reset Target");
prototype.addInputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetClassifier, "Reset Classifier");
prototype.addInputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedTarget, "Feed Target");
prototype.addInputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedClassifier, "Feed Classifier");
prototype.addOutputTrigger(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputTriggerId_ConfusionPerformed, "Confusion computing performed");
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_ConfusionMatrixDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,275 @@
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "ovpCBoxAlgorithmClassifierAccuracyMeasure.h"
#include <sstream>
#include <iomanip>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
static void reset_scores_button_cb(GtkToolButton* /*button*/, gpointer data)
{
for (auto& progress : static_cast<CBoxAlgorithmClassifierAccuracyMeasure*>(data)->m_ProgressBar)
{
progress.score = 0;
progress.nStimulation = 0;
}
}
static void show_percentages_toggle_button_cb(GtkToggleToolButton* button, gpointer data)
{
static_cast<CBoxAlgorithmClassifierAccuracyMeasure*>(data)->m_ShowPercentages = (gtk_toggle_tool_button_get_active(button) ? true : false);
}
static void show_scores_toggle_button_cb(GtkToggleToolButton* button, gpointer data)
{
static_cast<CBoxAlgorithmClassifierAccuracyMeasure*>(data)->m_ShowScores = (gtk_toggle_tool_button_get_active(button) ? true : false);
}
bool CBoxAlgorithmClassifierAccuracyMeasure::initialize()
{
const size_t nInput = this->getStaticBoxContext().getInputCount();
m_ProgressBar.resize(getStaticBoxContext().getInputCount() - 1); //-1 because the first input is the target
//classifier decoders
for (size_t i = 1; i < nInput; ++i)
{
m_classifierStimDecoders.push_back(new Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierAccuracyMeasure>());
m_classifierStimDecoders.back()->initialize(*this, i);
}
m_targetStimDecoder.initialize(*this, 0);
//widgets
m_mainWidgetInterface = gtk_builder_new();
gtk_builder_add_from_file(m_mainWidgetInterface,
Directories::getDataDir() + "/plugins/evaluation/openvibe-simple-visualization-ClassifierAccuracyMeasure.ui", nullptr);
m_toolbarWidgetInterface = gtk_builder_new();
gtk_builder_add_from_file(m_toolbarWidgetInterface,
Directories::getDataDir() + "/plugins/evaluation/openvibe-simple-visualization-ClassifierAccuracyMeasure.ui", nullptr);
gtk_builder_connect_signals(m_mainWidgetInterface, nullptr);
gtk_builder_connect_signals(m_toolbarWidgetInterface, nullptr);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "reset-score-button")), "clicked", G_CALLBACK(reset_scores_button_cb), this);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "show-percentages-toggle-button")), "toggled",
G_CALLBACK(show_percentages_toggle_button_cb), this);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "show-scores-toggle-button")), "toggled",
G_CALLBACK(show_scores_toggle_button_cb), this);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "classifier-accuracy-measure-toolbar")), "delete_event",
G_CALLBACK(gtk_widget_hide), nullptr);
m_mainWidget = GTK_WIDGET(gtk_builder_get_object(m_mainWidgetInterface, "classifier-accuracy-measure-table"));
m_toolbarWidget = GTK_WIDGET(gtk_builder_get_object(m_toolbarWidgetInterface, "classifier-accuracy-measure-toolbar"));
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, m_mainWidget);
m_visualizationCtx->setToolbar(*this, m_toolbarWidget);
m_ShowPercentages = (gtk_toggle_tool_button_get_active(
GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(m_toolbarWidgetInterface, "show-percentages-toggle-button"))) ? true : false);
m_ShowScores = (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(m_toolbarWidgetInterface, "show-scores-toggle-button")))
? true : false);
return true;
}
bool CBoxAlgorithmClassifierAccuracyMeasure::uninitialize()
{
const size_t nInput = this->getStaticBoxContext().getInputCount();
//decoders
for (size_t i = 0; i < nInput - 1; ++i)
{
m_classifierStimDecoders[i]->uninitialize();
delete m_classifierStimDecoders[i];
}
m_classifierStimDecoders.clear();
m_targetStimDecoder.uninitialize();
//widgets
g_object_unref(m_toolbarWidgetInterface);
m_toolbarWidgetInterface = nullptr;
g_object_unref(m_mainWidgetInterface);
m_mainWidgetInterface = nullptr;
if (m_visualizationCtx)
{
this->releasePluginObject(m_visualizationCtx);
m_visualizationCtx = nullptr;
}
return true;
}
bool CBoxAlgorithmClassifierAccuracyMeasure::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmClassifierAccuracyMeasure::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const Kernel::IBox& staticBoxContext = this->getStaticBoxContext();
const size_t nInput = staticBoxContext.getInputCount();
//input chunk 0 = targets
// we iterate over the "target" chunks and update the timeline
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_targetStimDecoder.decode(i);
if (m_targetStimDecoder.isHeaderReceived())
{
//header received
//adding the progress bars to the window
GtkTable* table = GTK_TABLE(gtk_builder_get_object(m_mainWidgetInterface, "classifier-accuracy-measure-table"));
gtk_table_resize(table, 1, guint(nInput - 1));
//@TODO i variable redefine replace alll i in the loop ( it's logical but must be verified
for (guint j = 0; j < nInput - 1; ++j)
{
GtkBuilder* builderBar = gtk_builder_new();
gtk_builder_add_from_file(
builderBar, Directories::getDataDir() + "/plugins/evaluation/openvibe-simple-visualization-ClassifierAccuracyMeasure.ui", nullptr);
GtkBuilder* builderLabel = gtk_builder_new();
gtk_builder_add_from_file(
builderLabel, Directories::getDataDir() + "/plugins/evaluation/openvibe-simple-visualization-ClassifierAccuracyMeasure.ui", nullptr);
GtkWidget* bar = GTK_WIDGET(gtk_builder_get_object(builderBar, "progress-bar-classifier-accuracy"));
GtkWidget* label = GTK_WIDGET(gtk_builder_get_object(builderLabel, "label-classifier-name"));
gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(bar)), bar);
gtk_table_attach(table, bar, j, j + 1, 0, 6, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(label)), label);
gtk_table_attach(table, label, j, j + 1, 6, 7, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
g_object_unref(builderBar);
g_object_unref(builderLabel);
progress_bar_t progressBar;
progressBar.progressBar = GTK_PROGRESS_BAR(bar);
progressBar.score = 0;
progressBar.nStimulation = 0;
progressBar.labelClassifier = GTK_LABEL(label);
gtk_progress_bar_set_fraction(progressBar.progressBar, 0);
CString inputName;
staticBoxContext.getInputName(j + 1, inputName);
gtk_progress_bar_set_text(progressBar.progressBar, inputName.toASCIIString());
gtk_label_set_text(progressBar.labelClassifier, inputName.toASCIIString());
m_ProgressBar[j] = (progressBar);
}
m_currentProcessingTimeLimit = 0;
}
if (m_targetStimDecoder.isBufferReceived())
{
//buffer received
//A new target comes, let's update the timeline with it
const IStimulationSet* dstStimSet = m_targetStimDecoder.getOutputStimulationSet();
for (size_t s = 0; s < dstStimSet->getStimulationCount(); ++s)
{
const uint64_t id = dstStimSet->getStimulationIdentifier(s);
const uint64_t date = dstStimSet->getStimulationDate(s);
m_targetsTimeLines.insert(std::pair<uint64_t, uint64_t>(date, id));
getLogManager() << Kernel::LogLevel_Trace << "New target inserted (" << id << "," << CTime(date) << ")\n";
}
//we updtae the time limit for processing classifier stim
const uint64_t chunkEndTime = boxContext.getInputChunkEndTime(0, i);
m_currentProcessingTimeLimit = MAX(chunkEndTime, m_currentProcessingTimeLimit);
}
if (m_targetStimDecoder.isEndReceived()) { }
boxContext.markInputAsDeprecated(0, i);
}
//input index 1-n = n classifier results
for (size_t ip = 1; ip < staticBoxContext.getInputCount(); ++ip)
{
for (size_t i = 0; i < boxContext.getInputChunkCount(ip); ++i)
{
// lets get the chunck end time
const uint64_t chunkEndTime = boxContext.getInputChunkEndTime(ip, i);
// if the incoming chunk is in the timeline
if (chunkEndTime <= m_currentProcessingTimeLimit)
{
if (!m_targetsTimeLines.empty())
{
// we can process it
m_classifierStimDecoders[ip - 1]->decode(i);
if (m_classifierStimDecoders[ip - 1]->isHeaderReceived()) { } //header received
if (m_classifierStimDecoders[ip - 1]->isBufferReceived())
{
//buffer received
const IStimulationSet* stimSet = m_classifierStimDecoders[ip - 1]->getOutputStimulationSet();
for (size_t s = 0; s < stimSet->getStimulationCount(); ++s)
{
//We need to locate the stimulation on the timeline
uint64_t id = stimSet->getStimulationIdentifier(s);
const uint64_t date = stimSet->getStimulationDate(s);
getLogManager() << Kernel::LogLevel_Trace << "New Classifier state received (" << id << "," << CTime(date) << ") from Classifier "
<< ip << "\n";
auto it = m_targetsTimeLines.begin();
bool cont = true;
while (it != m_targetsTimeLines.end() && cont)
{
auto nextTarget = it;
++nextTarget;
if ((nextTarget == m_targetsTimeLines.end() || date < nextTarget->first)
&& date > it->first)
{
if (id == it->second)
{
//+1 for this classifier !
m_ProgressBar[ip - 1].score++;
}
m_ProgressBar[ip - 1].nStimulation++;
cont = false;
}
++it;
}
//auto it = m_targetsTimeLines.lower_bound(l_stimulationFromClassifierDate);
}
std::stringstream ss;
ss << std::fixed;
ss << std::setprecision(2);
if (m_ShowScores) { ss << "score : " << m_ProgressBar[ip - 1].score << "/" << m_ProgressBar[ip - 1].nStimulation << "\n"; }
double percent = 0.0;
if (m_ProgressBar[ip - 1].nStimulation != 0) { percent = m_ProgressBar[ip - 1].score * 1. / m_ProgressBar[ip - 1].nStimulation; }
if (m_ShowPercentages) { ss << percent * 100 << "%\n"; }
gtk_progress_bar_set_fraction(m_ProgressBar[ip - 1].progressBar, percent);
gtk_progress_bar_set_text(m_ProgressBar[ip - 1].progressBar, ss.str().c_str());
}
if (m_targetStimDecoder.isEndReceived()) { }
}
boxContext.markInputAsDeprecated(ip, i);
}
}
}
return true;
}
#endif
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,129 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <gtk/gtk.h>
#include <map>
#include <vector>
#include <visualization-toolkit/ovviz_all.h>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
class CBoxAlgorithmClassifierAccuracyMeasure final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_ClassifierAccuracyMeasure)
protected:
//codecs
// for the TARGET
Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierAccuracyMeasure> m_targetStimDecoder;
// For the CLASSIFIERS
std::vector<Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierAccuracyMeasure>*> m_classifierStimDecoders;
// deduced timeline:
std::map<uint64_t, uint64_t> m_targetsTimeLines;
uint64_t m_currentProcessingTimeLimit = 0;
// Outputs: visualization in a gtk window
GtkBuilder* m_mainWidgetInterface = nullptr;
GtkBuilder* m_toolbarWidgetInterface = nullptr;
GtkWidget* m_mainWidget = nullptr;
GtkWidget* m_toolbarWidget = nullptr;
public:
typedef struct
{
GtkLabel* labelClassifier;
GtkProgressBar* progressBar;
size_t score;
size_t nStimulation;
} progress_bar_t;
std::vector<progress_bar_t> m_ProgressBar;
bool m_ShowPercentages = false;
bool m_ShowScores = false;
private:
VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr;
};
class CBoxAlgorithmClassifierAccuracyMeasureListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputNameChanged(Kernel::IBox& box, const size_t index) override
{
if (index == 0) { box.setInputName(0, "Targets"); } // forced
return true;
}
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_Stimulations); // all inputs must be stimulations
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmClassifierAccuracyMeasureDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Classifier Accuracy Measure"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Displays real-time classifier accuracies as vertical progress bars"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Evaluation/Classification"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-sort-ascending"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ClassifierAccuracyMeasure; }
IPluginObject* create() override { return new CBoxAlgorithmClassifierAccuracyMeasure; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmClassifierAccuracyMeasureListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Targets", OV_TypeId_Stimulations);
prototype.addInput("Classifier 1", OV_TypeId_Stimulations);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addInputSupport(OV_TypeId_Stimulations);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ClassifierAccuracyMeasureDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGTK
@@ -0,0 +1,160 @@
#include "ovpCBoxAlgorithmConfusionMatrix.h"
#include "../algorithms/ovpCAlgorithmConfusionMatrix.h"
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
bool CBoxAlgorithmConfusionMatrix::initialize()
{
//Initialize input/output
m_targetStimDecoder.initialize(*this, 0);
m_classifierStimDecoder.initialize(*this, 1);
m_encoder.initialize(*this, 0);
//CONFUSION MATRIX ALGORITHM
m_algorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_ConfusionMatrix));
m_algorithm->initialize();
Kernel::TParameterHandler<bool> percentHandler(m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Percentage));
percentHandler = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
Kernel::TParameterHandler<bool> sumsHandler(m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Sums));
if (!bool(percentHandler)) { sumsHandler = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1); }
else
{
this->getLogManager() << Kernel::LogLevel_Debug << "Asking for percentage. The value of the setting \"Sums\" will be ignored.\n";
sumsHandler = false;
}
const size_t nClass = getBoxAlgorithmContext()->getStaticBoxContext()->getSettingCount() - FIRST_CLASS_SETTING_INDEX;
std::vector<size_t> classCodes;
classCodes.resize(nClass);
for (size_t i = 0; i < nClass; ++i)
{
// classes are settings from 2 to n
classCodes[i] = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + FIRST_CLASS_SETTING_INDEX));
}
// verification...
for (size_t i = 0; i < nClass; ++i)
{
for (size_t j = i + 1; j < nClass; ++j)
{
if (classCodes[i] == classCodes[j])
{
const CString classValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + FIRST_CLASS_SETTING_INDEX);
getLogManager() << Kernel::LogLevel_Error << "You must use unique classes to compute a confusion matrix. Class " << i + 1 << " and " << j + 1 <<
" are the same (" << classValue << ").\n";
return false;
}
}
}
Kernel::TParameterHandler<IStimulationSet*> classesCodesHandler(m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassCodes));
for (size_t i = 0; i < classCodes.size(); ++i) { classesCodesHandler->appendStimulation(classCodes[i], 0, 0); }
//Link all input/output
Kernel::TParameterHandler<IStimulationSet*> classifierStimSetHandler(
m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassifierStimulationSet));
classifierStimSetHandler.setReferenceTarget(m_classifierStimDecoder.getOutputStimulationSet());
Kernel::TParameterHandler<IStimulationSet*> targetStimSetHandler(
m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_TargetStimulationSet));
targetStimSetHandler.setReferenceTarget(m_targetStimDecoder.getOutputStimulationSet());
Kernel::TParameterHandler<CMatrix*> matrixHandler(m_algorithm->getOutputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputParameterId_ConfusionMatrix));
m_encoder.getInputMatrix().setReferenceTarget(matrixHandler);
return true;
}
bool CBoxAlgorithmConfusionMatrix::uninitialize()
{
m_algorithm->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_algorithm);
m_encoder.uninitialize();
m_targetStimDecoder.uninitialize();
m_classifierStimDecoder.uninitialize();
return true;
}
bool CBoxAlgorithmConfusionMatrix::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmConfusionMatrix::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
//Input 0: Targets
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_targetStimDecoder.decode(i);
if (m_targetStimDecoder.isHeaderReceived())
{
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetTarget);
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
m_currentProcessingTimeLimit = 0;
}
if (m_targetStimDecoder.isBufferReceived())
{
uint64_t chunkEndTime = boxContext.getInputChunkEndTime(0, i);
m_currentProcessingTimeLimit = (chunkEndTime > m_currentProcessingTimeLimit ? chunkEndTime : m_currentProcessingTimeLimit);
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedTarget);
}
if (m_targetStimDecoder.isEndReceived())
{
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
}
//Input 1: Classifier results
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
const uint64_t tEnd = boxContext.getInputChunkEndTime(1, i);
if (tEnd <= m_currentProcessingTimeLimit)
{
m_classifierStimDecoder.decode(i);
if (m_classifierStimDecoder.isHeaderReceived()) { m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetClassifier); }
if (m_classifierStimDecoder.isBufferReceived())
{
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedClassifier);
if (m_algorithm->isOutputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputTriggerId_ConfusionPerformed))
{
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(1, i), boxContext.getInputChunkEndTime(1, i));
}
}
if (m_classifierStimDecoder.isEndReceived())
{
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(1, i), boxContext.getInputChunkEndTime(1, i));
}
boxContext.markInputAsDeprecated(1, i);
}
}
return true;
}
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,108 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iomanip>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
class CBoxAlgorithmConfusionMatrix final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_ConfusionMatrix)
protected:
Toolkit::TStimulationDecoder<CBoxAlgorithmConfusionMatrix> m_targetStimDecoder;
Toolkit::TStimulationDecoder<CBoxAlgorithmConfusionMatrix> m_classifierStimDecoder;
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmConfusionMatrix> m_encoder;
Kernel::IAlgorithmProxy* m_algorithm = nullptr;
uint64_t m_currentProcessingTimeLimit = 0;
};
class CBoxAlgorithmConfusionMatrixListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onSettingAdded(Kernel::IBox& box, const size_t index) override
{
std::stringstream value;
value << "OVTK_StimulationId_Label_" << std::setfill('0') << std::setw(2) << index - 2;
box.setSettingName(index, ("Class " + std::to_string(index - 1)).c_str());
box.setSettingType(index, OV_TypeId_Stimulation);
box.setSettingValue(index, value.str().c_str());
return true;
}
bool onSettingRemoved(Kernel::IBox& box, const size_t /*index*/) override
{
const size_t nSetting = box.getSettingCount();
const size_t nClass = nSetting - FIRST_CLASS_SETTING_INDEX;
for (size_t i = 0; i < nClass; ++i) { box.setSettingName(FIRST_CLASS_SETTING_INDEX + i, ("Class " + std::to_string(i + 1)).c_str()); }
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmConfusionMatrixDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Confusion Matrix"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Make a confusion matrix out of classification results coming from one classifier."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Evaluation/Classification"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ConfusionMatrix; }
IPluginObject* create() override { return new CBoxAlgorithmConfusionMatrix; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Targets", OV_TypeId_Stimulations);
prototype.addInput("Classification results", OV_TypeId_Stimulations);
prototype.addOutput("Confusion Matrix", OV_TypeId_StreamedMatrix);
prototype.addSetting("Percentages", OV_TypeId_Boolean, "true");
prototype.addSetting("Sums", OV_TypeId_Boolean, "false");
prototype.addSetting("Class 1", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Class 2", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
return true;
}
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmConfusionMatrixListener; }
virtual void releaseBoxListener(IBoxListener* listener) { delete listener; }
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ConfusionMatrixDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,220 @@
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "ovpCBoxAlgorithmKappaCoefficient.h"
#include "../algorithms/ovpCAlgorithmConfusionMatrix.h"
#include <map>
#include <sstream>
#include <vector>
#include <iomanip>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
static const size_t CLASS_LABEL_OFFSET = 1;
bool CBoxAlgorithmKappaCoef::initialize()
{
//Initialize input/output
m_targetStimDecoder.initialize(*this, 0);
m_classifierStimDecoder.initialize(*this, 1);
m_encoder.initialize(*this, 0);
//Confusion matrix algorithm
m_algorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_ConfusionMatrix));
m_algorithm->initialize();
Kernel::TParameterHandler<bool> percentHandler(m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Percentage));
percentHandler = false;
Kernel::TParameterHandler<bool> sumsHandler(m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Sums));
sumsHandler = true;
m_amountClass = getBoxAlgorithmContext()->getStaticBoxContext()->getSettingCount() - CLASS_LABEL_OFFSET;
std::vector<size_t> classCodes;
classCodes.resize(m_amountClass);
for (size_t i = 0; i < m_amountClass; ++i)
{
// classes are settings from 2 to n
classCodes[i] = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + CLASS_LABEL_OFFSET));
}
// Let's check that each identifier is unique
for (size_t i = 0; i < m_amountClass; ++i)
{
for (size_t j = i + 1; j < m_amountClass; ++j)
{
if (classCodes[i] == classCodes[j])
{
const CString value = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + CLASS_LABEL_OFFSET);
getLogManager() << Kernel::LogLevel_Error << "You must use unique classes to compute a Kappa coefficient. Class " << i + 1 << " and " << j + 1
<< " are the same (" << value.toASCIIString() << ").\n";
return false;
}
}
}
Kernel::TParameterHandler<IStimulationSet*> classesCodesHandler(
m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassCodes));
for (size_t i = 0; i < classCodes.size(); ++i) { classesCodesHandler->appendStimulation(classCodes[i], 0, 0); }
//Link all input/output
Kernel::TParameterHandler<IStimulationSet*> classifierStimSetHandler(
m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassifierStimulationSet));
classifierStimSetHandler.setReferenceTarget(m_classifierStimDecoder.getOutputStimulationSet());
Kernel::TParameterHandler<IStimulationSet*> targetStimSetHandler(
m_algorithm->getInputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_TargetStimulationSet));
targetStimSetHandler.setReferenceTarget(m_targetStimDecoder.getOutputStimulationSet());
op_confusionMatrix.initialize(m_algorithm->getOutputParameter(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputParameterId_ConfusionMatrix));
GtkTable* table = GTK_TABLE(gtk_table_new(2, 1, false));
m_kappaLabel = gtk_label_new("x");
gtk_table_attach(table, m_kappaLabel, 0, 1, 0, 5, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, GTK_WIDGET(table));
PangoContext* ctx = gtk_widget_get_pango_context(GTK_WIDGET(m_kappaLabel));
PangoFontDescription* fontDesc = pango_context_get_font_description(ctx);
pango_font_description_set_size(fontDesc, 40 * PANGO_SCALE);
gtk_widget_modify_font(m_kappaLabel, fontDesc);
return true;
}
bool CBoxAlgorithmKappaCoef::uninitialize()
{
//Log for the automatic test
this->getLogManager() << Kernel::LogLevel_Info << "Final value of Kappa " << m_kappaCoef << "\n";
m_algorithm->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_algorithm);
m_encoder.uninitialize();
m_targetStimDecoder.uninitialize();
m_classifierStimDecoder.uninitialize();
if (m_visualizationCtx)
{
this->releasePluginObject(m_visualizationCtx);
m_visualizationCtx = nullptr;
}
return true;
}
bool CBoxAlgorithmKappaCoef::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmKappaCoef::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
//Input 0: Targets
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_targetStimDecoder.decode(i);
if (m_targetStimDecoder.isHeaderReceived())
{
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetTarget);
m_encoder.getInputMatrix()->resize(1);
m_encoder.getInputMatrix()->setDimensionLabel(0, 0, "Kappa");
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
m_currentProcessingTimeLimit = 0;
}
if (m_targetStimDecoder.isBufferReceived())
{
uint64_t end = boxContext.getInputChunkEndTime(0, i);
m_currentProcessingTimeLimit = (end > m_currentProcessingTimeLimit ? end : m_currentProcessingTimeLimit);
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedTarget);
}
if (m_targetStimDecoder.isEndReceived())
{
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
}
//Input 1: Classifier results
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
const uint64_t end = boxContext.getInputChunkEndTime(1, i);
if (end <= m_currentProcessingTimeLimit)
{
m_classifierStimDecoder.decode(i);
if (m_classifierStimDecoder.isHeaderReceived()) { m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetClassifier); }
if (m_classifierStimDecoder.isBufferReceived())
{
m_algorithm->process(OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedClassifier);
if (m_algorithm->isOutputTriggerActive(OVP_Algorithm_ConfusionMatrixAlgorithm_OutputTriggerId_ConfusionPerformed))
{
//The confusion matrix has changed so we need to update the kappa coefficient
double* matrix = op_confusionMatrix->getBuffer();
//First we need the amount of sample that have been classified
const size_t total = size_t(matrix[(m_amountClass + 1) * (m_amountClass + 1) - 1]);
//Now we gonna compute the two sum we need to compute the kappa coefficient
//It's more easy to use a double loop
double observed = 0;
double expected = 0;
for (size_t j = 0; j < m_amountClass; ++j)
{
//We need to take the column sum in account
observed += matrix[j * (m_amountClass + 1) + j];
expected += (matrix[(m_amountClass + 1) * j + m_amountClass] * matrix[(m_amountClass + 1) * m_amountClass + j]);
}
observed /= total;
expected /= (total * total);
m_kappaCoef = (observed - expected) / (1 - expected);
updateKappaValue();
m_encoder.getInputMatrix()->getBuffer()[0] = m_kappaCoef;
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(1, i), boxContext.getInputChunkEndTime(1, i));
}
}
if (m_classifierStimDecoder.isEndReceived())
{
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(1, i), boxContext.getInputChunkEndTime(1, i));
}
boxContext.markInputAsDeprecated(1, i);
}
}
return true;
}
void CBoxAlgorithmKappaCoef::updateKappaValue() const
{
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << m_kappaCoef;
gtk_label_set(GTK_LABEL(m_kappaLabel), ss.str().c_str());
}
#endif
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,165 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGTK)
//You may have to change this path to match your folder organisation
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <sstream>
#include <gtk/gtk.h>
#include <visualization-toolkit/ovviz_all.h>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
/**
* \class CBoxAlgorithmKappaCoef
* \author Serrière Guillaume (Inria)
* \date Tue May 5 12:45:13 2015
* \brief The class CBoxAlgorithmKappaCoef describes the box Kappa coefficient.
*
*/
class CBoxAlgorithmKappaCoef final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_KappaCoef)
protected:
void updateKappaValue() const;
Toolkit::TStimulationDecoder<CBoxAlgorithmKappaCoef> m_targetStimDecoder;
Toolkit::TStimulationDecoder<CBoxAlgorithmKappaCoef> m_classifierStimDecoder;
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmKappaCoef> m_encoder;
Kernel::TParameterHandler<CMatrix*> op_confusionMatrix;
Kernel::IAlgorithmProxy* m_algorithm = nullptr;
size_t m_amountClass = 0;
uint64_t m_currentProcessingTimeLimit = 0;
double m_kappaCoef = 0;
GtkWidget* m_kappaLabel = nullptr;
private:
VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr;
};
// The box listener can be used to call specific callbacks whenever the box structure changes : input added, name changed, etc.
// Please uncomment below the callbacks you want to use.
class CBoxAlgorithmKappaCoefListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
{
if (index == 0)
{
CString nClass;
box.getSettingValue(index, nClass);
if (nClass.length() == 0) { return true; }
size_t nSetting;
std::stringstream ss(nClass.toASCIIString());
ss >> nSetting;
//First of all we prevent for the value to goes under 1.
if (nSetting < 1)
{
box.setSettingValue(index, "1");
nSetting = 1;
}
size_t nCurrent = box.getSettingCount() - 1;
//We have two choice 1/We need to add class, 2/We need to remove some
if (nCurrent < nSetting)
{
while (nCurrent < nSetting)
{
box.addSetting(("Stimulation of class " + std::to_string(nCurrent + 1)).c_str(), OVTK_TypeId_Stimulation, "");
++nCurrent;
}
}
else
{
while (nCurrent > nSetting)
{
box.removeSetting(box.getSettingCount() - 1);
--nCurrent;
}
}
}
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* \class CBoxAlgorithmKappaCoefDesc
* \author Serrière Guillaume (Inria)
* \date Tue May 5 12:45:13 2015
* \brief Descriptor of the box Kappa coefficient.
*
*/
class CBoxAlgorithmKappaCoefDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Kappa coefficient"); }
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Compute the kappa coefficient for the classifier."); }
CString getDetailedDescription() const override { return CString("The box computes kappa coefficient for a classifier."); }
CString getCategory() const override { return CString("Evaluation/Classification"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-yes"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_KappaCoef; }
IPluginObject* create() override { return new CBoxAlgorithmKappaCoef; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmKappaCoefListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Expected stimulations", OV_TypeId_Stimulations);
prototype.addInput("Found stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Confusion Matrix", OV_TypeId_StreamedMatrix);
prototype.addSetting("Number of classes", OV_TypeId_Integer, "2");
prototype.addSetting("Stimulation of class 1", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Stimulation of class 2", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_02");
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_KappaCoefDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGTK
@@ -0,0 +1,200 @@
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "ovpCBoxAlgorithmROCCurve.h"
#include <iostream>
#include <algorithm>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
static bool compareCTimelineStimulationPair(const CTimestampLabelPair& rElt1, const CTimestampLabelPair& rElt2) { return rElt1.first < rElt2.first; }
static bool compareValueAndStimulationTimelinePair(const CTimestampLabelPair& rElt1, const CTimestampValuesPair& rElt2) { return rElt1.first < rElt2.first; }
static bool compareRocValuePair(const CRocPairValue& rElt1, const CRocPairValue& rElt2) { return rElt1.second > rElt2.second; }
static bool isPositive(const CRocPairValue& rElt1) { return rElt1.first; }
bool CBoxAlgorithmROCCurve::initialize()
{
m_expectedDecoder.initialize(*this, 0);
m_classificationDecoder.initialize(*this, 1);
m_computationTrigger = CIdentifier(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
m_widget = GTK_WIDGET(gtk_notebook_new());
for (size_t i = 2; i < this->getStaticBoxContext().getSettingCount(); ++i)
{
CIdentifier classLabel(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i));
CString className = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
m_classStimSet.insert(classLabel);
m_drawerList.push_back(new CROCCurveDraw(GTK_NOTEBOOK(m_widget), i - 1, className));
}
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, m_widget);
return true;
}
bool CBoxAlgorithmROCCurve::uninitialize()
{
m_expectedDecoder.uninitialize();
m_classificationDecoder.uninitialize();
for (size_t i = 0; i < m_drawerList.size(); ++i) { delete m_drawerList[i]; }
//The m_valueTimeline vector contains each dynamically instantiate values that need to be free'd
for (size_t i = 0; i < m_valueTimeline.size(); ++i) { delete m_valueTimeline[i].second; }
if (m_visualizationCtx)
{
this->releasePluginObject(m_visualizationCtx);
m_visualizationCtx = nullptr;
}
return true;
}
bool CBoxAlgorithmROCCurve::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmROCCurve::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
//First let's deal with the expected.
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_expectedDecoder.decode(i);
if (m_expectedDecoder.isHeaderReceived()) { m_stimTimeline.clear(); }
if (m_expectedDecoder.isBufferReceived())
{
IStimulationSet* stimSet = m_expectedDecoder.getOutputStimulationSet();
for (size_t k = 0; k < stimSet->getStimulationCount(); ++k)
{
CIdentifier id = stimSet->getStimulationIdentifier(k);
if (m_classStimSet.find(id) != m_classStimSet.end()) { m_stimTimeline.push_back(CTimestampLabelPair(stimSet->getStimulationDate(k), id.id())); }
//We need to check if we receive the computation trigger
if (id == m_computationTrigger) { computeROCCurves(); }
}
}
}
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
m_classificationDecoder.decode(i);
if (m_classificationDecoder.isHeaderReceived()) { m_valueTimeline.clear(); }
if (m_classificationDecoder.isBufferReceived())
{
CMatrix* matrixValue = m_classificationDecoder.getOutputMatrix();
//The matrix is suppose to have only one dimension
double* arrayValue;
if (matrixValue->getBufferElementCount() == 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Received zero-sized buffer\n";
return false;
}
if (matrixValue->getBufferElementCount() > 1)
{
arrayValue = new double[matrixValue->getBufferElementCount()];
for (size_t k = 0; k < matrixValue->getBufferElementCount(); ++k) { arrayValue[k] = matrixValue->getBuffer()[k]; }
}
else
{
arrayValue = new double[2];
arrayValue[0] = matrixValue->getBuffer()[0];
arrayValue[1] = 1 - matrixValue->getBuffer()[0];
}
uint64_t timestamp = boxContext.getInputChunkEndTime(1, i); //the time in stimulation correspond to the end of the chunck (cf processorbox code)
m_valueTimeline.push_back(CTimestampValuesPair(timestamp, arrayValue));
}
}
return true;
}
bool CBoxAlgorithmROCCurve::computeROCCurves()
{
//Now we assiociate all values to the corresponding label
std::sort(m_stimTimeline.begin(), m_stimTimeline.end(), compareCTimelineStimulationPair);//ensure the timeline is ok
for (auto& val : m_valueTimeline)
{
auto bound = std::lower_bound(m_stimTimeline.begin(), m_stimTimeline.end(), val, compareValueAndStimulationTimelinePair);
if (bound != m_stimTimeline.begin())
{
--bound;
m_labelValueList.push_back(CLabelValuesPair(bound->second, val.second));
}
else
{
//Impossible to find the corresponding stimulation
this->getLogManager() << Kernel::LogLevel_Warning << "A result of classification cannot be connected to a class. The result will be discarded.\n";
}
}
//We cannot use the set because we need the correct order
for (size_t i = 2; i < this->getStaticBoxContext().getSettingCount(); ++i)
{
CIdentifier classLabel(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i));
computeOneROCCurve(classLabel, i - 2);
}
//Now we ask to the current page to draw itself
const gint currPage = gtk_notebook_current_page(GTK_NOTEBOOK(m_widget));
if (currPage < 0)
{
this->getLogManager() << Kernel::LogLevel_Trace <<
"No page is selected. The designer is probably in no visualization mode. Skipping the drawing phase\n";
}
else { m_drawerList[currPage]->forceRedraw(); }
return true;
}
bool CBoxAlgorithmROCCurve::computeOneROCCurve(const CIdentifier& classID, const size_t classIdx)
{
std::vector<CRocPairValue> values;
for (const auto& v : m_labelValueList)
{
CRocPairValue value;
value.first = v.first == classID.id();
value.second = v.second[classIdx];
values.push_back(value);
}
std::sort(values.begin(), values.end(), compareRocValuePair);
size_t nTruePositive = 0;
size_t nFalsePositive = 0;
const size_t nPositive = std::count_if(values.begin(), values.end(), isPositive);
const size_t nNegative = values.size() - nPositive;
std::vector<CCoordinate>& coordinateVector = m_drawerList[classIdx]->getCoordinateVector();
for (const auto& value : values)
{
value.first ? ++nTruePositive : ++nFalsePositive;
coordinateVector.push_back(CCoordinate(double(nFalsePositive) / nNegative, double(nTruePositive) / nPositive));
}
m_drawerList[classIdx]->generateCurve();
return true;
}
#endif
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,181 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "ovpCROCCurveDraw.h"
#include <gtk/gtk.h>
#include <set>
#include <map>
#include <iostream>
#include <sstream>
#include <visualization-toolkit/ovviz_all.h>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
typedef std::pair<uint64_t, uint64_t> CTimestampLabelPair;
typedef std::pair<uint64_t, double*> CTimestampValuesPair;
typedef std::pair<uint64_t, double*> CLabelValuesPair;
typedef std::pair<bool, double> CRocPairValue;
/**
* \class CBoxAlgorithmROCCurve
* \author Serrière Guillaume (Inria)
* \date Thu May 28 11:49:24 2015
* \brief The class CBoxAlgorithmROCCurve describes the box ROC curve.
* The roc curve is a graphical plot that represents the performance of a classifier. This curve is created by plotting the true positive
* rate against the false positive rate at various threshold settings.
*
*/
class CBoxAlgorithmROCCurve final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_ROCCurve)
private:
bool computeROCCurves();
bool computeOneROCCurve(const CIdentifier& classID, const size_t classIdx);
// Input decoder:
Toolkit::TStimulationDecoder<CBoxAlgorithmROCCurve> m_expectedDecoder;
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmROCCurve> m_classificationDecoder;
std::set<CIdentifier> m_classStimSet;
CIdentifier m_computationTrigger = CIdentifier::undefined();
std::vector<CTimestampLabelPair> m_stimTimeline;
std::vector<CTimestampValuesPair> m_valueTimeline;
std::vector<CLabelValuesPair> m_labelValueList;
//Display section
GtkWidget* m_widget = nullptr;
std::vector<CROCCurveDraw*> m_drawerList;
VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr;
};
// The box listener can be used to call specific callbacks whenever the box structure changes : input added, name changed, etc.
// Please uncomment below the callbacks you want to use.
class CBoxAlgorithmROCCurveListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
{
if (index == 1)
{
CString nClass;
box.getSettingValue(index, nClass);
//Could happen if we rewritte a number
if (nClass.length() == 0) { return true; }
size_t nSetting;
std::stringstream ss(nClass.toASCIIString());
ss >> nSetting;
//First of all we prevent for the value to goes under 1.
if (nSetting < 1)
{
box.setSettingValue(index, "1");
nSetting = 1;
}
size_t nCurrent = box.getSettingCount() - 2;
//We have two choice 1/We need to add class, 2/We need to remove some
if (nCurrent < nSetting)
{
while (nCurrent < nSetting)
{
box.addSetting(("Class " + std::to_string(nCurrent + 1) + " identifier").c_str(), OVTK_TypeId_Stimulation, "");
++nCurrent;
}
}
else
{
while (nCurrent > nSetting)
{
box.removeSetting(box.getSettingCount() - 1);
--nCurrent;
}
}
}
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* \class CBoxAlgorithmROCCurveDesc
* \author Serrière Guillaume (Inria)
* \date Thu May 28 11:49:24 2015
* \brief Descriptor of the box ROC curve.
*
*/
class CBoxAlgorithmROCCurveDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("ROC curve"); }
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Compute the ROC curve for each class."); }
CString getDetailedDescription() const override { return CString("The box computes the ROC curve for each class."); }
CString getCategory() const override { return CString("Evaluation/Classification"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-yes"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ROCCurve; }
IPluginObject* create() override { return new CBoxAlgorithmROCCurve; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmROCCurveListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Expected labels", OV_TypeId_Stimulations);
prototype.addInput("Probability values", OV_TypeId_StreamedMatrix);
prototype.addSetting("Computation trigger", OV_TypeId_Stimulation, "OVTK_StimulationId_ExperimentStop");
prototype.addSetting("Number of classes", OV_TypeId_Integer, "2");
prototype.addSetting("Class 1 identifier", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Class 2 identifier", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_02");
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ROCCurveDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGTK
@@ -0,0 +1,179 @@
#include "ovpCBoxAlgorithmStatisticGenerator.h"
#include <sstream>
#include <xml/IXMLHandler.h>
#include <xml/IXMLNode.h>
#include <limits>
#include <iomanip>
namespace {
const char* const STATISTIC_ROOT_NODE_NAME = "Statistic";
const char* const STIMULATION_LIST_NODE_NAME = "Stimulations-list";
const char* const STIMULATION_NODE_NAME = "Stimulation";
const char* const IDENTIFIER_CODE_NODE_NAME = "Identifier";
const char* const IDENTIFIER_LABEL_NODE_NAME = "Label";
const char* const AMOUNT_NODE_NAME = "Count";
const char* const CHANNEL_LIST_NODE_NAME = "Channel-list";
const char* const CHANNEL_NODE_NAME = "Channel";
const char* const CHANNEL_LABEL_NODE_NAME = "Name";
const char* const CHANNEL_MIN_NODE_NAME = "Minimum";
const char* const CHANNEL_MAX_NODE_NAME = "Maximum";
const char* const CHANNEL_MEAN_NODE_NAME = "Mean";
} // namespace
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
bool CBoxAlgorithmStatisticGenerator::initialize()
{
m_signalDecoder.initialize(*this, 0);
m_stimDecoder.initialize(*this, 1);
m_stimulations.clear();
m_hasBeenStreamed = false;
m_filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
if (m_filename == CString(""))
{
this->getLogManager() << Kernel::LogLevel_Error << "The filename is empty\n";
return false;
}
return true;
}
bool CBoxAlgorithmStatisticGenerator::uninitialize()
{
bool res = true;
m_signalDecoder.uninitialize();
m_stimDecoder.uninitialize();
if (m_hasBeenStreamed)
{
XML::IXMLNode* rootNode = XML::createNode(STATISTIC_ROOT_NODE_NAME);
XML::IXMLNode* stimNode = XML::createNode(STIMULATION_LIST_NODE_NAME);
for (const auto& s : m_stimulations)
{
XML::IXMLNode* node = XML::createNode(STIMULATION_NODE_NAME);
XML::IXMLNode* idNode = XML::createNode(IDENTIFIER_CODE_NODE_NAME);
XML::IXMLNode* labelNode = XML::createNode(IDENTIFIER_LABEL_NODE_NAME);
XML::IXMLNode* amountNode = XML::createNode(AMOUNT_NODE_NAME);
CIdentifier id = s.first;
std::stringstream ss;
ss << std::fixed << std::setprecision(10) << m_stimulations[id];
idNode->setPCData(id.str().c_str());
labelNode->setPCData(this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id.id()).toASCIIString());
amountNode->setPCData(ss.str().c_str());
node->addChild(idNode);
node->addChild(labelNode);
node->addChild(amountNode);
stimNode->addChild(node);
}
rootNode->addChild(stimNode);
XML::IXMLNode* channelsNode = XML::createNode(CHANNEL_LIST_NODE_NAME);
for (size_t i = 0; i < m_signalInfos.size(); ++i)
{
signal_info_t& signalInfo = m_signalInfos[i];
XML::IXMLNode* node = XML::createNode(CHANNEL_NODE_NAME);
XML::IXMLNode* nodeName = XML::createNode(CHANNEL_LABEL_NODE_NAME);
nodeName->setPCData(signalInfo.name.toASCIIString());
node->addChild(nodeName);
node->addChild(getDoubleNode(CHANNEL_MAX_NODE_NAME, signalInfo.max));
node->addChild(getDoubleNode(CHANNEL_MIN_NODE_NAME, signalInfo.min));
node->addChild(getDoubleNode(CHANNEL_MEAN_NODE_NAME, signalInfo.sum / signalInfo.nSample));
channelsNode->addChild(node);
}
rootNode->addChild(channelsNode);
XML::IXMLHandler* handler = XML::createXMLHandler();
if (!handler->writeXMLInFile(*rootNode, m_filename.toASCIIString())) { res = false; }
handler->release();
rootNode->release();
}
return res;
}
bool CBoxAlgorithmStatisticGenerator::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmStatisticGenerator::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_signalDecoder.decode(i);
if (m_signalDecoder.isHeaderReceived())
{
const size_t mountChannel = m_signalDecoder.getOutputMatrix()->getDimensionSize(0);
m_hasBeenStreamed = true;
for (size_t j = 0; j < mountChannel; ++j)
{
signal_info_t info = {
m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, j), std::numeric_limits<double>::max(), -std::numeric_limits<double>::max(), 0, 0
};
m_signalInfos.push_back(info);
}
}
if (m_signalDecoder.isBufferReceived())
{
const size_t nSample = m_signalDecoder.getOutputMatrix()->getDimensionSize(1);
double* buffer = m_signalDecoder.getOutputMatrix()->getBuffer();
for (size_t j = 0; j < m_signalInfos.size(); ++j)
{
signal_info_t& info = m_signalInfos[j];
for (size_t k = 0; k < nSample; ++k)
{
const double sample = buffer[j * nSample + k];
info.sum += sample;
if (sample < info.min) { info.min = sample; }
if (sample > info.max) { info.max = sample; }
}
info.nSample += nSample;
}
}
}
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
m_stimDecoder.decode(i);
if (m_stimDecoder.isHeaderReceived()) { m_hasBeenStreamed = true; }
if (m_stimDecoder.isBufferReceived())
{
IStimulationSet& stimSet = *(m_stimDecoder.getOutputStimulationSet());
for (size_t j = 0; j < stimSet.getStimulationCount(); ++j) { m_stimulations[stimSet.getStimulationIdentifier(j)]++; }
}
}
return true;
}
XML::IXMLNode* CBoxAlgorithmStatisticGenerator::getDoubleNode(const char* const nodeName, const double value)
{
XML::IXMLNode* tmp = XML::createNode(nodeName);
std::stringstream ss;
ss << std::fixed << std::setprecision(10) << value;
tmp->setPCData(ss.str().c_str());
return tmp;
}
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,102 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <map>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
typedef struct
{
CString name;
double min;
double max;
double sum;
size_t nSample;
} signal_info_t;
/**
* \class CBoxAlgorithmStatisticGenerator
* \author Serrière Guillaume (Inria)
* \date Thu Apr 30 15:24:39 2015
* \brief The class CBoxAlgorithmStatisticGenerator describes the box Statistic generator.
*
*/
class CBoxAlgorithmStatisticGenerator final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_StatisticGenerator)
private:
static XML::IXMLNode* getDoubleNode(const char* nodeName, double value);
// Input decoder:
Toolkit::TSignalDecoder<CBoxAlgorithmStatisticGenerator> m_signalDecoder;
Toolkit::TStimulationDecoder<CBoxAlgorithmStatisticGenerator> m_stimDecoder;
CString m_filename;
std::map<CIdentifier, size_t> m_stimulations;
std::vector<signal_info_t> m_signalInfos;
bool m_hasBeenStreamed = false;
};
/**
* \class CBoxAlgorithmStatisticGeneratorDesc
* \author Serrière Guillaume (Inria)
* \date Thu Apr 30 15:24:39 2015
* \brief Descriptor of the box Statistic generator.
*
*/
class CBoxAlgorithmStatisticGeneratorDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("General statistics generator"); }
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Generate statistics on signal."); }
CString getDetailedDescription() const override { return CString("Generate some general purpose statistics on signal and store them in a file."); }
CString getCategory() const override { return CString("Evaluation"); }
CString getVersion() const override { return CString("0.1"); }
CString getStockItemName() const override { return CString("gtk-yes"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_StatisticGenerator; }
IPluginObject* create() override { return new CBoxAlgorithmStatisticGenerator; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Signal",OV_TypeId_Signal);
prototype.addInput("Stimulations",OV_TypeId_Stimulations);
prototype.addSetting("Filename for saving",OV_TypeId_Filename, "${Path_UserData}/statistics-dump.xml");
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_StatisticGeneratorDesc)
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,203 @@
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "ovpCROCCurveDraw.h"
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
static void size_allocate_cb(GtkWidget* /*widget*/, GdkRectangle* rectangle, gpointer data) { static_cast<CROCCurveDraw*>(data)->resizeEvent(rectangle); }
static void area_expose_cb(GtkWidget* /*widget*/, GdkEventExpose* /*event*/, gpointer data) { static_cast<CROCCurveDraw*>(data)->exposeEnvent(); }
CROCCurveDraw::CROCCurveDraw(GtkNotebook* notebook, const size_t classIndex, CString& className)
{
m_margin = 50;
m_classIdx = classIndex;
m_hasBeenInit = false;
m_hasBeenExposed = false;
m_drawableArea = gtk_drawing_area_new();
gtk_widget_set_size_request(m_drawableArea, 700, 600);
g_signal_connect(G_OBJECT(m_drawableArea), "expose_event", G_CALLBACK(area_expose_cb), this);
g_signal_connect(G_OBJECT(m_drawableArea), "size-allocate", G_CALLBACK(size_allocate_cb), this);
GtkWidget* label = gtk_label_new(className.toASCIIString());
gtk_notebook_append_page(notebook, m_drawableArea, label);
//get left ruler widget's font description
PangoContext* ctx = gtk_widget_get_pango_context(m_drawableArea);
PangoFontDescription* fontDesc = pango_context_get_font_description(ctx);
//adapt the allocated height per label to the font's height (plus 4 pixel to add some spacing)
if (pango_font_description_get_size_is_absolute(fontDesc)) { m_pixelsPerLeftRulerLabel = pango_font_description_get_size(fontDesc) + 4; }
else { m_pixelsPerLeftRulerLabel = pango_font_description_get_size(fontDesc) / PANGO_SCALE + 4; }
}
void CROCCurveDraw::generateCurve()
{
GtkAllocation allocation;
gtk_widget_get_allocation(m_drawableArea, &allocation);
const size_t width = allocation.width - 2 * m_margin;
const size_t height = allocation.height - 2 * m_margin;
m_pointList.clear();
for (size_t i = 0; i < m_coordinateList.size(); ++i)
{
GdkPoint point;
point.x = gint(m_coordinateList[i].first * width + m_margin);
point.y = gint((allocation.height - m_margin) - m_coordinateList[i].second * height);
m_pointList.push_back(point);
}
m_hasBeenInit = true;
}
void CROCCurveDraw::exposeEnvent()
{
m_hasBeenExposed = true;
redraw();
}
void CROCCurveDraw::resizeEvent(GdkRectangle* /*rectangle*/)
{
GtkAllocation alloc;
gtk_widget_get_allocation(m_drawableArea, &alloc);
if (!m_hasBeenInit) { return; }
generateCurve();
redraw();
}
void CROCCurveDraw::redraw()
{
if (!m_hasBeenInit || !m_hasBeenExposed) { return; }
GtkAllocation allocation;
gtk_widget_get_allocation(m_drawableArea, &allocation);
gdk_draw_rectangle(m_drawableArea->window, GTK_WIDGET(m_drawableArea)->style->white_gc, TRUE, 0, 0, allocation.width, allocation.height);
GdkColor lineColor = { 0, 35000, 35000, 35000 };
GdkGC* gc = gdk_gc_new((m_drawableArea)->window);
gdk_gc_set_rgb_fg_color(gc, &lineColor);
//Left ruler
gdk_draw_line((m_drawableArea)->window, gc, gint(m_margin), gint(m_margin), gint(m_margin), gint(allocation.height - m_margin));
drawLeftMark(m_margin, m_margin, "1");
drawLeftMark(m_margin, allocation.height / 2, "0.5");
drawLeftMark(m_margin, allocation.height - m_margin, "0");
//*** Black magic section to rotate the text of the left ruler. The solution comes from the internet (gtk doc), it works so
// don't touch it unless you are sure of what you are doing
PangoContext* context = gtk_widget_get_pango_context(m_drawableArea);
GdkScreen* screen = gdk_drawable_get_screen(m_drawableArea->window);
PangoRenderer* renderer = gdk_pango_renderer_get_default(screen);
gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), m_drawableArea->window);
GdkGC* rotationGc = gdk_gc_new(m_drawableArea->window);
gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), rotationGc);
int width, height;
PangoMatrix matrix = PANGO_MATRIX_INIT;
pango_matrix_translate(&matrix, 0, double(allocation.height + 100) / 2);
PangoLayout* layout = pango_layout_new(context);
pango_layout_set_text(layout, "True Positive Rate", -1);
PangoFontDescription* desc = pango_context_get_font_description(context);
pango_layout_set_font_description(layout, desc);
GdkColor color = { 0, 0, 0, 0 };
gdk_pango_renderer_set_override_color(GDK_PANGO_RENDERER(renderer), PANGO_RENDER_PART_FOREGROUND, &color);
pango_matrix_rotate(&matrix, 90);
pango_context_set_matrix(context, &matrix);
pango_layout_context_changed(layout);
pango_layout_get_size(layout, &width, &height);
pango_renderer_draw_layout(renderer, layout, 15, (allocation.height + height) / 2);
gdk_pango_renderer_set_override_color(GDK_PANGO_RENDERER(renderer), PANGO_RENDER_PART_FOREGROUND, nullptr);
gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), nullptr);
gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), nullptr);
pango_matrix_rotate(&matrix, -90);
pango_context_set_matrix(context, &matrix);
pango_layout_context_changed(layout);
g_object_unref(layout);
g_object_unref(context);
g_object_unref(rotationGc);
//** End of black magic section
//Bottom ruler
gdk_draw_line((m_drawableArea)->window, gc, gint(m_margin), gint(allocation.height - m_margin), gint(allocation.width - m_margin),
gint(allocation.height - m_margin));
drawBottomMark(m_margin, allocation.height - m_margin, "0");
drawBottomMark(allocation.width / 2, allocation.height - m_margin, "0.5");
drawBottomMark(allocation.width - m_margin, allocation.height - m_margin, "1");
int textW;
int textH;
PangoLayout* text = gtk_widget_create_pango_layout(m_drawableArea, "False positive rate");
pango_layout_set_justify(text, PANGO_ALIGN_CENTER);
pango_layout_get_pixel_size(text, &textW, &textH);
gdk_draw_layout(m_drawableArea->window, GTK_WIDGET(m_drawableArea)->style->black_gc, allocation.width / 2 - textW / 2, allocation.height - 15, text);
g_object_unref(text);
if (m_pointList.empty())
{
gdk_draw_lines((m_drawableArea)->window, GTK_WIDGET(m_drawableArea)->style->black_gc, &(m_pointList[0]), gint(m_pointList.size()));
}
gdk_gc_set_line_attributes(gc, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_BEVEL);
gdk_draw_line((m_drawableArea)->window, gc, gint(m_margin), gint(allocation.height - m_margin), gint(allocation.width - m_margin), gint(m_margin));
g_object_unref(gc);
}
void CROCCurveDraw::drawLeftMark(const size_t w, const size_t h, const char* label) const
{
gint textW;
gint textH;
PangoLayout* text = gtk_widget_create_pango_layout(m_drawableArea, label);
pango_layout_set_width(text, 28);
pango_layout_set_justify(text, PANGO_ALIGN_LEFT);
pango_layout_get_pixel_size(text, &textW, &textH);
gdk_draw_layout(m_drawableArea->window, GTK_WIDGET(m_drawableArea)->style->black_gc, gint(w - 20) - (textW / 2), gint(h) - (textH / 2), text);
GdkColor lineColor = { 0, 35000, 35000, 35000 };
GdkGC* gc = gdk_gc_new((m_drawableArea)->window);
gdk_gc_set_rgb_fg_color(gc, &lineColor);
gdk_draw_line(m_drawableArea->window, gc, w - 5, h, w, h);
g_object_unref(gc);
}
void CROCCurveDraw::drawBottomMark(const size_t w, const size_t h, const char* label) const
{
int textW;
int textH;
PangoLayout* text = gtk_widget_create_pango_layout(m_drawableArea, label);
pango_layout_set_width(text, 28);
pango_layout_set_justify(text, PANGO_ALIGN_LEFT);
pango_layout_get_pixel_size(text, &textW, &textH);
gdk_draw_layout(m_drawableArea->window, GTK_WIDGET(m_drawableArea)->style->black_gc, gint(w) - (textW / 2), gint(h + 14), text);
GdkColor lineColor = { 0, 35000, 35000, 35000 };
GdkGC* gc = gdk_gc_new((m_drawableArea)->window);
gdk_gc_set_rgb_fg_color(gc, &lineColor);
gdk_draw_line(m_drawableArea->window, gc, gint(w), gint(h + 5), gint(w), gint(h));
g_object_unref(gc);
}
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGTK
@@ -0,0 +1,56 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGTK)
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <gtk/gtk.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
typedef std::pair<double, double> CCoordinate;
//The aim of the class is to handle the graphical part of a RocCurve
class CROCCurveDraw final
{
public:
CROCCurveDraw(GtkNotebook* notebook, size_t classIndex, CString& className);
~CROCCurveDraw() { }
std::vector<CCoordinate>& getCoordinateVector() { return m_coordinateList; }
void generateCurve();
//Callbak functions, should not be called
void resizeEvent(GdkRectangle* rectangle);
void exposeEnvent();
//This function is called when the cruve should be redraw for an external reason
void forceRedraw() { redraw(); }
private:
size_t m_margin = 0;
size_t m_classIdx = 0;
std::vector<GdkPoint> m_pointList;
std::vector<CCoordinate> m_coordinateList;
size_t m_pixelsPerLeftRulerLabel = 0;
GtkWidget* m_drawableArea = nullptr;
bool m_hasBeenInit = false;
//For a mytical reason, gtk says that the DrawableArea is not a DrawableArea unless it's been exposed at least once...
// So we need to if the DrawableArea as been exposed
bool m_hasBeenExposed = false;
void redraw();
void drawLeftMark(size_t w, size_t h, const char* label) const;
void drawBottomMark(size_t w, size_t h, const char* label) const;
};
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGTK
@@ -0,0 +1,40 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_ClassifierAccuracyMeasure OpenViBE::CIdentifier(0x48395CE7, 0x17D62550)
#define OVP_ClassId_BoxAlgorithm_ClassifierAccuracyMeasureDesc OpenViBE::CIdentifier(0x067F38CC, 0x084A6ED3)
#define OVP_ClassId_Algorithm_ConfusionMatrix OpenViBE::CIdentifier(0x699F416B, 0x3BAE4324)
#define OVP_ClassId_Algorithm_ConfusionMatrixDesc OpenViBE::CIdentifier(0x4CDD225D, 0x6C9A59DB)
#define OVP_ClassId_BoxAlgorithm_ConfusionMatrix OpenViBE::CIdentifier(0x1AB625DA, 0x3B2502CE)
#define OVP_ClassId_BoxAlgorithm_ConfusionMatrixDesc OpenViBE::CIdentifier(0x52237A64, 0x63555613)
#define OVP_ClassId_BoxAlgorithm_KappaCoef OpenViBE::CIdentifier(0x160D8F1B, 0xD864C5BB)
#define OVP_ClassId_BoxAlgorithm_KappaCoefDesc OpenViBE::CIdentifier(0xD8BA2199, 0xD252BECB)
#define OVP_ClassId_BoxAlgorithm_ROCCurve OpenViBE::CIdentifier(0x06FE5B1B, 0xDE066FEC)
#define OVP_ClassId_BoxAlgorithm_ROCCurveDesc OpenViBE::CIdentifier(0xCB5DFCEA, 0xAF41EAB2)
#define OVP_ClassId_BoxAlgorithm_StatisticGenerator OpenViBE::CIdentifier(0x83EDA40B, 0x425FBFFE)
#define OVP_ClassId_BoxAlgorithm_StatisticGeneratorDesc OpenViBE::CIdentifier(0x35A0CB63, 0x78882C28)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Sums OpenViBE::CIdentifier(0x75502E8E, 0x05D838EE)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_Percentage OpenViBE::CIdentifier(0x7E504E8E, 0x058858EE)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_TargetStimulationSet OpenViBE::CIdentifier(0x7E504E8F, 0x058858EF)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassifierStimulationSet OpenViBE::CIdentifier(0x45220B61, 0x13FD7491)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputParameterId_ClassCodes OpenViBE::CIdentifier(0x67780C91, 0x2A556C51)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_OutputParameterId_ConfusionMatrix OpenViBE::CIdentifier(0x67780C91, 0x2A556C51)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetTarget OpenViBE::CIdentifier(0x4D390BDA, 0x6A180667)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_ResetClassifier OpenViBE::CIdentifier(0x3C132C38, 0x557D2503)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedTarget OpenViBE::CIdentifier(0x6B1E76B3, 0x06741B21)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_InputTriggerId_FeedClassifier OpenViBE::CIdentifier(0x3EFC64B8, 0x5ACC3125)
#define OVP_Algorithm_ConfusionMatrixAlgorithm_OutputTriggerId_ConfusionPerformed OpenViBE::CIdentifier(0x790C2277, 0x3D041A63)
#define FIRST_CLASS_SETTING_INDEX 2
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
@@ -0,0 +1,35 @@
#include "ovp_defines.h"
#include "box-algorithms/ovpCBoxAlgorithmStatisticGenerator.h"
#include "box-algorithms/ovpCBoxAlgorithmKappaCoefficient.h"
#include "box-algorithms/ovpCBoxAlgorithmConfusionMatrix.h"
#include "box-algorithms/ovpCBoxAlgorithmROCCurve.h"
#include "box-algorithms/ovpCBoxAlgorithmClassifierAccuracyMeasure.h"
#include "algorithms/ovpCAlgorithmConfusionMatrix.h"
namespace OpenViBE {
namespace Plugins {
namespace Evaluation {
OVP_Declare_Begin()
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
OVP_Declare_New(CBoxAlgorithmStatisticGeneratorDesc);
#if defined(TARGET_HAS_ThirdPartyGTK)
OVP_Declare_New(CBoxAlgorithmKappaCoefDesc);
OVP_Declare_New(CBoxAlgorithmROCCurveDesc);
#endif
OVP_Declare_New(CAlgorithmConfusionMatrixDesc);
OVP_Declare_New(CBoxAlgorithmConfusionMatrixDesc);
#if defined(TARGET_HAS_ThirdPartyGTK)
OVP_Declare_New(CBoxAlgorithmClassifierAccuracyMeasureDesc);
#endif
OVP_Declare_End()
} // namespace Evaluation
} // namespace Plugins
} // namespace OpenViBE