Mentions légales du service

Skip to content
Snippets Groups Projects

Draft: Resolve "Feature: ERD Detection"

Open Thomas PRAMPART requested to merge 76-feature-erd-detection into development
Files
5
///-------------------------------------------------------------------------------------------------
///
/// \file CBoxAlgorithmERDDetection.cpp
/// \brief Implementation of the ERD Detection box
/// \author Thomas Prampart (Inria).
/// \version 0.0.1.
/// \date Mon Apr 26 14:58:42 2021.
///
/// \copyright (C) 2021 INRIA
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as published
/// by the Free Software Foundation, either version 3 of the License, or
/// (at your option) any later version.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/// GNU Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program. If not, see <https://www.gnu.org/licenses/>.
///-------------------------------------------------------------------------------------------------
#include <algorithm>
#include <iostream>
#include <numeric>
#include <xml/IXMLHandler.h>
#include <xml/IXMLNode.h>
#include <openvibe/ovAssert.h>
#include "CBoxAlgorithmERDDetection.hpp"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmERDDetection::initialize()
{
m_restSignalDecoder.initialize(*this, 0);
m_trialSignalDecoder.initialize(*this, 1);
m_stimulationEncoder.initialize(*this, 0);
m_matrixEncoder.initialize(*this, 1);
m_oMatrix = m_matrixEncoder.getInputMatrix();
m_calibrationFile = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_analyticsFile = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_mode = EBoxMode(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)));
m_erdRecordsPercentile = static_cast<double>(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3));
std::ostringstream out;
out.precision(16);
switch (m_mode) {
case EBoxMode::Calibration:
m_numReferences = 1;
break;
case EBoxMode::Processing:
// Loop through all the pairs of threshold/stimulation settings
m_numReferences = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
for (size_t i = 0; i < (getBoxAlgorithmContext()->getStaticBoxContext()->getSettingCount() - m_numMandatoryProcessSettings) / 2; ++i) {
auto res = m_erdThresholdEvents.emplace(static_cast<double>(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), (2 * i) + m_numMandatoryProcessSettings)) / 100.0,
CIdentifier(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), (2 * i) + m_numMandatoryProcessSettings + 1 )));
if (!res.second) {
this->getLogManager() << Kernel::LogLevel_Warning << "Threshold value ["
<< static_cast<double>(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), (2 * i) + m_numMandatoryProcessSettings))
<< "] cannot be assigned more than once." << "\n";
}
}
loadCalibrationData();
break;
default:
this->getLogManager() << Kernel::LogLevel_Fatal << "Box mode not supported: " << toString(m_mode) << "\n";
break;
}
return true;
}
bool CBoxAlgorithmERDDetection::uninitialize()
{
std::cout << "Uninitialize box\n";
saveCalibrationData();
saveAnalyticsData();
m_restSignalDecoder.uninitialize();
m_trialSignalDecoder.uninitialize();
m_stimulationEncoder.uninitialize();
m_matrixEncoder.uninitialize();
return true;
}
/*******************************************************************************/
uint64_t CBoxAlgorithmERDDetection::getClockFrequency()
{
return 128LL << 32;
}
/*******************************************************************************/
bool CBoxAlgorithmERDDetection::processClock(Kernel::CMessageClock&)
{
// Run on clock to make sure stims (even empty) are encoded regularly at the end of the process method.
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmERDDetection::processInput(const size_t index)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*******************************************************************************/
bool CBoxAlgorithmERDDetection::process()
{
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
// Send headers on first call
if (!m_headersSent) {
// Stimulation header
m_stimulationEncoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, m_lastStimulationTime.time(), getPlayerContext().getCurrentTime());
m_lastStimulationTime = getPlayerContext().getCurrentTime();
// Matrix header
m_oMatrix->resize(1);
m_matrixEncoder.encodeHeader();
boxContext.markOutputAsReadyToSend(1, getPlayerContext().getCurrentTime(), getPlayerContext().getCurrentTime());
m_headersSent = true;
return true;
} else {
m_stimulationEncoder.getInputStimulationSet()->clear();
m_oMatrix[0][0] = 0.0;
}
//iterate over all chunk on input 0
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i) {
// decode the chunk i
m_restSignalDecoder.decode(i);
// the decoder may have decoded 3 different parts : the header, a buffer or the end of stream.
if (m_restSignalDecoder.isHeaderReceived()) {}
if (m_restSignalDecoder.isBufferReceived()) {
// Buffer received. For example the signal values
// Access to the buffer can be done thanks to :
CMatrix* matrix = m_restSignalDecoder.getOutputMatrix(); // the StreamedMatrix of samples.
OV_ERROR_UNLESS_KRF(matrix->getSize() == 1, "Rest input (input 0): Excpected single sample chunks. This chunk received is " << matrix->getSize() << " long", Kernel::ErrorType::BadProcessing);
m_rest = (*matrix)[0];
}
if (m_restSignalDecoder.isEndReceived()) {}
}
CTime currentTime = CTime(getPlayerContext().getCurrentTime());
//iterate over all chunk on input 1
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i) {
// decode the chunk i
m_trialSignalDecoder.decode(i);
// the decoder may have decoded 3 different parts : the header, a buffer or the end of stream.
if (m_trialSignalDecoder.isHeaderReceived()) {
m_inputSamplingRate = m_trialSignalDecoder.getOutputSamplingRate();
}
if (m_trialSignalDecoder.isBufferReceived()) {
// Buffer received. For example the signal values
// Access to the buffer can be done thanks to :
CMatrix* matrix = m_trialSignalDecoder.getOutputMatrix(); // the StreamedMatrix of samples.
OV_ERROR_UNLESS_KRF(matrix->getSize() == 1, "Trial input (input 1): Excpected single sample chunks. This chunk received is " << matrix->getSize() << " long", Kernel::ErrorType::BadProcessing);
double trial = (*matrix)[0];
// erd value
if (m_rest != 0.0) {
double erd = (trial - m_rest) / m_rest * 100.0;
if(erd <= 0) {
m_erdRecords.push_back(erd);
m_oMatrix[0][0] = erd;
}
m_matrixEncoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(1, currentTime.time(), CTime(currentTime + CTime(1.0 / m_inputSamplingRate)).time());
if (m_mode == EBoxMode::Processing) {
if (m_erdReference != 0.0) {
// normalisation
double normalized_erd = erd / m_erdReference;
// m_erdThresholdEvents stored in descending order
for (auto& threshold: m_erdThresholdEvents) {
// Compare to threshold in %
if (normalized_erd >= threshold.first) {
m_stimulationEncoder.getInputStimulationSet()->appendStimulation(threshold.second.id(), currentTime.time() , 0);
break;
}
}
if (m_stimulationEncoder.getInputStimulationSet()->getStimulationCount() == 0) {
m_stimulationEncoder.getInputStimulationSet()->appendStimulation(OVTK_StimulationId_Label_00, currentTime.time() , 0);
}
} else {
this->getLogManager() << Kernel::LogLevel_Warning << "ERD Reference is null. Impossible to run ERD detection. \n";
}
}
} else {
std::ostringstream out;
out.precision(16);
out << "Rest signal is null. Impossible to calculcate ERD" << m_rest;
this->getLogManager() << Kernel::LogLevel_Warning << out.str().c_str() << "\n";
}
}
if (m_restSignalDecoder.isEndReceived()) {}
}
m_stimulationEncoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, m_lastStimulationTime.time(), currentTime.time());
m_lastStimulationTime = currentTime;
return true;
}
bool CBoxAlgorithmERDDetection::saveCalibrationData()
{
if (m_erdRecords.empty()) {
this->getLogManager() << Kernel::LogLevel_Warning << "Calibration data not saved. No ERDs recorded" << "\n";
return false;
}
XML::IXMLHandler* handler = XML::createXMLHandler();
XML::IXMLNode* root = XML::createNode("ERD-Detection-Calibration");
root->addAttribute("Creator", this->getConfigurationManager().expand("${Application_Name}"));
root->addAttribute("CreatorVersion", this->getConfigurationManager().expand("${Application_Version}"));
std::ostringstream out;
out.precision(16);
XML::IXMLNode* tmpNode;
for (size_t i = 0; i < m_erdReferences.size(); ++i) {
tmpNode = XML::createNode(("ERD-Reference-"+std::to_string(i+1)).c_str());
out << m_erdReferences[i];
tmpNode->setPCData(out.str().c_str());
out.str("");
root->addChild(tmpNode);
}
tmpNode = XML::createNode(("ERD-Reference-"+std::to_string(m_erdReferences.size()+1)).c_str());
std::sort(m_erdRecords.begin(), m_erdRecords.end());
out << m_erdRecords[static_cast<size_t>(m_erdRecords.size()*m_erdRecordsPercentile)];
tmpNode->setPCData(out.str().c_str());
root->addChild(tmpNode);
this->getLogManager() << Kernel::LogLevel_Info << "Saved calibration ERD-References" << "\n";
bool save = handler->writeXMLInFile(*root, m_calibrationFile.toASCIIString());
handler->release();
root->release();
if (!save) {
OV_ERROR_KRF("Failed saving configuration to file [" << m_calibrationFile << "]", Kernel::ErrorType::BadFileWrite);
}
return true;
}
bool CBoxAlgorithmERDDetection::loadCalibrationData()
{
XML::IXMLHandler* handler = XML::createXMLHandler();
XML::IXMLNode* rootNode = handler->parseFile(m_calibrationFile);
OV_ERROR_UNLESS_KRF(rootNode, "Unable to load calibration data from " << m_calibrationFile << " - ", Kernel::ErrorType::BadParsing);
int refNumber = 1;
XML::IXMLNode* tmp = rootNode->getChildByName(("ERD-Reference-"+std::to_string(refNumber)).c_str());
while (tmp) {
m_erdReferences.push_back(std::stod(tmp->getPCData()));
this->getLogManager() << Kernel::LogLevel_Debug << "Calibration reference loaded: " << m_erdReferences[refNumber-1] << "\n";
refNumber++;
tmp = rootNode->getChildByName(("ERD-Reference-"+std::to_string(refNumber)).c_str());
}
OV_ERROR_UNLESS_KRF(m_erdReferences.size() > 0, "Unable to get ERD references from file " << m_calibrationFile, Kernel::ErrorType::BadParsing);
size_t startIndex = 0;
if (m_erdReferences.size() > m_numReferences) {
startIndex = m_erdReferences.size() - m_numReferences;
}
for (size_t i = startIndex; i < m_erdReferences.size() ; ++i) {
m_erdReference += m_erdReferences[i];
this->getLogManager() << Kernel::LogLevel_Debug << "Calibration reference added: " << m_erdReferences[refNumber-1] << "\n";
}
m_erdReference = m_erdReference / (m_erdReferences.size() - startIndex);
this->getLogManager() << Kernel::LogLevel_Info << "Calibration reference ERD: " << m_erdReference << "\n";
rootNode->release();
handler->release();
return true;
}
bool CBoxAlgorithmERDDetection::saveAnalyticsData()
{
static const int NUM_ANALYTICS = 2;
if (m_erdRecords.empty()) {
this->getLogManager() << Kernel::LogLevel_Warning << "Calibration data not saved. No ERDs recorded" << "\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Info << "CBoxAlgorithmERDDetection::saveAnalyticsData: parseFile\n";
XML::IXMLHandler* handler = XML::createXMLHandler();
XML::IXMLNode* rootNode = handler->parseFile(m_analyticsFile);
if (!rootNode) {
rootNode = XML::createNode("ERD-Detection-Analytics");
rootNode->addAttribute("Creator", this->getConfigurationManager().expand("${Application_Name}"));
rootNode->addAttribute("CreatorVersion", this->getConfigurationManager().expand("${Application_Version}"));
}
this->getLogManager() << Kernel::LogLevel_Info << "rootNode: " << rootNode->getXML() << "\n";
std::ostringstream out;
out.precision(16);
// Reference for this run
size_t refNumber = rootNode->getChildCount() / NUM_ANALYTICS + 1;
XML::IXMLNode* erdMaxNode = XML::createNode(("ERD-Max-"+std::to_string(refNumber)).c_str());
double maxErd = *std::min_element(m_erdRecords.begin(), m_erdRecords.end());
out << maxErd;
erdMaxNode->setPCData(out.str().c_str());
rootNode->addChild(erdMaxNode);
out.str("");
XML::IXMLNode* erdAvgNode = XML::createNode(("ERD-Average-"+std::to_string(refNumber)).c_str());
double avgErd = std::accumulate(m_erdRecords.begin(), m_erdRecords.end(), 0.0) / double(m_erdRecords.size());
out << avgErd;
erdAvgNode->setPCData(out.str().c_str());
rootNode->addChild(erdAvgNode);
this->getLogManager() << Kernel::LogLevel_Info << "CBoxAlgorithmERDDetection::saveAnalyticsData: writeXMLInFile\n";
bool dataSaved = handler->writeXMLInFile(*rootNode, m_analyticsFile.toASCIIString());
handler->release();
rootNode->release();
return dataSaved;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
Loading