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,193 @@
#include "ovpCAlgorithmMatrixAverage.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
// ________________________________________________________________________________________________________________
//
bool CAlgorithmMatrixAverage::initialize()
{
ip_averagingMethod.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod));
ip_matrixCount.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount));
ip_matrix.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix));
op_averagedMatrix.initialize(getOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix));
m_nAverageSamples = 0;
return true;
}
bool CAlgorithmMatrixAverage::uninitialize()
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete *it; }
m_history.clear();
op_averagedMatrix.uninitialize();
ip_matrix.uninitialize();
ip_matrixCount.uninitialize();
ip_averagingMethod.uninitialize();
return true;
}
// ________________________________________________________________________________________________________________
//
bool CAlgorithmMatrixAverage::process()
{
CMatrix* iMatrix = ip_matrix;
CMatrix* oMatrix = op_averagedMatrix;
bool shouldPerformAverage = false;
if (this->isInputTriggerActive(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset))
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete*it; }
m_history.clear();
oMatrix->copyDescription(*iMatrix);
}
if (this->isInputTriggerActive(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix))
{
if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Moving))
{
CMatrix* swapMatrix;
if (m_history.size() >= ip_matrixCount)
{
swapMatrix = m_history.front();
m_history.pop_front();
}
else
{
swapMatrix = new CMatrix();
swapMatrix->copyDescription(*iMatrix);
}
swapMatrix->copyContent(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (m_history.size() == ip_matrixCount);
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::MovingImmediate))
{
CMatrix* swapMatrix;
if (m_history.size() >= ip_matrixCount)
{
swapMatrix = m_history.front();
m_history.pop_front();
}
else
{
swapMatrix = new CMatrix();
swapMatrix->copyDescription(*iMatrix);
}
swapMatrix->copyContent(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (!m_history.empty());
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Block))
{
CMatrix* swapMatrix = new CMatrix();
if (m_history.size() >= ip_matrixCount)
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete *it; }
m_history.clear();
}
swapMatrix->copy(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (m_history.size() == ip_matrixCount);
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Cumulative))
{
m_history.push_back(iMatrix);
shouldPerformAverage = true;
}
else { shouldPerformAverage = false; }
}
if (shouldPerformAverage)
{
oMatrix->resetBuffer();
if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Cumulative))
{
CMatrix* matrix = m_history.at(0);
m_nAverageSamples++;
if (m_nAverageSamples == 1) // If it's the first matrix, the average is the first matrix
{
double* buffer = matrix->getBuffer();
const size_t size = matrix->getBufferElementCount();
m_averageMatrices.clear();
m_averageMatrices.insert(m_averageMatrices.begin(), buffer, buffer + size);
}
else
{
if (matrix->getBufferElementCount() != m_averageMatrices.size()) { return false; }
const double n = double(m_nAverageSamples);
double* iBuffer = matrix->getBuffer();
for (double& value : m_averageMatrices)
{
// calculate cumulative mean as
// mean{k} = mean{k-1} + (new_value - mean{k-1}) / k
// which is a recurrence equivalent to mean{k] = mean{k-1} * (k-1)/k + new_value/k
// but more numerically stable
value += (*iBuffer - value) / n;
iBuffer++;
}
}
double* oBuffer = oMatrix->getBuffer();
for (const double& value : m_averageMatrices)
{
*oBuffer = double(value);
oBuffer++;
}
m_history.clear();
}
else
{
const size_t n = oMatrix->getBufferElementCount();
const double scale = 1. / m_history.size();
for (CMatrix* matrix : m_history)
{
double* oBuffer = oMatrix->getBuffer();
double* iBuffer = matrix->getBuffer();
for (size_t i = 0; i < n; ++i)
{
*oBuffer += *iBuffer * scale;
oBuffer++;
iBuffer++;
}
}
}
this->activateOutputTrigger(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed, true);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
#include <deque>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CAlgorithmMatrixAverage final : 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_MatrixAverage)
protected:
Kernel::TParameterHandler<uint64_t> ip_averagingMethod;
Kernel::TParameterHandler<uint64_t> ip_matrixCount;
Kernel::TParameterHandler<CMatrix*> ip_matrix;
Kernel::TParameterHandler<CMatrix*> op_averagedMatrix;
std::deque<CMatrix*> m_history;
std::vector<double> m_averageMatrices;
size_t m_nAverageSamples = 0;
};
class CAlgorithmMatrixAverageDesc final : public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Matrix average"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString(""); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Averaging"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_MatrixAverage; }
IPluginObject* create() override { return new CAlgorithmMatrixAverage(); }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix, "Matrix", Kernel::ParameterType_Matrix);
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount, "Matrix count", Kernel::ParameterType_UInteger);
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod, "Averaging Method", Kernel::ParameterType_UInteger);
prototype.addOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix, "Averaged matrix", Kernel::ParameterType_Matrix);
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset, "Reset");
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix, "Feed matrix");
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_ForceAverage, "Force average");
prototype.addOutputTrigger(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed, "Average performed");
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_MatrixAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,277 @@
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "ovpCAlgorithmOnlineCovariance.h"
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
#define COV_DEBUG 0
#if COV_DEBUG
void CAlgorithmOnlineCovariance::dumpMatrix(Kernel::ILogManager &rMgr, const MatrixXdRowMajor &mat, const CString &desc)
{
rMgr << Kernel::LogLevel_Info << desc << "\n";
for (int i = 0 ; i < mat.rows() ; i++)
{
rMgr << Kernel::LogLevel_Info << "Row " << i << ": ";
for (int j = 0 ; j < mat.cols() ; j++) { rMgr << mat(i,j) << " "; }
rMgr << "\n";
}
}
#else
void CAlgorithmOnlineCovariance::dumpMatrix(Kernel::ILogManager& /* mgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
#endif
bool CAlgorithmOnlineCovariance::initialize()
{
m_n = 0;
return true;
}
bool CAlgorithmOnlineCovariance::uninitialize() { return true; }
bool CAlgorithmOnlineCovariance::process()
{
// Note: The input parameters must have been set by the caller by now
const Kernel::TParameterHandler<double> ip_Shrinkage(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage));
const Kernel::TParameterHandler<bool> ip_TraceNormalization(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization));
const Kernel::TParameterHandler<uint64_t> ip_UpdateMethod(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod));
const Kernel::TParameterHandler<CMatrix*> ip_FeatureVectorSet(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors));
Kernel::TParameterHandler<CMatrix*> op_Mean(getOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_Mean));
Kernel::TParameterHandler<CMatrix*> op_CovarianceMatrix(getOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix));
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_Reset))
{
OV_ERROR_UNLESS_KRF(ip_Shrinkage >= 0.0 && ip_Shrinkage <= 1.0, "Invalid shrinkage parameter (expected value between 0 and 1)", Kernel::ErrorType::BadInput);
OV_ERROR_UNLESS_KRF(ip_FeatureVectorSet->getDimensionCount() == 2,
"Invalid feature vector with " << ip_FeatureVectorSet->getDimensionCount() << " dimensions (expected dim = 2)",
Kernel::ErrorType::BadInput);
const size_t nRows = ip_FeatureVectorSet->getDimensionSize(0);
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(nRows >= 1 && nCols >= 1, "Invalid input matrix [" << nRows << "x" << nCols << "(minimum expected = 1x1)", Kernel::ErrorType::BadInput);
this->getLogManager() << Kernel::LogLevel_Debug << "Using shrinkage coeff " << ip_Shrinkage << " ...\n";
this->getLogManager() << Kernel::LogLevel_Debug << "Trace normalization is " << (ip_TraceNormalization ? "[on]" : "[off]") << "\n";
this->getLogManager() << Kernel::LogLevel_Debug << "Using update method " << getTypeManager().getEnumerationEntryNameFromValue(
OVP_TypeId_OnlineCovariance_UpdateMethod, ip_UpdateMethod) << "\n";
// Set the output buffers
op_Mean->resize(1, nCols);
op_CovarianceMatrix->resize(nCols, nCols);
// These keep track of the non-normalized incremental estimates
m_mean.resize(1, nCols);
m_mean.setZero();
m_cov.resize(nCols, nCols);
m_cov.setZero();
m_n = 0;
}
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_Update))
{
const size_t nRows = ip_FeatureVectorSet->getDimensionSize(0);
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
const double* buffer = ip_FeatureVectorSet->getBuffer();
OV_ERROR_UNLESS_KRF(buffer, "Input buffer is NULL", Kernel::ErrorType::BadInput);
// Cast our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
const Eigen::Map<MatrixXdRowMajor> sampleChunk(const_cast<double*>(buffer), nRows, nCols);
// Update the mean & cov estimates
if (ip_UpdateMethod == uint64_t(EUpdateMethod::ChunkAverage))
{
// 'Average of per-chunk covariance matrices'. This might not be a proper cov over
// the dataset, but seems occasionally produce nicely smoothed results when used for CSP.
const Eigen::MatrixXd chunkMean = sampleChunk.colwise().mean();
const Eigen::MatrixXd chunkCentered = sampleChunk.rowwise() - chunkMean.row(0);
Eigen::MatrixXd chunkCov = (1.0 / double(nRows)) * chunkCentered.transpose() * chunkCentered;
if (ip_TraceNormalization)
{
// This normalization can be seen e.g. Muller-Gerkin & al., 1999. Presumably the idea is to normalize the
// scale of each chunk in order to compensate for possible signal power drift over time during the EEG recording,
// making each chunks' covariance contribute similarly to the average regardless of
// the current average power. Such a normalization could also be implemented in its own
// box and not done here.
chunkCov = chunkCov / chunkCov.trace();
}
m_mean += chunkMean;
m_cov += chunkCov;
m_n++;
// dumpMatrix(this->getLogManager(), sampleChunk, "SampleChunk");
// dumpMatrix(this->getLogManager(), sampleCenteredMean, "SampleCenteredMean");
}
else if (ip_UpdateMethod == uint64_t(EUpdateMethod::Incremental))
{
// Incremental sample-per-sample cov updating.
// It should be implementing the Youngs & Cramer algorithm as described in
// Chan, Golub, Leveq, "Updating formulae and a pairwise algorithm...", 1979
size_t start = 0;
if (m_n == 0)
{
m_mean = sampleChunk.row(0);
start = 1;
m_n = 1;
}
Eigen::MatrixXd chunkContribution;
chunkContribution.resizeLike(m_cov);
chunkContribution.setZero();
for (size_t i = start; i < nRows; ++i)
{
m_mean += sampleChunk.row(i);
const Eigen::MatrixXd diff = (m_n + 1.0) * sampleChunk.row(i) - m_mean;
const Eigen::MatrixXd outerProd = diff.transpose() * diff;
chunkContribution += 1.0 / (m_n * (m_n + 1.0)) * outerProd;
m_n++;
}
if (ip_TraceNormalization) { chunkContribution = chunkContribution / chunkContribution.trace(); }
m_cov += chunkContribution;
// dumpMatrix(this->getLogManager(), sampleChunk, "Sample");
}
#if 0
else if(method == 2)
{
// Increment sample counts
const size_t countBefore = m_n;
const size_t countChunk = nRows;
const size_t countAfter = countBefore + countChunk;
const MatrixXd sampleSum = sampleChunk.colwise().sum();
// Center the chunk
const MatrixXd sampleCentered = sampleChunk.rowwise() - sampleSum.row(0)*(1.0/(double)countChunk);
const MatrixXd sampleCoMoment = (sampleCentered.transpose() * sampleCentered);
m_cov = m_cov + sampleCoMoment;
if(countBefore>0)
{
const MatrixXd meanDifference = (countChunk/(double)countBefore) * m_mean - sampleSum;
const MatrixXd meanDiffOuterProduct = meanDifference.transpose()*meanDifference;
m_cov += meanDiffOuterProduct*countBefore/(countChunk*countAfter);
}
m_mean = m_mean + sampleSum;
m_n = countAfter;
}
else
{
// Increment sample counts
const size_t countBefore = m_n;
const size_t countChunk = nRows;
const size_t countAfter = countBefore + countChunk;
// Insert our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
const Map<MatrixXdRowMajor> dataMatrix(const_cast<double*>(buffer),nRows,nCols);
// Estimate the current sample means
const MatrixXdRowMajor sampleMean = dataMatrix.colwise().mean();
// Center the current data with the previous(!) mean
const MatrixXdRowMajor sampleCentered = dataMatrix.rowwise() - m_mean.row(0);
// Estimate the current covariance
const MatrixXd sampleCov = (sampleCentered.transpose() * sampleCentered) * (1.0/(double)nRows);
// fixme: recheck the weights ...
// Update the global mean and cov
if(countBefore>0)
{
m_mean = ( m_mean*countBefore + sampleMean*nRows) / (double)countAfter;
m_cov = ( m_cov*countBefore + sampleCov*(countBefore/(double)countAfter) ) / (double)countAfter;
}
else
{
m_mean = sampleMean;
m_cov = sampleCov;
}
m_n = countAfter;
}
#endif
else { OV_ERROR_KRF("Unknown update method [" << CIdentifier(ip_UpdateMethod).str() << "]", Kernel::ErrorType::BadSetting); }
}
// Give output with regularization (mix prior + cov)?
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_GetCov))
{
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(m_n > 0, "No sample to compute covariance", Kernel::ErrorType::BadConfig);
// Converters to CMatrix
Eigen::Map<MatrixXdRowMajor> outputMean(op_Mean->getBuffer(), 1, nCols);
Eigen::Map<MatrixXdRowMajor> outputCov(op_CovarianceMatrix->getBuffer(), nCols, nCols);
// The shrinkage parameter pulls the covariance matrix towards diagonal covariance
Eigen::MatrixXd priorCov;
priorCov.resizeLike(m_cov);
priorCov.setIdentity();
// Mix the prior and the sample estimates according to the shrinkage parameter. We scale by 1/n to normalize
outputMean = m_mean / double(m_n);
outputCov = ip_Shrinkage * priorCov + (1.0 - ip_Shrinkage) * (m_cov / double(m_n));
// Debug block
dumpMatrix(this->getLogManager(), outputMean, "Data mean");
dumpMatrix(this->getLogManager(), m_cov / double(m_n), "Data cov");
dumpMatrix(this->getLogManager(), ip_Shrinkage * priorCov, "Prior cov");
dumpMatrix(this->getLogManager(), outputCov, "Output cov");
}
// Give just the output with no shrinkage?
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_GetCovRaw))
{
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(m_n > 0, "No sample to compute covariance", Kernel::ErrorType::BadConfig);
// Converters to CMatrix
Eigen::Map<MatrixXdRowMajor> outputMean(op_Mean->getBuffer(), 1, nCols);
Eigen::Map<MatrixXdRowMajor> outputCov(op_CovarianceMatrix->getBuffer(), nCols, nCols);
// We scale by 1/n to normalize
outputMean = m_mean / double(m_n);
outputCov = m_cov / double(m_n);
// Debug block
dumpMatrix(this->getLogManager(), outputMean, "Data mean");
dumpMatrix(this->getLogManager(), outputCov, "Data Cov");
}
return true;
}
#endif // TARGET_HAS_ThirdPartyEIGEN
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,89 @@
/*
*
* Incremental covariance estimators with shrinkage
*
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <Eigen/Dense>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CAlgorithmOnlineCovariance final : virtual public Toolkit::TAlgorithm<IAlgorithm>
{
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_OnlineCovariance)
protected:
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
static void dumpMatrix(Kernel::ILogManager& mgr, const MatrixXdRowMajor& mat, const CString& desc);
// These are non-normalized estimates for the corresp. statistics
Eigen::MatrixXd m_cov;
Eigen::MatrixXd m_mean;
// The divisor for the above estimates to do the normalization
uint64_t m_n = 0;
};
class CAlgorithmOnlineCovarianceDesc final : virtual public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Online Covariance"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Incrementally computes covariance with shrinkage."); }
CString getDetailedDescription() const override { return CString("Regularized covariance output is computed as (diag*shrink + cov)"); }
CString getCategory() const override { return CString(""); }
CString getVersion() const override { return CString("0.5"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_OnlineCovariance; }
IPluginObject* create() override { return new CAlgorithmOnlineCovariance; }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage, "Shrinkage", Kernel::ParameterType_Float);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors, "Input vectors", Kernel::ParameterType_Matrix);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod, "Cov update method", Kernel::ParameterType_Enumeration,
OVP_TypeId_OnlineCovariance_UpdateMethod);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization, "Trace normalization", Kernel::ParameterType_Boolean);
// The algorithm returns these outputs
prototype.addOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_Mean, "Mean vector", Kernel::ParameterType_Matrix);
prototype.addOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix, "Covariance matrix", Kernel::ParameterType_Matrix);
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Reset, "Reset the algorithm");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Update, "Append a chunk of data");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCov, "Get the current regularized covariance matrix & mean");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCovRaw, "Get the current covariance matrix & mean");
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_OnlineCovarianceDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEIGEN
@@ -0,0 +1,93 @@
#include "ovpCBoxAlgorithmChannelRename.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmChannelRename::initialize()
{
std::vector<CString> tokens;
const CString setting = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const size_t nToken = split(setting, Toolkit::String::TSplitCallback<std::vector<CString>>(tokens), OV_Value_EnumeratedStringSeparator);
m_names.clear();
for (size_t i = 0; i < nToken; ++i) { m_names.push_back(tokens[i].toASCIIString()); }
this->getStaticBoxContext().getOutputType(0, m_typeID);
if (m_typeID == OV_TypeId_Signal)
{
m_decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else if (m_typeID == OV_TypeId_StreamedMatrix || m_typeID == OV_TypeId_CovarianceMatrix || m_typeID == OV_TypeId_TimeFrequency)
{
m_decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else if (m_typeID == OV_TypeId_Spectrum)
{
m_decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else { OV_ERROR_KRF("Incompatible stream type", Kernel::ErrorType::BadConfig); }
ip_Matrix = m_encoder.getInputMatrix();
op_Matrix = m_decoder.getOutputMatrix();
m_encoder.getInputMatrix().setReferenceTarget(m_decoder.getOutputMatrix());
if (m_typeID == OV_TypeId_Signal) { m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate()); }
if (m_typeID == OV_TypeId_Spectrum)
{
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
m_encoder.getInputFrequencyAbcissa().setReferenceTarget(m_decoder.getOutputFrequencyAbcissa());
}
return true;
}
bool CBoxAlgorithmChannelRename::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmChannelRename::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmChannelRename::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t chunk = 0; chunk < boxContext.getInputChunkCount(0); ++chunk)
{
m_decoder.decode(chunk);
if (m_decoder.isHeaderReceived())
{
ip_Matrix->copyDescription(*op_Matrix);
for (size_t channel = 0; channel < ip_Matrix->getDimensionSize(0) && channel < m_names.size(); ++channel)
{
ip_Matrix->setDimensionLabel(0, channel, m_names[channel].c_str());
}
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived()) { m_encoder.encodeBuffer(); }
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, chunk), boxContext.getInputChunkEndTime(0, chunk));
boxContext.markInputAsDeprecated(0, chunk);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,106 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <string>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmChannelRename 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_ChannelRename)
protected:
Toolkit::TGenericDecoder<CBoxAlgorithmChannelRename> m_decoder;
Toolkit::TGenericEncoder<CBoxAlgorithmChannelRename> m_encoder;
CIdentifier m_typeID = CIdentifier::undefined();
Kernel::TParameterHandler<CMatrix*> ip_Matrix;
Kernel::TParameterHandler<CMatrix*> op_Matrix;
std::vector<std::string> m_names;
};
class CBoxAlgorithmChannelRenameListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmChannelRenameDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Channel Rename"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Renames channels of different types of streamed matrices"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("1.1.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ChannelRename; }
IPluginObject* create() override { return new CBoxAlgorithmChannelRename; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmChannelRenameListener; }
void releaseBoxListener(IBoxListener* boxListener) const override { delete boxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input matrix", OV_TypeId_Signal);
prototype.addOutput("Output matrix", OV_TypeId_Signal);
prototype.addSetting("New channel names", OV_TypeId_String, "Channel 1;Channel 2");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_TimeFrequency);
prototype.addInputSupport(OV_TypeId_CovarianceMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_TimeFrequency);
prototype.addOutputSupport(OV_TypeId_CovarianceMatrix);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ChannelRenameDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,292 @@
#include "ovpCBoxAlgorithmChannelSelector.h"
#include <limits>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace {
size_t FindChannel(const CMatrix& matrix, const CString& channel, const EMatchMethod matchMethod, const size_t start = 0)
{
size_t result = std::numeric_limits<size_t>::max();
const size_t nChannel = matrix.getDimensionSize(0);
if (matchMethod == EMatchMethod::Name)
{
for (size_t i = start; i < matrix.getDimensionSize(0); ++i)
{
if (Toolkit::String::isAlmostEqual(matrix.getDimensionLabel(0, i), channel, false)) { result = i; }
}
}
else if (matchMethod == EMatchMethod::Index)
{
try
{
const int value = std::stoi(channel.toASCIIString());
if (value < 0)
{
size_t idx = size_t(- value - 1); // => makes it 0-indexed !
if (idx < nChannel)
{
idx = nChannel - idx - 1; // => reverses index
if (start <= idx) { result = idx; }
}
}
if (value > 0)
{
const size_t index = size_t(value - 1); // => makes it 0-indexed !
if (index < nChannel) { if (start <= index) { result = index; } }
}
}
catch (const std::exception&)
{
// catch block intentionnaly left blank
}
}
else if (matchMethod == EMatchMethod::Smart)
{
if (result == std::numeric_limits<size_t>::max()) { result = FindChannel(matrix, channel, EMatchMethod::Name, start); }
if (result == std::numeric_limits<size_t>::max()) { result = FindChannel(matrix, channel, EMatchMethod::Index, start); }
}
return result;
}
} // namespace
bool CBoxAlgorithmChannelSelector::initialize()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
CIdentifier typeID;
boxContext.getOutputType(0, typeID);
m_decoder = nullptr;
m_encoder = nullptr;
if (typeID == OV_TypeId_Signal)
{
auto* encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
encoder->getInputSamplingRate().setReferenceTarget(decoder->getOutputSamplingRate());
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else if (typeID == OV_TypeId_Spectrum)
{
auto* encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
encoder->getInputFrequencyAbscissa().setReferenceTarget(decoder->getOutputFrequencyAbscissa());
encoder->getInputSamplingRate().setReferenceTarget(decoder->getOutputSamplingRate());
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else if (typeID == OV_TypeId_StreamedMatrix)
{
auto* encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else { OV_ERROR_KRF("Invalid input type [" << typeID.str() << "]", Kernel::ErrorType::BadInput); }
m_vLookup.clear();
return true;
}
bool CBoxAlgorithmChannelSelector::uninitialize()
{
if (m_decoder)
{
m_decoder->uninitialize();
delete m_decoder;
}
if (m_encoder)
{
m_encoder->uninitialize();
delete m_encoder;
}
return true;
}
bool CBoxAlgorithmChannelSelector::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmChannelSelector::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder->decode(i);
if (m_decoder->isHeaderReceived())
{
CString settingValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const ESelectionMethod selectionMethod = ESelectionMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
const EMatchMethod matchMethod = EMatchMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)));
if (selectionMethod == ESelectionMethod::Select_EEG)
{
// ______________________________________________________________________________________________________________________________________________________
//
// Collects channels with names corresponding to EEG
// ______________________________________________________________________________________________________________________________________________________
//
CString eegChannelNames = this->getConfigurationManager().expand("${Box_ChannelSelector_EEGChannelNames}");
std::vector<CString> token;
const size_t nToken = split(eegChannelNames, Toolkit::String::TSplitCallback<std::vector<CString>>(token),
OV_Value_EnumeratedStringSeparator);
for (size_t j = 0; j < m_iMatrix->getDimensionSize(0); ++j)
{
for (size_t k = 0; k < nToken; ++k)
{
if (Toolkit::String::isAlmostEqual(m_iMatrix->getDimensionLabel(0, j), token[k], false)) { m_vLookup.push_back(j); }
}
}
}
else
{
// ______________________________________________________________________________________________________________________________________________________
//
// Splits the channel list in order to build up the look up table
// The look up table is later used to fill in the matrix content
// ______________________________________________________________________________________________________________________________________________________
//
std::vector<CString> tokens;
const size_t nToken = split(settingValue, Toolkit::String::TSplitCallback<std::vector<CString>>(tokens),
OV_Value_EnumeratedStringSeparator);
for (size_t j = 0; j < nToken; ++j)
{
std::vector<CString> subTokens;
// Checks if the token is a range
if (split(tokens[j], Toolkit::String::TSplitCallback<std::vector<CString>>(subTokens),
OV_Value_RangeStringSeparator) == 2)
{
// Finds the first & second part of the range (only index based)
size_t startIdx = FindChannel(*m_iMatrix, subTokens[0], EMatchMethod::Index);
size_t endIdx = FindChannel(*m_iMatrix, subTokens[1], EMatchMethod::Index);
// When first or second part is not found but associated token is empty, don't consider this as an error
if (startIdx == std::numeric_limits<size_t>::max() && subTokens[0] == CString("")) { startIdx = 0; }
if (endIdx == std::numeric_limits<size_t>::max() && subTokens[1] == CString("")) { endIdx = m_iMatrix->getDimensionSize(0) - 1; }
// After these corections, if either first or second token were not found, or if start index is greater than start index, consider this an error and invalid range
OV_ERROR_UNLESS_KRF(
startIdx != std::numeric_limits<size_t>::max() && endIdx != std::numeric_limits<size_t>::max() && startIdx <= endIdx,
"Invalid channel range [" << tokens[j] << "] - splitted as [" << subTokens[0] << "][" << subTokens[1] << "]",
Kernel::ErrorType::BadSetting);
// The range is valid so selects all the channels in this range
this->getLogManager() << Kernel::LogLevel_Debug << "For range [" << tokens[j] << "] :\n";
for (size_t k = startIdx; k <= endIdx; ++k)
{
m_vLookup.push_back(k);
this->getLogManager() << Kernel::LogLevel_Debug << " Selected channel [" << k + 1 << "]\n";
}
}
else
{
// This is not a range, so we can consider the whole token as a single token name
size_t found = false;
size_t index = std::numeric_limits<size_t>::max();
// Looks for all the channels with this name
while ((index = FindChannel(*m_iMatrix, tokens[j], matchMethod, index + 1)) != std::numeric_limits<size_t>::max())
{
found = true;
m_vLookup.push_back(index);
this->getLogManager() << Kernel::LogLevel_Debug << "Selected channel [" << index + 1 << "]\n";
}
OV_ERROR_UNLESS_KRF(found, "Invalid channel [" << tokens[j] << "]", Kernel::ErrorType::BadSetting);
}
}
// ______________________________________________________________________________________________________________________________________________________
//
// When selection method is set to reject
// We have to revert the selection building up a new look up table and replacing the old one
// ______________________________________________________________________________________________________________________________________________________
//
if (selectionMethod == ESelectionMethod::Reject)
{
std::vector<size_t> inversedLookup;
for (size_t j = 0; j < m_iMatrix->getDimensionSize(0); ++j)
{
bool selected = false;
for (size_t k = 0; k < m_vLookup.size(); ++k) { selected |= (m_vLookup[k] == j); }
if (!selected) { inversedLookup.push_back(j); }
}
m_vLookup = inversedLookup;
}
}
// ______________________________________________________________________________________________________________________________________________________
//
// Now we have the exact topology of the output matrix :)
// ______________________________________________________________________________________________________________________________________________________
//
OV_ERROR_UNLESS_KRF(!m_vLookup.empty(), "No channel selected", Kernel::ErrorType::BadConfig);
m_oMatrix->resize(m_vLookup.size(), m_iMatrix->getDimensionSize(1));
for (size_t j = 0; j < m_vLookup.size(); ++j)
{
if (m_vLookup[j] < m_iMatrix->getDimensionSize(0)) { m_oMatrix->setDimensionLabel(0, j, m_iMatrix->getDimensionLabel(0, m_vLookup[j])); }
else { m_oMatrix->setDimensionLabel(0, j, "Missing channel"); }
}
for (size_t j = 0; j < m_iMatrix->getDimensionSize(1); ++j) { m_oMatrix->setDimensionLabel(1, j, m_iMatrix->getDimensionLabel(1, j)); }
m_encoder->encodeHeader();
}
if (m_decoder->isBufferReceived())
{
// ______________________________________________________________________________________________________________________________________________________
//
// When a buffer is received, just copy the channel content depending on the look up table
// ______________________________________________________________________________________________________________________________________________________
//
const size_t nSample = m_oMatrix->getDimensionSize(1);
for (size_t j = 0; j < m_vLookup.size(); ++j)
{
if (m_vLookup[j] < m_iMatrix->getDimensionSize(0))
{
memcpy(m_oMatrix->getBuffer() + j * nSample, m_iMatrix->getBuffer() + m_vLookup[j] * nSample, nSample * sizeof(double));
}
}
m_encoder->encodeBuffer();
}
if (m_decoder->isEndReceived()) { m_encoder->encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,160 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <string>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmChannelSelector 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_ChannelSelector)
protected:
Toolkit::TDecoder<CBoxAlgorithmChannelSelector>* m_decoder = nullptr;
Toolkit::TEncoder<CBoxAlgorithmChannelSelector>* m_encoder = nullptr;
CMatrix* m_iMatrix = nullptr;
CMatrix* m_oMatrix = nullptr;
std::vector<size_t> m_vLookup;
};
class CBoxAlgorithmChannelSelectorListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onOutputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(0, typeID);
if (typeID == OV_TypeId_Signal || typeID == OV_TypeId_Spectrum || typeID == OV_TypeId_StreamedMatrix)
{
box.setInputType(0, typeID);
return true;
}
box.getInputType(0, typeID);
box.setOutputType(0, typeID);
OV_ERROR_KRF("Invalid output type [" << typeID.str() << "] (expected Signal, Spectrum or Streamed Matrix)", Kernel::ErrorType::BadOutput);
}
bool onInputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(0, typeID);
if (typeID == OV_TypeId_Signal || typeID == OV_TypeId_Spectrum || typeID == OV_TypeId_StreamedMatrix)
{
box.setOutputType(0, typeID);
return true;
}
box.getOutputType(0, typeID);
box.setInputType(0, typeID);
OV_ERROR_KRF("Invalid input type [" << typeID.str() << "] (expected Signal, Spectrum or Streamed Matrix)", Kernel::ErrorType::BadInput);
}
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
{
//we are only interested in the setting 0 and the type changes (select or reject)
if ((index == 0 || index == 1) && (!m_hasUserSetName))
{
CString channels;
box.getSettingValue(0, channels);
CString method;
CIdentifier enumID = CIdentifier::undefined();
box.getSettingValue(1, method);
box.getSettingType(1, enumID);
const ESelectionMethod methodID = ESelectionMethod(this->getTypeManager().getEnumerationEntryValueFromName(enumID, method));
if (methodID == ESelectionMethod::Reject) { channels = CString("!") + channels; }
box.setName(channels);
}
return true;
}
bool onNameChanged(Kernel::IBox& box) override
//when user set box name manually
{
if (m_hasUserSetName)
{
const CString rename = box.getName();
if (rename == CString("Channel Selector"))
{//default name, we switch back to default behaviour
m_hasUserSetName = false;
}
}
else { m_hasUserSetName = true; }
return true;
}
bool initialize() override
{
m_hasUserSetName = false;//need to initialize this value
return true;
}
private:
bool m_hasUserSetName = false;
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmChannelSelectorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Channel Selector"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Select a subset of signal channels"); }
CString getDetailedDescription() const override { return CString("Selection can be based on channel name (case-sensitive) or index starting from 0"); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ChannelSelector; }
IPluginObject* create() override { return new CBoxAlgorithmChannelSelector; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmChannelSelectorListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Channel List", OV_TypeId_String, ":");
prototype.addSetting("Action", OVP_TypeId_SelectionMethod, "Select");
prototype.addSetting("Channel Matching Method", OVP_TypeId_MatchMethod, "Smart");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,128 @@
#include "ovpCBoxAlgorithmCrop.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmCrop::initialize()
{
CIdentifier inputTypeID;
getStaticBoxContext().getInputType(0, inputTypeID);
if (inputTypeID == OV_TypeId_StreamedMatrix)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
}
else if (inputTypeID == OV_TypeId_FeatureVector)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorEncoder));
}
else if (inputTypeID == OV_TypeId_Signal)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
}
else if (inputTypeID == OV_TypeId_Spectrum)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
}
else { return false; }
m_decoder->initialize();
m_encoder->initialize();
if (inputTypeID == OV_TypeId_StreamedMatrix) { }
else if (inputTypeID == OV_TypeId_FeatureVector) { }
else if (inputTypeID == OV_TypeId_Signal)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
}
else if (inputTypeID == OV_TypeId_Spectrum)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling));
}
m_matrix = new CMatrix();
Kernel::TParameterHandler<CMatrix*>(m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix)).setReferenceTarget(m_matrix);
Kernel::TParameterHandler<CMatrix*>(m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix)).setReferenceTarget(m_matrix);
m_cropMethod = ECropMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0)));
m_minCropValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_maxCropValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
OV_ERROR_UNLESS_KRF(m_minCropValue < m_maxCropValue,
"Invalid crop values: minimum crop value [" << m_minCropValue << "] should be lower than the maximum crop value ["
<< m_maxCropValue << "]", Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmCrop::uninitialize()
{
delete m_matrix;
m_encoder->uninitialize();
m_decoder->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_encoder);
getAlgorithmManager().releaseAlgorithm(*m_decoder);
return true;
}
bool CBoxAlgorithmCrop::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmCrop::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
Kernel::TParameterHandler<const IMemoryBuffer*> iHandle(
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<IMemoryBuffer*> oHandle(m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
iHandle = boxContext.getInputChunk(0, i);
oHandle = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
double* buffer = m_matrix->getBuffer();
for (size_t j = 0; j < m_matrix->getBufferElementCount(); j++, buffer++)
{
if (*buffer < m_minCropValue && (m_cropMethod == ECropMethod::Min || m_cropMethod == ECropMethod::MinMax)) { *buffer = m_minCropValue; }
if (*buffer > m_maxCropValue && (m_cropMethod == ECropMethod::Max || m_cropMethod == ECropMethod::MinMax)) { *buffer = m_maxCropValue; }
}
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,98 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmCrop 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_Crop)
protected:
CMatrix* m_matrix = nullptr;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
double m_minCropValue = 0;
double m_maxCropValue = 0;
ECropMethod m_cropMethod = ECropMethod::MinMax;
};
class CBoxAlgorithmCropListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmCropDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Crop"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Truncates signal values to a specified range"); }
CString getDetailedDescription() const override { return CString("Minimum or maximum or both limits can be specified"); }
CString getCategory() const override { return CString("Signal processing/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_Crop; }
IPluginObject* create() override { return new CBoxAlgorithmCrop; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmCropListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input matrix", OV_TypeId_StreamedMatrix);
prototype.addOutput("Output matrix", OV_TypeId_StreamedMatrix);
prototype.addSetting("Crop method", OVP_TypeId_CropMethod, "MinMax");
prototype.addSetting("Min crop value", OV_TypeId_Float, "-1");
prototype.addSetting("Max crop value", OV_TypeId_Float, "1");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_FeatureVector);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_FeatureVector);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CropDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,146 @@
#include "ovpCBoxAlgorithmEpochAverage.h"
#include "../../algorithms/basic/ovpCAlgorithmMatrixAverage.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmEpochAverage::initialize()
{
CIdentifier inputTypeId;
getStaticBoxContext().getInputType(0, inputTypeId);
if (inputTypeId == OV_TypeId_StreamedMatrix || inputTypeId == OV_TypeId_TimeFrequency)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
}
else if (inputTypeId == OV_TypeId_FeatureVector)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorEncoder));
}
else if (inputTypeId == OV_TypeId_Signal)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
}
else if (inputTypeId == OV_TypeId_Spectrum)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
}
else { return false; }
m_decoder->initialize();
m_encoder->initialize();
m_matrixAverage = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_MatrixAverage));
m_matrixAverage->initialize();
if (inputTypeId == OV_TypeId_StreamedMatrix) { }
else if (inputTypeId == OV_TypeId_FeatureVector) { }
else if (inputTypeId == OV_TypeId_Signal)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
}
else if (inputTypeId == OV_TypeId_Spectrum)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling));
}
ip_averagingMethod.initialize(m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod));
ip_matrixCount.initialize(m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount));
ip_averagingMethod = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
ip_matrixCount = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1));
m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix)->setReferenceTarget(
m_matrixAverage->getOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix));
OV_ERROR_UNLESS_KRF(ip_matrixCount > 0, "Invalid number of epochs (expected value > 0)", Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmEpochAverage::uninitialize()
{
CIdentifier inputTypeID;
getStaticBoxContext().getInputType(0, inputTypeID);
if (inputTypeID == OV_TypeId_StreamedMatrix || inputTypeID == OV_TypeId_FeatureVector || inputTypeID == OV_TypeId_Signal ||
inputTypeID == OV_TypeId_Spectrum)
{
ip_averagingMethod.uninitialize();
ip_matrixCount.uninitialize();
m_matrixAverage->uninitialize();
m_encoder->uninitialize();
m_decoder->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_matrixAverage);
getAlgorithmManager().releaseAlgorithm(*m_encoder);
getAlgorithmManager().releaseAlgorithm(*m_decoder);
}
return true;
}
bool CBoxAlgorithmEpochAverage::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmEpochAverage::process()
{
Kernel::IBoxIO& boxContext = getDynamicBoxContext();
const size_t nInput = this->getStaticBoxContext().getInputCount();
for (size_t i = 0; i < nInput; ++i)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
Kernel::TParameterHandler<const IMemoryBuffer*> iMemoryBufferHandle(
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<IMemoryBuffer*> oMemoryBufferHandle(
m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
iMemoryBufferHandle = boxContext.getInputChunk(i, j);
oMemoryBufferHandle = boxContext.getOutputChunk(i);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
m_matrixAverage->process(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset);
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
m_matrixAverage->process(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix);
if (m_matrixAverage->isOutputTriggerActive(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
boxContext.markInputAsDeprecated(i, j);
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,104 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmEpochAverage 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_EpochAverage)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::IAlgorithmProxy* m_matrixAverage = nullptr;
Kernel::TParameterHandler<uint64_t> ip_matrixCount;
Kernel::TParameterHandler<uint64_t> ip_averagingMethod;
};
class CBoxAlgorithmEpochAverageListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmEpochAverageDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Epoch average"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Averages matrices among time, this can be used to enhance ERPs"); }
CString getDetailedDescription() const override
{
return CString("This box can average matrices of different types including signal, spectrum or feature vectors");
}
CString getCategory() const override { return CString("Signal processing/Averaging"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_EpochAverage; }
IPluginObject* create() override { return new CBoxAlgorithmEpochAverage(); }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmEpochAverageListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input epochs", OV_TypeId_StreamedMatrix);
prototype.addOutput("Averaged epochs", OV_TypeId_StreamedMatrix);
prototype.addSetting("Averaging type", OVP_TypeId_EpochAverageMethod, "Moving epoch average");
prototype.addSetting("Epoch count", OV_TypeId_Integer, "4");
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_FeatureVector);
prototype.addInputSupport(OV_TypeId_TimeFrequency);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_FeatureVector);
prototype.addOutputSupport(OV_TypeId_TimeFrequency);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_EpochAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,40 @@
#include "ovpCBoxAlgorithmIdentity.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
void CBoxAlgorithmIdentity::release() { delete this; }
bool CBoxAlgorithmIdentity::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmIdentity::process()
{
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
const size_t nInput = getBoxAlgorithmContext()->getStaticBoxContext()->getInputCount();
uint64_t tStart = 0;
uint64_t tEnd = 0;
size_t size = 0;
const uint8_t* buffer = nullptr;
for (size_t i = 0; i < nInput; ++i)
{
for (size_t j = 0; j < boxContext->getInputChunkCount(i); ++j)
{
boxContext->getInputChunk(i, j, tStart, tEnd, size, buffer);
boxContext->appendOutputChunkData(i, buffer, size);
boxContext->markOutputAsReadyToSend(i, tStart, tEnd);
boxContext->markInputAsDeprecated(i, j);
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,121 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmIdentity final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_Identity)
};
class CBoxAlgorithmIdentityListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
static bool check(Kernel::IBox& box)
{
size_t i;
for (i = 0; i < box.getInputCount(); ++i) { box.setInputName(i, ("Input stream " + std::to_string(i + 1)).c_str()); }
for (i = 0; i < box.getOutputCount(); ++i) { box.setOutputName(i, ("Output stream " + std::to_string(i + 1)).c_str()); }
return true;
}
bool onDefaultInitialized(Kernel::IBox& box) override
{
box.setInputType(0, OV_TypeId_Signal);
box.setOutputType(0, OV_TypeId_Signal);
return true;
}
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_Signal);
box.addOutput("", OV_TypeId_Signal, box.getUnusedInputIdentifier());
check(box);
return true;
}
bool onInputRemoved(Kernel::IBox& box, const size_t index) override
{
box.removeOutput(index);
check(box);
return true;
}
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputAdded(Kernel::IBox& box, const size_t index) override
{
box.setOutputType(index, OV_TypeId_Signal);
box.addInput("", OV_TypeId_Signal, box.getUnusedOutputIdentifier());
check(box);
return true;
}
bool onOutputRemoved(Kernel::IBox& box, const size_t index) override
{
box.removeInput(index);
check(box);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmIdentityDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Identity"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Duplicates input to output"); }
CString getDetailedDescription() const override { return CString("This simply duplicates intput on its output"); }
CString getCategory() const override { return CString("Signal processing/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_Identity; }
IPluginObject* create() override { return new CBoxAlgorithmIdentity(); }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmIdentityListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input stream", OV_TypeId_Signal);
prototype.addOutput("Output stream", OV_TypeId_Signal);
prototype.addFlag(Kernel::BoxFlag_CanAddOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_IdentityDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,132 @@
#include "ovpCBoxAlgorithmReferenceChannel.h"
#include <limits>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace {
size_t FindChannel(const CMatrix& matrix, const CString& channel, const EMatchMethod matchMethod, const size_t start = 0)
{
size_t res = std::numeric_limits<size_t>::max();
if (matchMethod == EMatchMethod::Name)
{
for (size_t i = start; i < matrix.getDimensionSize(0); ++i)
{
if (Toolkit::String::isAlmostEqual(matrix.getDimensionLabel(0, i), channel, false)) { res = i; }
}
}
else if (matchMethod == EMatchMethod::Index)
{
try
{
size_t value = std::stoul(channel.toASCIIString());
value--; // => makes it 0-indexed !
if (start <= size_t(value) && size_t(value) < matrix.getDimensionSize(0)) { res = size_t(value); }
}
catch (const std::exception&)
{
// catch block intentionnaly left blank
}
}
else if (matchMethod == EMatchMethod::Smart)
{
if (res == std::numeric_limits<size_t>::max()) { res = FindChannel(matrix, channel, EMatchMethod::Name, start); }
if (res == std::numeric_limits<size_t>::max()) { res = FindChannel(matrix, channel, EMatchMethod::Index, start); }
}
return res;
}
} // namespace
bool CBoxAlgorithmReferenceChannel::initialize()
{
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
return true;
}
bool CBoxAlgorithmReferenceChannel::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmReferenceChannel::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmReferenceChannel::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
if (m_decoder.isHeaderReceived())
{
CMatrix& iMatrix = *m_decoder.getOutputMatrix();
CMatrix& oMatrix = *m_encoder.getInputMatrix();
OV_ERROR_UNLESS_KRF(iMatrix.getDimensionSize(0) >= 2,
"Invalid input matrix with [" << iMatrix.getDimensionSize(0) << "] channels (expected channels >= 2)", Kernel::ErrorType::BadInput);
CString channel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const EMatchMethod method = EMatchMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
m_referenceChannelIdx = FindChannel(iMatrix, channel, method, 0);
OV_ERROR_UNLESS_KRF(m_referenceChannelIdx != std::numeric_limits<size_t>::max(), "Invalid channel [" << channel << "]: channel not found",
Kernel::ErrorType::BadSetting);
if (FindChannel(*m_decoder.getOutputMatrix(), channel, method, m_referenceChannelIdx + 1) != std::numeric_limits<size_t>::max())
{
OV_WARNING_K("Multiple channels match for setting [" << channel << "]. Selecting [" << m_referenceChannelIdx << "]");
}
oMatrix.resize(iMatrix.getDimensionSize(0) - 1, iMatrix.getDimensionSize(1));
for (size_t j = 0, k = 0; j < iMatrix.getDimensionSize(0); ++j)
{
if (j != m_referenceChannelIdx) { oMatrix.setDimensionLabel(0, k++, iMatrix.getDimensionLabel(0, j)); }
}
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived())
{
CMatrix& iMatrix = *m_decoder.getOutputMatrix();
CMatrix& oMatrix = *m_encoder.getInputMatrix();
double* iBuffer = iMatrix.getBuffer();
double* oBuffer = oMatrix.getBuffer();
double* refBuffer = iMatrix.getBuffer() + m_referenceChannelIdx * iMatrix.getDimensionSize(1);
const size_t nChannel = iMatrix.getDimensionSize(0);
const size_t nSample = iMatrix.getDimensionSize(1);
for (size_t j = 0; j < nChannel; ++j)
{
if (j != m_referenceChannelIdx)
{
for (size_t k = 0; k < nSample; ++k) { oBuffer[k] = iBuffer[k] - refBuffer[k]; }
oBuffer += nSample;
}
iBuffer += nSample;
}
m_encoder.encodeBuffer();
}
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,62 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmReferenceChannel 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_ReferenceChannel)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmReferenceChannel> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmReferenceChannel> m_encoder;
size_t m_referenceChannelIdx = 0;
};
class CBoxAlgorithmReferenceChannelDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Reference Channel"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Subtracts the value of the reference channel from all other channels"); }
CString getDetailedDescription() const override { return CString("Reference channel must be specified as a parameter for the box"); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ReferenceChannel; }
IPluginObject* create() override { return new CBoxAlgorithmReferenceChannel; }
// virtual IBoxListener* createBoxListener() const { return new CBoxAlgorithmReferenceChannelListener; }
// virtual void releaseBoxListener(IBoxListener* listener) const { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Channel", OV_TypeId_String, "Ref_Nose");
prototype.addSetting("Channel Matching Method", OVP_TypeId_MatchMethod, "Smart");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,188 @@
#include "ovpCBoxAlgorithmSignalDecimation.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmSignalDecimation::initialize()
{
m_decoder = nullptr;
m_encoder = nullptr;
m_decimationFactor = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
OV_ERROR_UNLESS_KRF(m_decimationFactor > 1, "Invalid decimation factor [" << m_decimationFactor << "] (expected value > 1)",
Kernel::ErrorType::BadSetting);
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_SignalDecoder_InputParameterId_MemoryBufferToDecode));
op_pMatrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Matrix));
op_sampling.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
m_encoder->initialize();
ip_sampling.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling));
ip_pMatrix.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Matrix));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_SignalEncoder_OutputParameterId_EncodedMemoryBuffer));
m_nChannel = 0;
m_iSampleIdx = 0;
m_iNSamplePerBlock = 0;
m_oSampling = 0;
m_oSampleIdx = 0;
m_oNSamplePerBlock = 0;
m_nTotalSample = 0;
m_startTimeBase = 0;
m_lastStartTime = 0;
m_lastEndTime = 0;
return true;
}
bool CBoxAlgorithmSignalDecimation::uninitialize()
{
op_buffer.uninitialize();
ip_pMatrix.uninitialize();
ip_sampling.uninitialize();
if (m_encoder)
{
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
}
op_sampling.uninitialize();
op_pMatrix.uninitialize();
ip_buffer.uninitialize();
if (m_decoder)
{
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
}
return true;
}
bool CBoxAlgorithmSignalDecimation::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSignalDecimation::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
const uint64_t tStart = boxContext.getInputChunkStartTime(0, i);
const uint64_t tEnd = boxContext.getInputChunkEndTime(0, i);
if (tStart != m_lastEndTime)
{
m_startTimeBase = tStart;
m_iSampleIdx = 0;
m_oSampleIdx = 0;
m_nTotalSample = 0;
}
m_lastStartTime = tStart;
m_lastEndTime = tEnd;
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedHeader))
{
m_iSampleIdx = 0;
m_iNSamplePerBlock = op_pMatrix->getDimensionSize(1);
m_iSampling = op_sampling;
OV_ERROR_UNLESS_KRF(m_iSampling%m_decimationFactor == 0,
"Failed to decimate: input sampling frequency [" << m_iSampling << "] not multiple of decimation factor [" <<
m_decimationFactor << "]", Kernel::ErrorType::BadSetting);
m_oSampleIdx = 0;
m_oNSamplePerBlock = size_t(m_iNSamplePerBlock / m_decimationFactor);
m_oNSamplePerBlock = (m_oNSamplePerBlock ? m_oNSamplePerBlock : 1);
m_oSampling = op_sampling / m_decimationFactor;
OV_ERROR_UNLESS_KRF(m_oSampling != 0, "Failed to decimate: output sampling frequency is 0", Kernel::ErrorType::BadOutput);
m_nChannel = op_pMatrix->getDimensionSize(0);
m_nTotalSample = 0;
ip_pMatrix->copyDescription(*op_pMatrix);
ip_pMatrix->setDimensionSize(1, m_oNSamplePerBlock);
ip_sampling = m_oSampling;
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeHeader);
ip_pMatrix->resetBuffer();
boxContext.markOutputAsReadyToSend(0, tStart, tStart); // $$$ supposes we have one node per chunk
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedBuffer))
{
double* iBuffer = op_pMatrix->getBuffer();
double* oBuffer = ip_pMatrix->getBuffer() + m_oSampleIdx;
for (size_t j = 0; j < m_iNSamplePerBlock; ++j)
{
double* iBufferTmp = iBuffer;
double* oBufferTmp = oBuffer;
for (size_t k = 0; k < m_nChannel; ++k)
{
*oBufferTmp += *iBufferTmp;
oBufferTmp += m_oNSamplePerBlock;
iBufferTmp += m_iNSamplePerBlock;
}
m_iSampleIdx++;
if (m_iSampleIdx == m_decimationFactor)
{
m_iSampleIdx = 0;
oBufferTmp = oBuffer;
for (size_t k = 0; k < m_nChannel; ++k)
{
*oBufferTmp /= m_decimationFactor;
oBufferTmp += m_oNSamplePerBlock;
}
oBuffer++;
m_oSampleIdx++;
if (m_oSampleIdx == m_oNSamplePerBlock)
{
oBuffer = ip_pMatrix->getBuffer();
m_oSampleIdx = 0;
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeBuffer);
const uint64_t tStartSample = m_startTimeBase + CTime(m_oSampling, m_nTotalSample).time();
const uint64_t tEndSample = m_startTimeBase + CTime(m_oSampling, m_nTotalSample + m_oNSamplePerBlock).time();
boxContext.markOutputAsReadyToSend(0, tStartSample, tEndSample);
m_nTotalSample += m_oNSamplePerBlock;
ip_pMatrix->resetBuffer();
}
}
iBuffer++;
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(0, tStart, tStart); // $$$ supposes we have one node per chunk
}
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,83 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSignalDecimation 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_SignalDecimation)
protected:
size_t m_decimationFactor = 0;
size_t m_nChannel = 0;
size_t m_iSampleIdx = 0;
size_t m_iNSamplePerBlock = 0;
size_t m_iSampling = 0;
size_t m_oSampleIdx = 0;
size_t m_oNSamplePerBlock = 0;
size_t m_oSampling = 0;
size_t m_nTotalSample = 0;
uint64_t m_startTimeBase = 0;
uint64_t m_lastStartTime = 0;
uint64_t m_lastEndTime = 0;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<CMatrix*> op_pMatrix;
Kernel::TParameterHandler<uint64_t> op_sampling;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<uint64_t> ip_sampling;
Kernel::TParameterHandler<CMatrix*> ip_pMatrix;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
};
class CBoxAlgorithmSignalDecimationDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Signal Decimation"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Reduces the sampling frequency to a divider of the original sampling frequency"); }
CString getDetailedDescription() const override
{
return CString("No pre filtering applied - Number of samples per block have to be a multiple of the decimation factor");
}
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SignalDecimation; }
IPluginObject* create() override { return new CBoxAlgorithmSignalDecimation; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Decimation factor", OV_TypeId_Integer, "8");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalDecimationDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,215 @@
/*********************************************************************
* Software License Agreement (AGPL-3 License)
*
* OpenViBE SDK
* Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015
* Copyright (C) Inria, 2015-2017,V1.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "ovpCBoxAlgorithmZeroCrossingDetector.h"
#include <vector>
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmZeroCrossingDetector::initialize()
{
m_encoder1.initialize(*this, 1);
m_encoder2.initialize(*this, 2);
m_hysteresis = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_windowTimeD = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
OV_ERROR_UNLESS_KRF(m_windowTimeD > 0, "Invalid negative number for window length", Kernel::ErrorType::BadSetting);
m_stimId1 = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
m_stimId2 = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
CIdentifier typeID;
this->getStaticBoxContext().getInputType(0, typeID);
if (typeID == OV_TypeId_Signal)
{
Toolkit::TSignalDecoder<CBoxAlgorithmZeroCrossingDetector>* decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmZeroCrossingDetector>(*this, 0);
Toolkit::TSignalEncoder<CBoxAlgorithmZeroCrossingDetector>* encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmZeroCrossingDetector>(*this, 0);
encoder->getInputSamplingRate().setReferenceTarget(decoder->getOutputSamplingRate());
m_decoder = decoder;
m_encoder0 = encoder;
}
else if (typeID == OV_TypeId_StreamedMatrix)
{
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmZeroCrossingDetector>* decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmZeroCrossingDetector>(*this, 0);
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmZeroCrossingDetector>* encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmZeroCrossingDetector>(*this, 0);
m_decoder = decoder;
m_encoder0 = encoder;
}
else { OV_ERROR_KRF("Invalid input type [" << typeID.str() << "]", Kernel::ErrorType::BadInput); }
return true;
}
bool CBoxAlgorithmZeroCrossingDetector::uninitialize()
{
m_encoder0.uninitialize();
m_encoder1.uninitialize();
m_encoder2.uninitialize();
m_decoder.uninitialize();
return true;
}
bool CBoxAlgorithmZeroCrossingDetector::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmZeroCrossingDetector::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
size_t j, k;
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
m_encoder1.getInputStimulationSet()->clear();
const size_t nChannel = m_decoder.getOutputMatrix()->getDimensionSize(0);
const size_t nSample = m_decoder.getOutputMatrix()->getDimensionSize(1);
if (m_decoder.isHeaderReceived())
{
m_encoder0.getInputMatrix()->copyDescription(*m_decoder.getOutputMatrix());
CMatrix* o2Matrix = m_encoder2.getInputMatrix();
o2Matrix->resize(nChannel, nSample);
m_signals.clear();
m_signals.resize(nChannel, 0);
m_states.clear();
m_states.resize(nChannel);
m_chunks.clear();
m_chunks.resize(nChannel);
m_samples.clear();
m_samples.resize(nChannel);
m_nChunk = 0;
m_encoder0.encodeHeader();
m_encoder1.encodeHeader();
m_encoder2.encodeHeader();
}
if (m_decoder.isBufferReceived())
{
if (m_nChunk == 0)
{
m_sampling = nSample * size_t((1LL << 32) / (boxContext.getInputChunkEndTime(0, i) - boxContext.getInputChunkStartTime(0, i)));
m_windowTime = size_t(m_windowTimeD * m_sampling);
}
double* iBuffer = m_decoder.getOutputMatrix()->getBuffer();
double* oBuffer0 = m_encoder0.getInputMatrix()->getBuffer();
double* oBuffer2 = m_encoder2.getInputMatrix()->getBuffer();
// ZC detector, with hysteresis
std::vector<double> signals(nSample + 1, 0);
for (j = 0; j < nChannel; ++j)
{
// signal, with the last sample of the previous chunk
signals[0] = m_signals[j];
for (k = 0; k < nSample; ++k) { signals[k + 1] = iBuffer[k + j * nSample]; }
m_signals[j] = signals.back();
if (m_nChunk == 0) { m_states[j] = (signals[1] >= 0) ? 1 : -1; }
for (k = 0; k < nSample; ++k)
{
uint64_t stimulationDate;
if (m_sampling > 0) { stimulationDate = boxContext.getInputChunkStartTime(0, i) + CTime(m_sampling, k).time(); }
else if (nSample == 1) { stimulationDate = boxContext.getInputChunkEndTime(0, i); }
else
{
OV_ERROR_KRF("Can only process chunks with sampling rate larger or equal to 1 or chunks with exactly one sample.",
Kernel::ErrorType::OutOfBound);
}
if ((m_states[j] == 1) && (signals[k] > -m_hysteresis) && (signals[k + 1] < -m_hysteresis))
{
// negative ZC : positive-to-negative
oBuffer0[k + j * nSample] = -1;
m_encoder1.getInputStimulationSet()->appendStimulation(m_stimId2, stimulationDate, 0);
m_states[j] = -1;
}
else if ((m_states[j] == -1) && (signals[k] < m_hysteresis) && (signals[k + 1] > m_hysteresis))
{
// positive ZC : negative-to-positive
oBuffer0[k + j * nSample] = 1;
m_encoder1.getInputStimulationSet()->appendStimulation(m_stimId1, stimulationDate, 0);
m_states[j] = 1;
// for the rythm estimation
m_chunks[j].push_back(m_nChunk);
m_samples[j].push_back(k);
}
else { oBuffer0[k + j * nSample] = 0; }
}
}
// rythm estimation, in events per min
for (j = 0; j < nChannel; ++j)
{
int compt = 0;
// supression of peaks older than m_windowTime by decreasing indices, to avoid overflow
for (size_t index = m_chunks[j].size(); index >= 1; index--)
{
const size_t kk = index - 1;
if (((m_nChunk + 1) * nSample - (m_samples[j][kk] + m_chunks[j][kk] * nSample)) < m_windowTime) { compt += 1; }
else
{
m_samples[j].erase(m_samples[j].begin() + kk);
m_chunks[j].erase(m_chunks[j].begin() + kk);
}
}
for (k = 0; k < nSample; ++k) { oBuffer2[k + j * nSample] = 60.0 * compt / m_windowTimeD; }
}
m_nChunk++;
m_encoder0.encodeBuffer();
m_encoder1.encodeBuffer();
m_encoder2.encodeBuffer();
}
if (m_decoder.isEndReceived())
{
m_encoder0.encodeEnd();
m_encoder1.encodeEnd();
m_encoder2.encodeEnd();
}
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markOutputAsReadyToSend(1, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markOutputAsReadyToSend(2, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,168 @@
/*********************************************************************
* Software License Agreement (AGPL-3 License)
*
* OpenViBE SDK
* Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015
* Copyright (C) Inria, 2015-2017,V1.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmZeroCrossingDetector 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_ZeroCrossingDetector)
protected:
Toolkit::TGenericDecoder<CBoxAlgorithmZeroCrossingDetector> m_decoder;
Toolkit::TGenericEncoder<CBoxAlgorithmZeroCrossingDetector> m_encoder0;
Toolkit::TStimulationEncoder<CBoxAlgorithmZeroCrossingDetector> m_encoder1;
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmZeroCrossingDetector> m_encoder2;
std::vector<double> m_signals;
std::vector<int> m_states;
double m_hysteresis = 0;
uint64_t m_nChunk = 0;
size_t m_sampling = 0;
double m_windowTimeD = 0;
size_t m_windowTime = 0;
std::vector<std::vector<size_t>> m_chunks;
std::vector<std::vector<size_t>> m_samples;
uint64_t m_stimId1 = 0;
uint64_t m_stimId2 = 0;
};
class CBoxAlgorithmZeroCrossingDetectorListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
return onConnectorTypeChanged(box, index, typeID, false);
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
return onConnectorTypeChanged(box, index, typeID, true);
}
static bool onConnectorTypeChanged(Kernel::IBox& box, const size_t index, const CIdentifier& typeID, const bool outputChanged)
{
if (index == 0)
{
if (typeID == OV_TypeId_Signal)
{
box.setInputType(0, OV_TypeId_Signal);
box.setOutputType(0, OV_TypeId_Signal);
}
else if (typeID == OV_TypeId_StreamedMatrix)
{
box.setInputType(0, OV_TypeId_StreamedMatrix);
box.setOutputType(0, OV_TypeId_StreamedMatrix);
}
else
{
// Invalid i/o type identifier
CIdentifier originalTypeID = CIdentifier::undefined();
if (outputChanged)
{
// Restores output
box.getInputType(0, originalTypeID);
box.setOutputType(0, originalTypeID);
}
else
{
// Restores input
box.getOutputType(0, originalTypeID);
box.setInputType(0, originalTypeID);
}
}
}
if (index == 1) { box.setOutputType(1, OV_TypeId_Stimulations); }
if (index == 2) { box.setOutputType(2, OV_TypeId_StreamedMatrix); }
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmZeroCrossingDetectorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Zero-Crossing Detector"); }
CString getAuthorName() const override { return CString("Quentin Barthelemy"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Detects zero-crossings of the signal"); }
CString getDetailedDescription() const override
{
return CString(
"Detects zero-crossings of the signal for each channel, with 1 for positive zero-crossings (negative-to-positive), -1 for negatives ones (positive-to-negative), 0 otherwise. For all channels, stimulations mark positive and negatives zero-crossings. For each channel, the rythm is computed in events per min.");
}
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ZeroCrossingDetector; }
IPluginObject* create() override { return new CBoxAlgorithmZeroCrossingDetector; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmZeroCrossingDetectorListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Zero-crossing signal", OV_TypeId_Signal);
prototype.addOutput("Zero-crossing stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Events rythm (per min)", OV_TypeId_StreamedMatrix);
prototype.addSetting("Hysteresis threshold", OV_TypeId_Float, "0.01");
prototype.addSetting("Rythm estimation window (in sec)", OV_TypeId_Float, "10");
prototype.addSetting("Negative-to-positive stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_ThresholdPassed_Positive");
prototype.addSetting("Positive-to-negative stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_ThresholdPassed_Negative");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ZeroCrossingDetectorDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,253 @@
#include <cmath>
#include <algorithm>
#include "ovpCBoxAlgorithmStimulationBasedEpoching.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
static const int INPUT_SIGNAL_IDX = 0;
static const int INPUT_STIMULATIONS_IDX = 1;
static const int OUTPUT_SIGNAL_IDX = 0;
bool CBoxAlgorithmStimulationBasedEpoching::initialize()
{
m_epochDurationInSeconds = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const double epochOffset = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_stimulationID = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
m_epochDuration = CTime(m_epochDurationInSeconds).time();
const int epochOffsetSign = (epochOffset > 0) - (epochOffset < 0);
m_epochOffset = epochOffsetSign * int64_t(CTime(std::fabs(epochOffset)).time());
m_lastReceivedStimulationDate = 0;
m_lastStimulationChunkStartTime = 0;
m_lastSignalChunkEndTime = 0;
m_signalDecoder.initialize(*this, 0);
m_stimDecoder.initialize(*this, 1);
m_encoder.initialize(*this, 0);
m_encoder.getInputSamplingRate().setReferenceTarget(m_signalDecoder.getOutputSamplingRate());
m_nChannel = 0;
m_sampling = 0;
m_cachedChunks.clear();
OV_ERROR_UNLESS_KRF(m_epochDurationInSeconds > 0,
"Epocher setting is invalid. Duration (= " << m_epochDurationInSeconds << ") must have a strictly positive value.",
Kernel::ErrorType::Internal);
return true;
}
bool CBoxAlgorithmStimulationBasedEpoching::uninitialize()
{
m_signalDecoder.uninitialize();
m_encoder.uninitialize();
m_stimDecoder.uninitialize();
m_cachedChunks.clear();
return true;
}
bool CBoxAlgorithmStimulationBasedEpoching::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmStimulationBasedEpoching::process()
{
Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();
for (size_t chunk = 0; chunk < boxCtx.getInputChunkCount(INPUT_SIGNAL_IDX); ++chunk)
{
OV_ERROR_UNLESS_KRF(m_signalDecoder.decode(chunk), "Failed to decode chunk", Kernel::ErrorType::Internal);
CMatrix* iMatrix = m_signalDecoder.getOutputMatrix();
uint64_t iChunkStartTime = boxCtx.getInputChunkStartTime(INPUT_SIGNAL_IDX, chunk);
uint64_t iChunkEndTime = boxCtx.getInputChunkEndTime(INPUT_SIGNAL_IDX, chunk);
if (m_signalDecoder.isHeaderReceived())
{
CMatrix* oMatrix = m_encoder.getInputMatrix();
m_nChannel = iMatrix->getDimensionSize(0);
m_nSamplePerInputBuffer = iMatrix->getDimensionSize(1);
m_sampling = m_signalDecoder.getOutputSamplingRate();
OV_ERROR_UNLESS_KRZ(m_sampling, Kernel::LogLevel_Error << "Input sampling frequency is equal to 0. Plugin can not process.",
Kernel::ErrorType::Internal);
m_nSampleCountOutputEpoch = size_t(CTime(m_epochDurationInSeconds).toSampleCount(m_sampling));
oMatrix->resize(m_nChannel, m_nSampleCountOutputEpoch);
for (size_t channel = 0; channel < m_nChannel; ++channel) { oMatrix->setDimensionLabel(0, channel, iMatrix->getDimensionLabel(0, channel)); }
m_encoder.encodeHeader();
boxCtx.markOutputAsReadyToSend(OUTPUT_SIGNAL_IDX, 0, 0);
}
if (m_signalDecoder.isBufferReceived())
{
OV_ERROR_UNLESS_KRF((iChunkStartTime >= m_lastSignalChunkEndTime), "Stimulation Based Epoching can not work on overlapping signal",
Kernel::ErrorType::Internal);
// Cache the signal data
m_cachedChunks.emplace_back(iChunkStartTime, iChunkEndTime, new CMatrix());
m_cachedChunks.back().matrix->copy(*iMatrix);
m_lastSignalChunkEndTime = iChunkEndTime;
}
if (m_signalDecoder.isEndReceived())
{
m_encoder.encodeEnd();
boxCtx.markOutputAsReadyToSend(OUTPUT_SIGNAL_IDX, iChunkStartTime, iChunkEndTime);
}
}
for (size_t chunk = 0; chunk < boxCtx.getInputChunkCount(INPUT_STIMULATIONS_IDX); ++chunk)
{
m_stimDecoder.decode(chunk);
// We only handle buffers and ignore stimulation headers and ends
if (m_stimDecoder.isBufferReceived())
{
for (size_t stimulation = 0; stimulation < m_stimDecoder.getOutputStimulationSet()->getStimulationCount(); ++stimulation)
{
if (m_stimDecoder.getOutputStimulationSet()->getStimulationIdentifier(stimulation) == m_stimulationID)
{
// Stimulations are put into cache, we ignore stimulations that would produce output chunks with negative start date (after applying the offset)
uint64_t date = m_stimDecoder.getOutputStimulationSet()->getStimulationDate(stimulation);
if (date < m_lastReceivedStimulationDate)
{
OV_WARNING_K(
"Skipping stimulation (received at date " << CTime(date) << ") that predates an already received stimulation (at date "
<< CTime(m_lastReceivedStimulationDate) << ")");
}
else if (int64_t(date) + m_epochOffset >= 0)
{
m_receivedStimulations.push_back(date);
m_lastReceivedStimulationDate = date;
}
}
m_lastStimulationChunkStartTime = boxCtx.getInputChunkEndTime(INPUT_STIMULATIONS_IDX, chunk);
}
}
}
// Process the received stimulations
uint64_t lastProcessedStimDate = 0;
for (const auto& stimDate : m_receivedStimulations)
{
const uint64_t epochStartTime = uint64_t(int64_t(stimDate) + m_epochOffset);
// No cache available
if (m_cachedChunks.empty()) { break; }
// During normal functioning only chunks that will no longer be useful are deprecated, this is to avoid failure in case of a bug
if (m_cachedChunks.front().startTime > epochStartTime)
{
OV_WARNING_K("Skipped creating an epoch on a timespan with no signal. The input signal probably contains non-contiguous chunks.");
break;
}
// We only process stimulations for which we have received enough signal to create an epoch
if (m_lastSignalChunkEndTime >= epochStartTime + m_epochDuration)
{
auto* oBuffer = m_encoder.getInputMatrix()->getBuffer();
size_t oBufferIdx = 0;
size_t idx = 0;
auto tStart = m_cachedChunks[idx].startTime;
auto tEnd = m_cachedChunks[idx].endTime;
// Find the first chunk that contains data interesting for the sent epoch
while (tStart > epochStartTime || tEnd < epochStartTime)
{
idx += 1;
if (idx == m_cachedChunks.size()) { break; }
tStart = m_cachedChunks[idx].startTime;
tEnd = m_cachedChunks[idx].endTime;
}
// If we have found a chunk that contains samples in the current epoch
if (idx != m_cachedChunks.size())
{
uint64_t iBufferIdx = CTime(epochStartTime - tStart).toSampleCount(m_sampling);
while (oBufferIdx < m_nSampleCountOutputEpoch)
{
const auto oTime = epochStartTime + CTime(m_sampling, oBufferIdx).time();
// If we handle non-dyadic sampling rates then we do not have a guarantee that all chunks will be
// dated with exact values. We add a bit of wiggle room around the incoming chunks to consider
// whether a sample is in them or not. This wiggle room will be of half of the sample duration
// on each side.
const uint64_t timeTolerance = CTime(m_sampling, 1).time() / 2;
if (iBufferIdx == m_nSamplePerInputBuffer)
{
// advance to beginning of the next cached chunk
idx += 1;
if (idx == m_cachedChunks.size()) { break; }
tStart = m_cachedChunks[idx].startTime;
tEnd = m_cachedChunks[idx].endTime;
iBufferIdx = 0;
// Case of non-consecutive chunks
if (tStart > oTime + timeTolerance) { break; }
}
else if (tStart <= oTime + timeTolerance && oTime <= tEnd + timeTolerance)
{
const auto& iBuffer = m_cachedChunks[idx].matrix->getBuffer();
for (size_t channel = 0; channel < m_nChannel; ++channel)
{
oBuffer[channel * m_nSampleCountOutputEpoch + oBufferIdx] = iBuffer[channel * m_nSamplePerInputBuffer + iBufferIdx];
}
oBufferIdx += 1;
iBufferIdx += 1;
}
else { OV_ERROR_KRF("Can not construct the output chunk due to internal error", Kernel::ErrorType::Internal); }
}
}
// If the epoch is not complete (due to holes in signal)
if (oBufferIdx == m_nSampleCountOutputEpoch)
{
m_encoder.encodeBuffer();
boxCtx.markOutputAsReadyToSend(OUTPUT_SIGNAL_IDX, epochStartTime, epochStartTime + m_epochDuration);
}
else { OV_WARNING_K("Skipped creating an epoch on a timespan with no signal. The input signal probably contains non-contiguous chunks."); }
lastProcessedStimDate = stimDate;
}
// We only process stimulations for which we have received enough signal to create an epoch
// No more complete epochs can be constructed
else { break; }
}
// Remove all stimulations for which the epochs have been constructed and sent
m_receivedStimulations.erase(std::remove_if(m_receivedStimulations.begin(), m_receivedStimulations.end(),
[&lastProcessedStimDate](const uint64_t& stimulationDate) { return stimulationDate <= lastProcessedStimDate; }),
m_receivedStimulations.end());
// Deprecate cached chunks which will no longer be used because they are too far back in history compared to received stimulations
const uint64_t lastUsefulChunkEndTime = m_receivedStimulations.empty() ? m_lastStimulationChunkStartTime : m_receivedStimulations.front();
auto cutoffTime = int64_t(lastUsefulChunkEndTime) + m_epochOffset;
if (cutoffTime > 0)
{
m_cachedChunks.erase(std::remove_if(m_cachedChunks.begin(), m_cachedChunks.end(), [cutoffTime](const SCachedChunk& chunk)
{
return chunk.endTime < uint64_t(cutoffTime);
}), m_cachedChunks.end());
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,110 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <deque>
#include <memory>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmStimulationBasedEpoching 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_StimulationBasedEpoching)
private:
Toolkit::TSignalDecoder<CBoxAlgorithmStimulationBasedEpoching> m_signalDecoder;
Toolkit::TStimulationDecoder<CBoxAlgorithmStimulationBasedEpoching> m_stimDecoder;
Toolkit::TSignalEncoder<CBoxAlgorithmStimulationBasedEpoching> m_encoder;
uint64_t m_stimulationID = 0;
double m_epochDurationInSeconds = 0;
uint64_t m_epochDuration = 0;
int64_t m_epochOffset = 0;
// Input matrix parameters
size_t m_sampling = 0;
size_t m_nSamplePerInputBuffer = 0;
// Output matrix dimensions
size_t m_nChannel = 0;
size_t m_nSampleCountOutputEpoch = 0;
uint64_t m_lastSignalChunkEndTime = 0;
uint64_t m_lastStimulationChunkStartTime = 0;
uint64_t m_lastReceivedStimulationDate = 0;
std::deque<uint64_t> m_receivedStimulations;
struct SCachedChunk
{
SCachedChunk(const uint64_t startTime, const uint64_t endTime, CMatrix* matrix)
: startTime(startTime), endTime(endTime), matrix(matrix) {}
SCachedChunk& operator=(SCachedChunk&& other)
{
this->startTime = other.startTime;
this->endTime = other.endTime;
this->matrix = std::move(other.matrix);
return *this;
}
uint64_t startTime;
uint64_t endTime;
std::unique_ptr<CMatrix> matrix;
};
std::deque<SCachedChunk> m_cachedChunks;
};
class CBoxAlgorithmStimulationBasedEpochingDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return "Stimulation based epoching"; }
CString getAuthorName() const override { return "Jozef Legeny"; }
CString getAuthorCompanyName() const override { return "Mensia Technologies"; }
CString getShortDescription() const override { return "Slices signal into chunks of a desired length following a stimulation event."; }
CString getDetailedDescription() const override { return "Slices signal into chunks of a desired length following a stimulation event."; }
CString getCategory() const override { return "Signal processing/Epoching"; }
CString getVersion() const override { return "2.0"; }
CString getSoftwareComponent() const override { return "openvibe-sdk"; }
CString getAddedSoftwareVersion() const override { return "0.0.0"; }
CString getUpdatedSoftwareVersion() const override { return "0.1.0"; }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_StimulationBasedEpoching; }
IPluginObject* create() override { return new CBoxAlgorithmStimulationBasedEpoching; }
CString getStockItemName() const override { return "gtk-cut"; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addInput("Input stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Epoched signal", OV_TypeId_Signal);
prototype.addSetting("Epoch duration (in sec)", OV_TypeId_Float, "1");
prototype.addSetting("Epoch offset (in sec)", OV_TypeId_Float, "0.5");
prototype.addSetting("Stimulation to epoch from", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_StimulationBasedEpochingDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,167 @@
#include "ovpCBoxAlgorithmTimeBasedEpoching.h"
#include <iostream>
#include <algorithm>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmTimeBasedEpoching::initialize()
{
m_duration = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_interval = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
OV_ERROR_UNLESS_KRF(m_duration>0 && m_interval>0,
"Epocher settings are invalid (duration:" << m_duration << "|interval:"
<< m_interval << "). These parameters should be strictly positive.", Kernel::ErrorType::Internal);
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
return true;
}
bool CBoxAlgorithmTimeBasedEpoching::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmTimeBasedEpoching::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmTimeBasedEpoching::process()
{
IDynamicBoxContext& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
OV_ERROR_UNLESS_KRF(m_decoder.decode(i), "Failed to decode chunk", Kernel::ErrorType::Internal);
CMatrix* iMatrix = m_decoder.getOutputMatrix();
CMatrix* oMatrix = m_encoder.getInputMatrix();
const size_t nChannel = iMatrix->getDimensionSize(0);
const size_t nISample = iMatrix->getDimensionSize(1);
if (m_decoder.isHeaderReceived())
{
m_lastInputEndTime = 0;
m_oSampleIdx = 0;
m_oChunkIdx = 0;
m_referenceTime = 0;
m_sampling = m_decoder.getOutputSamplingRate();
OV_ERROR_UNLESS_KRZ(m_sampling, "Input sampling frequency is equal to 0. Plugin can not process.", Kernel::ErrorType::Internal);
m_oNSample = size_t(m_duration * m_sampling); // sample count per output epoch
m_oNSampleBetweenEpoch = size_t(m_interval * m_sampling);
OV_ERROR_UNLESS_KRF(m_oNSample>0 && m_oNSampleBetweenEpoch>0,
"Input sampling frequency is [" << m_sampling << "]. This is too low in order to produce epochs of ["
<< m_duration << "] seconds with an interval of [" << m_interval << "] seconds.", Kernel::ErrorType::Internal);
oMatrix->resize(nChannel, m_oNSample);
for (size_t c = 0; c < nChannel; ++c) { oMatrix->setDimensionLabel(0, c, iMatrix->getDimensionLabel(0, c)); }
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, 0, 0);
}
if (m_decoder.isBufferReceived())
{
const uint64_t iTStart = boxContext.getInputChunkStartTime(0, i);
const uint64_t iTEnd = boxContext.getInputChunkEndTime(0, i);
if (m_lastInputEndTime != iTStart)
{
// reset
m_referenceTime = iTStart; // reference time = start time of the first chunk of the continuous stream of chunks
m_oSampleIdx = 0;
m_oChunkIdx = 0;
}
m_lastInputEndTime = iTEnd;
// **********************************
//
// Epoching
//
// **********************************
double* iBuffer = iMatrix->getBuffer();
double* oBuffer = oMatrix->getBuffer();
size_t sampleProcessed = 0;
// Iterates on bytes to process
while (sampleProcessed != nISample)
{
if (m_oSampleIdx < m_oNSample) // Some samples should be filled
{
// Copies samples to buffer
const size_t sampleToFill = std::min(m_oNSample - m_oSampleIdx, nISample - sampleProcessed);
for (size_t c = 0; c < nChannel; ++c)
{
memcpy(oBuffer + c * m_oNSample + m_oSampleIdx, iBuffer + c * nISample + sampleProcessed, sampleToFill * sizeof(double));
}
m_oSampleIdx += sampleToFill;
sampleProcessed += sampleToFill;
if (m_oSampleIdx == m_oNSample) // An epoch has been totally filled !
{
// Calculates start and end time of output
const uint64_t oTStart = m_referenceTime + CTime(m_sampling, m_oChunkIdx * m_oNSampleBetweenEpoch).time();
const uint64_t oTEnd = m_referenceTime + CTime(m_sampling, m_oChunkIdx * m_oNSampleBetweenEpoch + m_oNSample).time();
m_oChunkIdx++;
// Writes epoch
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, oTStart, oTEnd);
if (m_oNSampleBetweenEpoch < m_oNSample)
{
// Shifts samples for next epoch when overlap
const size_t samplesToSave = m_oNSample - m_oNSampleBetweenEpoch;
for (size_t c = 0; c < nChannel; ++c)
{
memmove(oBuffer + c * m_oNSample, oBuffer + c * m_oNSample + m_oNSample - samplesToSave, samplesToSave * sizeof(double));
}
// The counter can be reset
m_oSampleIdx = samplesToSave;
}
}
}
else
{
// The next few samples are useless: the stream of chunks is not continuous, we can remove the samples before the discontinuity
const size_t sampleToSkip = std::min(m_oNSampleBetweenEpoch - m_oSampleIdx, nISample - sampleProcessed);
m_oSampleIdx += sampleToSkip;
sampleProcessed += sampleToSkip;
if (m_oSampleIdx == m_oNSampleBetweenEpoch)
{
// The counter can be reset
m_oSampleIdx = 0;
}
}
}
}
if (m_decoder.isEndReceived())
{
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,71 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmTimeBasedEpoching 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_TimeBasedEpoching)
private:
Toolkit::TSignalDecoder<CBoxAlgorithmTimeBasedEpoching> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmTimeBasedEpoching> m_encoder;
double m_duration = 0;
double m_interval = 0;
size_t m_sampling = 0;
size_t m_oNSample = 0;
size_t m_oNSampleBetweenEpoch = 0;
size_t m_oSampleIdx = 0;
size_t m_oChunkIdx = 0;
uint64_t m_lastInputEndTime = 0;
uint64_t m_referenceTime = 0;
};
class CBoxAlgorithmTimeBasedEpochingDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Time based epoching"); }
CString getAuthorName() const override { return CString("Quentin Barthelemy"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Generates signal 'slices' or 'blocks' having a specified duration and interval"); }
CString getDetailedDescription() const override { return CString("Interval can be used to control the overlap of epochs"); }
CString getCategory() const override { return CString("Signal processing/Epoching"); }
CString getVersion() const override { return CString("2.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CString getStockItemName() const override { return CString("gtk-cut"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TimeBasedEpoching; }
IPluginObject* create() override { return new CBoxAlgorithmTimeBasedEpoching(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Epoched signal", OV_TypeId_Signal);
prototype.addSetting("Epoch duration (in sec)", OV_TypeId_Float, "1");
prototype.addSetting("Epoch intervals (in sec)", OV_TypeId_Float, "0.5");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,103 @@
#include "ovpCBoxAlgorithmCommonAverageReference.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmCommonAverageReference::initialize()
{
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_SignalDecoder_InputParameterId_MemoryBufferToDecode));
op_matrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Matrix));
op_sampling.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
m_encoder->initialize();
ip_matrix.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Matrix));
ip_sampling.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_SignalEncoder_OutputParameterId_EncodedMemoryBuffer));
op_matrix = &m_oMatrix;
ip_matrix = &m_oMatrix;
ip_sampling.setReferenceTarget(op_sampling);
return true;
}
bool CBoxAlgorithmCommonAverageReference::uninitialize()
{
op_buffer.uninitialize();
ip_sampling.uninitialize();
ip_matrix.uninitialize();
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
op_sampling.uninitialize();
op_matrix.uninitialize();
ip_buffer.uninitialize();
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
return true;
}
bool CBoxAlgorithmCommonAverageReference::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmCommonAverageReference::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedHeader))
{
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeHeader);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedBuffer))
{
const size_t nChannel = m_oMatrix.getDimensionSize(0), nSample = m_oMatrix.getDimensionSize(1);
for (size_t j = 0; j < nSample; ++j)
{
double* buffer = m_oMatrix.getBuffer() + j;
double sum = 0;
for (size_t c = nChannel; c != 0; c--)
{
sum += *buffer;
buffer += nSample;
}
const double mean = sum / nChannel;
for (size_t c = nChannel; c != 0; c--)
{
buffer -= nSample;
*buffer -= mean;
}
}
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeBuffer);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeEnd);
}
boxContext.markInputAsDeprecated(0, i);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,70 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmCommonAverageReference 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_CommonAverageReference)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<CMatrix*> op_matrix;
Kernel::TParameterHandler<uint64_t> op_sampling;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<CMatrix*> ip_matrix;
Kernel::TParameterHandler<uint64_t> ip_sampling;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
CMatrix m_oMatrix;
};
class CBoxAlgorithmCommonAverageReferenceDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Common Average Reference"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Re-reference the signal to common average reference"); }
CString getDetailedDescription() const override
{
return CString(
"Re-referencing the signal to common average reference consists in subtracting from each sample the average value of the samples of all electrodes at this time");
}
CString getCategory() const override { return CString("Signal processing/Spatial Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_CommonAverageReference; }
IPluginObject* create() override { return new CBoxAlgorithmCommonAverageReference; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,393 @@
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "ovpCBoxAlgorithmRegularizedCSPTrainer.h"
#include <sstream>
#include <cstdio>
#include <Eigen/Eigenvalues>
#include <fs/Files.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
// typedef Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > MatrixXdRowMajor;
bool CBoxAlgorithmRegularizedCSPTrainer::initialize()
{
m_stimDecoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_nClasses = this->getStaticBoxContext().getInputCount() - 1;
m_covProxies.resize(m_nClasses);
m_stimID = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_configFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_filtersPerClass = size_t(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)));
m_saveAsBoxConf = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
OV_ERROR_UNLESS_KRF(m_filtersPerClass > 0, // && m_filtersPerClass%2 == 0,
"Invalid filter dimension number [" << m_filtersPerClass << "] (expected value > 0)", // even ?
Kernel::ErrorType::BadSetting);
m_hasBeenInitialized = true;
m_signalDecoders.resize(m_nClasses);
for (size_t i = 0; i < m_nClasses; ++i)
{
m_signalDecoders[i].initialize(*this, i + 1);
const CIdentifier covAlgId = this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_OnlineCovariance);
OV_ERROR_UNLESS_KRF(covAlgId != CIdentifier::undefined(), "Failed to create online covariance algorithm", Kernel::ErrorType::BadResourceCreation);
m_covProxies[i].cov = &this->getAlgorithmManager().getAlgorithm(covAlgId);
OV_ERROR_UNLESS_KRF(m_covProxies[i].cov->initialize(), "Failed to initialize online covariance algorithm", Kernel::ErrorType::Internal);
// Set the params of the cov algorithm
Kernel::TParameterHandler<uint64_t> updateMethod(m_covProxies[i].cov->getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod));
Kernel::TParameterHandler<bool> traceNormalization(m_covProxies[i].cov->getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization));
Kernel::TParameterHandler<double> shrinkage(m_covProxies[i].cov->getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage));
updateMethod = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
traceNormalization = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
shrinkage = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 6);
}
m_tikhonov = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 7);
OV_ERROR_UNLESS_KRF(m_configFilename != CString(""), "Output filename is required in box configuration", Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmRegularizedCSPTrainer::uninitialize()
{
m_stimDecoder.uninitialize();
m_encoder.uninitialize();
if (m_hasBeenInitialized)
{
for (size_t i = 0; i < m_nClasses; ++i)
{
m_signalDecoders[i].uninitialize();
if (m_covProxies[i].cov)
{
m_covProxies[i].cov->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_covProxies[i].cov);
}
}
}
m_covProxies.clear();
return true;
}
bool CBoxAlgorithmRegularizedCSPTrainer::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmRegularizedCSPTrainer::updateCov(const size_t index)
{
Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();
SIncrementalCovarianceProxy& curCovProxy(m_covProxies[index]);
for (size_t i = 0; i < boxCtx.getInputChunkCount(index + 1); ++i)
{
auto* decoder = &m_signalDecoders[index];
const CMatrix* matrix = decoder->getOutputMatrix();
decoder->decode(i);
if (decoder->isHeaderReceived())
{
Kernel::TParameterHandler<CMatrix*> features(curCovProxy.cov->getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors));
features->resize(matrix->getDimensionSize(1), matrix->getDimensionSize(0));
OV_ERROR_UNLESS_KRF(m_filtersPerClass <= matrix->getDimensionSize(0),
"Invalid CSP filter dimension of [" << m_filtersPerClass << "] for stream " << i+1 <<
" (expected value must be less than input channel count ["<< matrix->getDimensionSize(1) <<"])",
Kernel::ErrorType::BadSetting);
curCovProxy.cov->activateInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Reset, true);
OV_ERROR_UNLESS_KRF(curCovProxy.cov->process(), "Failed to parametrize covariance algorithm", Kernel::ErrorType::Internal);
}
if (decoder->isBufferReceived())
{
Kernel::TParameterHandler<CMatrix*> features(curCovProxy.cov->getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors));
// transpose data
const size_t nChannels = matrix->getDimensionSize(0);
const size_t nSamples = matrix->getDimensionSize(1);
const Eigen::Map<MatrixXdRowMajor> inputMapper(const_cast<double*>(matrix->getBuffer()), nChannels, nSamples);
Eigen::Map<MatrixXdRowMajor> outputMapper(features->getBuffer(), nSamples, nChannels);
outputMapper = inputMapper.transpose();
curCovProxy.cov->activateInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Update, true);
curCovProxy.cov->process();
curCovProxy.nBuffers++;
curCovProxy.nSamples += nSamples;
}
// if (decoder->isEndReceived()) { } // nop
}
return true;
}
//
// Returns a sample-weighted average of given covariance matrices that does not include the cov of SkipIndex
//
// @todo error handling is a bit scarce
//
// @note This will recompute the weights on every call, but given how small amount of
// computations we're speaking of, there's not much point in optimizing.
//
bool CBoxAlgorithmRegularizedCSPTrainer::outclassCovAverage(const size_t skipIndex, const std::vector<Eigen::MatrixXd>& cov, Eigen::MatrixXd& covAvg)
{
if (cov.empty() || skipIndex >= cov.size()) { return false; }
std::vector<double> classWeights;
uint64_t totalOutclassSamples = 0;
// Compute the total number of samples
for (size_t i = 0; i < m_nClasses; ++i) { if (i != skipIndex) { totalOutclassSamples += m_covProxies[i].nSamples; } }
// Compute weigths for averaging
classWeights.resize(m_nClasses);
for (size_t i = 0; i < m_nClasses; ++i)
{
classWeights[i] = i == skipIndex ? 0 : m_covProxies[i].nSamples / double(totalOutclassSamples);
this->getLogManager() << Kernel::LogLevel_Debug << "Condition " << i + 1 << " averaging weight = " << classWeights[i] << "\n";
}
// Average the covs
covAvg.resizeLike(cov[0]);
covAvg.setZero();
for (size_t i = 0; i < m_nClasses; ++i) { covAvg += (classWeights[i] * cov[i]); }
return true;
}
bool CBoxAlgorithmRegularizedCSPTrainer::computeCSP(const std::vector<Eigen::MatrixXd>& cov, std::vector<Eigen::MatrixXd>& sortedEigenVectors,
std::vector<Eigen::VectorXd>& sortedEigenValues)
{
this->getLogManager() << Kernel::LogLevel_Info << "Compute CSP Begin\n";
// We wouldn't need to store all this -- they are kept for debugging purposes
std::vector<Eigen::VectorXd> eigenValues(m_nClasses);
std::vector<Eigen::MatrixXd> eigenVectors(m_nClasses), covInv(m_nClasses), covProd(m_nClasses);
Eigen::MatrixXd tikhonov, outclassCov;
tikhonov.resizeLike(cov[0]);
tikhonov.setIdentity();
tikhonov *= m_tikhonov;
sortedEigenVectors.resize(m_nClasses);
sortedEigenValues.resize(m_nClasses);
// To get the CSP filters, we compute two sets of eigenvectors, eig(inv(sigma2+tikhonov)*sigma1) and eig(inv(sigma1+tikhonov)*sigma2
// and pick the ones corresponding to the largest eigenvalues as spatial filters [following Lotte & Guan 2011]. Assumes the shrink
// of the sigmas (if its used) has been performed inside the cov computation algorithm.
Eigen::EigenSolver<Eigen::MatrixXd> solver;
for (size_t c = 0; c < m_nClasses; ++c)
{
try { covInv[c] = (cov[c] + tikhonov).inverse(); }
catch (...) { OV_ERROR_KRF("Inversion failed for condition [" << c + 1 << "]", Kernel::ErrorType::BadProcessing); }
// Compute covariance in all the classes except 'classIndex'.
OV_ERROR_UNLESS_KRF(outclassCovAverage(c, cov, outclassCov), "Outclass cov computation failed for condition [" << c + 1 << "]",
Kernel::ErrorType::BadProcessing);
covProd[c] = covInv[c] * outclassCov;
try { solver.compute(covProd[c]); }
catch (...) { OV_ERROR_KRF("EigenSolver failed for condition [" << c + 1 << "]", Kernel::ErrorType::BadProcessing); }
eigenValues[c] = solver.eigenvalues().real();
eigenVectors[c] = solver.eigenvectors().real();
// Sort the vectors -_-
std::vector<std::pair<double, int>> indexes;
indexes.reserve(eigenValues[c].size());
for (int i = 0; i < eigenValues[c].size(); ++i) { indexes.emplace_back(std::make_pair((eigenValues[c])[i], i)); }
sort(indexes.begin(), indexes.end(), std::greater<std::pair<double, int>>());
sortedEigenValues[c].resizeLike(eigenValues[c]);
sortedEigenVectors[c].resizeLike(eigenVectors[c]);
for (int i = 0; i < eigenValues[c].size(); ++i)
{
sortedEigenValues[c][i] = eigenValues[c][indexes[i].second];
//@todo @FIXME This fonction work sometimes randomly
sortedEigenVectors[c].col(i) = eigenVectors[c].col(indexes[i].second);
}
}
return true;
}
bool CBoxAlgorithmRegularizedCSPTrainer::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
bool shouldTrain = false;
uint64_t date = 0, startTime = 0, endTime = 0;
// Handle input stimulations
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_stimDecoder.decode(i);
if (m_stimDecoder.isHeaderReceived())
{
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (m_stimDecoder.isBufferReceived())
{
const Kernel::TParameterHandler<IStimulationSet*> stimSet(m_stimDecoder.getOutputStimulationSet());
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
if (stimSet->getStimulationIdentifier(j) == m_stimID)
{
date = stimSet->getStimulationDate(stimSet->getStimulationCount() - 1);
startTime = boxContext.getInputChunkStartTime(0, i);
endTime = boxContext.getInputChunkEndTime(0, i);
shouldTrain = true;
break;
}
}
}
if (m_stimDecoder.isEndReceived()) { m_encoder.encodeEnd(); }
}
// Update all covs with the current data chunks (if any)
for (size_t i = 0; i < m_nClasses; ++i) { if (!updateCov(i)) { return false; } }
if (shouldTrain)
{
this->getLogManager() << Kernel::LogLevel_Info << "Received train stimulation - be patient\n";
const CMatrix* input = m_signalDecoders[0].getOutputMatrix();
const size_t nChannels = input->getDimensionSize(0);
this->getLogManager() << Kernel::LogLevel_Debug << "Computing eigen vector decomposition...\n";
// Get out the covariances
std::vector<Eigen::MatrixXd> cov(m_nClasses);
for (size_t i = 0; i < m_nClasses; ++i)
{
OV_ERROR_UNLESS_KRF(m_covProxies[i].nSamples >= 2,
"Invalid sample count of [" <<m_covProxies[i].nSamples << "] for condition number " << i << " (expected value > 2)",
Kernel::ErrorType::BadProcessing);
Kernel::TParameterHandler<CMatrix*> op_cov(m_covProxies[i].cov->getOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix));
// Get regularized cov
m_covProxies[i].cov->activateInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCov, true);
OV_ERROR_UNLESS_KRF(m_covProxies[i].cov->process(), "Failed to retrieve regularized covariance", Kernel::ErrorType::Internal);
const Eigen::Map<MatrixXdRowMajor> covMapper(op_cov->getBuffer(), nChannels, nChannels);
cov[i] = covMapper;
// Get vanilla cov
m_covProxies[i].cov->activateInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCovRaw, true);
OV_ERROR_UNLESS_KRF(m_covProxies[i].cov->process(), "Failed to retrieve vanilla covariance", Kernel::ErrorType::Internal);
}
// Sanity check
for (size_t i = 1; i < m_nClasses; ++i)
{
OV_ERROR_UNLESS_KRF(cov[i-1].rows() == cov[i].rows() && cov[i-1].cols() == cov[i].cols(),
"Mismatch between the number of channel in both input streams", Kernel::ErrorType::BadValue);
}
this->getLogManager() << Kernel::LogLevel_Info << "Data covariance dims are [" << cov[0].rows() << "x" << cov[0].cols()
<< "]. Number of samples per condition : \n";
for (size_t i = 0; i < m_nClasses; ++i)
{
this->getLogManager() << Kernel::LogLevel_Info << " cond " << i + 1 << " = " << m_covProxies[i].nBuffers
<< " chunks, sized " << input->getDimensionSize(1) << " -> " << m_covProxies[i].nSamples << " samples\n";
// this->getLogManager() << Kernel::LogLevel_Info << "Using shrinkage coeff " << m_Shrinkage << " ...\n";
}
// Compute the actual CSP using the obtained covariance matrices
std::vector<Eigen::MatrixXd> sortedVectors;
std::vector<Eigen::VectorXd> sortedValues;
OV_ERROR_UNLESS_KRF(computeCSP(cov, sortedVectors, sortedValues), "Failure when computing CSP", Kernel::ErrorType::BadProcessing);
// Create a CMatrix mapper that can spool the filters to a file
CMatrix selectedVectors;
selectedVectors.resize(m_filtersPerClass * m_nClasses, nChannels);
Eigen::Map<MatrixXdRowMajor> selectedVectorsMapper(selectedVectors.getBuffer(), m_filtersPerClass * m_nClasses, nChannels);
for (size_t c = 0; c < m_nClasses; ++c)
{
selectedVectorsMapper.block(c * m_filtersPerClass, 0, m_filtersPerClass, nChannels) = sortedVectors[c].block(0, 0, nChannels, m_filtersPerClass).
transpose();
this->getLogManager() << Kernel::LogLevel_Info << "The " << m_filtersPerClass << " filter(s) for cond " << c + 1 << " cover "
<< 100.0 * sortedValues[c].head(m_filtersPerClass).sum() / sortedValues[c].sum() << "% of corresp. eigenvalues\n";
}
if (m_saveAsBoxConf)
{
std::ofstream file;
file.open(m_configFilename.toASCIIString(), std::ofstream::binary);
OV_ERROR_UNLESS_KRF(file.is_open(), "Failed to open file located at [" << m_configFilename << "]", Kernel::ErrorType::BadFileRead);
file << "<OpenViBE-SettingsOverride>\n";
file << "\t<SettingValue>";
const size_t n = m_filtersPerClass * m_nClasses * nChannels;
for (size_t i = 0; i < n; ++i) { file << std::scientific << selectedVectors.getBuffer()[i]; }
file << "</SettingValue>\n";
file << "\t<SettingValue>" << m_filtersPerClass * m_nClasses << "</SettingValue>\n";
file << "\t<SettingValue>" << nChannels << "</SettingValue>\n";
file << "\t<SettingValue></SettingValue>\n";
file << "</OpenViBE-SettingsOverride>\n";
file.close();
}
else
{
for (size_t i = 0; i < selectedVectors.getDimensionSize(0); ++i)
{
std::stringstream label;
label << "Cond " << i / m_filtersPerClass + 1 << " filter " << i % m_filtersPerClass + 1;
selectedVectors.setDimensionLabel(0, i, label.str().c_str());
}
OV_ERROR_UNLESS_KRF(Toolkit::Matrix::saveToTextFile(selectedVectors, m_configFilename, 10),
"Failed to save file to location [" << m_configFilename << "]",
Kernel::ErrorType::BadFileWrite);
}
this->getLogManager() << Kernel::LogLevel_Info << "Regularized CSP Spatial filter trained successfully.\n";
// Clean data, so if there's a new train stimulation, we'll start again.
// @note possibly this should be a parameter in the future to allow incremental training
for (auto& c : m_covProxies)
{
c.cov->activateInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Reset, true);
c.nSamples = 0;
c.nBuffers = 0;
}
m_encoder.getInputStimulationSet()->clear();
m_encoder.getInputStimulationSet()->appendStimulation(OVTK_StimulationId_TrainCompleted, date, 0);
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEIGEN
@@ -0,0 +1,123 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "../../algorithms/basic/ovpCAlgorithmOnlineCovariance.h"
#include <Eigen/Eigenvalues>
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmRegularizedCSPTrainer 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;
protected:
bool updateCov(size_t index);
bool outclassCovAverage(size_t skipIndex, const std::vector<Eigen::MatrixXd>& cov, Eigen::MatrixXd& covAvg);
bool computeCSP(const std::vector<Eigen::MatrixXd>& cov, std::vector<Eigen::MatrixXd>& sortedEigenVectors,
std::vector<Eigen::VectorXd>& sortedEigenValues);
Toolkit::TStimulationDecoder<CBoxAlgorithmRegularizedCSPTrainer> m_stimDecoder;
std::vector<Toolkit::TSignalDecoder<CBoxAlgorithmRegularizedCSPTrainer>> m_signalDecoders;
Toolkit::TStimulationEncoder<CBoxAlgorithmRegularizedCSPTrainer> m_encoder;
uint64_t m_stimID = 0;
CString m_configFilename;
size_t m_filtersPerClass = 0;
bool m_saveAsBoxConf = false;
bool m_hasBeenInitialized = false;
double m_tikhonov = 0.0;
struct SIncrementalCovarianceProxy
{
Kernel::IAlgorithmProxy* cov = nullptr;
size_t nBuffers = 0;
size_t nSamples = 0;
};
std::vector<SIncrementalCovarianceProxy> m_covProxies;
size_t m_nClasses = 0;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_RegularizedCSPTrainer)
};
class CBoxAlgorithmRegularizedCSPTrainerListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputName(index, ("Signal condition " + std::to_string(index)).c_str());
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmRegularizedCSPTrainerDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Regularized CSP Trainer"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Computes Common Spatial Pattern filters with regularization"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_RegularizedCSPTrainer; }
IPluginObject* create() override { return new CBoxAlgorithmRegularizedCSPTrainer; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmRegularizedCSPTrainerListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
prototype.addInput("Signal condition 1", OV_TypeId_Signal);
prototype.addInput("Signal condition 2", OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addSetting("Train Trigger", OV_TypeId_Stimulation, "OVTK_GDF_End_Of_Session");
prototype.addSetting("Spatial filter configuration", OV_TypeId_Filename, "");
prototype.addSetting("Filters per condition", OV_TypeId_Integer, "2");
prototype.addSetting("Save filters as box config", OV_TypeId_Boolean, "false");
// Params of the cov algorithm; would be better to poll the params from the algorithm, however this is not straightforward to do
prototype.addSetting("Covariance update", OVP_TypeId_OnlineCovariance_UpdateMethod, "Chunk average");
prototype.addSetting("Trace normalization", OV_TypeId_Boolean, "false");
prototype.addSetting("Shrinkage coefficient", OV_TypeId_Float, "0.0");
prototype.addSetting("Tikhonov coefficient", OV_TypeId_Float, "0.0");
prototype.addOutput("Train-completed Flag", OV_TypeId_Stimulations);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_RegularizedCSPTrainerDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEIGEN
@@ -0,0 +1,250 @@
#include "ovpCBoxAlgorithmSpatialFilter.h"
#include <sstream>
#include <string>
#if defined TARGET_HAS_ThirdPartyEIGEN
#include <Eigen/Dense>
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
#endif
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
size_t CBoxAlgorithmSpatialFilter::loadCoefs(const CString& coefs, const char c1, const char c2, const size_t nRows, const size_t nCols)
{
// Count the number of entries
// @Note To avoid doing a ton of subsequent memory allocations (very slow on Windows debug builds), we first count the number of entries in the vector. If the file format had specified the vector dimension, we wouldn't have to do this step.
size_t count = 0;
const char* ptr = coefs.toASCIIString();
while (*ptr != 0)
{
// Skip separator characters
while (*ptr == c1 || *ptr == c2) { ptr++; }
if (*ptr == 0) { break; }
// Ok, we have reached something that is not NULL or separator, assume its a number
count++;
// Skip the normal characters
while (*ptr != c1 && *ptr != c2 && *ptr != 0) { ptr++; }
}
OV_ERROR_UNLESS_KRZ(count == nRows*nCols, "Invalid computed coefficients count [" << count << "] (expected " << nRows * nCols << " coefficients)",
Kernel::ErrorType::BadProcessing);
// Resize in one step for efficiency.
m_filterBank.resize(nRows, nCols);
double* filter = m_filterBank.getBuffer();
// Ok, convert to floats
ptr = coefs.toASCIIString();
size_t idx = 0;
while (*ptr != 0)
{
const size_t size = 1024;
char buffer[size];
// Skip separator characters
while (*ptr == c1 || *ptr == c2) { ptr++; }
if (*ptr == 0) { break; }
// Copy the normal characters, don't exceed buffer size
size_t i = 0;
while (*ptr != c1 && *ptr != c2 && *ptr != 0)
{
if (i < size - 1) { buffer[i++] = *ptr; }
else { break; }
ptr++;
}
buffer[i] = 0;
OV_ERROR_UNLESS_KRZ(idx < count, "Invalid parsed coefficient number [" << idx << "] (expected maximium " << count << " coefficients)",
Kernel::ErrorType::BadProcessing);
// Finally, convert
try { filter[idx] = std::stod(buffer); }
catch (const std::exception&)
{
const size_t row = idx / nRows + 1;
const size_t col = idx % nRows + 1;
OV_ERROR_KRZ("Failed to parse coefficient number [" << idx << "] at matrix positions [" << row << "," << col << "]", Kernel::ErrorType::BadProcessing);
}
idx++;
}
return idx;
}
bool CBoxAlgorithmSpatialFilter::initialize()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
m_decoder = nullptr;
m_encoder = nullptr;
CIdentifier id;
boxContext.getInputType(0, id);
if (id == OV_TypeId_StreamedMatrix)
{
m_decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSpatialFilter>(*this, 0);
m_encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmSpatialFilter>(*this, 0);
}
else if (id == OV_TypeId_Signal)
{
m_decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmSpatialFilter>(*this, 0);
m_encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmSpatialFilter>(*this, 0);
static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmSpatialFilter>*>(m_encoder)->getInputSamplingRate().setReferenceTarget(
static_cast<Toolkit::TSignalDecoder<CBoxAlgorithmSpatialFilter>*>(m_decoder)->getOutputSamplingRate());
}
else if (id == OV_TypeId_Spectrum)
{
m_decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmSpatialFilter>(*this, 0);
m_encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmSpatialFilter>(*this, 0);
static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmSpatialFilter>*>(m_encoder)->getInputFrequencyAbscissa().setReferenceTarget(
static_cast<Toolkit::TSpectrumDecoder<CBoxAlgorithmSpatialFilter>*>(m_decoder)->getOutputFrequencyAbscissa());
static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmSpatialFilter>*>(m_encoder)->getInputSamplingRate().setReferenceTarget(
static_cast<Toolkit::TSpectrumDecoder<CBoxAlgorithmSpatialFilter>*>(m_decoder)->getOutputSamplingRate());
}
else { OV_ERROR_KRF("Invalid input stream type [" << id.str() << "]", Kernel::ErrorType::BadInput); }
// If we have a filter file, use dimensions and coefficients from that. Otherwise, use box config params.
const CString filterFile = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
if (filterFile != CString(""))
{
OV_ERROR_UNLESS_KRF(Toolkit::Matrix::loadFromTextFile(m_filterBank, filterFile),
"Failed to load filter parameters from file at location [" << filterFile << "]", Kernel::ErrorType::BadFileRead);
OV_ERROR_UNLESS_KRF(m_filterBank.getDimensionCount() == 2,
"Invalid filter matrix in file " << filterFile << ": found [" << m_filterBank.getDimensionCount() <<
"] dimensions (expected 2 dimension)", Kernel::ErrorType::BadConfig);
#if defined(DEBUG)
Toolkit::Matrix::saveToTextFile(m_filterBank, this->getConfigurationManager().expand("${Path_UserData}/spatialfilter_debug.txt"));
#endif
}
else
{
const CString coefs = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
// The double cast is needed until FSettingValueAutoCast supports size_t.
const size_t nOChannels = size_t(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
const size_t nIChannels = size_t(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)));
const size_t nCoefs = loadCoefs(coefs, ' ', OV_Value_EnumeratedStringSeparator, nOChannels, nIChannels);
OV_ERROR_UNLESS_KRF(nCoefs == nOChannels * nIChannels,
"Invalid number of coefficients [" << nCoefs << "] (expected "<< nOChannels * nIChannels
<< " coefficients)", Kernel::ErrorType::BadConfig);
#if defined(DEBUG)
Toolkit::Matrix::saveToTextFile(m_filterBank, this->getConfigurationManager().expand("${Path_UserData}/spatialfilter_debug.txt"));
#endif
}
return true;
}
bool CBoxAlgorithmSpatialFilter::uninitialize()
{
if (m_decoder)
{
m_decoder->uninitialize();
delete m_decoder;
m_decoder = nullptr;
}
if (m_encoder)
{
m_encoder->uninitialize();
delete m_encoder;
m_encoder = nullptr;
}
return true;
}
bool CBoxAlgorithmSpatialFilter::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSpatialFilter::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder->decode(i);
if (m_decoder->isHeaderReceived())
{
// we can treat them all as matrix decoders as they all inherit from it
const CMatrix* iMatrix = (static_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSpatialFilter>*>(m_decoder))->getOutputMatrix();
const size_t nChannelIn = iMatrix->getDimensionSize(0);
const size_t nSampleIn = iMatrix->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(nChannelIn != 0 && nSampleIn != 0,
"Invalid matrix size with zero dimension on input [" << nChannelIn << " x " << nSampleIn << "]",
Kernel::ErrorType::BadConfig);
const size_t nChannelFilterIn = m_filterBank.getDimensionSize(1);
const size_t nChannelFilterOut = m_filterBank.getDimensionSize(0);
OV_ERROR_UNLESS_KRF(nChannelIn == nChannelFilterIn,
"Invalid input channel count [" << nChannelIn << "] (expected " << nChannelFilterIn << " channel count)",
Kernel::ErrorType::BadConfig);
CMatrix* oMatrix = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmSpatialFilter>*>(m_encoder)->getInputMatrix();
oMatrix->resize(nChannelFilterOut, nSampleIn);
// Name channels
for (size_t j = 0; j < oMatrix->getDimensionSize(0); ++j) { oMatrix->setDimensionLabel(0, j, ("sFiltered " + std::to_string(j)).c_str()); }
m_encoder->encodeHeader();
}
if (m_decoder->isBufferReceived())
{
const CMatrix* iMatrix = static_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSpatialFilter>*>(m_decoder)->getOutputMatrix();
CMatrix* oMatrix = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmSpatialFilter>*>(m_encoder)->getInputMatrix();
const double* in = iMatrix->getBuffer();
double* out = oMatrix->getBuffer();
const size_t nChannelIn = iMatrix->getDimensionSize(0);
const size_t nChannelOut = oMatrix->getDimensionSize(0);
const size_t nSample = iMatrix->getDimensionSize(1);
#if defined TARGET_HAS_ThirdPartyEIGEN
//@TODO check this part we only create matrix ?
const Eigen::Map<MatrixXdRowMajor> inMapper(const_cast<double*>(in), nChannelIn, nSample);
const Eigen::Map<MatrixXdRowMajor> filterMapper(m_filterBank.getBuffer(), m_filterBank.getDimensionSize(0), m_filterBank.getDimensionSize(1));
Eigen::Map<MatrixXdRowMajor> outMapper(out, nChannelOut, nSample);
outMapper = filterMapper * inMapper;
#else
const double* filter = m_filterBank.getBuffer();
memset(out, 0, nSample*nChannelOut*sizeof(double));
for (size_t j = 0; j < nChannelOut; ++j)
{
for (size_t k = 0; k < nChannelIn; ++k)
{
for (size_t l = 0; l < nSample; ++l) { out[j*nSample+l] += filter[j * nChannelIn + k] * in[k * nSample + l]; }
}
}
#endif
m_encoder->encodeBuffer();
}
if (m_decoder->isEndReceived()) { m_encoder->encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,107 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSpatialFilter 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_SpatialFilter)
protected:
Toolkit::TDecoder<CBoxAlgorithmSpatialFilter>* m_decoder = nullptr;
Toolkit::TEncoder<CBoxAlgorithmSpatialFilter>* m_encoder = nullptr;
CMatrix m_filterBank;
private:
// Loads the m_vCoefficient vector (representing a matrix) from the given string. c1 and c2 are separator characters between floats.
size_t loadCoefs(const CString& coefs, char c1, char c2, size_t nRows, size_t nCols);
};
class CBoxAlgorithmSpatialFilterListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(0, typeID);
box.setOutputType(0, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(0, typeID);
box.setInputType(0, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmSpatialFilterDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Spatial Filter"); }
CString getAuthorName() const override { return CString("Yann Renard, Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Maps M inputs to N outputs by multiplying the each input vector with a matrix"); }
CString getDetailedDescription() const override
{
return CString(
"The applied coefficient matrix must be specified as a box parameter. The filter processes each sample independently of the past samples.");
}
CString getCategory() const override { return CString("Signal processing/Filtering"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SpatialFilter; }
IPluginObject* create() override { return new CBoxAlgorithmSpatialFilter; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmSpatialFilterListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input Signal", OV_TypeId_Signal);
prototype.addOutput("Output Signal", OV_TypeId_Signal);
prototype.addSetting("Spatial Filter Coefficients", OV_TypeId_String, "1;0;0;0;0;1;0;0;0;0;1;0;0;0;0;1");
prototype.addSetting("Number of Output Channels", OV_TypeId_Integer, "4");
prototype.addSetting("Number of Input Channels", OV_TypeId_Integer, "4");
prototype.addSetting("Filter matrix file", OV_TypeId_Filename, "");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_Signal);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SpatialFilterDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,333 @@
#include "ovpCBoxAlgorithmTemporalFilter.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace {
typedef Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::BandPass<32>, 1, Dsp::DirectFormII> CButterworthBandPass;
typedef Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::BandStop<32>, 1, Dsp::DirectFormII> CButterworthBandStop;
typedef Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass<32>, 1, Dsp::DirectFormII> CButterworthHighPass;
typedef Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass<32>, 1, Dsp::DirectFormII> CButterworthLowPass;
std::shared_ptr<Dsp::Filter> createButterworthFilter(const EFilterType type, const size_t nSmooth)
{
switch (type)
{
case EFilterType::BandPass: return std::static_pointer_cast<Dsp::Filter>(std::make_shared<CButterworthBandPass>(int(nSmooth)));
case EFilterType::BandStop: return std::static_pointer_cast<Dsp::Filter>(std::make_shared<CButterworthBandStop>(int(nSmooth)));
case EFilterType::HighPass: return std::static_pointer_cast<Dsp::Filter>(std::make_shared<CButterworthHighPass>(int(nSmooth)));
case EFilterType::LowPass: return std::static_pointer_cast<Dsp::Filter>(std::make_shared<CButterworthLowPass>(int(nSmooth)));
default: return nullptr;
}
}
bool getButterworthParameters(Dsp::Params& parameters, const size_t frequency, const EFilterType type, const size_t order,
const double lowCut, const double highCut, const double /*ripple*/)
{
parameters[0] = double(frequency);
parameters[1] = double(order);
switch (type)
{
case EFilterType::BandPass:
case EFilterType::BandStop:
parameters[2] = .5 * (highCut + lowCut);
parameters[3] = 1. * (highCut - lowCut);
break;
case EFilterType::HighPass:
parameters[2] = lowCut;
break;
case EFilterType::LowPass:
parameters[2] = highCut;
break;
default:
return false;
}
return true;
}
/*
typedef Dsp::SmoothedFilterDesign<Dsp::ChebyshevI::Design::BandPass<4>, 1, Dsp::DirectFormII> CChebyshevBandPass;
typedef Dsp::SmoothedFilterDesign<Dsp::ChebyshevI::Design::BandStop<4>, 1, Dsp::DirectFormII> CChebyshevBandStop;
typedef Dsp::SmoothedFilterDesign<Dsp::ChebyshevI::Design::HighPass<4>, 1, Dsp::DirectFormII> CChebyshevHighPass;
typedef Dsp::SmoothedFilterDesign<Dsp::ChebyshevI::Design::LowPass<4>, 1, Dsp::DirectFormII> CChebyshevLowPass;
std::shared_ptr < Dsp::Filter > createChebishevFilter(size_t type, size_t nSmooth)
{
switch(type)
{
case EFilterType::BandPass: return std::shared_ptr < Dsp::Filter >(new CChebyshevBandPass(int(nSmooth)));
case EFilterType::BandStop: return std::shared_ptr < Dsp::Filter >(new CChebyshevBandStop(int(nSmooth)));
case EFilterType::HighPass: return std::shared_ptr < Dsp::Filter >(new CChebyshevHighPass(int(nSmooth)));
case EFilterType::LowPass: return std::shared_ptr < Dsp::Filter >(new CChebyshevLowPass(int(nSmooth)));
default:
break;
}
return NULL;
}
bool getChebishevParameters(Dsp::Params& params, size_t type, size_t sampling, size_t order, double lowCut, double highCut, double ripple)
{
params[0]=int(sampling);
params[1]=int(order);
switch(type)
{
case EFilterType::BandPass:
case EFilterType::BandStop:
params[2]=.5*(highCut+lowCut);
params[3]=1.*(highCut-lowCut);
params[4]=ripple;
break;
case EFilterType::HighPass:
params[2]=highCut; // TO CHECK : lowCut ?
params[3]=ripple;
break;
case EFilterType::LowPass:
params[2]=highCut;
params[3]=lowCut; // TO CHECK : ripple ?
break;
default:
return false;
}
return true;
}
*/
typedef bool (*fpGetParameters_t)(Dsp::Params& params, size_t sampling, EFilterType type, size_t order, double lowCut, double highCut, double ripple);
typedef std::shared_ptr<Dsp::Filter> (*fpCreateFilter_t)(EFilterType type, size_t nSmooth);
} // namespace
bool CBoxAlgorithmTemporalFilter::initialize()
{
m_method = EFilterMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0))); // cast Needed for x32
m_type = EFilterType(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1))); // cast Needed for x32
const int64_t order = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
m_lowCut = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
m_highCut = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
OV_ERROR_UNLESS_KRF(order >= 1, "Invalid filter order [" << order << "] (expected value >= 1)", Kernel::ErrorType::BadSetting);
m_order = size_t(order);
if (m_type == EFilterType::LowPass)
{
OV_ERROR_UNLESS_KRF(m_highCut > 0, "Invalid high cut-off frequency [" << m_highCut << "] (expected value > 0)", Kernel::ErrorType::BadSetting);
}
else if (m_type == EFilterType::HighPass)
{
OV_ERROR_UNLESS_KRF(m_lowCut > 0, "Invalid low cut-off frequency [" << m_lowCut << "] (expected value > 0)", Kernel::ErrorType::BadSetting);
}
else if (m_type == EFilterType::BandPass || m_type == EFilterType::BandStop)
{
OV_ERROR_UNLESS_KRF(m_lowCut >= 0, "Invalid low cut-off frequency [" << m_lowCut << "] (expected value >= 0)", Kernel::ErrorType::BadSetting);
OV_ERROR_UNLESS_KRF(m_highCut > 0, "Invalid high cut-off frequency [" << m_highCut << "] (expected value > 0)", Kernel::ErrorType::BadSetting);
OV_ERROR_UNLESS_KRF(m_highCut > m_lowCut,
"Invalid cut-off frequencies [" << m_lowCut << "," << m_highCut << "] (expected low frequency < high frequency)",
Kernel::ErrorType::BadSetting);
}
else { OV_ERROR_KRF("Invalid filter type", Kernel::ErrorType::BadSetting); }
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
m_encoder.getInputMatrix().setReferenceTarget(m_decoder.getOutputMatrix());
return true;
}
bool CBoxAlgorithmTemporalFilter::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmTemporalFilter::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmTemporalFilter::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
size_t j;
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
const size_t nChannel = m_decoder.getOutputMatrix()->getDimensionSize(0);
const size_t nSample = m_decoder.getOutputMatrix()->getDimensionSize(1);
if (m_decoder.isHeaderReceived())
{
if (m_type != EFilterType::LowPass) // verification for high-pass, band-pass and band-stop filters
{
OV_ERROR_UNLESS_KRF(m_lowCut <= m_decoder.getOutputSamplingRate()*.5,
"Invalid low cut-off frequency [" << m_lowCut << "] (expected value must meet nyquist criteria for sampling rate "
<< m_decoder.getOutputSamplingRate() << ")", Kernel::ErrorType::BadConfig);
}
if (m_type != EFilterType::HighPass) // verification for low-pass, band-pass and band-stop filters
{
OV_ERROR_UNLESS_KRF(m_highCut <= m_decoder.getOutputSamplingRate()*.5,
"Invalid high cut-off frequency [" << m_highCut << "] (expected value must meet nyquist criteria for sampling rate "
<< m_decoder.getOutputSamplingRate() << ")", Kernel::ErrorType::BadConfig);
}
m_filters.clear();
//m_vFilter2.clear();
fpGetParameters_t fpGetParameters;
fpCreateFilter_t fpCreateFilter;
if (m_method == EFilterMethod::Butterworth) // Butterworth
{
fpGetParameters = getButterworthParameters;
fpCreateFilter = createButterworthFilter;
}
else if (m_method == EFilterMethod::Chebyshev) // Chebyshev
{
OV_ERROR_KRF("Chebyshev method not implemented", Kernel::ErrorType::NotImplemented);
//fpGetParameters = getChebishevParameters;
//fpCreateFilter = createChebishevFilter;
}
else if (m_method == EFilterMethod::YuleWalker) // YuleWalker
{
OV_ERROR_KRF("YuleWalker method not implemented", Kernel::ErrorType::NotImplemented);
//fpGetParameters = getYuleWalkerParameters;
//fpCreateFilter = createYuleWalkerFilter;
}
else { OV_ERROR_KRF("Invalid filter method", Kernel::ErrorType::BadSetting); }
if (m_type == EFilterType::HighPass)
{
this->getLogManager() << Kernel::LogLevel_Debug << "Low cut frequency of the High pass filter : " << m_lowCut << "Hz\n";
}
if (m_type == EFilterType::LowPass)
{
this->getLogManager() << Kernel::LogLevel_Debug << "High cut frequency of the Low pass filter : " << m_highCut << "Hz\n";
}
const size_t frequency = m_decoder.getOutputSamplingRate();
const size_t nSmooth = 100 * frequency;
Dsp::Params params;
(*fpGetParameters)(params, frequency, m_type, m_order, m_lowCut, m_highCut, m_ripple);
for (j = 0; j < nChannel; ++j)
{
std::shared_ptr<Dsp::Filter> filter = (*fpCreateFilter)(m_type, nSmooth);
filter->setParams(params);
m_filters.push_back(filter);
/*std::shared_ptr < Dsp::Filter > filter2=(*fpCreateFilter)(m_type, nSmoothingSample);
l_pFilter2->setParams(filterParameters);
m_vFilter2.push_back(filter2);*/
}
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived())
{
double* buffer = m_decoder.getOutputMatrix()->getBuffer();
//"french cook" to reduce transient for bandpass and highpass filters
if (m_firstSamples.empty())
{
m_firstSamples.resize(nChannel, 0); //initialization to 0
if (m_type == EFilterType::BandPass || m_type == EFilterType::HighPass)
{
for (j = 0; j < nChannel; ++j)
{
m_firstSamples[j] = buffer[j * nSample]; //first value of the signal = DC offset
}
}
}
for (j = 0; j < nChannel; ++j)
{
//for bandpass and highpass filters, suppression of the value m_firstSamples = DC offset
//otherwise, no treatment, since m_firstSamples = 0
for (size_t k = 0; k < nSample; ++k) { buffer[k] -= m_firstSamples[j]; }
if (m_filters[j]) { m_filters[j]->process(int(nSample), &buffer); }
buffer += nSample;
}
m_encoder.encodeBuffer();
}
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
/*
//zero-phase filtering, with two different filters
void CBoxAlgorithmTemporalFilter::filtfilt2(std::shared_ptr < Dsp::Filter > pFilter1, std::shared_ptr < Dsp::Filter > pFilter2, size_t SampleCount, double* buffer)
{
size_t j;
//1rst filtering
pFilter1->process(SampleCount, &buffer);
//reversal of the buffer
for (j=0; j<SampleCount/2; ++j)
{
double temporalVar = buffer[j];
buffer[j] = buffer[SampleCount-1-j];
buffer[SampleCount-1-j] = temporalVar;
}
//2nd filtering
pFilter2->process(SampleCount, &buffer);
//reversal of the buffer
for (j=0; j<SampleCount/2; ++j)
{
double temporalVar = buffer[j];
buffer[j] = buffer[SampleCount-1-j];
buffer[SampleCount-1-j] = temporalVar;
}
}
//zero-phase filtering, with mirror signals on the edges
void CBoxAlgorithmTemporalFilter::filtfilt2mirror (Dsp::Filter* pFilter1, Dsp::Filter* pFilter2, size_t SampleCount, double* buffer)
{
size_t j;
//construction of mirror signals
size_t transientLength = std::min( 3*(pFilter1->getPoleZeros().size()-1), SampleCount/2 );
std::vector<double> tmp;
tmp.resize(SampleCount+2*transientLength);
for (j=0; j<transientLength; ++j)
{
tmp[j] = 2*buffer[0]-buffer[transientLength-j];
}
for (j=0; j<SampleCount; ++j)
{
tmp[j+transientLength] = buffer[j];
}
for (j=0; j<transientLength; ++j)
{
tmp[j+transientLength+SampleCount] = 2*buffer[SampleCount-1]-buffer[SampleCount-1-j-1];
}
SampleCount+=2*transientLength;
double* res;
res = &tmp[0];
//zero-phase filtering
filtfilt2 (pFilter1, pFilter2, SampleCount, res);
//central part of the buffer
for (j=0; j<SampleCount-2*transientLength; ++j)
{
buffer[j] = res[j+transientLength];
}
}
*/
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,85 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <memory>
#include <dsp-filters/Dsp.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmTemporalFilter 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_TemporalFilter)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmTemporalFilter> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmTemporalFilter> m_encoder;
EFilterMethod m_method = EFilterMethod::Butterworth;
EFilterType m_type = EFilterType::BandPass;
size_t m_order = 0;
double m_lowCut = 0;
double m_highCut = 0;
double m_ripple = 0; // for Chebyshev
std::vector<std::shared_ptr<Dsp::Filter>> m_filters;
//std::vector < std::shared_ptr < Dsp::Filter > > m_filters;
std::vector<double> m_firstSamples;
};
class CBoxAlgorithmTemporalFilterDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Temporal Filter"); }
CString getAuthorName() const override { return CString("Yann Renard & Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Temporal filtering based on various one-way IIR filter designs"); }
CString getDetailedDescription() const override
{
return CString("Applies a temporal filter, based on various one-way IIR filter designs, to the input stream.");
}
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TemporalFilter; }
IPluginObject* create() override { return new CBoxAlgorithmTemporalFilter; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Filter Method", OVP_TypeId_FilterMethod, "Butterworth");
prototype.addSetting("Filter Type", OVP_TypeId_FilterType, "Band Pass");
prototype.addSetting("Filter Order", OV_TypeId_Integer, "4");
prototype.addSetting("Low Cut-off Frequency (Hz)", OV_TypeId_Float, "1");
prototype.addSetting("High Cut-off Frequency (Hz)", OV_TypeId_Float, "40");
prototype.addSetting("Band Pass Ripple (dB)", OV_TypeId_Float, "0.5");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TemporalFilterDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,96 @@
#include "ovpCBoxAlgorithmSignalAverage.h"
#include <cmath>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
void CBoxAlgorithmSignalAverage::computeAverage()
{
const double* input = m_decoder.getOutputMatrix()->getBuffer();
double* output = m_encoder.getInputMatrix()->getBuffer();
const size_t nChannel = m_decoder.getOutputMatrix()->getDimensionSize(0);
const size_t nSample = m_decoder.getOutputMatrix()->getDimensionSize(1);
//computes and stores the average for each channel
for (size_t c = 0; c < nChannel; ++c)
{
double sum = 0;
for (size_t i = 0; i < nSample; ++i) { sum += input[(c * nSample) + i]; }
output[c] = sum / nSample;
}
}
bool CBoxAlgorithmSignalAverage::initialize()
{
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
return true;
}
bool CBoxAlgorithmSignalAverage::uninitialize()
{
m_encoder.uninitialize();
m_decoder.uninitialize();
return true;
}
bool CBoxAlgorithmSignalAverage::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSignalAverage::process()
{
IDynamicBoxContext* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
// Process input data
for (size_t i = 0; i < boxContext->getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
if (m_decoder.isHeaderReceived())
{
// Construct the properties of the output stream
const CMatrix* iMatrix = m_decoder.getOutputMatrix();
CMatrix* oMatrix = m_encoder.getInputMatrix();
// Sampling rate will be decimated in the output
const uint64_t iSampling = m_decoder.getOutputSamplingRate();
const size_t iSampleCount = iMatrix->getDimensionSize(1);
const uint64_t newSampling = uint64_t(ceil(double(iSampling) / double(iSampleCount)));
m_encoder.getInputSamplingRate() = newSampling;
// We keep the number of channels, but the output chunk size will be 1
oMatrix->resize(iMatrix->getDimensionSize(0), 1);
for (size_t j = 0; j < oMatrix->getDimensionSize(0); ++j) { oMatrix->setDimensionLabel(0, j, iMatrix->getDimensionLabel(0, j)); }
m_encoder.encodeHeader();
getBoxAlgorithmContext()->getDynamicBoxContext()->markOutputAsReadyToSend(0, 0, 0);
}
if (m_decoder.isBufferReceived())
{
const uint64_t tStart = boxContext->getInputChunkStartTime(0, i);
const uint64_t tEnd = boxContext->getInputChunkEndTime(0, i);
computeAverage();
m_encoder.encodeBuffer();
getBoxAlgorithmContext()->getDynamicBoxContext()->markOutputAsReadyToSend(0, tStart, tEnd);
}
// if (m_decoder.isEndReceived()) { } // NOP
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,66 @@
#pragma once
#include "../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include <string>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
/**
*/
class CBoxAlgorithmSignalAverage final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmSignalAverage() {}
void release() override {}
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SignalAverage)
protected:
void computeAverage();
// Needed to read the input and write the output
Toolkit::TSignalDecoder<CBoxAlgorithmSignalAverage> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmSignalAverage> m_encoder;
};
/**
* Description of the channel selection plugin
*/
class CSignalAverageDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Signal average"); }
CString getAuthorName() const override { return CString("Bruno Renier"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Computes the average of each input buffer."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Averaging"); }
CString getVersion() const override { return CString("0.5"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SignalAverage; }
IPluginObject* create() override { return new CBoxAlgorithmSignalAverage(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Filtered signal", OV_TypeId_Signal);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,232 @@
#include "ovpCBoxAlgorithmSimpleDSP.h"
#include <system/ovCTime.h>
#include <iostream>
#include <sstream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmSimpleDSP::initialize()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
m_variables = new double*[boxContext.getInputCount()];
OV_ERROR_UNLESS_KRF(m_variables, "Failed to allocate arrays of floats for [" << boxContext.getInputCount() << "] inputs", Kernel::ErrorType::BadAlloc);
const CString equation = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_parser = new CEquationParser(*this, m_variables, boxContext.getInputCount());
OV_ERROR_UNLESS_KRF(m_parser, "Failed to create equation parser", Kernel::ErrorType::BadAlloc);
OV_ERROR_UNLESS_KRF(m_parser->compileEquation(equation.toASCIIString()), "Failed to compile equation [" << equation << "]", Kernel::ErrorType::Internal);
m_equationType = m_parser->getTreeCategory();
m_equationParam = m_parser->getTreeParameter();
CIdentifier streamType;
boxContext.getOutputType(0, streamType);
OV_ERROR_UNLESS_KRF(this->getTypeManager().isDerivedFromStream(streamType, OV_TypeId_StreamedMatrix),
"Invalid output stream [" << streamType.str() << "] (expected stream must derive from OV_TypeId_StreamedMatrix)", Kernel::ErrorType::Internal);
if (streamType == OV_TypeId_StreamedMatrix)
{
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
m_encoder->initialize();
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
Kernel::IAlgorithmProxy* decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
decoder->initialize();
m_decoders.push_back(decoder);
}
}
else if (streamType == OV_TypeId_FeatureVector)
{
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorEncoder));
m_encoder->initialize();
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
Kernel::IAlgorithmProxy* decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorDecoder));
decoder->initialize();
m_decoders.push_back(decoder);
}
}
else if (streamType == OV_TypeId_Signal)
{
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
m_encoder->initialize();
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
Kernel::IAlgorithmProxy* decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
decoder->initialize();
Kernel::TParameterHandler<uint64_t> ip_sampling(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling));
Kernel::TParameterHandler<uint64_t> op_sampling(decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
ip_sampling.setReferenceTarget(op_sampling);
m_decoders.push_back(decoder);
}
}
else if (streamType == OV_TypeId_Spectrum)
{
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
m_encoder->initialize();
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
{
Kernel::IAlgorithmProxy* decoder = &this->getAlgorithmManager().getAlgorithm(
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
decoder->initialize();
Kernel::TParameterHandler<CMatrix*> op_CenterBands(m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa));
Kernel::TParameterHandler<CMatrix*> ip_CenterBands(decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
ip_CenterBands.setReferenceTarget(op_CenterBands);
decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling)->setReferenceTarget(
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling));
m_decoders.push_back(decoder);
}
}
else
{
OV_ERROR_KRF("Type [name=" << this->getTypeManager().getTypeName(streamType) << ":id=" << streamType.str() << "] not yet implemented",
Kernel::ErrorType::NotImplemented);
}
m_checkDates = this->getConfigurationManager().expandAsBoolean("${Plugin_SignalProcessing_SimpleDSP_CheckChunkDates}", true);
this->getLogManager() << Kernel::LogLevel_Trace << (m_checkDates ? "Checking chunk dates..." : "Not checking chunk dates !") << "\n";
return true;
}
bool CBoxAlgorithmSimpleDSP::uninitialize()
{
for (auto& d : m_decoders)
{
d->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*d);
}
m_decoders.clear();
if (m_encoder)
{
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
}
delete m_parser;
m_parser = nullptr;
delete [] m_variables;
m_variables = nullptr;
return true;
}
bool CBoxAlgorithmSimpleDSP::processInput(const size_t /*index*/)
{
IDynamicBoxContext& boxContext = this->getDynamicBoxContext();
const size_t nInput = this->getStaticBoxContext().getInputCount();
if (boxContext.getInputChunkCount(0) == 0) { return true; }
const uint64_t tStart = boxContext.getInputChunkStartTime(0, 0);
const uint64_t tEnd = boxContext.getInputChunkEndTime(0, 0);
for (size_t i = 1; i < nInput; ++i)
{
if (boxContext.getInputChunkCount(i) == 0) { return true; }
if (m_checkDates)
{
OV_ERROR_UNLESS_KRF(tStart == boxContext.getInputChunkStartTime(i, 0) || tEnd == boxContext.getInputChunkEndTime(i, 0),
"Invalid chunk dates (disable this error check by setting Plugin_SignalProcessing_SimpleDSP_CheckChunkDates to false)",
Kernel::ErrorType::BadInput);
}
}
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSimpleDSP::process()
{
IDynamicBoxContext& boxContext = this->getDynamicBoxContext();
const size_t nInput = this->getStaticBoxContext().getInputCount();
size_t nHeader = 0;
size_t nBuffer = 0;
size_t nEnd = 0;
Kernel::TParameterHandler<CMatrix*> ip_matrix(m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix));
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer(m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
m_matrices.clear();
op_buffer = boxContext.getOutputChunk(0);
for (size_t i = 0; i < nInput; ++i)
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_decoders[i]->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<CMatrix*> op_matrix(m_decoders[i]->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
ip_buffer = boxContext.getInputChunk(i, 0);
m_decoders[i]->process();
if (m_decoders[i]->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
if (i != 0)
{
OV_ERROR_UNLESS_KRF(m_matrices[0]->getBufferElementCount() == op_matrix->getBufferElementCount(),
"Invalid matrix dimension [" << m_matrices[0]->getBufferElementCount() << "] (expected value = "
<< op_matrix->getBufferElementCount() <<")", Kernel::ErrorType::BadValue);
}
nHeader++;
}
if (m_decoders[i]->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer)) { nBuffer++; }
if (m_decoders[i]->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd)) { nEnd++; }
m_matrices.push_back(op_matrix);
boxContext.markInputAsDeprecated(i, 0);
}
OV_ERROR_UNLESS_KRF((!nHeader || nHeader == nInput) && (!nBuffer || nBuffer == nInput) && (!nEnd || nEnd == nInput),
"Invalid stream structure", Kernel::ErrorType::BadValue);
if (nHeader)
{
ip_matrix->copyDescription(*m_matrices[0]);
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
}
if (nBuffer)
{
this->evaluate();
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
}
if (nEnd) { m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd); }
if (nHeader || nBuffer || nEnd) { boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, 0), boxContext.getInputChunkEndTime(0, 0)); }
return true;
}
void CBoxAlgorithmSimpleDSP::evaluate()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
for (size_t i = 0; i < boxContext.getInputCount(); ++i) { m_variables[i] = m_matrices[i]->getBuffer(); }
Kernel::TParameterHandler<CMatrix*> ip_pMatrix(m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix));
double* buffer = ip_pMatrix->getBuffer();
double* bufferEnd = ip_pMatrix->getBuffer() + ip_pMatrix->getBufferElementCount();
while (buffer != bufferEnd)
{
*buffer = m_parser->executeEquation();
for (size_t i = 0; i < boxContext.getInputCount(); ++i) { m_variables[i]++; }
buffer++;
}
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,122 @@
#pragma once
#include "../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include "../ovpCSimpleDSP/ovpCEquationParser.h"
#include <vector>
#include <cstdio>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSimpleDSP final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmSimpleDSP() { }
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
void evaluate();
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SimpleDSP)
protected:
std::vector<Kernel::IAlgorithmProxy*> m_decoders;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
std::vector<CMatrix*> m_matrices;
CEquationParser* m_parser = nullptr;
uint64_t m_equationType = OP_USERDEF;
double m_equationParam = 0;
double** m_variables = nullptr;
bool m_checkDates = false;
};
class CBoxAlgorithmSimpleDSPListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
char name[1024];
sprintf(name, "Input - %c", char('A' + index));
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(0, typeID);
box.setInputType(index, typeID);
box.setInputName(index, name);
return true;
}
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(0, typeID);
for (size_t i = 0; i < box.getInputCount(); ++i) { box.setInputType(i, typeID); }
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setOutputType(0, typeID);
for (size_t i = 0; i < box.getInputCount(); ++i) { box.setInputType(i, typeID); }
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmSimpleDSPDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Simple DSP"); }
CString getAuthorName() const override { return CString("Bruno Renier / Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA / IRISA"); }
CString getShortDescription() const override { return CString("Apply mathematical formulaes to matrices."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SimpleDSP; }
IPluginObject* create() override { return new CBoxAlgorithmSimpleDSP(); }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmSimpleDSPListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input - A", OV_TypeId_Signal);
prototype.addOutput("Output", OV_TypeId_Signal);
prototype.addSetting("Equation", OV_TypeId_String, "x");
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_FeatureVector);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_FeatureVector);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SimpleDSPDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,137 @@
#define _USE_MATH_DEFINES
#include <cmath>
#include "ovpCBoxAlgorithmWindowing.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmWindowing::initialize()
{
//reads the plugin settings
m_windowMethod = EWindowMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0)));
if (m_windowMethod != EWindowMethod::None && m_windowMethod != EWindowMethod::Hamming && m_windowMethod != EWindowMethod::Hanning
&& m_windowMethod != EWindowMethod::Hann && m_windowMethod != EWindowMethod::Blackman && m_windowMethod != EWindowMethod::Triangular
&& m_windowMethod != EWindowMethod::SquareRoot) { OV_ERROR_KRF("No valid windowing method set.\n", Kernel::ErrorType::BadSetting); }
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_encoder.getInputMatrix().setReferenceTarget(m_decoder.getOutputMatrix());
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
return true;
}
bool CBoxAlgorithmWindowing::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmWindowing::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmWindowing::process()
{
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
// Process input data
for (size_t i = 0; i < boxContext->getInputChunkCount(0); ++i)
{
const uint64_t startTime = boxContext->getInputChunkStartTime(0, i);
const uint64_t endTime = boxContext->getInputChunkEndTime(0, i);
m_decoder.decode(i);
CMatrix* matrix = m_decoder.getOutputMatrix();
if (m_decoder.isHeaderReceived())
{
/*
* Depending on the Window method, we compute the coefficient vector
* To be applied on each channel.
*/
m_windowCoefs.resize(matrix->getDimensionSize(1));
const size_t n = m_windowCoefs.size();
if (m_windowMethod == EWindowMethod::Hamming)
{
for (size_t k = 0; k < n; ++k) { m_windowCoefs[k] = 0.54 - 0.46 * cos(2. * M_PI * double(k) / (double(n) - 1.)); }
}
else if (m_windowMethod == EWindowMethod::Hann || m_windowMethod == EWindowMethod::Hanning)
{
for (size_t k = 0; k < n; ++k) { m_windowCoefs[k] = 0.5 * (1. - cos(2. * M_PI * double(k) / (double(n) - 1.))); }
}
else if (m_windowMethod == EWindowMethod::Blackman)
{
for (size_t k = 0; k < n; ++k)
{
m_windowCoefs[k] = 0.42 - 0.5 * cos(2. * M_PI * double(k) / (double(n) - 1.)) + 0.08 * cos(4. * M_PI * double(k) / (double(n) - 1.));
}
}
else if (m_windowMethod == EWindowMethod::Triangular)
{
/* from MATLAB implementation, as ITPP documentation seems to be flawed */
for (size_t k = 1; k <= (n + 1) / 2; ++k)
{
if (n % 2 == 1) { m_windowCoefs[k - 1] = double((2. * double(k)) / (double(n) + 1.)); }
else { m_windowCoefs[k - 1] = double((2. * double(k) - 1.) / double(n)); }
}
for (size_t k = n / 2 + 1; k <= n; ++k)
{
if (n % 2 == 1) { m_windowCoefs[k - 1] = double(2. - (2. * double(k)) / (double(n) + 1.)); }
else { m_windowCoefs[k - 1] = double(2. - (2. * double(k) - 1.) / double(n)); }
}
}
else if (m_windowMethod == EWindowMethod::SquareRoot)
{
for (size_t k = 1; k <= (n + 1) / 2; ++k)
{
if (n % 2 == 1) { m_windowCoefs[k - 1] = sqrt(2. * double(k) / (double(n) + 1.)); }
else { m_windowCoefs[k - 1] = sqrt((2. * double(k) - 1.) / double(n)); }
}
for (size_t k = n / 2 + 1; k <= n; ++k)
{
if (n % 2 == 1) { m_windowCoefs[k - 1] = sqrt(2. - (2. * double(k)) / (double(n) + 1.)); }
else { m_windowCoefs[k - 1] = sqrt(2. - (2. * double(k) - 1.) / double(n)); }
}
}
else if (m_windowMethod == EWindowMethod::None) { for (size_t k = 0; k < n; ++k) { m_windowCoefs[k] = 1; } }
else { OV_ERROR_KRF("The windows method chosen is not supported.\n", Kernel::ErrorType::BadSetting); }
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived())
{
/* We filter each channel with the window function */
for (size_t j = 0; j < matrix->getDimensionSize(0); ++j) // channels
{
for (size_t k = 0; k < matrix->getDimensionSize(1); ++k) // samples
{
matrix->getBuffer()[j * matrix->getDimensionSize(1) + k] *= m_windowCoefs[k];
}
}
m_encoder.encodeBuffer();
}
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext->markOutputAsReadyToSend(0, startTime, endTime);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,60 @@
#pragma once
#include "../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmWindowing 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_Windowing)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmWindowing> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmWindowing> m_encoder;
EWindowMethod m_windowMethod = EWindowMethod::None;
std::vector<double> m_windowCoefs;
};
class CBoxAlgorithmWindowingDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Windowing"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Applies a windowing function to the signal."); }
CString getDetailedDescription() const override { return CString("Applies a windowing function to the signal."); }
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_Windowing; }
IPluginObject* create() override { return new CBoxAlgorithmWindowing(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Window method", OVP_TypeId_WindowMethod, "Hamming");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_WindowingDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,282 @@
#include "ovpCBoxAlgorithmXDAWNTrainer.h"
#include "fs/Files.h"
#include <cstdio>
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
CBoxAlgorithmXDAWNTrainer::CBoxAlgorithmXDAWNTrainer() {}
bool CBoxAlgorithmXDAWNTrainer::initialize()
{
m_trainStimulationID = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_filterFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
OV_ERROR_UNLESS_KRF(m_filterFilename.length() != 0, "The filter filename is empty.\n", Kernel::ErrorType::BadSetting);
if (FS::Files::fileExists(m_filterFilename))
{
FILE* file = FS::Files::open(m_filterFilename, "wt");
OV_ERROR_UNLESS_KRF(file != nullptr, "The filter file exists but cannot be used.\n", Kernel::ErrorType::BadFileRead);
fclose(file);
}
const int filterDimension = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
OV_ERROR_UNLESS_KRF(filterDimension > 0, "The dimension of the filter must be strictly positive.\n", Kernel::ErrorType::OutOfBound);
m_filterDim = size_t(filterDimension);
m_saveAsBoxConfig = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
m_stimDecoder.initialize(*this, 0);
m_signalDecoder[0].initialize(*this, 1);
m_signalDecoder[1].initialize(*this, 2);
m_stimEncoder.initialize(*this, 0);
return true;
}
bool CBoxAlgorithmXDAWNTrainer::uninitialize()
{
m_stimDecoder.uninitialize();
m_signalDecoder[0].uninitialize();
m_signalDecoder[1].uninitialize();
m_stimEncoder.uninitialize();
return true;
}
bool CBoxAlgorithmXDAWNTrainer::processInput(const size_t index)
{
if (index == 0) { this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(); }
return true;
}
bool CBoxAlgorithmXDAWNTrainer::process()
{
Kernel::IBoxIO& dynamicBoxContext = this->getDynamicBoxContext();
bool train = false;
for (size_t i = 0; i < dynamicBoxContext.getInputChunkCount(0); ++i)
{
m_stimEncoder.getInputStimulationSet()->clear();
m_stimDecoder.decode(i);
if (m_stimDecoder.isHeaderReceived()) { m_stimEncoder.encodeHeader(); }
if (m_stimDecoder.isBufferReceived())
{
for (size_t j = 0; j < m_stimDecoder.getOutputStimulationSet()->getStimulationCount(); ++j)
{
const uint64_t stimulationId = m_stimDecoder.getOutputStimulationSet()->getStimulationIdentifier(j);
if (stimulationId == m_trainStimulationID)
{
train = true;
m_stimEncoder.getInputStimulationSet()->appendStimulation(
OVTK_StimulationId_TrainCompleted, m_stimDecoder.getOutputStimulationSet()->getStimulationDate(j), 0);
}
}
m_stimEncoder.encodeBuffer();
}
if (m_stimDecoder.isEndReceived()) { m_stimEncoder.encodeEnd(); }
dynamicBoxContext.markOutputAsReadyToSend(0, dynamicBoxContext.getInputChunkStartTime(0, i), dynamicBoxContext.getInputChunkEndTime(0, i));
}
if (train)
{
std::vector<size_t> erpSampleIndexes;
std::array<Eigen::MatrixXd, 2> X; // X[0] is session matrix, X[1] is averaged ERP
std::array<Eigen::MatrixXd, 2> C; // Covariance matrices
std::array<size_t, 2> n;
size_t nChannel = 0;
this->getLogManager() << Kernel::LogLevel_Info << "Received train stimulation...\n";
// Decodes input signals
for (size_t j = 0; j < 2; ++j)
{
n[j] = 0;
for (size_t i = 0; i < dynamicBoxContext.getInputChunkCount(j + 1); ++i)
{
Toolkit::TSignalDecoder<CBoxAlgorithmXDAWNTrainer>& decoder = m_signalDecoder[j];
decoder.decode(i);
CMatrix* matrix = decoder.getOutputMatrix();
nChannel = matrix->getDimensionSize(0);
const size_t nSample = matrix->getDimensionSize(1);
const size_t sampling = size_t(decoder.getOutputSamplingRate());
if (decoder.isHeaderReceived())
{
OV_ERROR_UNLESS_KRF(sampling > 0, "Input sampling frequency is equal to 0. Plugin can not process.\n", Kernel::ErrorType::OutOfBound);
OV_ERROR_UNLESS_KRF(nChannel > 0, "For condition " << j + 1 << " got no channel in signal stream.\n", Kernel::ErrorType::OutOfBound);
OV_ERROR_UNLESS_KRF(nSample > 0, "For condition " << j + 1 << " got no samples in signal stream.\n", Kernel::ErrorType::OutOfBound);
OV_ERROR_UNLESS_KRF(m_filterDim <= nChannel, "The filter dimension must not be superior than the channel count.\n", Kernel::ErrorType::OutOfBound);
if (!n[0]) // Initialize signal buffer (X[0]) only when receiving input signal header.
{
X[j].resize(nChannel, (dynamicBoxContext.getInputChunkCount(j + 1) - 1) * nSample);
}
else // otherwise, only ERP averaging buffer (X[1]) is reset
{
X[j] = Eigen::MatrixXd::Zero(nChannel, nSample);
}
}
if (decoder.isBufferReceived())
{
Eigen::MatrixXd A = Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>(
matrix->getBuffer(), nChannel, nSample);
switch (j)
{
case 0: // Session
X[j].block(0, n[j] * A.cols(), A.rows(), A.cols()) = A;
break;
case 1: // ERP
X[j] = X[j] + A; // Computes sumed ERP
// $$$ Assumes continuous session signal starting at date 0
{
size_t ERPSampleIndex = size_t(((dynamicBoxContext.getInputChunkStartTime(j + 1, i) >> 16) * sampling) >> 16);
erpSampleIndexes.push_back(ERPSampleIndex);
}
break;
default:
break;
}
n[j]++;
}
#if 0
if (decoder.isEndReceived())
{
}
#endif
}
OV_ERROR_UNLESS_KRF(n[j] != 0, "Did not have input signal for condition " << j + 1 << "\n", Kernel::ErrorType::BadValue);
switch (j)
{
case 0: // Session
break;
case 1: // ERP
X[j] = X[j] / double(n[j]); // Averages ERP
break;
default:
break;
}
}
// We need equal number of channels
OV_ERROR_UNLESS_KRF(X[0].rows() == X[1].rows(),
"Dimension mismatch, first input had " << size_t(X[0].rows()) << " channels while second input had " << size_t(X[1].rows()) <<
" channels\n",
Kernel::ErrorType::BadValue);
// Grabs usefull values
const size_t sampleCountSession = X[0].cols();
const size_t sampleCountERP = X[1].cols();
// Now we compute matrix D
const Eigen::MatrixXd DI = Eigen::MatrixXd::Identity(sampleCountERP, sampleCountERP);
Eigen::MatrixXd D = Eigen::MatrixXd::Zero(sampleCountERP, sampleCountSession);
for (size_t sampleIndex : erpSampleIndexes) { D.block(0, sampleIndex, sampleCountERP, sampleCountERP) += DI; }
// Computes covariance matrices
C[0] = X[0] * X[0].transpose();
C[1] = /*Y * Y.transpose();*/ X[1] * /* D.transpose() * */ (D * D.transpose()).fullPivLu().inverse() /* * D */ * X[1].transpose();
// Solves generalized eigen decomposition
const Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> eigenSolver(C[0].selfadjointView<Eigen::Lower>(), C[1].selfadjointView<Eigen::Lower>());
if (eigenSolver.info() != Eigen::Success)
{
const enum Eigen::ComputationInfo error = eigenSolver.info();
const char* errorMessage = "unknown";
switch (error)
{
case Eigen::NumericalIssue: errorMessage = "Numerical issue";
break;
case Eigen::NoConvergence: errorMessage = "No convergence";
break;
// case Eigen::InvalidInput: errorMessage="Invalid input"; break; // FIXME
default: break;
}
OV_ERROR_KRF("Could not solve generalized eigen decomposition, got error[" << CString(errorMessage) << "]\n",
Kernel::ErrorType::BadProcessing);
}
// Create a CMatrix mapper that can spool the filters to a file
CMatrix eigenVectors;
eigenVectors.resize(m_filterDim, nChannel);
Eigen::Map<MatrixXdRowMajor> vectorsMapper(eigenVectors.getBuffer(), m_filterDim, nChannel);
vectorsMapper.block(0, 0, m_filterDim, nChannel) = eigenSolver.eigenvectors().block(0, 0, nChannel, m_filterDim).transpose();
// Saves filters
FILE* file = FS::Files::open(m_filterFilename.toASCIIString(), "wt");
OV_ERROR_UNLESS_KRF(file != nullptr, "Could not open file [" << m_filterFilename << "] for writing.\n", Kernel::ErrorType::BadFileWrite);
if (m_saveAsBoxConfig)
{
fprintf(file, "<OpenViBE-SettingsOverride>\n");
fprintf(file, "\t<SettingValue>");
for (size_t i = 0; i < eigenVectors.getBufferElementCount(); ++i) { fprintf(file, "%e ", eigenVectors.getBuffer()[i]); }
fprintf(file, "</SettingValue>\n");
fprintf(file, "\t<SettingValue>%zu</SettingValue>\n", m_filterDim);
fprintf(file, "\t<SettingValue>%zu</SettingValue>\n", nChannel);
fprintf(file, "\t<SettingValue></SettingValue>\n");
fprintf(file, "</OpenViBE-SettingsOverride>");
}
else
{
OV_ERROR_UNLESS_KRF(Toolkit::Matrix::saveToTextFile(eigenVectors, m_filterFilename),
"Unable to save to [" << m_filterFilename << "]\n", Kernel::ErrorType::BadFileWrite);
}
OV_WARNING_UNLESS_K(fclose(file) == 0, "Could not close file[" << m_filterFilename << "].\n");
this->getLogManager() << Kernel::LogLevel_Info << "Training finished and saved to [" << m_filterFilename << "]!\n";
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,82 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <Eigen/Eigen>
#include <array>
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmXDAWNTrainer final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmXDAWNTrainer();
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_InriaXDAWNTrainer)
protected:
Toolkit::TStimulationDecoder<CBoxAlgorithmXDAWNTrainer> m_stimDecoder;
std::array<Toolkit::TSignalDecoder<CBoxAlgorithmXDAWNTrainer>, 2> m_signalDecoder;
Toolkit::TStimulationEncoder<CBoxAlgorithmXDAWNTrainer> m_stimEncoder;
uint64_t m_trainStimulationID = 0;
CString m_filterFilename;
size_t m_filterDim = 0;
bool m_saveAsBoxConfig = false;
};
class CBoxAlgorithmXDAWNTrainerDesc final : public IBoxAlgorithmDesc
{
public:
void release() override {}
CString getName() const override { return CString("xDAWN Trainer"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Trains spatial filters that best highlight Evoked Response Potentials (ERP) such as P300"); }
CString getDetailedDescription() const override
{
return CString("Trains spatial filters that best highlight Evoked Response Potentials (ERP) such as P300");
}
CString getCategory() const override { return CString("Signal processing/Spatial Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CString getStockItemName() const override { return CString("gtk-zoom-100"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_InriaXDAWNTrainer; }
IPluginObject* create() override { return new CBoxAlgorithmXDAWNTrainer; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
prototype.addInput("Session signal", OV_TypeId_Signal);
prototype.addInput("Evoked potential epochs", OV_TypeId_Signal);
prototype.addOutput("Train-completed Flag", OV_TypeId_Stimulations);
prototype.addSetting("Train stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_Train");
prototype.addSetting("Spatial filter configuration", OV_TypeId_Filename, "");
prototype.addSetting("Filter dimension", OV_TypeId_Integer, "4");
prototype.addSetting("Save as box config", OV_TypeId_Boolean, "true");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_InriaXDAWNTrainerDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,187 @@
/*********************************************************************
* Software License Agreement (AGPL-3 License)
*
* OpenViBE SDK
* Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015
* Copyright (C) Inria, 2015-2017,V1.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "ovpCBoxAlgorithmSignalResampling.h"
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace SigProSTD {
template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr,
typename std::enable_if<std::is_unsigned<T>::value>::type* = nullptr>
T gcd(T a, T b)
{
T t;
if (a > b) // ensure b > a
{
t = b;
b = a;
a = t;
}
while (b != 0)
{
t = a % b;
a = b;
b = t;
}
return a;
}
} // namespace SigProSTD
bool CBoxAlgorithmSignalResampling::initialize()
{
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
const int64_t oSampling = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_NewSampling);
const int64_t nOutSample = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_SampleCountPerBuffer);
OV_ERROR_UNLESS_KRF(oSampling > 0, "Invalid output sampling rate [" << oSampling << "] (expected value > 0)", Kernel::ErrorType::BadSetting);
OV_ERROR_UNLESS_KRF(nOutSample > 0, "Invalid sample count per buffer [" << nOutSample << "] (expected value > 0)", Kernel::ErrorType::BadSetting);
m_oSampling = size_t(oSampling);
m_oNSample = size_t(nOutSample);
m_nFractionalDelayFilterSample = 6;
m_transitionBandPercent = 45;
m_stopBandAttenuation = 49;
m_iSampling = 0;
m_encoder.getInputSamplingRate() = uint64_t(m_oSampling);
return true;
}
bool CBoxAlgorithmSignalResampling::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmSignalResampling::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSignalResampling::process()
{
m_boxContext = &this->getDynamicBoxContext();
for (size_t i = 0; i < m_boxContext->getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
CMatrix* iMatrix = m_decoder.getOutputMatrix();
CMatrix* oMatrix = m_encoder.getInputMatrix();
const size_t nChannel = iMatrix->getDimensionSize(0);
const size_t nSample = iMatrix->getDimensionSize(1);
if (m_decoder.isHeaderReceived())
{
m_iSampling = size_t(m_decoder.getOutputSamplingRate());
OV_ERROR_UNLESS_KRF(m_iSampling > 0, "Invalid input sampling rate [" << m_iSampling << "] (expected value > 0)", Kernel::ErrorType::BadInput);
this->getLogManager() << Kernel::LogLevel_Info << "Resampling from [" << m_iSampling << "] Hz to [" << m_oSampling << "] Hz.\n";
double src = 1.0 * m_oSampling / m_iSampling;
const size_t gcd = size_t(SigProSTD::gcd(m_iSampling, m_oSampling));
size_t factorUpsampling = m_oSampling / gcd;
size_t factorDownsampling = m_iSampling / gcd;
if (src <= 0.5 || src > 1.0)
{
this->getLogManager() << Kernel::LogLevel_Info << "Sampling rate conversion [" << src << "] : upsampling by a factor of [" << factorUpsampling
<< "], low-pass filtering, and downsampling by a factor of [" << factorDownsampling << "].\n";
}
else
{
OV_WARNING_K("Sampling rate conversion [" << src << "] : upsampling by a factor of [" << factorUpsampling
<< "], low-pass filtering, and downsampling by a factor of [" << factorDownsampling << "]");
}
m_resampler.setFractionalDelayFilterSampleCount(m_nFractionalDelayFilterSample);
m_resampler.setTransitionBand(m_transitionBandPercent);
m_resampler.setStopBandAttenuation(m_stopBandAttenuation);
m_resampler.reset(nChannel, m_iSampling, m_oSampling);
double builtInLatency = m_resampler.getBuiltInLatency();
if (builtInLatency <= 0.15) { this->getLogManager() << Kernel::LogLevel_Trace << "Latency induced by the resampling is [" << builtInLatency << "] s.\n"; }
else if (0.15 < builtInLatency && builtInLatency <= 0.5)
{
this->getLogManager() << Kernel::LogLevel_Info << "Latency induced by the resampling is [" << builtInLatency << "] s.\n";
}
else if (0.5 < builtInLatency) { OV_WARNING_K("Latency induced by the resampling is [" << builtInLatency << "] s."); }
oMatrix->copyDescription(*iMatrix);
oMatrix->setDimensionSize(1, m_oNSample);
m_oTotalSample = 0;
m_encoder.encodeHeader();
m_boxContext->markOutputAsReadyToSend(0, 0, 0);
}
if (m_decoder.isBufferReceived())
{
// re-sampling sample-wise via a callback
m_resampler.resample(*this, iMatrix->getBuffer(), nSample);
//this->getLogManager() << Kernel::LogLevel_Info << "count = " << count << ".\n";
// encoding made in the callback (see next function)
}
if (m_decoder.isEndReceived())
{
m_encoder.encodeEnd();
m_boxContext->markOutputAsReadyToSend(0, (uint64_t((m_oTotalSample % m_oNSample) << 32) / m_oSampling),
(uint64_t((m_oTotalSample % m_oNSample) << 32) / m_oSampling));
}
}
return true;
}
void CBoxAlgorithmSignalResampling::processResampler(const double* sample, const size_t nChannel) const
{
double* buffer = m_encoder.getInputMatrix()->getBuffer();
const uint64_t oSampleIdx = m_oTotalSample % m_oNSample;
for (size_t j = 0; j < nChannel; ++j) { buffer[j * m_oNSample + oSampleIdx] = sample[j]; }
m_oTotalSample++;
if ((m_oTotalSample % m_oNSample) == 0)
{
m_encoder.encodeBuffer();
m_boxContext->markOutputAsReadyToSend(0, (uint64_t((m_oTotalSample - m_oNSample) << 32) / m_oSampling),
(uint64_t((m_oTotalSample) << 32) / m_oSampling));
}
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,113 @@
/*********************************************************************
* Software License Agreement (AGPL-3 License)
*
* OpenViBE SDK
* Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015
* Copyright (C) Inria, 2015-2017,V1.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "ovCResampler.h"
#include <Eigen/Eigen>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
typedef Common::Resampler::CResamplerSd CResampler;
class CBoxAlgorithmSignalResampling final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>, CResampler::ICallback
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
// implementation for TResampler::ICallback
void processResampler(const double* sample, const size_t nChannel) const override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SignalResampling)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmSignalResampling> m_decoder;
mutable Toolkit::TSignalEncoder<CBoxAlgorithmSignalResampling> m_encoder;
size_t m_oSampling = 0;
size_t m_oNSample = 0;
int m_nFractionalDelayFilterSample = 0;
double m_transitionBandPercent = 0;
double m_stopBandAttenuation = 0;
size_t m_iSampling = 0;
mutable uint64_t m_oTotalSample = 0;
CResampler m_resampler;
Kernel::IBoxIO* m_boxContext = nullptr;
};
class CBoxAlgorithmSignalResamplingDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Signal Resampling"); }
CString getAuthorName() const override { return CString("Quentin Barthelemy"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Resamples and re-epochs input signal to chosen sampling frequency"); }
CString getDetailedDescription() const override
{
return CString("The input signal is resampled, down-sampled or up-sampled, at a chosen sampling frequency and then re-epoched.");
}
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("2.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SignalResampling; }
IPluginObject* create() override { return new CBoxAlgorithmSignalResampling; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("New Sampling Frequency", OV_TypeId_Integer, "128", false,
OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_NewSampling);
prototype.addSetting("Sample Count Per Buffer", OV_TypeId_Integer, "8", false,
OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_SampleCountPerBuffer);
prototype.addSetting("Low Pass Filter Signal Before Downsampling", OV_TypeId_Boolean, "true", false,
OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_LowPassFilterSignalFlag); // displayed for backward compatibility, but never used
//prototype.addSetting("New Sampling Frequency",OV_TypeId_Integer,"128");
//prototype.addSetting("Sample Count Per Buffer",OV_TypeId_Integer,"8");
//prototype.addSetting("Low Pass Filter Signal Before Downsampling", OV_TypeId_Boolean, "true"); // displayed for backward compatibility, but never used
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalResamplingDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,256 @@
#define _USE_MATH_DEFINES
#include <cmath>
#include "ovpCBoxAlgorithmContinuousWaveletAnalysis.h"
#include <string.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace SigProSTD {
double WaveletFourierFactor(const char* type, const double param)
{
double factor = -1;
if (strcmp(type, "morlet") == 0) { factor = 4.0 * M_PI / (param + std::sqrt(2 + param * param)); }
else if (strcmp(type, "paul") == 0) { factor = 4.0 * M_PI / (2 * param + 1); }
else if (strcmp(type, "dog") == 0) { factor = 2.0 * M_PI / std::sqrt(param + 0.5); }
return factor;
}
double WaveletScale2Period(const char* type, const double param, const double scale) { return WaveletFourierFactor(type, param) * scale; }
double WaveletScale2Freq(const char* type, const double param, const double scale) { return 1.0 / (WaveletFourierFactor(type, param) * scale); }
double WaveletFreq2Scale(const char* type, const double param, const double frequency) { return 1.0 / (WaveletFourierFactor(type, param) * frequency); }
} // namespace SigProSTD
bool CBoxAlgorithmContinuousWaveletAnalysis::initialize()
{
m_decoder.initialize(*this, 0);
for (size_t i = 0; i < 4; ++i) { m_encoders[i].initialize(*this, i); }
const EContinuousWaveletType type = EContinuousWaveletType(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0)));
m_waveletParam = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_nScaleJ = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
m_highestFreq = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
const double frequencySpacing = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
if (type == EContinuousWaveletType::Morlet)
{
m_waveletType = "morlet";
if (m_waveletParam < 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Morlet wavelet parameter should be positive.\n";
return false;
}
}
else if (type == EContinuousWaveletType::Paul)
{
m_waveletType = "paul";
if (m_waveletParam <= 0 || m_waveletParam > 20)
{
this->getLogManager() << Kernel::LogLevel_Error << "Paul wavelet parameter should be included in ]0,20].\n";
return false;
}
if (std::ceil(m_waveletParam) != m_waveletParam)
{
this->getLogManager() << Kernel::LogLevel_Error << "Paul wavelet parameter should be an integer.\n";
return false;
}
}
else if (type == EContinuousWaveletType::DOG)
{
m_waveletType = "dog";
if (m_waveletParam <= 0 || size_t(m_waveletParam) % 2 == 1)
{
this->getLogManager() << Kernel::LogLevel_Error << "Derivative of Gaussian wavelet parameter should be strictly positive and even.\n";
return false;
}
if (std::ceil(m_waveletParam) != m_waveletParam)
{
this->getLogManager() << Kernel::LogLevel_Error << "Derivative of Gaussian wavelet parameter should be an integer.\n";
return false;
}
}
else
{
this->getLogManager() << Kernel::LogLevel_Error << "Unknown wavelet type.\n";
return false;
}
if (m_nScaleJ <= 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Number of frequencies can not be negative.\n";
return false;
}
if (m_highestFreq <= 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Highest frequency can not be negative.\n";
return false;
}
if (frequencySpacing <= 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Frequency spacing can not be negative.\n";
return false;
}
m_smallestScaleS0 = SigProSTD::WaveletFreq2Scale(m_waveletType, m_waveletParam, m_highestFreq);
m_scaleSpacingDj = SigProSTD::WaveletFreq2Scale(m_waveletType, m_waveletParam, frequencySpacing);
m_scaleType = "pow";
m_scalePowerBaseA0 = 2; // base of power if ScaleType = "pow"
return true;
}
bool CBoxAlgorithmContinuousWaveletAnalysis::uninitialize()
{
m_decoder.uninitialize();
for (auto& e : m_encoders) { e.uninitialize(); }
cwt_free(m_waveletTransform);
m_waveletTransform = nullptr;
return true;
}
bool CBoxAlgorithmContinuousWaveletAnalysis::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
/*The following algorithm is taken from:
Wavelib library, https://github.com/rafat/wavelib
A Practical Guide to Wavelet Analysis, 1998
C Torrence, and GP Compo*/
bool CBoxAlgorithmContinuousWaveletAnalysis::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
CMatrix* iMatrix = m_decoder.getOutputMatrix();
size_t nChannel = iMatrix->getDimensionSize(0);
size_t nSample = iMatrix->getDimensionSize(1);
if (m_decoder.isHeaderReceived())
{
size_t sampling = m_decoder.getOutputSamplingRate();
this->getLogManager() << Kernel::LogLevel_Trace << "Input signal is [" << nChannel << " x " << nSample << "] @ " << sampling << "Hz.\n";
if (sampling == 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Input sampling frequency is equal to 0. Plugin can not process.\n";
return false;
}
m_samplingPeriodDt = 1.0 / sampling;
if (m_highestFreq > 0.5 * sampling)
{
this->getLogManager() << Kernel::LogLevel_Error << "Highest frequency (" << m_highestFreq <<
" Hz) is above Nyquist criterion (sampling rate is "
<< sampling << " Hz), can not proceed!\n";
return false;
}
const int nScaleLimit = int(std::log2(nSample * m_samplingPeriodDt / m_smallestScaleS0) / m_scaleSpacingDj); // Eq.(10)
if (int(m_nScaleJ) > nScaleLimit)
{
this->getLogManager() << Kernel::LogLevel_Error << "Frequency count [" << m_nScaleJ << "] is superior to the limit [" << nScaleLimit << "].\n";
return false;
}
// initialize CWT
m_waveletTransform = cwt_init(const_cast<char*>(m_waveletType), m_waveletParam, int(nSample), m_samplingPeriodDt, int(m_nScaleJ));
if (!m_waveletTransform)
{
this->getLogManager() << Kernel::LogLevel_Error << "Error during CWT initialization.\n";
return false;
}
// define scales of CWT
if (setCWTScales(m_waveletTransform, m_smallestScaleS0, m_scaleSpacingDj, const_cast<char*>(m_scaleType), m_scalePowerBaseA0) != 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Error during CWT scales definition.\n";
return false;
}
//cwt_summary(m_waveletTransform); // FOR DEBUG
for (size_t j = 0; j < 4; ++j)
{
CMatrix* oMatrix = m_encoders[j].getInputMatrix();
oMatrix->resize({ nChannel, m_nScaleJ, nSample });
for (size_t c = 0; c < nChannel; ++c) { oMatrix->setDimensionLabel(0, c, iMatrix->getDimensionLabel(0, c)); }
for (size_t scaleIndex = 0; scaleIndex < m_nScaleJ; ++scaleIndex)
{
const double scaleValue = m_waveletTransform->scale[scaleIndex];
const double frequencyValue = SigProSTD::WaveletScale2Freq(m_waveletType, m_waveletParam, scaleValue);
std::string frequencyString = std::to_string(frequencyValue);
oMatrix->setDimensionLabel(1, scaleIndex, frequencyString.c_str());
}
for (size_t sampleIdx = 0; sampleIdx < nSample; ++sampleIdx)
{
std::string sampleString = std::to_string(sampleIdx * m_samplingPeriodDt);
oMatrix->setDimensionLabel(2, sampleIdx, sampleString.c_str());
}
m_encoders[j].encodeHeader();
}
}
if (m_decoder.isBufferReceived())
{
double* ibuffer = iMatrix->getBuffer();
double* oAmplitudeBuffer = m_encoders[0].getInputMatrix()->getBuffer();
double* oPhaseBuffer = m_encoders[1].getInputMatrix()->getBuffer();
double* oRealPartBuffer = m_encoders[2].getInputMatrix()->getBuffer();
double* oImagPartBuffer = m_encoders[3].getInputMatrix()->getBuffer();
for (size_t c = 0; c < nChannel; ++c)
{
// compute CWT
if (cwt(m_waveletTransform, ibuffer) != 0)
{
this->getLogManager() << Kernel::LogLevel_Error << "Error during CWT computation.\n";
return false;
}
// format of m_waveletTransform->output: dimensions = m_nScaleJ * nSample, stored in row major format
for (size_t scaleIdx = 0; scaleIdx < m_nScaleJ; ++scaleIdx)
{
for (size_t sampleIdx = 0; sampleIdx < nSample; ++sampleIdx)
{
const double real = m_waveletTransform->output[sampleIdx + scaleIdx * nSample].re;
const double imag = m_waveletTransform->output[sampleIdx + scaleIdx * nSample].im;
const size_t outputIdx = sampleIdx + (m_nScaleJ - scaleIdx - 1) * nSample + c * nSample * m_nScaleJ; // t+f*T+c*T*F
oAmplitudeBuffer[outputIdx] = std::sqrt(real * real + imag * imag);
oPhaseBuffer[outputIdx] = std::atan2(imag, real);
oRealPartBuffer[outputIdx] = real;
oImagPartBuffer[outputIdx] = imag;
}
}
ibuffer += nSample;
}
for (auto& e : m_encoders) { e.encodeBuffer(); }
}
if (m_decoder.isEndReceived()) { for (auto& e : m_encoders) { e.encodeEnd(); } }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markOutputAsReadyToSend(1, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markOutputAsReadyToSend(2, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markOutputAsReadyToSend(3, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,82 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include <wavelib/header/wavelib.h>
#include <array>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmContinuousWaveletAnalysis 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_ContinuousWaveletAnalysis)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmContinuousWaveletAnalysis> m_decoder;
std::array<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmContinuousWaveletAnalysis>, 4> m_encoders;
const char* m_waveletType = nullptr;
double m_waveletParam = 0;
size_t m_nScaleJ = 0;
double m_highestFreq = 0;
double m_smallestScaleS0 = 0;
double m_scaleSpacingDj = 0;
const char* m_scaleType = nullptr;
int m_scalePowerBaseA0 = 0;
double m_samplingPeriodDt = 0;
cwt_object m_waveletTransform = nullptr;
};
class CBoxAlgorithmContinuousWaveletAnalysisDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Continuous Wavelet Analysis"); }
CString getAuthorName() const override { return CString("Quentin Barthelemy"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Performs a Time-Frequency Analysis using CWT."); }
CString getDetailedDescription() const override { return CString("Performs a Time-Frequency Analysis using Continuous Wavelet Transform."); }
CString getCategory() const override { return CString("Signal processing/Spectral Analysis"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("1.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("1.0.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_ContinuousWaveletAnalysis; }
IPluginObject* create() override { return new CBoxAlgorithmContinuousWaveletAnalysis(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Amplitude", OV_TypeId_TimeFrequency);
prototype.addOutput("Phase", OV_TypeId_TimeFrequency);
prototype.addOutput("Real Part", OV_TypeId_TimeFrequency);
prototype.addOutput("Imaginary Part", OV_TypeId_TimeFrequency);
prototype.addSetting("Wavelet type", OVP_TypeId_ContinuousWaveletType, "Morlet wavelet");
prototype.addSetting("Wavelet parameter", OV_TypeId_Float, "4");
prototype.addSetting("Number of frequencies", OV_TypeId_Integer, "60");
prototype.addSetting("Highest frequency", OV_TypeId_Float, "35");
prototype.addSetting("Frequency spacing", OV_TypeId_Float, "12.5");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_ContinuousWaveletAnalysisDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,169 @@
#include "ovpCBoxAlgorithmFrequencyBandSelector.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
#include <vector>
#include <string>
namespace {
std::vector<std::string> split(const std::string& str, const char c)
{
std::vector<std::string> result;
size_t i = 0;
while (i < str.length())
{
size_t j = i;
while (j < str.length() && str[j] != c) { j++; }
if (i != j) { result.push_back(std::string(str, i, j - i)); }
i = j + 1;
}
return result;
}
} // namespace
bool CBoxAlgorithmFrequencyBandSelector::initialize()
{
const CString settingValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
std::vector<std::string> setting = split(settingValue.toASCIIString(), OV_Value_EnumeratedStringSeparator);
bool hadError = false;
CString errorMsg;
m_selecteds.clear();
for (auto it = setting.begin(); it != setting.end(); ++it)
{
bool good = true;
std::vector<std::string> settingRange = split(*it, OV_Value_RangeStringSeparator);
if (settingRange.size() == 1)
{
try
{
double value = std::stod(settingRange[0]);
m_selecteds.push_back(std::pair<double, double>(value, value));
}
catch (const std::exception&) { good = false; }
}
else if (settingRange.size() == 2)
{
try
{
double low = std::stod(settingRange[0]);
double high = std::stod(settingRange[1]);
m_selecteds.push_back(std::pair<double, double>(std::min(low, high), std::max(low, high)));
}
catch (const std::exception&) { good = false; }
}
if (!good)
{
errorMsg = CString("Invalid frequency band [") + it->c_str() + "]";
hadError = true;
}
}
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_SpectrumDecoder_InputParameterId_MemoryBufferToDecode));
op_matrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Matrix));
op_bands.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
m_encoder->initialize();
ip_matrix.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Matrix));
ip_frequencyAbscissa.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_SpectrumEncoder_OutputParameterId_EncodedMemoryBuffer));
ip_frequencyAbscissa.setReferenceTarget(op_bands);
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling)
->setReferenceTarget(m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling));
ip_matrix = &m_oMatrix;
op_matrix = &m_oMatrix;
OV_ERROR_UNLESS_KRF(!hadError || !m_selecteds.empty(), errorMsg, Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmFrequencyBandSelector::uninitialize()
{
op_buffer.uninitialize();
ip_frequencyAbscissa.uninitialize();
ip_matrix.uninitialize();
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
op_bands.uninitialize();
op_matrix.uninitialize();
ip_buffer.uninitialize();
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
return true;
}
bool CBoxAlgorithmFrequencyBandSelector::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmFrequencyBandSelector::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedHeader))
{
m_selectionFactors.clear();
for (size_t j = 0; j < ip_frequencyAbscissa->getDimensionSize(0); ++j)
{
double frequencyAbscissa = ip_frequencyAbscissa->getBuffer()[j];
const bool selected = std::any_of(m_selecteds.begin(), m_selecteds.end(), [frequencyAbscissa](const BandRange& currentBandRange)
{
return currentBandRange.first <= frequencyAbscissa && frequencyAbscissa <= currentBandRange.second;
});
m_selectionFactors.push_back(selected ? 1. : 0.);
}
m_encoder->process(OVP_GD_Algorithm_SpectrumEncoder_InputTriggerId_EncodeHeader);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedBuffer))
{
size_t offset = 0;
for (size_t j = 0; j < m_oMatrix.getDimensionSize(0); ++j)
{
for (size_t k = 0; k < m_oMatrix.getDimensionSize(1); ++k)
{
m_oMatrix.getBuffer()[offset] = m_selectionFactors[k] * m_oMatrix.getBuffer()[offset];
offset++;
}
}
m_encoder->process(OVP_GD_Algorithm_SpectrumEncoder_InputTriggerId_EncodeBuffer);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_SpectrumEncoder_InputTriggerId_EncodeEnd);
}
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,81 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
#include <map>
#include <algorithm>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
typedef std::pair<double, double> BandRange;
class CBoxAlgorithmFrequencyBandSelector 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_FrequencyBandSelector)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<CMatrix*> op_matrix;
Kernel::TParameterHandler<CMatrix*> op_bands;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<CMatrix*> ip_matrix;
Kernel::TParameterHandler<CMatrix*> ip_frequencyAbscissa;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
CMatrix m_oMatrix;
std::vector<BandRange> m_selecteds;
std::vector<double> m_selectionFactors;
};
class CBoxAlgorithmFrequencyBandSelectorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Frequency Band Selector"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override
{
return CString(
"Preserves some spectrum coefficients and puts the others to zero depending on a list of frequencies / frequency bands to select");
}
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Spectral Analysis"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_FrequencyBandSelector; }
IPluginObject* create() override { return new CBoxAlgorithmFrequencyBandSelector; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input spectrum", OV_TypeId_Spectrum);
prototype.addOutput("Output spectrum", OV_TypeId_Spectrum);
prototype.addSetting("Frequencies to select", OV_TypeId_String, "8:12;16:24");
// @fixme Use OV_Value_RangeStringSeparator / OV_Value_EnumeratedStringSeparator tokens above
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,240 @@
#include "ovpCBoxAlgorithmSpectralAnalysis.h"
#include <Eigen/Eigen>
// additional Eigen module
#include <unsupported/Eigen/FFT>
#include <cmath>
#include <sstream>
#include <iostream>
#include <functional>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
static double amplitude(const size_t channelIdx, const size_t fftIdx, const Eigen::MatrixXcd& matrix)
{
return sqrt(matrix(channelIdx, fftIdx).real() * matrix(channelIdx, fftIdx).real() + matrix(channelIdx, fftIdx).imag() * matrix(channelIdx, fftIdx).imag());
}
static double phase(const size_t channelIdx, const size_t fftIdx, const Eigen::MatrixXcd& matrix)
{
return atan2(matrix(channelIdx, fftIdx).imag(), matrix(channelIdx, fftIdx).real());
}
static double realPart(const size_t channelIdx, const size_t fftIdx, const Eigen::MatrixXcd& matrix) { return matrix(channelIdx, fftIdx).real(); }
static double imaginaryPart(const size_t channelIdx, const size_t fftIdx, const Eigen::MatrixXcd& matrix) { return matrix(channelIdx, fftIdx).imag(); }
bool CBoxAlgorithmSpectralAnalysis::initialize()
{
m_decoder.initialize(*this, 0);
m_frequencyAbscissa = new CMatrix();
// Amplitude
m_spectrumEncoders.push_back(new Toolkit::TSpectrumEncoder<CBoxAlgorithmSpectralAnalysis>(*this, 0));
m_isSpectrumEncoderActive.push_back(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
// Phase
m_spectrumEncoders.push_back(new Toolkit::TSpectrumEncoder<CBoxAlgorithmSpectralAnalysis>(*this, 1));
m_isSpectrumEncoderActive.push_back(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1));
//Real Part
m_spectrumEncoders.push_back(new Toolkit::TSpectrumEncoder<CBoxAlgorithmSpectralAnalysis>(*this, 2));
m_isSpectrumEncoderActive.push_back(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2));
// Imaginary part
m_spectrumEncoders.push_back(new Toolkit::TSpectrumEncoder<CBoxAlgorithmSpectralAnalysis>(*this, 3));
m_isSpectrumEncoderActive.push_back(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3));
for (auto& curEncoder : m_spectrumEncoders)
{
curEncoder->getInputFrequencyAbscissa().setReferenceTarget(m_frequencyAbscissa);
curEncoder->getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
}
this->getLogManager() << Kernel::LogLevel_Trace << "Spectral components selected : [ "
<< (m_isSpectrumEncoderActive[0] ? CString("AMP ") : "") << (m_isSpectrumEncoderActive[1] ? CString("PHASE ") : "")
<< (m_isSpectrumEncoderActive[2] ? CString("REAL ") : "") << (m_isSpectrumEncoderActive[3] ? CString("IMG ") : "") << "]\n";
return true;
}
bool CBoxAlgorithmSpectralAnalysis::uninitialize()
{
for (size_t i = 0; i < m_spectrumEncoders.size(); ++i)
{
m_spectrumEncoders[i]->uninitialize();
delete m_spectrumEncoders[i];
}
m_spectrumEncoders.clear();
m_decoder.uninitialize();
return true;
}
bool CBoxAlgorithmSpectralAnalysis::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSpectralAnalysis::process()
{
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
// Process input data
for (size_t i = 0; i < boxContext->getInputChunkCount(0); ++i)
{
const uint64_t startTime = boxContext->getInputChunkStartTime(0, i);
const uint64_t endTime = boxContext->getInputChunkEndTime(0, i);
m_decoder.decode(i);
CMatrix* matrix = m_decoder.getOutputMatrix();
if (m_decoder.isHeaderReceived())
{
m_nChannel = matrix->getDimensionSize(0);
m_nSample = matrix->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(m_nSample > 1, "Input sample count lower or equal to 1 is not supported by the box.", Kernel::ErrorType::BadInput);
m_sampling = size_t(m_decoder.getOutputSamplingRate());
OV_ERROR_UNLESS_KRF(m_sampling > 0, "Invalid sampling rate [" << m_sampling << "] (expected value > 0)", Kernel::ErrorType::BadInput);
// size of the spectrum
m_sizeFFT = m_nSample / 2 + 1;
// Constructing the frequency band description matrix, same for every possible output (and given through reference target mechanism)
m_frequencyAbscissa->resize(m_sizeFFT); // FFTSize frequency abscissa
// Frequency values
for (size_t frequencyAbscissaIdx = 0; frequencyAbscissaIdx < m_sizeFFT; ++frequencyAbscissaIdx)
{
m_frequencyAbscissa->getBuffer()[frequencyAbscissaIdx] = frequencyAbscissaIdx * (double(m_sampling) / m_nSample);
}
// All spectra share the same header structure
for (size_t encoderIdx = 0; encoderIdx < m_spectrumEncoders.size(); ++encoderIdx)
{
// We build the chunk only if the encoder is activated
if (m_isSpectrumEncoderActive[encoderIdx])
{
// Spectrum matrix
CMatrix* spectrum = m_spectrumEncoders[encoderIdx]->getInputMatrix();
spectrum->resize(m_nChannel, m_sizeFFT);
// Spectrum channel names
for (size_t j = 0; j < m_nChannel; ++j) { spectrum->setDimensionLabel(0, j, matrix->getDimensionLabel(0, j)); }
// We also name the spectrum bands "Abscissa"
for (size_t j = 0; j < m_sizeFFT; ++j) { spectrum->setDimensionLabel(1, j, std::to_string(m_frequencyAbscissa->getBuffer()[j]).c_str()); }
m_spectrumEncoders[encoderIdx]->encodeHeader();
boxContext->markOutputAsReadyToSend(encoderIdx, startTime, endTime);
}
}
}
if (m_decoder.isBufferReceived())
{
// Compute the FFT
Eigen::FFT<double> eigenFFT;
eigenFFT.SetFlag(eigenFFT.HalfSpectrum); // REAL signal => spectrum with conjugate symmetry
// This matrix will contain the channels spectra (COMPLEX values, RowMajor for copy into openvibe matrix)
Eigen::MatrixXcd spectra = Eigen::MatrixXcd::Zero(m_nChannel, m_sizeFFT);
for (size_t j = 0; j < m_nChannel; ++j)
{
Eigen::VectorXd samples = Eigen::VectorXd::Zero(m_nSample);
for (size_t k = 0; k < m_nSample; ++k) { samples(k) = matrix->getBuffer()[j * m_nSample + k]; }
Eigen::VectorXcd spectrum; // initialization useless: EigenFFT resizes spectrum in function .fwd()
// EigenFFT
eigenFFT.fwd(spectrum, samples);
// return of a mirror spectrum of size 2*m_sizeFFT: so we take only the first m_sizeFFT values
spectra.row(j) = spectrum;
}
// multiplication by sqrt(2), since half spectrum has been removed
if (m_nSample % 2 == 0)
{
// even case : DC and Nyquist bins are not concerned
spectra.block(0, 1, m_nChannel, m_sizeFFT - 2) *= std::sqrt(2);
}
else
{
// odd case : DC bin is not concerned
spectra.block(0, 1, m_nChannel, m_sizeFFT - 1) *= std::sqrt(2);
}
for (size_t encoderIdx = 0; encoderIdx < m_spectrumEncoders.size(); ++encoderIdx)
{
// We build the chunk only if the encoder is activated
if (m_isSpectrumEncoderActive[encoderIdx])
{
std::function<double(size_t, size_t, const Eigen::MatrixXcd&)> processResult;
switch (encoderIdx)
{
case 0:
processResult = amplitude;
break;
case 1:
processResult = phase;
break;
case 2:
processResult = realPart;
break;
case 3:
processResult = imaginaryPart;
break;
default:
OV_ERROR_KRF("Invalid decoder output.\n", Kernel::ErrorType::BadProcessing);
}
CMatrix* spectrum = m_spectrumEncoders[encoderIdx]->getInputMatrix();
for (size_t j = 0; j < m_nChannel; ++j)
{
for (size_t k = 0; k < m_sizeFFT; ++k) { spectrum->getBuffer()[j * m_sizeFFT + k] = processResult(j, k, spectra); }
}
m_spectrumEncoders[encoderIdx]->encodeBuffer();
boxContext->markOutputAsReadyToSend(encoderIdx, startTime, endTime);
}
}
}
if (m_decoder.isEndReceived())
{
for (size_t encoderIdx = 0; encoderIdx < m_spectrumEncoders.size(); ++encoderIdx)
{
// We build the chunk only if the encoder is activated
if (m_isSpectrumEncoderActive[encoderIdx])
{
m_spectrumEncoders[encoderIdx]->encodeEnd();
boxContext->markOutputAsReadyToSend(encoderIdx, startTime, endTime);
}
}
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,74 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSpectralAnalysis 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_SpectralAnalysis)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmSpectralAnalysis> m_decoder;
std::vector<Toolkit::TSpectrumEncoder<CBoxAlgorithmSpectralAnalysis>*> m_spectrumEncoders;
std::vector<bool> m_isSpectrumEncoderActive;
size_t m_nChannel = 0;
size_t m_nSample = 0;
size_t m_sampling = 0;
size_t m_sizeFFT = 0;
CMatrix* m_frequencyAbscissa = nullptr;
};
class CBoxAlgorithmSpectralAnalysisDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Spectral Analysis"); }
CString getAuthorName() const override { return CString("Laurent Bonnet / Quentin Barthelemy"); }
CString getAuthorCompanyName() const override { return CString("Mensia Technologies SA"); }
CString getShortDescription() const override { return CString("Performs a Spectral Analysis using FFT."); }
CString getDetailedDescription() const override { return CString("Performs a Spectral Analysis using FFT."); }
CString getCategory() const override { return CString("Signal processing/Spectral Analysis"); }
CString getVersion() const override { return CString("1.2"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_SpectralAnalysis; }
IPluginObject* create() override { return new CBoxAlgorithmSpectralAnalysis(); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Amplitude", OV_TypeId_Spectrum);
prototype.addOutput("Phase", OV_TypeId_Spectrum);
prototype.addOutput("Real Part", OV_TypeId_Spectrum);
prototype.addOutput("Imaginary Part", OV_TypeId_Spectrum);
prototype.addSetting("Amplitude", OV_TypeId_Boolean, "true");
prototype.addSetting("Phase", OV_TypeId_Boolean, "false");
prototype.addSetting("Real Part", OV_TypeId_Boolean, "false");
prototype.addSetting("Imaginary Part", OV_TypeId_Boolean, "false");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_SpectralAnalysisDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,105 @@
#include "ovpCBoxAlgorithmSpectrumAverage.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmSpectrumAverage::initialize()
{
m_bZeroCare = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_decoder->initialize();
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
m_encoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_SpectrumDecoder_InputParameterId_MemoryBufferToDecode));
op_matrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Matrix));
ip_matrix.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
return true;
}
bool CBoxAlgorithmSpectrumAverage::uninitialize()
{
ip_matrix.uninitialize();
op_matrix.uninitialize();
if (m_encoder)
{
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
}
if (m_decoder)
{
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
}
return true;
}
bool CBoxAlgorithmSpectrumAverage::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSpectrumAverage::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedHeader))
{
ip_matrix->copyDescription(*op_matrix);
ip_matrix->setDimensionSize(1, 1);
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedBuffer))
{
double* iMatrix = ip_matrix->getBuffer();
double* oMatrix = op_matrix->getBuffer();
const size_t nChannel = op_matrix->getDimensionSize(0);
const size_t nBand = op_matrix->getDimensionSize(1);
for (size_t j = 0; j < nChannel; ++j)
{
double mean = 0;
size_t n = 0;
for (size_t k = 0; k < nBand; ++k)
{
mean += *oMatrix;
n += (m_bZeroCare || *oMatrix != 0) ? 1 : 0;
oMatrix++;
}
*iMatrix = (n == 0 ? 0 : mean / n);
iMatrix++;
}
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SpectrumDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd);
}
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSpectrumAverage final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
// virtual uint64_t getClockFrequency();
bool initialize() override;
bool uninitialize() override;
// virtual bool processClock(Kernel::CMessageClock& msg);
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SpectrumAverage)
protected:
bool m_bZeroCare = false;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<CMatrix*> ip_matrix;
Kernel::TParameterHandler<CMatrix*> op_matrix;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
std::vector<size_t> m_selectedIndices;
};
class CBoxAlgorithmSpectrumAverageDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Spectrum Average"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Computes the average of all the frequency band powers for a spectrum"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Spectral Analysis"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SpectrumAverage; }
IPluginObject* create() override { return new CBoxAlgorithmSpectrumAverage; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Spectrum", OV_TypeId_Spectrum);
prototype.addOutput("Spectrum average", OV_TypeId_StreamedMatrix);
prototype.addSetting("Considers zeros", OV_TypeId_Boolean, "false");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SpectrumAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,460 @@
#include "ovpCAbstractTree.h"
#include <iostream>
#include <vector>
#include <algorithm>
//#define CABSTRACTTREE_DEBUG
void CAbstractTree::simplifyTree()
{
bool change = true;
//while stability hasn't been reached
while (change)
{
CAbstractTreeNode* node = m_root;
//tries to simplify the tree.
change = m_root->simplify(node);
//if the root node has changed
if (node != m_root)
{
//delete the old one
delete m_root;
m_root = node;
}
}
}
// Dirty hack to avoid GCC 4.3 crash at compilation time
static void ClearChildren(std::vector<CAbstractTreeNode*>& children) { for (size_t i = 0; i < children.size(); ++i) { delete children[i]; } }
CAbstractTreeParentNode::~CAbstractTreeParentNode()
{
// Dirty hack to avoid GCC 4.3 crash at compilation time
ClearChildren(m_Children);
}
void CAbstractTreeParentNode::levelOperators()
{
const size_t nChildren = m_Children.size();
std::vector<CAbstractTreeNode*> newChildren;
//for all the node's children
for (size_t i = 0; i < nChildren; ++i)
{
CAbstractTreeNode* child = m_Children[i];
//recursively try to level the childs' operators
child->levelOperators();
//if the child is a terminal node
if (child->isTerminal())
{
//add it to the children list
newChildren.push_back(child);
}
else
{
//else it's a parent node
CAbstractTreeParentNode* childParentNode = reinterpret_cast<CAbstractTreeParentNode*>(child);
//if the child and the current node have the same id
if (m_ID == childParentNode->getOperatorIdentifier())
{
switch (m_ID)
{
//check if it is the ID of the + or * operators
case OP_ADD:
case OP_MUL:
//if it is, we can group the child's children with the current node's children
newChildren.insert(newChildren.end(), childParentNode->getChildren().begin(), childParentNode->getChildren().end());
//we don't want it to destroy its old children
childParentNode->getChildren().clear();
//we no longer need this child
delete childParentNode;
childParentNode = nullptr;
break;
default:
//this kind of node isn't an associative one, so keep the child
newChildren.push_back(child);
break;
}
}
else { newChildren.push_back(child); }
}
}
m_Children = newChildren;
//for + or *
if (isAssociative())
{
//if the node is associative/commutative, reorder the children
sort(m_Children.begin(), m_Children.end(), CAbstractTreeNodeOrderingFunction());
}
}
bool CAbstractTreeParentNode::simplify(CAbstractTreeNode*& node)
{
//result boolean, true if a child has changed
bool hasChanged = false;
//true if a child has changed
bool childrenChanged = true;
//number of children of this node
const size_t nChildren = m_Children.size();
//while the children aren't stable
while (childrenChanged)
{
childrenChanged = false;
//try to simplify all the children
for (size_t i = 0; i < nChildren; ++i)
{
CAbstractTreeNode* child = m_Children[i];
childrenChanged = child->simplify(child);
//if there has been a change, actualize hasChanged
hasChanged |= childrenChanged;
//if the child has become a new node
if (m_Children[i] != child)
{
//delete the old one and replace it
delete m_Children[i];
m_Children[i] = child;
}
}
}
//unary operator
if (nChildren == 1)
{
//if we can already compute the result
if (m_Children[0]->isConstant())
{
const double value = reinterpret_cast<CAbstractTreeValueNode*>(m_Children[0])->getValue();
switch (m_ID)
{
case OP_NEG: node = new CAbstractTreeValueNode(-value);
break;
case OP_ABS: node = new CAbstractTreeValueNode(abs(value));
break;
case OP_ACOS: node = new CAbstractTreeValueNode(acos(value));
break;
case OP_ASIN: node = new CAbstractTreeValueNode(asin(value));
break;
case OP_ATAN: node = new CAbstractTreeValueNode(atan(value));
break;
case OP_CEIL: node = new CAbstractTreeValueNode(ceil(value));
break;
case OP_COS: node = new CAbstractTreeValueNode(cos(value));
break;
case OP_EXP: node = new CAbstractTreeValueNode(exp(value));
break;
case OP_FLOOR: node = new CAbstractTreeValueNode(floor(value));
break;
case OP_LOG: node = new CAbstractTreeValueNode(log(value));
break;
case OP_LOG10: node = new CAbstractTreeValueNode(log10(value));
break;
case OP_RAND: node = new CAbstractTreeValueNode(rand() * value / RAND_MAX);
break;
case OP_SIN: node = new CAbstractTreeValueNode(sin(value));
break;
case OP_SQRT: node = new CAbstractTreeValueNode(sqrt(value));
break;
case OP_TAN: node = new CAbstractTreeValueNode(tan(value));
break;
default: break;
}
hasChanged = true;
}
}
//binary operator not associative
else if (nChildren == 2 && !isAssociative())
{
//if we can already compute the result
if (m_Children[0]->isConstant() && m_Children[1]->isConstant())
{
switch (m_ID)
{
case OP_DIV:
{
const double total = reinterpret_cast<CAbstractTreeValueNode*>(m_Children[0])->getValue()
/ reinterpret_cast<CAbstractTreeValueNode*>(m_Children[1])->getValue();
//delete the old value nodes
delete m_Children[0];
m_Children[0] = nullptr;
delete m_Children[1];
m_Children[1] = nullptr;
node = new CAbstractTreeValueNode(total);
hasChanged = true;
break;
}
case OP_POW:
{
const double total = pow(reinterpret_cast<CAbstractTreeValueNode*>(m_Children[0])->getValue(),
reinterpret_cast<CAbstractTreeValueNode*>(m_Children[1])->getValue());
//delete the old value nodes
delete m_Children[0];
m_Children[0] = nullptr;
delete m_Children[1];
m_Children[1] = nullptr;
node = new CAbstractTreeValueNode(total);
hasChanged = true;
break;
}
default: break;
}
}
//test special cases (X/1), ..., simplify
else if (m_ID == OP_DIV)
{
if (!m_Children[0]->isConstant() && m_Children[1]->isConstant())
{
if (reinterpret_cast<CAbstractTreeValueNode*>(m_Children[1])->getValue() == 1)
{
node = m_Children[0];
m_Children.clear();
hasChanged = true;
}
}
}
}
//if the node is an associative operation node, there are at least two children and at least two are constants
else if (nChildren >= 2 && isAssociative())
{
//For commutative nodes
//The order of the children may have changed due to previous child simplification
sort(m_Children.begin(), m_Children.end(), CAbstractTreeNodeOrderingFunction());
//the new children if there are changes
std::vector<CAbstractTreeNode*> newChildren;
//iterator on the children
size_t i = 0;
double total = 0;
switch (m_ID)
{
case OP_ADD:
total = 0;
//add the values of all the constant children
for (i = 0; i < nChildren && m_Children[i]->isConstant(); ++i)
{
total += reinterpret_cast<CAbstractTreeValueNode*>(m_Children[i])->getValue();
//delete the old value nodes
delete m_Children[i];
m_Children[i] = nullptr;
}
break;
case OP_MUL:
total = 1;
//multiply the values of all the constant children
for (i = 0; i < nChildren && m_Children[i]->isConstant(); ++i)
{
total *= reinterpret_cast<CAbstractTreeValueNode*>(m_Children[i])->getValue();
//delete the old value nodes
delete m_Children[i];
m_Children[i] = nullptr;
}
break;
default: break;
}
//if there were only value nodes, we can replace the current parent node by a value node
if (i == nChildren)
{
node = new CAbstractTreeValueNode(total);
hasChanged = true;
// cout<<l_TotalValue<<endl;
}
//if there are still some other children, but we reduced at least two children
else if (i > 1)
{
//adds the new result node to the list
newChildren.push_back(new CAbstractTreeValueNode(total));
//adds the other remaining children
for (; i < nChildren; ++i) { newChildren.push_back(m_Children[i]); }
//we keep this node, but modify its children
m_Children = newChildren;
hasChanged = true;
}
else if (i == 1)
{
//nothing changed
if ((total == 0 && m_ID == OP_ADD) ||
(total == 1 && m_ID == OP_MUL))
{
if (nChildren - i == 1)
{
node = m_Children[i];
m_Children.clear();
}
else
{
//don't keep the valueNode
//adds the other remaining children
for (; i < nChildren; ++i) { newChildren.push_back(m_Children[i]); }
//we keep this node, but modify its children
m_Children = newChildren;
}
hasChanged = true;
}
else if (total == 0 && m_ID == OP_MUL)
{
//kill this node and replace it by a 0 node
node = new CAbstractTreeValueNode(0);
hasChanged = true;
}
else
{
//undo changes
m_Children[0] = new CAbstractTreeValueNode(total);
}
}
}
return hasChanged;
}
void CAbstractTreeParentNode::useNegationOperator()
{
const size_t nChildren = m_Children.size();
//try to use the negation operator in all the children
for (size_t i = 0; i < nChildren; ++i)
{
CAbstractTreeNode* child = m_Children[i];
child->useNegationOperator();
}
//replace (/ Something -1) by (NEG Something)
if (m_ID == OP_DIV)
{
if (m_Children[1]->isConstant())
{
if (reinterpret_cast<CAbstractTreeValueNode*>(m_Children[1])->getValue() == -1)
{
m_ID = OP_NEG;
m_Children.pop_back();
}
}
}
//replace (* -1 ...) by (NEG (* ...))
else if (m_ID == OP_MUL)
{
if (m_Children[0]->isConstant())
{
if (reinterpret_cast<CAbstractTreeValueNode*>(m_Children[0])->getValue() == -1)
{
m_ID = OP_NEG;
m_IsAssociative = false;
//if there were just two children : replace (* -1 Sth) by (NEG Sth)
if (nChildren == 2)
{
m_Children[0] = m_Children[1];
m_Children.pop_back();
}
//>2 there were more than two children
else
{
CAbstractTreeParentNode* newOperatorNode = new CAbstractTreeParentNode(OP_MUL, true);
for (size_t i = 1; i < nChildren; ++i) { newOperatorNode->addChild(m_Children[i]); }
m_Children.clear();
m_Children.push_back(newOperatorNode);
}
}
}
}
}
void CAbstractTree::generateCode(CEquationParser& parser) { m_root->generateCode(parser); }
void CAbstractTreeParentNode::generateCode(CEquationParser& parser)
{
const size_t nChildren = m_Children.size();
parser.push_op(m_ID);
for (size_t i = 0; i < nChildren; ++i) { m_Children[i]->generateCode(parser); }
}
void CAbstractTreeValueNode::generateCode(CEquationParser& parser) { parser.push_value(m_value); }
void CAbstractTreeVariableNode::generateCode(CEquationParser& parser) { parser.push_var(m_index); }
void CAbstractTree::recognizeSpecialTree(uint64_t& treeId, double& parameter)
{
//default
treeId = OP_USERDEF;
parameter = 0;
//the root node is a value node or variable node
if (m_root->isTerminal())
{
//if it is a variable node
if (!m_root->isConstant()) { treeId = OP_NONE; }
return;
}
CAbstractTreeParentNode* parent = reinterpret_cast<CAbstractTreeParentNode*>(m_root);
std::vector<CAbstractTreeNode*>& children = parent->getChildren();
const size_t nChildren = children.size();
const uint64_t nodeId = parent->getOperatorIdentifier();
//unary operator/function
if (nChildren == 1) { if (children[0]->isTerminal() && !children[0]->isConstant()) { treeId = nodeId; } }
//binary
else if (nChildren == 2)
{
std::array<bool, 2> isVariable;
isVariable[0] = children[0]->isTerminal() && !children[0]->isConstant();
isVariable[1] = children[1]->isTerminal() && !children[1]->isConstant();
//(* X X)
if (nodeId == OP_MUL && isVariable[0] && isVariable[1]) { treeId = OP_X2; }
//pow(X,2)
else if (nodeId == OP_POW && isVariable[0] && children[1]->isConstant()) { treeId = OP_X2; }
//(+ Cst X) or (* Cst X)
else if (parent->isAssociative() && children[0]->isConstant() && isVariable[1])
{
treeId = nodeId;
parameter = reinterpret_cast<CAbstractTreeValueNode*>(children[0])->getValue();
}
// (/ X Cst)
else if (nodeId == OP_DIV && isVariable[0] && children[1]->isConstant())
{
treeId = OP_DIV;
parameter = reinterpret_cast<CAbstractTreeValueNode*>(children[1])->getValue();
}
}
//else do nothing
}
@@ -0,0 +1,406 @@
#pragma once
#include "../ovp_defines.h"
#include "ovpCEquationParserGrammar.h"
#include "ovpCEquationParser.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <boost/spirit/include/classic_ast.hpp>
#include <vector>
#include <string>
class CEquationParser;
/**
* Abstract class for an AST tree node
*
*/
class CAbstractTreeNode
{
protected:
//! True if this is a terminal node
bool m_isTerminal = false;
//! True if this node contains a constant value
bool m_isConstant = false;
public:
CAbstractTreeNode(const bool bTerminal, const bool bIsConstant) : m_isTerminal(bTerminal), m_isConstant(bIsConstant) { }
//! virtual destructor
virtual ~CAbstractTreeNode() { }
/**
* Used to know if this node is a leaf.
* \return True if the node is a leaf.
*/
virtual bool isTerminal() const { return m_isTerminal; }
/**
* Used to know if this node is a constant value node.
* \return True if the node is a constant value node.
*/
virtual bool isConstant() const { return m_isConstant; }
//! Prints the node to stdout.
virtual void print(OpenViBE::Kernel::ILogManager& rLogManager) = 0;
/**
* Used to simplify this node (and its children if any).
* \param pModifiedNode Reference to a pointer to modify if the
* current node object is to be destroyed and replaced. This pointer
* will contain the address of the new node.
*/
virtual bool simplify(CAbstractTreeNode*& pModifiedNode) = 0;
/**
* Part of the process of simplification.
* Levels recursively the associative operators nodes.
*/
virtual void levelOperators() = 0;
/**
* Changes the tree so it uses the NEG operator whenever it is possible.
* (ie replaces (* -1 X) by (NEG X)
*/
virtual void useNegationOperator() = 0;
/**
* Generates the set of function calls needed to do the desired computation.
* \param oParser The parser containing the function pointers stack and function contexts stack.
*/
virtual void generateCode(CEquationParser& oParser) = 0;
};
/**
* A tree's parent node (has children).
*
*/
class CAbstractTreeParentNode : public CAbstractTreeNode
{
public:
//! Children of this node
std::vector<CAbstractTreeNode*> m_Children;
//! The node operator's identifier
uint64_t m_ID = 0;
//! True if the node is "associative"
bool m_IsAssociative = false;
//Constructors
CAbstractTreeParentNode(const uint64_t nodeId, const bool isAssociative = false)
: CAbstractTreeNode(false, false), m_ID(nodeId), m_IsAssociative(isAssociative) { }
CAbstractTreeParentNode(const uint64_t nodeId, CAbstractTreeNode* child, const bool isAssociative = false)
: CAbstractTreeNode(false, false), m_ID(nodeId), m_IsAssociative(isAssociative) { m_Children.push_back(child); }
CAbstractTreeParentNode(const uint64_t nodeId, CAbstractTreeNode* leftChild, CAbstractTreeNode* rightChild, const bool isAssociative = false)
: CAbstractTreeNode(false, false), m_ID(nodeId), m_IsAssociative(isAssociative)
{
m_Children.push_back(leftChild);
m_Children.push_back(rightChild);
}
CAbstractTreeParentNode(const uint64_t nodeId, CAbstractTreeNode* testChild, CAbstractTreeNode* ifChild, CAbstractTreeNode* thenChild,
const bool isAssociative = false)
: CAbstractTreeNode(false, false), m_ID(nodeId), m_IsAssociative(isAssociative)
{
m_Children.push_back(testChild);
m_Children.push_back(ifChild);
m_Children.push_back(thenChild);
}
/**
* Returns the node's operator identifier.
* \return The operator identifier
*/
uint64_t getOperatorIdentifier() const { return m_ID; }
/**
* Used to know if the node is an associative node.
* \return True if the node is an associative one.
*/
bool isAssociative() const { return m_IsAssociative; }
/**
* Returns the vector of children of the node.
* \return A reference to the vector of children.
*/
virtual std::vector<CAbstractTreeNode*>& getChildren() { return m_Children; }
/**
* Adds a child to this node.
* \param child The child to add.
*/
virtual void addChild(CAbstractTreeNode* child) { m_Children.push_back(child); }
//! Destructor.
~CAbstractTreeParentNode() override;
//! Debug function, prints the node and its children (prefix notation)
void print(OpenViBE::Kernel::ILogManager& logManager) override
{
std::string op;
switch (m_ID)
{
case OP_NEG: op = "-";
break;
case OP_ADD: op = "+";
break;
case OP_SUB: op = "-";
break;
case OP_MUL: op = "*";
break;
case OP_DIV: op = "/";
break;
case OP_ABS: op = "abs";
break;
case OP_ACOS: op = "cos";
break;
case OP_ASIN: op = "sin";
break;
case OP_ATAN: op = "atan";
break;
case OP_CEIL: op = "ceil";
break;
case OP_COS: op = "cos";
break;
case OP_EXP: op = "exp";
break;
case OP_FLOOR: op = "floor";
break;
case OP_LOG: op = "log";
break;
case OP_LOG10: op = "log10";
break;
case OP_POW: op = "pow";
break;
case OP_RAND: op = "rand";
break;
case OP_SIN: op = "sin";
break;
case OP_SQRT: op = "sqrt";
break;
case OP_TAN: op = "tan";
break;
case OP_IF_THEN_ELSE: op = "?:";
break;
case OP_CMP_L: op = "<";
break;
case OP_CMP_G: op = ">";
break;
case OP_CMP_LE: op = "<=";
break;
case OP_CMP_GE: op = ">=";
break;
case OP_CMP_E: op = "==";
break;
case OP_CMP_NE: op = "!=";
break;
case OP_BOOL_AND: op = "&";
break;
case OP_BOOL_OR: op = "|";
break;
case OP_BOOL_NOT: op = "!";
break;
case OP_BOOL_XOR: op = "^";
break;
case OP_USERDEF: op = "UserDefined";
break;
case OP_NONE: op = "None";
break;
case OP_X2: op = "X^2";
break;
default: op = "UnknownOp";
break;
}
logManager << "(" << op << " ";
for (size_t i = 0; i < m_Children.size(); ++i)
{
if (m_Children[i] == nullptr) { }
else { m_Children[i]->print(logManager); }
logManager << " ";
}
logManager << ")";
}
bool simplify(CAbstractTreeNode*& node) override;
void levelOperators() override;
void useNegationOperator() override;
void generateCode(CEquationParser& parser) override;
};
/**
* Class for terminal nodes containing a single value.
*
*/
class CAbstractTreeValueNode : public CAbstractTreeNode
{
protected:
//! Value associated with the node.
double m_value = 0;
public:
explicit CAbstractTreeValueNode(const double value) : CAbstractTreeNode(true, true), m_value(value) {}
//! Destructor
~CAbstractTreeValueNode() override { }
/**
* Used to set the value of the node.
* \param value The node's new value.
*/
void setValue(const double value) { m_value = value; }
/**
* Used to know the value of the node.
* \return The node's value.
*/
double getValue() const { return m_value; }
void print(OpenViBE::Kernel::ILogManager& logManager) override { logManager << m_value; }
bool simplify(CAbstractTreeNode*& modifiedNode) override
{
modifiedNode = this;
return false;
}
void levelOperators() override { }
void useNegationOperator() override { }
void generateCode(CEquationParser& parser) override;
};
/**
* Class for terminal nodes referencing a variable.
*/
class CAbstractTreeVariableNode : public CAbstractTreeNode
{
public:
explicit CAbstractTreeVariableNode(const size_t index) : CAbstractTreeNode(true, false), m_index(index) { }
~CAbstractTreeVariableNode() override { }
void print(OpenViBE::Kernel::ILogManager& logManager) override
{
char name[2];
name[0] = char('a' + m_index);
name[1] = 0;
logManager << name;
}
bool simplify(CAbstractTreeNode*& modifiedNode) override
{
modifiedNode = this;
return false;
}
void levelOperators() override { }
void useNegationOperator() override { }
void generateCode(CEquationParser& parser) override;
protected:
size_t m_index = 0;
};
/**
* Main class for the AST.
* Contains the root of the tree.
*/
class CAbstractTree
{
protected:
//! the root of the AST tree.
CAbstractTreeNode* m_root = nullptr;
public:
//! Constructor
explicit CAbstractTree(CAbstractTreeNode* root) : m_root(root) { }
//! Destructor
~CAbstractTree() { delete m_root; }
//! Prints the whole tree.
void printTree(OpenViBE::Kernel::ILogManager& logManager) const { m_root->print(logManager); }
/**
* Used to simplify the tree.
*/
void simplifyTree();
/**
* Part of the process of simplification.
* Levels recursively the associative operators nodes.
*/
void levelOperators() const { m_root->levelOperators(); }
/**
* Changes the tree so it uses the NEG operator whenever it is possible.
* (ie replaces (* -1 X) by (NEG X)
*/
void useNegationOperator() const { m_root->useNegationOperator(); }
/**
* Generates the set of function calls needed to do the desired computation.
* \param parser The parser containing the function pointers stack and function contexts stack.
*/
void generateCode(CEquationParser& parser);
/**
* Tries to recognize simple tree structures (X*X, X*Cste, X+Cste,...)
* \param treeId The identifier of the tree (OP_USERDEF for non special tree).
* \param parameter The optional parameter if it is a special tree.
*/
void recognizeSpecialTree(uint64_t& treeId, double& parameter);
};
/**
* Functor used to compare two nodes.
* The order is as follow : Constants, Variables, ParentNodes
*/
struct CAbstractTreeNodeOrderingFunction
{
bool operator()(CAbstractTreeNode* const & pFirstNode, CAbstractTreeNode* const & pSecondNode) const
{
#if 0
if( (pFirstNode->isConstant()) ||
(pFirstNode->isTerminal() && !pSecondNode->isConstant()) ||
(!pFirstNode->isTerminal() && !pSecondNode->isTerminal())) { return true; }
else { return false; }
#else
// Check isConstant flag
if (pFirstNode->isConstant() && !pSecondNode->isConstant()) { return true; }
if (!pFirstNode->isConstant() && pSecondNode->isConstant()) { return false; }
// Check isTerminal flag
if (pFirstNode->isTerminal() && !pSecondNode->isTerminal()) { return true; }
if (!pFirstNode->isTerminal() && pSecondNode->isTerminal()) { return false; }
// At this point, isTerminal and isConstant are the same for both value
// Order is not important any more, we just compare the pointer values
// so to have strict ordering function
return pFirstNode < pSecondNode;
#endif
}
};
@@ -0,0 +1,420 @@
#include "ovpCEquationParser.h"
#include <cstdlib>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <functional>
#include <cctype>
#define _EQ_PARSER_DEBUG_LOG_(level, message) m_parentPlugin.getLogManager() << level << message << "\n";
#define _EQ_PARSER_DEBUG_PRINT_TREE_(level) { m_parentPlugin.getLogManager() << level; m_tree->printTree(m_parentPlugin.getLogManager()); m_parentPlugin.getLogManager() << "\n"; }
// because std::tolower has multiple signatures,
// it can not be easily used in std::transform
// this workaround is taken from http://www.gcek.net/ref/books/sw/cpp/ticppv2/
template <class T>
static T ToLower(T c) { return std::tolower(c); }
// BOOST::Ast should be able to remove spaces / tabs etc but
// unfortunately, it seems it does not work correcly in some
// cases so I add this sanitizer function to clear the Simple DSP
// equation before sending it to BOOST::Ast
static std::string FindAndReplace(std::string s, const std::string& f, const std::string& r)
{
size_t i;
while ((i = s.find(f)) != std::string::npos) { s.replace(i, f.length(), r); }
return s;
}
std::array<functionPointer, 32> CEquationParser::m_functionTable =
{
&op_neg, &op_add, &op_sub, &op_mul, &op_div,
&op_abs, &op_acos, &op_asin, &op_atan,
&op_ceil, &op_cos, &op_exp, &op_floor,
&op_log, &op_log10, &op_power, &op_rand, &op_sin,
&op_sqrt, &op_tan,
&op_if_then_else,
&op_cmp_lower,
&op_cmp_greater,
&op_cmp_lower_equal,
&op_cmp_greater_equal,
&op_cmp_equal,
&op_cmp_not_equal,
&op_bool_and,
&op_bool_or,
&op_bool_not,
&op_bool_xor,
};
CEquationParser::~CEquationParser()
{
delete[] m_functionListBase;
delete[] m_functionContextListBase;
delete[] m_stack;
delete m_tree;
}
bool CEquationParser::compileEquation(const char* equation)
{
// BOOST::Ast should be able to remove spaces / tabs etc but
// unfortunately, it seems it does not work correcly in some
// cases so I add this sanitizer function to clear the Simple DSP
// equation before sending it to BOOST::Ast
std::string str(equation);
str = FindAndReplace(str, " ", "");
str = FindAndReplace(str, "\t", "");
str = FindAndReplace(str, "\n", "");
//parses the equation
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Parsing equation [" << str << "]...");
const boost::spirit::classic::tree_parse_info<> info = ast_parse(str.c_str(), m_grammar >> boost::spirit::classic::end_p, boost::spirit::classic::space_p);
//If the parsing was successful
if (info.full)
{
//creates the AST
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Creating abstract tree...");
createAbstractTree(info);
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
#if 0
//CONSTANT FOLDING
//levels the associative/commutative operators (+ and *)
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Leveling tree...");
m_tree->levelOperators();
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
//simplifies the AST
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Simplifying tree...");
m_tree->simplifyTree();
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
//tries to replace nodes to use the NEG operator and reduce complexity
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Generating bytecode...");
m_tree->useNegationOperator();
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
//Detects if it is a special tree (updates m_treeCategory and m_treeParameter)
_EQ_PARSER_DEBUG_LOG_(OpenViBE::Kernel::LogLevel_Trace, "Recognizing special tree...");
m_tree->recognizeSpecialTree(m_treeCategory, m_treeParameter);
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
//Unrecognize special tree
_EQ_PARSER_DEBUG_LOG_("Unrecognizing special tree...");
m_treeCategory = OP_USERDEF;
_EQ_PARSER_DEBUG_PRINT_TREE_(OpenViBE::Kernel::LogLevel_Debug);
#endif
//If it is not a special tree, we need to generate some code to reach the result
if (m_treeCategory == OP_USERDEF)
{
//allocates the function stack
m_functionList = new functionPointer[m_functionStackSize];
m_functionListBase = m_functionList;
//Allocates the function context stack
m_functionContextList = new UFunctionContext[m_functionContextStackSize];
m_functionContextListBase = m_functionContextList;
m_stack = new double[m_stackSize];
//generates the code
m_tree->generateCode(*this);
//computes the number of steps to get to the result
m_nOperations = m_functionList - m_functionListBase;
}
return true;
}
std::string error;
const size_t pos = str.find(info.stop);
if (pos != std::string::npos)
{
for (size_t i = 0; i < pos; ++i) { error += " "; }
error += "^--Here\n";
}
OV_ERROR("Failed parsing equation \n[" << equation << "]\n " << error, OpenViBE::Kernel::ErrorType::BadParsing, false,
m_parentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getErrorManager(),
m_parentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager());
}
void CEquationParser::createAbstractTree(boost::spirit::classic::tree_parse_info<> oInfo) { m_tree = new CAbstractTree(createNode(oInfo.trees.begin())); }
CAbstractTreeNode* CEquationParser::createNode(iter_t const& i) const
{
if (i->value.id() == SEquationGrammar::expressionID)
{
if (*i->value.begin() == '+')
{
return new CAbstractTreeParentNode(OP_ADD, createNode(i->children.begin()), createNode(i->children.begin() + 1), true);
}
//replaces (- X Y) by (+ X (-Y)) (in fact (+ X (* -1 Y)) )
if (*i->value.begin() == '-')
{
return new CAbstractTreeParentNode(OP_ADD, createNode(i->children.begin()),
new CAbstractTreeParentNode(OP_MUL, new CAbstractTreeValueNode(-1), createNode(i->children.begin() + 1), true),
true);
}
}
else if (i->value.id() == SEquationGrammar::termID)
{
if (*i->value.begin() == '*')
{
return new CAbstractTreeParentNode(OP_MUL, createNode(i->children.begin()), createNode(i->children.begin() + 1), true);
}
if (*i->value.begin() == '/') { return new CAbstractTreeParentNode(OP_DIV, createNode(i->children.begin()), createNode(i->children.begin() + 1)); }
}
else if (i->value.id() == SEquationGrammar::factorID)
{
if (*i->value.begin() == '-')
{
// -X => (* -1 X), useful to simplify the tree later
return new CAbstractTreeParentNode(OP_MUL, new CAbstractTreeValueNode(-1), createNode(i->children.begin()), true);
}
if (*i->value.begin() == '+') { return createNode(i->children.begin()); }
}
else if (i->value.id() == SEquationGrammar::realID)
{
const std::string value(i->value.begin(), i->value.end());
return new CAbstractTreeValueNode(strtod(value.c_str(), nullptr));
}
else if (i->value.id() == SEquationGrammar::variableID)
{
size_t idx = 0;
std::string value(i->value.begin(), i->value.end());
if (value != "x" && value != "X")
{
if (value[0] >= 'a' && value[0] <= 'z') { idx = value[0] - 'a'; }
if (value[0] >= 'A' && value[0] <= 'Z') { idx = value[0] - 'A'; }
}
if (idx >= m_nVariable)
{
OV_WARNING("Missing input " << idx+1 << " (referenced with variable [" << value << "])",
m_parentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager());
return new CAbstractTreeValueNode(0);
}
return new CAbstractTreeVariableNode(idx);
}
else if (i->value.id() == SEquationGrammar::constantID)
{
std::string value(i->value.begin(), i->value.end());
//converts the string to lowercase
std::transform(value.begin(), value.end(), value.begin(), ::ToLower<std::string::value_type>);
//creates a new value node from the value looked up in the constant's symbols table
return new CAbstractTreeValueNode(*find(mathConstant_p, value.c_str()));
}
else if (i->value.id() == SEquationGrammar::functionID)
{
std::string value(i->value.begin(), i->value.end());
uint64_t* functionID;
//converts the string to lowercase
std::transform(value.begin(), value.end(), value.begin(), ::ToLower<std::string::value_type>);
//gets the function's Id from the unary function's symbols table
if ((functionID = find(unaryFunction_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), false);
}
//gets the function's Id from the binary function's symbols table
if ((functionID = find(binaryFunction_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
}
else if (i->value.id() == SEquationGrammar::ifthenID)
{
return new CAbstractTreeParentNode(OP_IF_THEN_ELSE, createNode(i->children.begin()), createNode(i->children.begin() + 1),
createNode(i->children.begin() + 2), false);
}
else if (i->value.id() == SEquationGrammar::comparisonID)
{
std::string value(i->value.begin(), i->value.end());
uint64_t* functionID;
//converts the string to lowercase
std::transform(value.begin(), value.end(), value.begin(), ::ToLower<std::string::value_type>);
//gets the function's Id from the comparison function's symbols table
if ((functionID = find(comparison1Function_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
//gets the function's Id from the comparison function's symbols table
if ((functionID = find(comparison2Function_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
}
else if (i->value.id() == SEquationGrammar::booleanID)
{
std::string value(i->value.begin(), i->value.end());
uint64_t* functionID;
//converts the string to lowercase
std::transform(value.begin(), value.end(), value.begin(), ::ToLower<std::string::value_type>);
//gets the function's Id from the binary boolean function's symbols table
if ((functionID = find(binaryBoolean1Function_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
//gets the function's Id from the binary boolean function's symbols table
if ((functionID = find(binaryBoolean2Function_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
//gets the function's Id from the binary boolean function's symbols table
if ((functionID = find(binaryBoolean3Function_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), createNode(i->children.begin() + 1), false);
}
//gets the function's Id from the binary boolean function's symbols table
if ((functionID = find(unaryBooleanFunction_p, value.c_str())) != nullptr)
{
return new CAbstractTreeParentNode(*functionID, createNode(i->children.begin()), false);
}
}
return nullptr;
}
void CEquationParser::push_value(const double value)
{
*(m_functionList++) = op_loadVal;
(*(m_functionContextList++)).direct_value = value;
}
void CEquationParser::push_var(const size_t index)
{
*(m_functionList++) = op_loadVar;
(*(m_functionContextList++)).indirect_value = &m_variable[index];
}
void CEquationParser::push_op(const size_t op)
{
*(m_functionList++) = m_functionTable[op];
(*(m_functionContextList++)).indirect_value = nullptr;
}
// Functions called by our "pseudo - VM"
void CEquationParser::op_neg(double*& stack, UFunctionContext& /*ctx*/) { *stack = - (*stack); }
void CEquationParser::op_add(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
*(stack) = *(stack + 1) + *(stack);
}
void CEquationParser::op_sub(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
*(stack) = *(stack + 1) - *(stack);
}
void CEquationParser::op_mul(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
*(stack) = *(stack + 1) * *(stack);
}
void CEquationParser::op_div(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
*(stack) = *(stack + 1) / *(stack);
}
void CEquationParser::op_power(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
*stack = pow(*(stack + 1), *(stack));
}
void CEquationParser::op_abs(double*& stack, UFunctionContext& /*ctx*/) { *stack = fabs(*(stack)); }
void CEquationParser::op_acos(double*& stack, UFunctionContext& /*ctx*/) { *stack = acos(*(stack)); }
void CEquationParser::op_asin(double*& stack, UFunctionContext& /*ctx*/) { *stack = asin(*(stack)); }
void CEquationParser::op_atan(double*& stack, UFunctionContext& /*ctx*/) { *stack = atan(*(stack)); }
void CEquationParser::op_ceil(double*& stack, UFunctionContext& /*ctx*/) { *stack = ceil(*(stack)); }
void CEquationParser::op_cos(double*& stack, UFunctionContext& /*ctx*/) { *stack = cos(*(stack)); }
void CEquationParser::op_exp(double*& stack, UFunctionContext& /*ctx*/) { *stack = exp(*(stack)); }
void CEquationParser::op_floor(double*& stack, UFunctionContext& /*ctx*/) { *stack = floor(*(stack)); }
void CEquationParser::op_log(double*& stack, UFunctionContext& /*ctx*/) { *stack = log(*(stack)); }
void CEquationParser::op_log10(double*& stack, UFunctionContext& /*ctx*/) { *stack = log10(*(stack)); }
void CEquationParser::op_rand(double*& stack, UFunctionContext& /*ctx*/) { *stack = rand() * *(stack) / RAND_MAX; }
void CEquationParser::op_sin(double*& stack, UFunctionContext& /*ctx*/) { *stack = sin(*(stack)); }
void CEquationParser::op_sqrt(double*& stack, UFunctionContext& /*ctx*/) { *stack = sqrt(*(stack)); }
void CEquationParser::op_tan(double*& stack, UFunctionContext& /*ctx*/) { *stack = tan(*(stack)); }
void CEquationParser::op_if_then_else(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack--;
if (*(stack + 2)) { *stack = *(stack + 1); }
// else { *stack = *stack; }
}
void CEquationParser::op_cmp_lower(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] < stack[0] ? 1 : 0);
}
void CEquationParser::op_cmp_greater(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] > stack[0] ? 1 : 0);
}
void CEquationParser::op_cmp_lower_equal(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] <= stack[0] ? 1 : 0);
}
void CEquationParser::op_cmp_greater_equal(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] >= stack[0] ? 1 : 0);
}
void CEquationParser::op_cmp_equal(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] == stack[0] ? 1 : 0);
}
void CEquationParser::op_cmp_not_equal(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] != stack[0] ? 1 : 0);
}
void CEquationParser::op_bool_and(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] != 0 && stack[0] != 0 ? 1 : 0);
}
void CEquationParser::op_bool_or(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] != 0 || stack[0] != 0 ? 1 : 0);
}
void CEquationParser::op_bool_not(double*& stack, UFunctionContext& /*ctx*/) { stack[0] = stack[0] != 0 ? 0 : 1; }
void CEquationParser::op_bool_xor(double*& stack, UFunctionContext& /*ctx*/)
{
stack--;
stack[0] = (stack[1] != stack[0] ? 1 : 0);
}
void CEquationParser::op_loadVal(double*& stack, UFunctionContext& ctx) { *(++stack) = ctx.direct_value; }
void CEquationParser::op_loadVar(double*& stack, UFunctionContext& ctx) { *(++stack) = **(ctx.indirect_value); }
@@ -0,0 +1,196 @@
#pragma once
#include "../ovp_defines.h"
#include "ovpCEquationParserGrammar.h"
#include "ovpCAbstractTree.h"
#include <toolkit/ovtk_all.h>
#include <boost/spirit/include/classic_ast.hpp>
#include <vector>
#include <array>
typedef char const* iterator_t;
typedef boost::spirit::classic::tree_match<iterator_t> parse_tree_match_t;
typedef parse_tree_match_t::tree_iterator iter_t;
class CAbstractTree;
class CAbstractTreeNode;
/**
* Used to store the optional parameter of the function used by the pseudo-VM.
*
*/
union UFunctionContext
{
double direct_value = 0; //if the parameter if a value (push_val)
double** indirect_value; //if it is a pointer to a value (push_var)
};
//! Type of the functions in the function stack generated from the equation.
typedef void (*functionPointer)(double*& stack, UFunctionContext& oContext);
class CEquationParser
{
protected:
//! The AST produced by the parsing of the equation
CAbstractTree* m_tree = nullptr;
//! Grammar to use
SEquationGrammar m_grammar;
//! Pointer to the data referenced by X in the equation
double** m_variable = nullptr;
//! Number of accessible variables
size_t m_nVariable = 0;
//! Size of the "function stack" (where the sucessive function pointers are stored)
const size_t m_functionStackSize = 1024;
//! Pointer to the top of the function stack
functionPointer* m_functionList = nullptr;
//! Pointer to the base of the function stack
functionPointer* m_functionListBase = nullptr;
//! Size of the "function context stack" (where the sucessive function context are stored)
const size_t m_functionContextStackSize = 1024;
//! Pointer to the top of the function context stack
UFunctionContext* m_functionContextList = nullptr;
//! Pointer to the base of the function context stack
UFunctionContext* m_functionContextListBase = nullptr;
//! Size of the "local" stack
const size_t m_stackSize = 1024;
//! Pointer to the top of the "local" stack
double* m_stack = nullptr;
//! Number of pointers/contexts in the function/context stacks (same for both)
size_t m_nOperations = 0;
//! Table of function pointers
static std::array<functionPointer, 32> m_functionTable;
//! Category of the tree (OP_USERDEF or Special tree)
size_t m_treeCategory = OP_USERDEF;
//! Optional parameter in case of a special tree
double m_treeParameter = 0;
OpenViBE::Toolkit::TBoxAlgorithm<OpenViBE::Plugins::IBoxAlgorithm>& m_parentPlugin;
public:
/**
* Constructor.
* \param plugin
* \param variable Pointer to the data known as X in the equation.
* \param nVariable
*/
CEquationParser(OpenViBE::Toolkit::TBoxAlgorithm<OpenViBE::Plugins::IBoxAlgorithm>& plugin, double** variable, const size_t nVariable)
: m_variable(variable), m_nVariable(nVariable), m_parentPlugin(plugin) {}
//! Destructor.
~CEquationParser();
#if 0
void setVariable(double * pVariable){ m_pVariable=pVariable; }
#endif
/**
* Compiles the given equation, and generates the successive function calls to achieve the
* same result if needed (depends on m_treeCategory).
* \param equation The equation to use.
*/
bool compileEquation(const char* equation);
void push_value(double value);
void push_var(size_t index);
void push_op(size_t op);
/**
* Returns the tree's category.
* \return The tree's category.
*/
size_t getTreeCategory() const { return m_treeCategory; }
/**
* Returns the optional parameter.
* \return The optional parameter.
*/
double getTreeParameter() const { return m_treeParameter; }
/**
* Executes the successive function calls from the function stack and returns
* the result.
* \return The result of the equation applied to the value referenced by X.
*/
double executeEquation()
{
functionPointer* currentFunction = m_functionList - 1;
functionPointer* lastFunctionPointer = currentFunction - m_nOperations;
UFunctionContext* currentFunctionContext = m_functionContextList - 1;
//while there are function pointers
while (currentFunction != lastFunctionPointer)
{
//calls the function with the current function context
(*currentFunction)(m_stack, *currentFunctionContext);
//updates the stack pointers
currentFunction--;
currentFunctionContext--;
}
//pop and return the result
return *(m_stack--);
}
private:
void createAbstractTree(boost::spirit::classic::tree_parse_info<> oInfo);
CAbstractTreeNode* createNode(iter_t const& i) const;
public:
static void op_neg(double*& stack, UFunctionContext& ctx);
static void op_add(double*& stack, UFunctionContext& ctx);
static void op_div(double*& stack, UFunctionContext& ctx);
static void op_sub(double*& stack, UFunctionContext& ctx);
static void op_mul(double*& stack, UFunctionContext& ctx);
static void op_power(double*& stack, UFunctionContext& ctx);
static void op_abs(double*& stack, UFunctionContext& ctx);
static void op_acos(double*& stack, UFunctionContext& ctx);
static void op_asin(double*& stack, UFunctionContext& ctx);
static void op_atan(double*& stack, UFunctionContext& ctx);
static void op_ceil(double*& stack, UFunctionContext& ctx);
static void op_cos(double*& stack, UFunctionContext& ctx);
static void op_exp(double*& stack, UFunctionContext& ctx);
static void op_floor(double*& stack, UFunctionContext& ctx);
static void op_log(double*& stack, UFunctionContext& ctx);
static void op_log10(double*& stack, UFunctionContext& ctx);
static void op_rand(double*& stack, UFunctionContext& ctx);
static void op_sin(double*& stack, UFunctionContext& ctx);
static void op_sqrt(double*& stack, UFunctionContext& ctx);
static void op_tan(double*& stack, UFunctionContext& ctx);
static void op_if_then_else(double*& stack, UFunctionContext& ctx);
static void op_cmp_lower(double*& stack, UFunctionContext& ctx);
static void op_cmp_greater(double*& stack, UFunctionContext& ctx);
static void op_cmp_lower_equal(double*& stack, UFunctionContext& ctx);
static void op_cmp_greater_equal(double*& stack, UFunctionContext& ctx);
static void op_cmp_equal(double*& stack, UFunctionContext& ctx);
static void op_cmp_not_equal(double*& stack, UFunctionContext& ctx);
static void op_bool_and(double*& stack, UFunctionContext& ctx);
static void op_bool_or(double*& stack, UFunctionContext& ctx);
static void op_bool_not(double*& stack, UFunctionContext& ctx);
static void op_bool_xor(double*& stack, UFunctionContext& ctx);
static void op_loadVal(double*& stack, UFunctionContext& ctx);
static void op_loadVar(double*& stack, UFunctionContext& ctx);
};
@@ -0,0 +1,336 @@
#pragma once
#include "../ovp_defines.h"
#include <toolkit/ovtk_all.h>
#include <boost/spirit/include/classic_symbols.hpp>
#include <boost/spirit/include/classic_ast.hpp>
/**
* Enum of parent nodes identifiers.
*/
enum EByteCodes
{
OP_NEG,
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV,
OP_ABS,
OP_ACOS,
OP_ASIN,
OP_ATAN,
OP_CEIL,
OP_COS,
OP_EXP,
OP_FLOOR,
OP_LOG,
OP_LOG10,
OP_POW,
OP_RAND,
OP_SIN,
OP_SQRT,
OP_TAN,
OP_IF_THEN_ELSE,
OP_CMP_L,
OP_CMP_G,
OP_CMP_LE,
OP_CMP_GE,
OP_CMP_E,
OP_CMP_NE,
OP_BOOL_AND,
OP_BOOL_OR,
OP_BOOL_NOT,
OP_BOOL_XOR,
//used for special tree recognition
//The equation is not a special one
OP_USERDEF,
//Identity
OP_NONE,
//X*X
OP_X2
};
enum EVariables
{
OP_VAR_X=0,
OP_VAR_A,
OP_VAR_B,
OP_VAR_C,
OP_VAR_D,
OP_VAR_E,
OP_VAR_F,
OP_VAR_G,
OP_VAR_H,
OP_VAR_I,
OP_VAR_J,
OP_VAR_K,
OP_VAR_L,
OP_VAR_M,
OP_VAR_N,
OP_VAR_O,
OP_VAR_P,
};
/**
* Symbols table for unary functions.
*
*/
struct SUnaryFunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SUnaryFunctionSymbols()
{
add
("abs", OP_ABS)
("acos", OP_ACOS)
("asin", OP_ASIN)
("atan", OP_ATAN)
("ceil", OP_CEIL)
("cos", OP_COS)
("exp", OP_EXP)
("floor", OP_FLOOR)
("log", OP_LOG)
("log10", OP_LOG10)
("rand", OP_RAND)
("sin", OP_SIN)
("sqrt", OP_SQRT)
("tan", OP_TAN);
}
};
/**
* Symbols table for binary functions.
*
*/
struct SBinaryFunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SBinaryFunctionSymbols()
{
add
("pow", OP_POW);
}
};
/**
* Symbol tables for unary boolean operators
*
*/
struct SUnaryBooleanFunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SUnaryBooleanFunctionSymbols()
{
add
("!", OP_BOOL_NOT);
}
};
/**
* Symbol tables for binary boolean operators
*
*/
struct SBinaryBoolean1FunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SBinaryBoolean1FunctionSymbols()
{
add
("&&", OP_BOOL_AND)
("&", OP_BOOL_AND);
}
};
/**
* Symbol tables for binary boolean operators
*
*/
struct SBinaryBoolean2FunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SBinaryBoolean2FunctionSymbols()
{
add
("~", OP_BOOL_XOR)
("^", OP_BOOL_XOR);
}
};
/**
* Symbol tables for binary boolean operators
*
*/
struct SBinaryBoolean3FunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SBinaryBoolean3FunctionSymbols()
{
add
("||", OP_BOOL_OR)
("|", OP_BOOL_OR);
}
};
/**
* Symbols table for comparison 1 functions.
*
*/
struct SComparison1FunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SComparison1FunctionSymbols()
{
add
("<", OP_CMP_L)
(">", OP_CMP_G)
("<=", OP_CMP_LE)
(">=", OP_CMP_GE);
}
};
/**
* Symbols table for comparison 2 functions.
*
*/
struct SComparison2FunctionSymbols : boost::spirit::classic::symbols<uint64_t>
{
SComparison2FunctionSymbols()
{
add
("==", OP_CMP_E)
("!=", OP_CMP_NE)
("<>", OP_CMP_NE);
}
};
/**
* Symbols table for mathematical constants.
*
*/
struct SMathConstantSymbols : boost::spirit::classic::symbols<double>
{
SMathConstantSymbols()
{
add
("m_pi", 3.14159265358979323846)
("m_pi_2", 1.57079632679489661923)
("m_pi_4", 0.78539816339744830962)
("m_1_pi", 0.31830988618379067154)
("m_2_pi", 0.63661977236758134308)
("m_2_sqrt", 1.12837916709551257390)
("m_sqrt2", 1.41421356237309504880)
("m_sqrt1_2", 0.70710678118654752440)
("m_e", 2.7182818284590452354)
("m_log2e", 1.4426950408889634074)
("m_log10e", 0.43429448190325182765)
("m_ln", 0.69314718055994530942)
("m_ln10", 2.30258509299404568402);
}
};
/**
* Symbols table for variables.
*
*/
struct SVariableSymbols : boost::spirit::classic::symbols<uint64_t>
{
SVariableSymbols()
{
add
("x", OP_VAR_X)
("a", OP_VAR_A)
("b", OP_VAR_B)
("c", OP_VAR_C)
("d", OP_VAR_D)
("e", OP_VAR_E)
("f", OP_VAR_F)
("g", OP_VAR_G)
("h", OP_VAR_H)
("i", OP_VAR_I)
("j", OP_VAR_J)
("k", OP_VAR_K)
("l", OP_VAR_L)
("m", OP_VAR_M)
("n", OP_VAR_N)
("o", OP_VAR_O)
("p", OP_VAR_P);
}
};
static SUnaryFunctionSymbols unaryFunction_p;
static SBinaryFunctionSymbols binaryFunction_p;
static SUnaryBooleanFunctionSymbols unaryBooleanFunction_p;
static SBinaryBoolean1FunctionSymbols binaryBoolean1Function_p;
static SBinaryBoolean2FunctionSymbols binaryBoolean2Function_p;
static SBinaryBoolean3FunctionSymbols binaryBoolean3Function_p;
static SComparison1FunctionSymbols comparison1Function_p;
static SComparison2FunctionSymbols comparison2Function_p;
static SMathConstantSymbols mathConstant_p;
static SVariableSymbols variable_p;
/**
* The parser's grammar.
*/
struct SEquationGrammar : boost::spirit::classic::grammar<SEquationGrammar>
{
static const int realID = 1;
static const int variableID = 2;
static const int functionID = 3;
static const int constantID = 4;
static const int factorID = 6;
static const int termID = 7;
static const int expressionID = 8;
static const int ifthenID = 9;
static const int comparisonID = 10;
static const int booleanID = 11;
template <typename ScannerT>
struct definition
{
explicit definition(SEquationGrammar const& /*grammar*/)
{
using namespace boost::spirit::classic;
real = leaf_node_d[real_p];
variable = leaf_node_d[as_lower_d[variable_p]];
constant = leaf_node_d[as_lower_d[mathConstant_p]];
function = (root_node_d[as_lower_d[unaryFunction_p]] >> no_node_d[ch_p('(')] >> ifthen >> no_node_d[ch_p(')')])
| (root_node_d[as_lower_d[binaryFunction_p]] >> no_node_d[ch_p('(')] >> infix_node_d[(ifthen >> ',' >> ifthen)] >> no_node_d[ch_p(')')]);
factor = (function | constant | variable | real) | inner_node_d['(' >> expression >> ')']
| inner_node_d['(' >> ifthen >> ')'] | (root_node_d[ch_p('-')] >> factor) | (root_node_d[ch_p('+')] >> factor);
boolean = (root_node_d[unaryBooleanFunction_p] >> factor) | factor;
term = boolean >> *((root_node_d[ch_p('*')] >> boolean) | (root_node_d[ch_p('/')] >> boolean));
expression = term >> *((root_node_d[ch_p('+')] >> term) | (root_node_d[ch_p('-')] >> term));
comparison1 = (expression >> root_node_d[comparison1Function_p] >> expression) | expression;
comparison2 = (comparison1 >> root_node_d[comparison2Function_p] >> comparison1) | comparison1;
boolean1 = (comparison2 >> root_node_d[binaryBoolean1Function_p] >> comparison2) | comparison2;
boolean2 = (boolean1 >> root_node_d[binaryBoolean2Function_p] >> boolean1) | boolean1;
boolean3 = (boolean2 >> root_node_d[binaryBoolean3Function_p] >> boolean2) | boolean2;
ifthen = (boolean3 >> root_node_d[ch_p('?')] >> boolean3 >> no_node_d[ch_p(':')] >> boolean3) | boolean3;
}
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<booleanID>> boolean;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<booleanID>> boolean1;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<booleanID>> boolean2;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<booleanID>> boolean3;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<comparisonID>> comparison1;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<comparisonID>> comparison2;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<ifthenID>> ifthen;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<expressionID>> expression;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<termID>> term;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<factorID>> factor;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<realID>> real;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<variableID>> variable;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<functionID>> function;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<constantID>> constant;
boost::spirit::classic::rule<ScannerT, boost::spirit::classic::parser_context<>, boost::spirit::classic::parser_tag<ifthenID>> const& start() const
{
return ifthen;
}
};
};
@@ -0,0 +1,117 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_Algorithm_MatrixAverage OpenViBE::CIdentifier(0x5E5A6C1C, 0x6F6BEB03)
#define OVP_ClassId_Algorithm_MatrixAverageDesc OpenViBE::CIdentifier(0x1992881F, 0xC938C0F2)
#define OVP_ClassId_Algorithm_OnlineCovariance OpenViBE::CIdentifier(0x5ADD4F8E, 0x005D29C1)
#define OVP_ClassId_Algorithm_OnlineCovarianceDesc OpenViBE::CIdentifier(0x00CD2DEA, 0x4C000CEB)
#define OVP_ClassId_BoxAlgorithm_ChannelRename OpenViBE::CIdentifier(0x1FE50479, 0x39040F40)
#define OVP_ClassId_BoxAlgorithm_ChannelRenameDesc OpenViBE::CIdentifier(0x20EA1F00, 0x7AED5645)
#define OVP_ClassId_BoxAlgorithm_ChannelSelector OpenViBE::CIdentifier(0x361722E8, 0x311574E8)
#define OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc OpenViBE::CIdentifier(0x67633C1C, 0x0D610CD8)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReference OpenViBE::CIdentifier(0x009C0CE3, 0x6BDF71C3)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc OpenViBE::CIdentifier(0x0033EAF8, 0x09C65E4E)
#define OVP_ClassId_ContinuousWaveletAnalysis OpenViBE::CIdentifier(0x0A43133D, 0x6EAF25A7)
#define OVP_ClassId_ContinuousWaveletAnalysisDesc OpenViBE::CIdentifier(0x5B397A82, 0x76AE6F81)
#define OVP_ClassId_BoxAlgorithm_Crop OpenViBE::CIdentifier(0x7F1A3002, 0x358117BA)
#define OVP_ClassId_BoxAlgorithm_CropDesc OpenViBE::CIdentifier(0x64D619D7, 0x26CC42C9)
#define OVP_ClassId_BoxAlgorithm_EpochAverage OpenViBE::CIdentifier(0x21283D9F, 0xE76FF640)
#define OVP_ClassId_BoxAlgorithm_EpochAverageDesc OpenViBE::CIdentifier(0x95F5F43E, 0xBE629D82)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelector OpenViBE::CIdentifier(0x140C19C6, 0x4E6E187B)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc OpenViBE::CIdentifier(0x13462C56, 0x794E3C07)
#define OVP_ClassId_BoxAlgorithm_Identity OpenViBE::CIdentifier(0x5DFFE431, 0x35215C50)
#define OVP_ClassId_BoxAlgorithm_IdentityDesc OpenViBE::CIdentifier(0x54743810, 0x6A1A88CC)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannel OpenViBE::CIdentifier(0x444721AD, 0x78342CF5)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc OpenViBE::CIdentifier(0x42856103, 0x45B125AD)
#define OVP_ClassId_BoxAlgorithm_RegularizedCSPTrainer OpenViBE::CIdentifier(0x2EC14CC0, 0x428C48BD)
#define OVP_ClassId_BoxAlgorithm_RegularizedCSPTrainerDesc OpenViBE::CIdentifier(0x02205F54, 0x733C51EE)
#define OVP_ClassId_BoxAlgorithm_SignalAverage OpenViBE::CIdentifier(0x00642C4D, 0x5DF7E50A)
#define OVP_ClassId_BoxAlgorithm_SignalAverageDesc OpenViBE::CIdentifier(0x007CDCE9, 0x16034F77)
#define OVP_ClassId_BoxAlgorithm_SignalDecimation OpenViBE::CIdentifier(0x012F4BEA, 0x3BE37C66)
#define OVP_ClassId_BoxAlgorithm_SignalDecimationDesc OpenViBE::CIdentifier(0x1C5F1356, 0x1E685777)
#define OVP_ClassId_BoxAlgorithm_SignalResampling OpenViBE::CIdentifier(0x0E923A5E, 0xDA474058)
#define OVP_ClassId_BoxAlgorithm_SignalResamplingDesc OpenViBE::CIdentifier(0xA675A433, 0xC6690920)
#define OVP_ClassId_BoxAlgorithm_SimpleDSP OpenViBE::CIdentifier(0x00E26FA1, 0x1DBAB1B2)
#define OVP_ClassId_BoxAlgorithm_SimpleDSPDesc OpenViBE::CIdentifier(0x00C44BFE, 0x76C9269E)
#define OVP_ClassId_BoxAlgorithm_SpatialFilter OpenViBE::CIdentifier(0xDD332C6C, 0x195B4FD4)
#define OVP_ClassId_BoxAlgorithm_SpatialFilterDesc OpenViBE::CIdentifier(0x72A01C92, 0xF8C1FA24)
#define OVP_ClassId_SpectralAnalysis OpenViBE::CIdentifier(0x84218FF8, 0xA87E7995)
#define OVP_ClassId_SpectralAnalysisDesc OpenViBE::CIdentifier(0x0051E63C, 0x68E83AD1)
#define OVP_ClassId_BoxAlgorithm_SpectrumAverage OpenViBE::CIdentifier(0x0C092665, 0x61B82641)
#define OVP_ClassId_BoxAlgorithm_SpectrumAverageDesc OpenViBE::CIdentifier(0x24663D96, 0x71EA7295)
#define OVP_ClassId_BoxAlgorithm_StimulationBasedEpoching OpenViBE::CIdentifier(0x426163D1, 0x324237B0)
#define OVP_ClassId_BoxAlgorithm_StimulationBasedEpochingDesc OpenViBE::CIdentifier(0x4F60616D, 0x468E0A8C)
#define OVP_ClassId_BoxAlgorithm_TemporalFilter OpenViBE::CIdentifier(0xB4F9D042, 0x9D79F2E5)
#define OVP_ClassId_BoxAlgorithm_TemporalFilterDesc OpenViBE::CIdentifier(0x7BF6BA62, 0xAF829A37)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpoching OpenViBE::CIdentifier(0x00777FA0, 0x5DC3F560)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc OpenViBE::CIdentifier(0x00ABDABE, 0x41381683)
#define OVP_ClassId_Windowing OpenViBE::CIdentifier(0x002034AE, 0x6509FD8F)
#define OVP_ClassId_WindowingDesc OpenViBE::CIdentifier(0x602CF89F, 0x65BA6DA0)
#define OVP_ClassId_BoxAlgorithm_InriaXDAWNTrainer OpenViBE::CIdentifier(0x27542F6E, 0x14AA3548)
#define OVP_ClassId_BoxAlgorithm_InriaXDAWNTrainerDesc OpenViBE::CIdentifier(0x128A6013, 0x370B5C2C)
#define OVP_ClassId_BoxAlgorithm_ZeroCrossingDetector OpenViBE::CIdentifier(0x0016663F, 0x096A46A6)
#define OVP_ClassId_BoxAlgorithm_ZeroCrossingDetectorDesc OpenViBE::CIdentifier(0x63AA73A7, 0x1F0419A2)
// Type definitions
//---------------------------------------------------------------------------------------------------
// Filter method identifiers from OpenViBE 0.14.0
#define OVP_TypeId_FilterMethod OpenViBE::CIdentifier(0x2F2C606C, 0x8512ED68)
#define OVP_TypeId_FilterType OpenViBE::CIdentifier(0xFA20178E, 0x4CBA62E9)
#define OVP_TypeId_OnlineCovariance_UpdateMethod OpenViBE::CIdentifier(0x59E83F33, 0x592F1DD0)
#define OVP_TypeId_EpochAverageMethod OpenViBE::CIdentifier(0x6530BDB1, 0xD057BBFE)
#define OVP_TypeId_ContinuousWaveletType OpenViBE::CIdentifier(0x09177469, 0x52404583)
#define OVP_TypeId_CropMethod OpenViBE::CIdentifier(0xD0643F9E, 0x8E35FE0A)
#define OVP_TypeId_SelectionMethod OpenViBE::CIdentifier(0x3BCF9E67, 0x0C23994D)
#define OVP_TypeId_MatchMethod OpenViBE::CIdentifier(0x666F25E9, 0x3E5738D6)
#define OVP_TypeId_WindowMethod OpenViBE::CIdentifier(0x0A430FE4, 0x4F318280)
#include <string>
enum class EFilterMethod { Butterworth, Chebyshev, YuleWalker };
enum class EFilterType { LowPass, BandPass, HighPass, BandStop };
enum class EUpdateMethod { ChunkAverage, Incremental };
enum class EEpochAverageMethod { Moving, MovingImmediate, Block, Cumulative };
enum class EContinuousWaveletType { Morlet, Paul, DOG };
enum class ECropMethod { Min, Max, MinMax };
enum class ESelectionMethod { Select, Reject, Select_EEG };
enum class EMatchMethod { Name, Index, Smart };
enum class EWindowMethod { None, Hamming, Hanning, Hann, Blackman, Triangular, SquareRoot };
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OVP_Value_CoupledStringSeparator '-'
//#define OVP_Value_AllSelection '*'
#define OVP_Algorithm_MatrixAverage_InputParameterId_Matrix OpenViBE::CIdentifier(0x913E9C3B, 0x8A62F5E3)
#define OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount OpenViBE::CIdentifier(0x08563191, 0xE78BB265)
#define OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod OpenViBE::CIdentifier(0xE63CD759, 0xB6ECF6B7)
#define OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix OpenViBE::CIdentifier(0x03CE5AE5, 0xBD9031E0)
#define OVP_Algorithm_MatrixAverage_InputTriggerId_Reset OpenViBE::CIdentifier(0x670EC053, 0xADFE3F5C)
#define OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix OpenViBE::CIdentifier(0x50B6EE87, 0xDC42E660)
#define OVP_Algorithm_MatrixAverage_InputTriggerId_ForceAverage OpenViBE::CIdentifier(0xBF597839, 0xCD6039F0)
#define OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed OpenViBE::CIdentifier(0x2BFF029B, 0xD932A613)
#define OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage OpenViBE::CIdentifier(0x16577C7B, 0x4E056BF7)
#define OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors OpenViBE::CIdentifier(0x47E55F81, 0x27A519C4)
#define OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod OpenViBE::CIdentifier(0x1C4F444F, 0x3CA213E2)
#define OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization OpenViBE::CIdentifier(0x269D5E63, 0x3B6D486E)
#define OVP_Algorithm_OnlineCovariance_OutputParameterId_Mean OpenViBE::CIdentifier(0x3F1F50A3, 0x05504D0E)
#define OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix OpenViBE::CIdentifier(0x203A5472, 0x67C5324C)
#define OVP_Algorithm_OnlineCovariance_Process_Reset OpenViBE::CIdentifier(0x4C1C510C, 0x3CF56E7C) // to reset estimates to 0
#define OVP_Algorithm_OnlineCovariance_Process_Update OpenViBE::CIdentifier(0x72BF2277, 0x2974747B) // update estimates with a new chunk of data
#define OVP_Algorithm_OnlineCovariance_Process_GetCov OpenViBE::CIdentifier(0x2BBC4A91, 0x27050CFD) // also returns the mean estimate
#define OVP_Algorithm_OnlineCovariance_Process_GetCovRaw OpenViBE::CIdentifier(0x0915148C, 0x5F792B2A) // also returns the mean estimate
#define OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_NewSampling OpenViBE::CIdentifier(0x158A8EFD, 0xAA894F86)
#define OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_SampleCountPerBuffer OpenViBE::CIdentifier(0x588783F3, 0x8E8DCF86)
#define OVP_ClassId_BoxAlgorithm_SignalResampling_SettingId_LowPassFilterSignalFlag OpenViBE::CIdentifier(0xAFDD8EFD, 0x23EF94F6)
@@ -0,0 +1,134 @@
#include "algorithms/basic/ovpCAlgorithmMatrixAverage.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmIdentity.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmChannelRename.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmChannelSelector.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmEpochAverage.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmCrop.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmSignalDecimation.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmReferenceChannel.h"
#include "box-algorithms/basic/ovpCBoxAlgorithmZeroCrossingDetector.h"
#include "box-algorithms/epoching/ovpCBoxAlgorithmStimulationBasedEpoching.h"
#include "box-algorithms/epoching/ovpCBoxAlgorithmTimeBasedEpoching.h"
#include "box-algorithms/filters/ovpCBoxAlgorithmCommonAverageReference.h"
#include "box-algorithms/filters/ovpCBoxAlgorithmSpatialFilter.h"
#include "box-algorithms/filters/ovpCBoxAlgorithmTemporalFilter.h"
#include "box-algorithms/filters/ovpCBoxAlgorithmRegularizedCSPTrainer.h"
#include "algorithms/basic/ovpCAlgorithmOnlineCovariance.h"
#include "box-algorithms/spectral-analysis/ovpCBoxAlgorithmContinuousWaveletAnalysis.h"
#include "box-algorithms/spectral-analysis/ovpCBoxAlgorithmSpectralAnalysis.h"
#include "box-algorithms/spectral-analysis/ovpCBoxAlgorithmFrequencyBandSelector.h"
#include "box-algorithms/spectral-analysis/ovpCBoxAlgorithmSpectrumAverage.h"
#include "box-algorithms/resampling/ovpCBoxAlgorithmSignalResampling.h"
#include "box-algorithms/ovpCBoxAlgorithmSimpleDSP.h"
#include "box-algorithms/ovpCBoxAlgorithmSignalAverage.h"
#include "box-algorithms/ovpCBoxAlgorithmWindowing.h"
#include "box-algorithms/ovpCBoxAlgorithmXDAWNTrainer.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
OVP_Declare_Begin()
context.getTypeManager().registerEnumerationType(OVP_TypeId_EpochAverageMethod, "Epoch Average method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_EpochAverageMethod, "Moving epoch average", size_t(EEpochAverageMethod::Moving));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_EpochAverageMethod, "Moving epoch average (Immediate)",
size_t(EEpochAverageMethod::MovingImmediate));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_EpochAverageMethod, "Epoch block average", size_t(EEpochAverageMethod::Block));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_EpochAverageMethod, "Cumulative average", size_t(EEpochAverageMethod::Cumulative));
context.getTypeManager().registerEnumerationType(OVP_TypeId_CropMethod, "Crop method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_CropMethod, "Min", size_t(ECropMethod::Min));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_CropMethod, "Max", size_t(ECropMethod::Max));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_CropMethod, "Min/Max", size_t(ECropMethod::MinMax));
context.getTypeManager().registerEnumerationType(OVP_TypeId_SelectionMethod, "Selection method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_SelectionMethod, "Select", size_t(ESelectionMethod::Select));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_SelectionMethod, "Reject", size_t(ESelectionMethod::Reject));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_SelectionMethod, "Select EEG", size_t(ESelectionMethod::Select_EEG));
context.getTypeManager().registerEnumerationType(OVP_TypeId_MatchMethod, "Match method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_MatchMethod, "Name", size_t(EMatchMethod::Name));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_MatchMethod, "Index", size_t(EMatchMethod::Index));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_MatchMethod, "Smart", size_t(EMatchMethod::Smart));
// Temporal filter
context.getTypeManager().registerEnumerationType(OVP_TypeId_FilterMethod, "Filter method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterMethod, "Butterworth", size_t(EFilterMethod::Butterworth));
// context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterMethod, "Chebishev", size_t(EFilterMethod::Chebyshev));
// context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterMethod, "Yule Walked", size_t(EFilterMethod::YuleWalker));
context.getTypeManager().registerEnumerationType(OVP_TypeId_FilterType, "Filter type");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterType, "Low Pass", size_t(EFilterType::LowPass));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterType, "High Pass", size_t(EFilterType::HighPass));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterType, "Band Pass", size_t(EFilterType::BandPass));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_FilterType, "Band Stop", size_t(EFilterType::BandStop));
OVP_Declare_New(CAlgorithmMatrixAverageDesc)
OVP_Declare_New(CBoxAlgorithmIdentityDesc);
OVP_Declare_New(CBoxAlgorithmTimeBasedEpochingDesc);
OVP_Declare_New(CBoxAlgorithmChannelRenameDesc)
OVP_Declare_New(CBoxAlgorithmChannelSelectorDesc)
OVP_Declare_New(CBoxAlgorithmReferenceChannelDesc)
OVP_Declare_New(CBoxAlgorithmEpochAverageDesc)
OVP_Declare_New(CBoxAlgorithmCropDesc)
OVP_Declare_New(CBoxAlgorithmSignalDecimationDesc)
OVP_Declare_New(CBoxAlgorithmZeroCrossingDetectorDesc)
OVP_Declare_New(CBoxAlgorithmStimulationBasedEpochingDesc)
OVP_Declare_New(CBoxAlgorithmCommonAverageReferenceDesc)
OVP_Declare_New(CBoxAlgorithmSpatialFilterDesc)
OVP_Declare_New(CBoxAlgorithmTemporalFilterDesc)
#if defined TARGET_HAS_ThirdPartyEIGEN
context.getTypeManager().registerEnumerationType(OVP_TypeId_OnlineCovariance_UpdateMethod, "Update method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_OnlineCovariance_UpdateMethod, "Chunk average", size_t(EUpdateMethod::ChunkAverage));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_OnlineCovariance_UpdateMethod, "Per sample", size_t(EUpdateMethod::Incremental));
OVP_Declare_New(CBoxAlgorithmRegularizedCSPTrainerDesc)
OVP_Declare_New(CAlgorithmOnlineCovarianceDesc)
#endif
#if defined TARGET_HAS_R8BRAIN
OVP_Declare_New(CBoxAlgorithmSignalResamplingDesc)
#endif
OVP_Declare_New(CBoxAlgorithmSimpleDSPDesc)
OVP_Declare_New(CSignalAverageDesc)
// Wavelet Type
context.getTypeManager().registerEnumerationType(OVP_TypeId_ContinuousWaveletType, "Continuous Wavelet Type");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ContinuousWaveletType, "Morlet wavelet", size_t(EContinuousWaveletType::Morlet));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ContinuousWaveletType, "Paul wavelet", size_t(EContinuousWaveletType::Paul));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ContinuousWaveletType, "Derivative of Gaussian wavelet", size_t(EContinuousWaveletType::DOG));
OVP_Declare_New(CBoxAlgorithmContinuousWaveletAnalysisDesc);
OVP_Declare_New(CBoxAlgorithmFrequencyBandSelectorDesc)
OVP_Declare_New(CBoxAlgorithmSpectrumAverageDesc)
OVP_Declare_New(CBoxAlgorithmSpectralAnalysisDesc);
OVP_Declare_New(CBoxAlgorithmWindowingDesc);
context.getTypeManager().registerEnumerationType(OVP_TypeId_WindowMethod, "Window method");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "None", size_t(EWindowMethod::None));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Hamming", size_t(EWindowMethod::Hamming));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Hanning", size_t(EWindowMethod::Hanning));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Hann", size_t(EWindowMethod::Hann));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Blackman", size_t(EWindowMethod::Blackman));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Triangular", size_t(EWindowMethod::Triangular));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_WindowMethod, "Square root", size_t(EWindowMethod::SquareRoot));
OVP_Declare_New(CBoxAlgorithmXDAWNTrainerDesc);
OVP_Declare_End()
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE