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,733 @@
#include <iomanip>
#if defined TARGET_HAS_ThirdPartyMatlab
#include "ovpCBoxAlgorithmMatlabScripting.h"
#include <system/ovCMath.h>
#include <iostream>
#include <stdio.h>
#include <sstream>
#include <string>
#include <ctime>
#include <algorithm>
#include <mex.h>
#include <engine.h>
#include <fs/Files.h>
#if defined TARGET_OS_Windows
#include <windows.h>
#include <direct.h>
#define getCurrentDir _getcwd
#else
#include <unistd.h>
#define getCurrentDir getcwd
#endif
// Size of the internal buffer storing matlab messages, in char
#define MATLAB_BUFFER 2048
#define MATLAB_ENGINE ((Engine*)m_engineHandle)
namespace OpenViBE {
namespace Plugins {
namespace Matlab {
// Sanitizes a path so that it only has / or \ characters and has a / or \ in the end.
// @fixme should move to plugins-FS possibly
void CBoxAlgorithmMatlabScripting::sanitizePath(CString& pathToModify)
{
std::string tmpPath(pathToModify);
// Append / to end of path if its not there already
if (tmpPath.length() > 0)
{
const char lastChar = tmpPath.at(tmpPath.length() - 1);
if (lastChar != '\\' && lastChar != '/') { tmpPath += "/"; }
}
#if defined TARGET_OS_Windows
std::replace(tmpPath.begin(), tmpPath.end(), '/', '\\'); // Convert '/' to '\'
#endif
pathToModify = tmpPath.c_str();
}
// The checkFailureRoutine() verifies the result of a matlab call (via engine or helper functions) given as argument.
// If the result is false (the matlab call failed), the message msg is printed in the Error Log Level, and the macro returns false.
// The Matlab output buffer is then printed. If an error message is detected in the buffer, the same error message is printed and
// the macro returns false.
bool CBoxAlgorithmMatlabScripting::checkFailureRoutine(const bool result, const CString& msg)
{
OV_ERROR_UNLESS_KRF(result, msg, Kernel::ErrorType::BadProcessing);
m_errorDetected = false;
this->printOutputBufferWithFormat();
OV_ERROR_UNLESS_KRF(!m_errorDetected, msg, Kernel::ErrorType::BadProcessing);
return true;
}
bool CBoxAlgorithmMatlabScripting::openMatlabEngineSafely()
{
this->getLogManager() << Kernel::LogLevel_Trace << "Trying to open the Matlab engine\n";
#if defined TARGET_OS_Linux
m_engineHandle = ::engOpen(m_matlabPath.toASCIIString());
OV_ERROR_UNLESS_KRF(m_matlabEngine, "Could not open the Matlab engine.\nThe configured path to the matlab executable was expanded as '" << m_matlabPath << "'.", Kernel::ErrorType::BadProcessing);
}
#elif defined TARGET_OS_Windows
__try { m_engineHandle = engOpen(nullptr); }
__except (EXCEPTION_EXECUTE_HANDLER)
{
this->getLogManager() << Kernel::LogLevel_Error << "First call to the MATLAB engine failed.\n";
this->getLogManager() << Kernel::LogLevel_Error << "To use this box you must have MATLAB installed on your computer.\n";
#if defined(TARGET_ARCHITECTURE_x64)
this->getLogManager() << Kernel::LogLevel_Error << "For this build of OpenViBE, MATLAB needs to be a 64bit version.\n";
#else
this->getLogManager() << Kernel::LogLevel_Error << "For this build of OpenViBE, MATLAB needs to be a 32bit version.\n";
#endif
m_engineHandle = nullptr;
}
if (!MATLAB_ENGINE)
{
this->getLogManager() << Kernel::LogLevel_Error << "Could not open the Matlab engine.\nThe matlab binary path was reasoned to be '" << m_matlabPath << "'.\n";
return false;
}
#else
OV_ERROR_KRF("Only Linux and Windows are supported", Kernel::ErrorType::BadVersion);
#endif
return true;
}
bool CBoxAlgorithmMatlabScripting::initialize()
{
m_engineHandle = nullptr;
m_matlabBuffer = nullptr;
m_helper = new CMatlabHelper(this->getLogManager(), this->getErrorManager());
m_clockFrequency = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
for (size_t i = 0; i < getStaticBoxContext().getInputCount(); ++i)
{
CIdentifier type;
getStaticBoxContext().getInputType(i, type);
Toolkit::TDecoder<CBoxAlgorithmMatlabScripting>* decoder = nullptr;
if (type == OV_TypeId_StreamedMatrix) { decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_ChannelLocalisation) { decoder = new Toolkit::TChannelLocalisationDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_FeatureVector) { decoder = new Toolkit::TFeatureVectorDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Spectrum) { decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Signal) { decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Stimulations) { decoder = new Toolkit::TStimulationDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_ExperimentInfo) { decoder = new Toolkit::TExperimentInfoDecoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else { this->getLogManager() << Kernel::LogLevel_Warning << "Undefined type on input [" << i << "].\n"; }
if (decoder != nullptr)
{
m_decoders.insert(std::make_pair(i, decoder));
m_nInputHeaderSent = 0;
}
}
for (size_t i = 0; i < getStaticBoxContext().getOutputCount(); ++i)
{
CIdentifier type;
getStaticBoxContext().getOutputType(i, type);
Toolkit::TEncoder<CBoxAlgorithmMatlabScripting>* encoder = nullptr;
if (type == OV_TypeId_StreamedMatrix) { encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_ChannelLocalisation) { encoder = new Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_FeatureVector) { encoder = new Toolkit::TFeatureVectorEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Spectrum) { encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Signal) { encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_Stimulations) { encoder = new Toolkit::TStimulationEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else if (type == OV_TypeId_ExperimentInfo) { encoder = new Toolkit::TExperimentInfoEncoder<CBoxAlgorithmMatlabScripting>(*this, i); }
else { this->getLogManager() << Kernel::LogLevel_Warning << "Undefined type on input [" << i << "].\n"; }
if (encoder != nullptr)
{
m_encoders.insert(std::make_pair(i, encoder));
m_oHeaderStates.insert(std::make_pair(i, false));
}
}
m_matlabPath = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
#if defined TARGET_OS_Windows
if (!FS::Files::directoryExists(m_matlabPath) && FS::Files::fileExists(m_matlabPath))
{
// The path might be pointing to the executable, try to extract the directory
char parentPath[MAX_PATH];
FS::Files::getParentPath(m_matlabPath, parentPath);
m_matlabPath = CString(parentPath);
}
sanitizePath(m_matlabPath);
this->getLogManager() << Kernel::LogLevel_Trace << "Interpreting Matlab path as '" << m_matlabPath << "'\n";
OV_ERROR_UNLESS_KRF(FS::Files::directoryExists(m_matlabPath), "Configured Matlab path '" << m_matlabPath << "' does not seem to be a directory",
Kernel::ErrorType::BadConfig);
char* path = getenv("PATH");
OV_ERROR_UNLESS_KRF(path, "Could not access the environment variable PATH to add Matlab path to it.", Kernel::ErrorType::BadSetting);
std::string strPath = std::string(path);
size_t found = strPath.find(m_matlabPath.toASCIIString());
if (found == std::string::npos)
{
CString pathModif = path + CString(";") + m_matlabPath;
OV_ERROR_UNLESS_KRF(_putenv_s("PATH", pathModif) == 0, "Failed to modify the environment PATH with the Matlab path.",
Kernel::ErrorType::BadProcessing);
this->getLogManager() << Kernel::LogLevel_Trace << "Matlab Path '" << m_matlabPath << "' added to Windows PATH environment variable.\n";
}
else { this->getLogManager() << Kernel::LogLevel_Trace << "No need to add matlab to PATH\n"; }
#endif
if (!openMatlabEngineSafely()) { return false; }
m_matlabBuffer = new char[MATLAB_BUFFER + 1];
m_matlabBuffer[MATLAB_BUFFER] = '\0';
engOutputBuffer(MATLAB_ENGINE, m_matlabBuffer, MATLAB_BUFFER);
m_errorDetected = false;
// add the openvibe toolbox to matlab path
char curDir[FILENAME_MAX];
OV_ERROR_UNLESS_KRF(getCurrentDir(curDir, FILENAME_MAX), "Failed to get the execution directory.", Kernel::ErrorType::BadProcessing);
std::string curDirString = curDir;
std::replace(curDirString.begin(), curDirString.end(), '\\', '/');
this->getConfigurationManager().addOrReplaceConfigurationToken(CString("Path_Bin_Abs"), CString(curDirString.c_str()));
CString cmd = CString("addpath('") + Directories::getDataDir() + "/plugins/matlab');";
engEvalString(MATLAB_ENGINE, cmd.toASCIIString());
//if(!checkFailureRoutine(engEvalString(m_matlabEngine, (const char * )l_sOpenvibeToolboxPath) == 0, "An error occurred while adding the path to openvibe toolbox\n")) return false;
// If there is more than 1 Matlab box in the scenario, the path is set repeatedly
// resulting in warning messages in the buffer. We don't print them.
// this->printOutputBufferWithFormat();
CString workingDir = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
sanitizePath(workingDir);
// Try to find an unused box instance variable name
while (true)
{
std::stringstream ss;
ss.fill('0');
ss << "OV_BOX_0x" << std::setw(8) << std::hex << System::Math::randomI() << "_0x" << std::setw(8) << std::hex << System::Math::randomI();
m_boxInstanceVariableName = ss.str().c_str();
this->getLogManager() << Kernel::LogLevel_Trace << "Checking if variable " << m_boxInstanceVariableName << " is in use...\n";
mxArray* tmp = engGetVariable(MATLAB_ENGINE, m_boxInstanceVariableName.toASCIIString());
if (!tmp)
{
// Found unused name
break;
}
mxDestroyArray(tmp);
}
this->getLogManager() << Kernel::LogLevel_Trace << "Selected variable name " << m_boxInstanceVariableName << "\n";
this->getLogManager() << Kernel::LogLevel_Trace << "Setting working directory to " << workingDir << "\n";
cmd = CString("cd '") + workingDir + CString("'");
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while changing the working directory\n")) { return false; }
// executes the pre-run routine that defines the global identifiers for streams and stimulations codes
cmd = CString("run '") + Directories::getDataDir() + "/plugins/matlab/OV_define.m'";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while calling OV_define.m")) { return false; }
cmd = CString("run '") + Directories::getDataDir() + "/plugins/matlab/OV_stimulations.m'";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while calling OV_stimulations.m")) { return false; }
// executes the pre-run routine that construct the ov_box object
cmd = m_boxInstanceVariableName + " = OV_createBoxInstance(" + std::to_string(this->getStaticBoxContext().getInputCount()).c_str() + ","
+ std::to_string(this->getStaticBoxContext().getOutputCount()).c_str() + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while calling OV_createBoxInstance.m")) { return false; }
//First call to a function of the openvibe toolbox
// if it fails, the toolbox may be not installed
mxArray* box = engGetVariable(MATLAB_ENGINE, m_boxInstanceVariableName.toASCIIString());
OV_ERROR_UNLESS_KRF(box, "Failed to create the box instance with OV_createBoxInstance function.", Kernel::ErrorType::BadProcessing);
m_initializeFunc = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
m_processFunc = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
m_uninitializeFunc = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
// Check that the files actually exist
CString filename;
filename = workingDir + m_initializeFunc + ".m";
OV_ERROR_UNLESS_KRF(FS::Files::fileExists(filename.toASCIIString()), "Cannot open '" << filename << "'", Kernel::ErrorType::BadFileRead);
filename = workingDir + m_processFunc + ".m";
OV_ERROR_UNLESS_KRF(FS::Files::fileExists(filename.toASCIIString()), "Cannot open '" << filename << "'", Kernel::ErrorType::BadFileRead);
filename = workingDir + m_uninitializeFunc + ".m";
OV_ERROR_UNLESS_KRF(FS::Files::fileExists(filename.toASCIIString()), "Cannot open '" << filename << "'", Kernel::ErrorType::BadFileRead);
CString names = "{";
CString types = "{";
CString values = "{";
CString tmp;
CIdentifier typeID;
for (size_t i = 6; i < getStaticBoxContext().getSettingCount(); ++i)
{
// get the setting name
getStaticBoxContext().getSettingName(i, tmp);
names = names + "'" + tmp + "' ";
//setting type
getStaticBoxContext().getSettingType(i, typeID);
std::stringstream ss;
ss << typeID.id();
types = types + "uint64(" + ss.str().c_str() + ") ";
//setting value, typed
tmp = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
if (typeID == OV_TypeId_Stimulation)
{
uint64_t code = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
std::stringstream ss1;
ss1 << code;
values = values + CString(ss1.str().c_str()) + CString(" ");
// we keep the stimulation codes as doubles, to be able to put them in arrays with other doubles (such as timings).
// they are still comparable with uint64_t matlab values.
}
else
{
if (typeID == OV_TypeId_Stimulation || typeID == OV_TypeId_Boolean || typeID == OV_TypeId_Integer || typeID == OV_TypeId_Float)
{
values = values + tmp + " "; // we store the value, these types are readable by matlab directly
}
else { values = values + "'" + tmp + "' "; } // we store them as matlab strings using '
}
}
names += "}";
types += "}";
values += "}";
// On Windows, Matlab doesn't sometimes notice .m files have been changed, esp. if you have matlab box running while you change them
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, "clear functions;") == 0, "An error occurred while calling matlab 'clear functions;'\n"))
{
return false;
}
cmd = m_boxInstanceVariableName + " = OV_setSettings(" + m_boxInstanceVariableName + "," + names + "," + types + "," + values + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd.toASCIIString()) == 0, "Error calling [OV_setSettings]\n")) { return false; }
// we set the box clock frequency in the box structure, so it's accessible in the user scripts if needed
tmp = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
cmd = m_boxInstanceVariableName + ".clock_frequency = " + tmp + ";";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while setting the clock frequency\n")) { return false; }
cmd = m_boxInstanceVariableName + " = " + m_initializeFunc + "(" + m_boxInstanceVariableName + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while calling the initialize function\n")) { return false; }
m_helper->setMatlabEngine(MATLAB_ENGINE);
m_helper->setBoxInstanceVariableName(m_boxInstanceVariableName);
return true;
}
bool CBoxAlgorithmMatlabScripting::closeMatlabEngineSafely()
{
if (MATLAB_ENGINE == nullptr) { return true; }
this->getLogManager() << Kernel::LogLevel_Trace << "Trying to close Matlab engine\n";
#if defined TARGET_OS_Windows
__try
{
#endif
if (MATLAB_ENGINE) { if (engClose(MATLAB_ENGINE) != 0) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Could not close Matlab engine.\n"; } }
#if defined TARGET_OS_Windows
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
this->getLogManager() << Kernel::LogLevel_Error << "Closing MATLAB engine failed.\n"
<< "\tTo use this box you must have MATLAB (32 bits version) installed on your computer.\n";
return false;
}
#endif
return true;
}
bool CBoxAlgorithmMatlabScripting::uninitialize()
{
delete m_helper;
if (MATLAB_ENGINE != nullptr)
{
CString cmd = m_boxInstanceVariableName + " = " + m_uninitializeFunc + "(" + m_boxInstanceVariableName + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while calling the uninitialize function\n")
) { } // NOP, we still want to deallocate below
// Remove the box from the matlab workspace
cmd = "clear " + m_boxInstanceVariableName + ";";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, cmd) == 0, "An error occurred while clearing the box\n")
) { } // NOP, we still want to deallocate below
}
closeMatlabEngineSafely();
if (m_matlabBuffer)
{
delete[] m_matlabBuffer;
m_matlabBuffer = nullptr;
}
for (size_t i = 0; i < m_decoders.size(); ++i)
{
m_decoders[i]->uninitialize();
delete m_decoders[i];
}
for (size_t i = 0; i < m_encoders.size(); ++i)
{
m_encoders[i]->uninitialize();
delete m_encoders[i];
}
return true;
}
bool CBoxAlgorithmMatlabScripting::processClock(Kernel::CMessageClock& /*msg*/)
{
if (!MATLAB_ENGINE) { return true; }
const std::string cmd = std::string(m_boxInstanceVariableName) + ".clock = " + std::to_string(CTime(this->getPlayerContext().getCurrentTime()).toSeconds())
+ ";";
OV_ERROR_UNLESS_KRF(engEvalString(MATLAB_ENGINE, cmd.c_str()) == 0, "An error occurred while updating the box clock.", Kernel::ErrorType::BadProcessing);
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmMatlabScripting::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
// In openvibe, pointers to managers can change between the calls to process(), so we need to provide current ones every time
m_helper->setManagers(&this->getLogManager(), &this->getErrorManager());
for (size_t i = 0; i < getStaticBoxContext().getInputCount(); ++i)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
m_decoders[i]->decode(j);
CIdentifier type;
getStaticBoxContext().getInputType(i, type);
bool unknownStream = true;
bool receivedSomething = false;
if (m_decoders[i]->isHeaderReceived())
{
receivedSomething = true;
// this->getLogManager() << Kernel::LogLevel_Debug << "Received header\n";
if (type == OV_TypeId_StreamedMatrix)
{
CMatrix* matrix = static_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
if (!checkFailureRoutine(m_helper->setStreamedMatrixInputHeader(i, matrix), "Error calling [OV_setStreamMatrixInputHeader]\n"))
{
return false;
}
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_Signal)
{
CMatrix* matrix = static_cast<Toolkit::TSignalDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
const uint64_t frequency = static_cast<Toolkit::TSignalDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputSamplingRate();
if (!checkFailureRoutine(m_helper->setSignalInputHeader(i, matrix, frequency),
"Error calling [OV_setSignalInputHeader]\n")) { return false; }
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_FeatureVector)
{
CMatrix* matrix = static_cast<Toolkit::TFeatureVectorDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
if (!checkFailureRoutine(m_helper->setFeatureVectorInputHeader(i, matrix),
"Error calling [OV_setFeatureVectorInputHeader]\n")) { return false; }
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_Spectrum)
{
CMatrix* matrix = static_cast<Toolkit::TSpectrumDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
CMatrix* freqAbscissa = static_cast<Toolkit::TSpectrumDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputFrequencyAbscissa();
const uint64_t frequency = static_cast<Toolkit::TSpectrumDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputSamplingRate();
if (!checkFailureRoutine(m_helper->setSpectrumInputHeader(i, matrix, freqAbscissa, frequency),
"Error calling [OV_setSpectrumInputHeader]\n")) { return false; }
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_ChannelLocalisation)
{
CMatrix* matrix = static_cast<Toolkit::TChannelLocalisationDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
const bool dynamic = static_cast<Toolkit::TChannelLocalisationDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputDynamic();
if (!checkFailureRoutine(m_helper->setChannelLocalisationInputHeader(i, matrix, dynamic),
"Error calling [OV_setChannelLocalizationInputHeader]\n")) { return false; }
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_ExperimentInfo)
{
OV_WARNING_K(
"The Experiment Information Stream is not implemented with the Matlab Scripting Box. If this is relevant for your usage, please contact the official development Team.")
;
m_nInputHeaderSent++;
unknownStream = false;
}
if (type == OV_TypeId_Stimulations)
{
if (!checkFailureRoutine(m_helper->setStimulationsInputHeader(i), "Error calling [OV_setStimulationsInputHeader]\n")) { return false; }
m_nInputHeaderSent++;
unknownStream = false;
}
}
if (m_decoders[i]->isBufferReceived())
{
// this->getLogManager() << Kernel::LogLevel_Debug << "Received buffer\n";
receivedSomething = true;
//
if (type == OV_TypeId_StreamedMatrix || this->getTypeManager().isDerivedFromStream(type, OV_TypeId_StreamedMatrix))
{
CMatrix* matrix = static_cast<Toolkit::TSignalDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputMatrix();
const uint64_t start = boxContext.getInputChunkStartTime(i, j);
const uint64_t end = boxContext.getInputChunkEndTime(i, j);
if (!checkFailureRoutine(m_helper->addStreamedMatrixInputBuffer(i, matrix, start, end),
"Error calling [OV_addInputBuffer (Streamed Matrix or child stream)]\n")) { return false; }
unknownStream = false;
}
if (type == OV_TypeId_Stimulations)
{
IStimulationSet* stimSet = static_cast<Toolkit::TStimulationDecoder<CBoxAlgorithmMatlabScripting>*>(m_decoders[i])->getOutputStimulationSet();
const uint64_t start = boxContext.getInputChunkStartTime(i, j);
const uint64_t end = boxContext.getInputChunkEndTime(i, j);
if (stimSet->getStimulationCount() > 0)
{
this->getLogManager() << Kernel::LogLevel_Trace << "Inserting stimulation set with size " << stimSet->getStimulationCount() << "\n";
}
if (!checkFailureRoutine(m_helper->addStimulationsInputBuffer(i, stimSet, start, end),
"Error calling [OV_addInputBuffer (Stimulations)]\n")) { return false; }
unknownStream = false;
}
}
if (m_decoders[i]->isEndReceived())
{
this->getLogManager() << Kernel::LogLevel_Info << "Received end\n";
receivedSomething = true;
unknownStream = false;
// @FIXME should something additional be done here?
}
OV_ERROR_UNLESS_KRF(!receivedSomething || !unknownStream, "Unknown Stream Type " << type.str() << " on input [" << i << "].",
Kernel::ErrorType::BadProcessing);
}
}
if (m_nInputHeaderSent < getStaticBoxContext().getInputCount())
{
// not ready to process
return true;
}
// CALL TO PROCESS FUNCTION
std::string command = std::string(m_boxInstanceVariableName.toASCIIString()) + " = " + m_processFunc.toASCIIString() + "("
+ m_boxInstanceVariableName.toASCIIString() + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, command.c_str()) == 0, "Error calling the Process function.\n")) { return false; }
// Go through every output in the matlab box and copy the data to the C++ side
for (size_t i = 0; i < getStaticBoxContext().getOutputCount(); ++i)
{
// now we check for pending output chunk to be sent (output type independent call)
command = std::string("OV_PENDING_COUNT_TMP = OV_getNbPendingOutputChunk(") + m_boxInstanceVariableName.toASCIIString() + ", "
+ std::to_string(i + 1) + ");";
if (!checkFailureRoutine(engEvalString(MATLAB_ENGINE, command.c_str()) == 0, "Error calling [OV_getNbPendingOutputChunk].\n")) { return false; }
mxArray* pending = engGetVariable(MATLAB_ENGINE, "OV_PENDING_COUNT_TMP");
const double dPending = *mxGetPr(pending);
CIdentifier typeID;
getStaticBoxContext().getOutputType(i, typeID);
for (size_t c = 0; c < size_t(dPending); ++c)
{
// If no header were ever sent, we need to extract header information in the matlab box
// This header must have been set prior to sending the very first buffer.
// @FIXME the practice used below of assigning to getters is nasty, it should be refactored to e.g. using getter/setter pairs
if (!m_oHeaderStates[i])
{
bool unknownType = true;
if (typeID == OV_TypeId_StreamedMatrix)
{
CMatrix* matrixToSend = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
if (!checkFailureRoutine(m_helper->getStreamedMatrixOutputHeader(i, matrixToSend),
"Error calling [OV_getStreamedMatrixOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
unknownType = false;
}
else if (typeID == OV_TypeId_Signal)
{
CMatrix* matrixToSend = static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
uint64_t sampling = static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputSamplingRate();
if (!checkFailureRoutine(m_helper->getSignalOutputHeader(i, matrixToSend, sampling),
"Error calling [OV_getSignalOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
// Set the new sampling rate
static_cast<Toolkit::TSignalEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputSamplingRate() = sampling;
unknownType = false;
}
else if (typeID == OV_TypeId_FeatureVector)
{
CMatrix* matrixToSend = static_cast<Toolkit::TFeatureVectorEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
if (!checkFailureRoutine(m_helper->getFeatureVectorOutputHeader(i, matrixToSend),
"Error calling [OV_getFeatureVectorOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
unknownType = false;
}
else if (typeID == OV_TypeId_Spectrum)
{
CMatrix* matrixToSend = static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
CMatrix* freqAbscissa = static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputFrequencyAbscissa();
uint64_t frequency = static_cast<Toolkit::TSpectrumEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputSamplingRate();
if (!checkFailureRoutine(m_helper->getSpectrumOutputHeader(i, matrixToSend, freqAbscissa, frequency),
"Error calling [OV_getSpectrumOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
unknownType = false;
}
else if (typeID == OV_TypeId_ChannelLocalisation)
{
CMatrix* matrixToSend = static_cast<Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
bool dynamic = static_cast<Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputDynamic();
if (!checkFailureRoutine(m_helper->getChannelLocalisationOutputHeader(i, matrixToSend, dynamic),
"Error calling [OV_getChannelLocalizationOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
// Set the new channel localisation
static_cast<Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputDynamic() = dynamic;
unknownType = false;
}
else if (typeID == OV_TypeId_ExperimentInfo)
{
OV_WARNING_K(
"The Experiment Information Stream is not implemented with the Matlab Scripting Box. If this is relevant for your usage, please contact the official development Team.")
;
unknownType = false;
}
else if (typeID == OV_TypeId_Stimulations)
{
IStimulationSet* stimSet = static_cast<Toolkit::TStimulationEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputStimulationSet();
if (!checkFailureRoutine(m_helper->getStimulationsOutputHeader(i, stimSet),
"Error calling [OV_getStimulationsOutputHeader]. Did you correctly set the output header in the matlab structure ?\n")
) { return false; }
unknownType = false;
}
OV_ERROR_UNLESS_KRF(!unknownType, "Unknown Stream Type on output [" << i << "].", Kernel::ErrorType::BadProcessing);
m_encoders[i]->encodeHeader();
boxContext.markOutputAsReadyToSend(i, 0, 0);
m_oHeaderStates[i] = true;
}
bool unknownType = true;
uint64_t start = 0;
uint64_t end = 0;
if (typeID == OV_TypeId_StreamedMatrix || this->getTypeManager().isDerivedFromStream(typeID, OV_TypeId_StreamedMatrix))
{
CMatrix* matrixToSend = static_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputMatrix();
if (!checkFailureRoutine(m_helper->popStreamedMatrixOutputBuffer(i, matrixToSend, start, end),
"Error calling [OV_popOutputBufferReshape] for Streamed Matrix stream or child stream.\n")) { return false; }
unknownType = false;
}
if (typeID == OV_TypeId_Stimulations)
{
IStimulationSet* stimSet = static_cast<Toolkit::TStimulationEncoder<CBoxAlgorithmMatlabScripting>*>(m_encoders[i])->getInputStimulationSet();
stimSet->clear();
if (!checkFailureRoutine(m_helper->popStimulationsOutputBuffer(i, stimSet, start, end),
"Error calling [OV_popOutputBuffer] for Stimulation stream.\n")) { return false; }
unknownType = false;
}
OV_ERROR_UNLESS_KRF(!unknownType, "Unknown Stream Type on output [" << i << "].", Kernel::ErrorType::BadProcessing);
m_encoders[i]->encodeBuffer();
boxContext.markOutputAsReadyToSend(i, start, end);
}
mxDestroyArray(pending);
}
return true;
}
bool CBoxAlgorithmMatlabScripting::processInput(const size_t /*index*/)
{
OV_ERROR_UNLESS_KRF(getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(),
"Error while marking algorithm as ready to process", Kernel::ErrorType::Internal);
return true;
}
bool CBoxAlgorithmMatlabScripting::printOutputBufferWithFormat()
{
// the buffer for the console
// @FIXME allow seeing more of the error message!
std::stringstream ss;
ss << m_matlabBuffer;
if (!ss.str().empty())
{
const size_t errIdx1 = ss.str().find("??? ");
const size_t errIdx2 = ss.str().find("Error: ");
const size_t errIdx3 = ss.str().find("Error ");
// Find the earliest error message
size_t errIdx = std::string::npos;
if (errIdx == std::string::npos || (errIdx1 != std::string::npos && errIdx1 < errIdx)) { errIdx = errIdx1; }
if (errIdx == std::string::npos || (errIdx2 != std::string::npos && errIdx2 < errIdx)) { errIdx = errIdx2; }
if (errIdx == std::string::npos || (errIdx3 != std::string::npos && errIdx3 < errIdx)) { errIdx = errIdx3; }
size_t warnIdx = ss.str().find("Warning: ");
if (errIdx == std::string::npos && warnIdx == std::string::npos)
{
this->getLogManager() << Kernel::LogLevel_Info << "\n---- MATLAB BUFFER - INFO ----\n" << ss.str().substr(0, (warnIdx < errIdx) ? warnIdx : errIdx) << "\n";
}
if (warnIdx != std::string::npos)
{
this->getLogManager() << Kernel::LogLevel_Warning << "\n---- MATLAB BUFFER - WARNING ----\n" << ss.str().substr(warnIdx, errIdx) << "\n";
}
if (errIdx != std::string::npos)
{
this->getLogManager() << Kernel::LogLevel_Error << "\n---- MATLAB BUFFER - ERROR ! ----\n" << ss.str().substr(errIdx) << "\n";
m_errorDetected = true;
}
}
return true;
}
} // namespace Matlab
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyMatlab
@@ -0,0 +1,155 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyMatlab
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "../ovpCMatlabHelper.h"
#include <map>
namespace OpenViBE {
namespace Plugins {
namespace Matlab {
class CBoxAlgorithmMatlabScripting final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
uint64_t getClockFrequency() override { return m_clockFrequency << 32; }
bool initialize() override;
bool uninitialize() override;
bool processClock(Kernel::CMessageClock& msg) override;
bool process() override;
bool processInput(const size_t index) override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_MatlabScripting)
private:
bool openMatlabEngineSafely();
bool closeMatlabEngineSafely();
uint64_t m_clockFrequency = 0;
std::map<size_t, Toolkit::TDecoder<CBoxAlgorithmMatlabScripting>*> m_decoders;
size_t m_nInputHeaderSent = 0;
std::map<size_t, Toolkit::TEncoder<CBoxAlgorithmMatlabScripting>*> m_encoders;
std::map<size_t, bool> m_oHeaderStates;
void* m_engineHandle = nullptr;
CString m_boxInstanceVariableName; //must be unique
CMatlabHelper* m_helper = nullptr;
CString m_matlabPath; // On Linux, path of the executable. On Windows, the executable directory.
CString m_processFunc;
CString m_initializeFunc;
CString m_uninitializeFunc;
char* m_matlabBuffer = nullptr;
bool m_errorDetected = false;
bool printOutputBufferWithFormat();
bool checkFailureRoutine(bool result, const CString& msg);
static void sanitizePath(CString& pathToModify);
//void sendOutputHeader(CIdentifier idOutputType);
//void sendOutputBuffer(CIdentifier idOutputType);
};
class CBoxAlgorithmMatlabScriptingListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onSettingTypeChanged(Kernel::IBox& box, const size_t index) override
{
//we must have the first STATIC_SETTINGS_COUNT settings
if (index < 6)
{
this->getLogManager() << Kernel::LogLevel_Warning << "One of the predefined setting has changed type, falling back to default.\n";
box.setSettingType(index, OV_TypeId_String); // they are all strings
}
return true;
}
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
{
//we must have the first STATIC_SETTINGS_COUNT settings
CString str;
box.getSettingValue(0, str);
const uint64_t value = atoi(str.toASCIIString());
if (index == 0 && (value < 1 || value > 128))
{
OV_WARNING_K("Clock Frequency must be an integer between 1 and 128 Hz. Falling back to default.");
box.setSettingValue(index, "64");
}
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmMatlabScriptingDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Matlab Scripting"); }
CString getAuthorName() const override { return CString("L. Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Executes matlab scripts. To be used correctly, you must have Matlab installed."); }
CString getDetailedDescription() const override
{
return CString(
"User must implement the matlab functions:\n[box_out]=bci_Initialize(box_in)\n[box_out]=bci_Process(box_in)\n[box_out]=bci_Uninitialize(box_in)\nPlease refer to the dedicated documentation <openvibe.inria.fr/tutorial-using-matlab-with-openvibe> for more information.");
}
CString getCategory() const override { return CString("Scripting"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_MatlabScripting; }
IPluginObject* create() override { return new CBoxAlgorithmMatlabScripting; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Box clock frequency in Hz", OV_TypeId_Integer, "64");
#if defined TARGET_OS_Linux
prototype.addSetting("Matlab executable (path)", OV_TypeId_Filename, "[ssh user@host] /path/to/matlab");
#elif defined TARGET_OS_Windows
// On Windows, the executable is not actually used, but its path is parsed from it and added to PATH.
// Background: Matlab's engOpen() takes a different argument on Windows and on Linux. On Windows, we need to have Matlab
// on PATH, in Linux we need to provide a full path of the executable. However, we'd like our example scenarios to give
// correct instructions to the user on both platforms, hence we use a setting that contains the information for both use-cases.
prototype.addSetting("Matlab executable (path)", OV_TypeId_Filename, "C:/Program Files (x86)/MATLAB/R2013b/bin/win32/matlab.exe");
#else
#endif
prototype.addSetting("Matlab working directory", OV_TypeId_String, "${Player_ScenarioDirectory}");
prototype.addSetting("Initialize function", OV_TypeId_String, "matlab_Initialize");
prototype.addSetting("Process function", OV_TypeId_String, "matlab_Process");
prototype.addSetting("Uninitialize function", OV_TypeId_String, "matlab_Uninitialize");
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanAddOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
//prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmMatlabScriptingListener; }
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_MatlabScriptingDesc)
};
} // namespace Matlab
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyMatlab
@@ -0,0 +1,493 @@
#if defined TARGET_HAS_ThirdPartyMatlab
#include "ovpCMatlabHelper.h"
#include <iostream>
#include <sstream>
#include <mex.h>
//#include <engine.h>
#include <string>
#if defined TARGET_OS_Windows
#include <windows.h>
#endif
namespace OpenViBE {
namespace Plugins {
namespace Matlab {
//---------------------------------------------------------------------------------------------------------------
static std::string escapeMatlabString(const char* sStringToEscape)
{
std::string str = std::string(sStringToEscape);
auto it = str.begin();
while (it != str.end())
{
if (*it == '\'')
{
str.insert(it, '\'');
++it;
}
++it;
}
return str;
}
static std::string genLabelsList(CMatrix* matrix, const size_t axis = 0)
{
std::string labelsList;
for (size_t i = 0; i < matrix->getDimensionSize(axis); ++i)
{
labelsList += std::string("'") + escapeMatlabString(matrix->getDimensionLabel(axis, i)) + "' ";
}
return labelsList;
}
static uint64_t convertFromMArray(mxArray* array, const size_t index)
{
const double* ptr = ::mxGetPr(array);
const double timeValue = double(ptr[index]);
return CTime(timeValue).time();
}
static uint64_t castFromMArray(mxArray* array, const size_t index)
{
const double* ptr = ::mxGetPr(array);
const double value = double(ptr[index]);
return uint64_t(value);
}
static CString getNameFromCell(mxArray* names, const size_t index)
{
mxArray* cell = mxGetCell(names, index);
if (!cell) { return CString(""); }
const mwSize* cellSizes = mxGetDimensions(cell);
char* name = new char[cellSizes[1] + 1];
name[cellSizes[1]] = '\0';
for (size_t cellsize = 0; cellsize < size_t(cellSizes[1]); ++cellsize) { name[cellsize] = static_cast<char*>(mxGetData(cell))[cellsize * 2]; }
CString res = name;
delete name;
return res;
}
std::vector<CString> CMatlabHelper::getNamelist(const char* name) const
{
std::vector<CString> res;
mxArray* marray = engGetVariable(m_matlabEngine, name);
if (!marray)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [" << name << "]\n";
return res;
}
const mwSize nbCells = mxGetNumberOfElements(marray);
for (size_t cell = 0; cell < nbCells; ++cell) { res.push_back(getNameFromCell(marray, cell)); }
mxDestroyArray(marray);
return res;
}
uint32_t CMatlabHelper::getUi32FromEnv(const char* name) const
{
mxArray* marray = engGetVariable(m_matlabEngine, name);
if (!marray)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [" << name << "]\n";
return 0;
}
const uint32_t res = uint32_t(*mxGetPr(marray));
mxDestroyArray(marray);
return res;
}
uint64_t CMatlabHelper::getUi64FromEnv(const char* name) const
{
mxArray* marray = engGetVariable(m_matlabEngine, name);
if (!marray)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [" << name << "]\n";
return 0;
}
const uint64_t res = uint64_t(*mxGetPr(marray));
mxDestroyArray(marray);
return res;
}
uint64_t CMatlabHelper::genUi64FromEnvConverted(const char* name) const
{
mxArray* marray = engGetVariable(m_matlabEngine, name);
if (!marray)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [" << name << "]\n";
return 0;
}
const double value = double(*mxGetPr(marray));
const uint64_t res = CTime(value).time();
mxDestroyArray(marray);
return res;
}
//---------------------------------------------------------------------------------------------------------------
bool CMatlabHelper::setStreamedMatrixInputHeader(const size_t index, CMatrix* matrix) const
{
std::string labelList;
std::string dimensionSizes;
for (size_t dim = 0; dim < matrix->getDimensionCount(); ++dim)
{
dimensionSizes += std::to_string(matrix->getDimensionSize(dim)) + " ";
labelList += std::string("{") + genLabelsList(matrix, dim) + "} ";
}
//function box_out = OV_setStreamedMatrixInputInputHeader(box_in, input_index, dimension_count, dimension_sizes, dimension_labels)
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_setStreamedMatrixInputHeader(" + m_boxInstanceVariableName.toASCIIString() + ","
+ std::to_string(index + 1) + "," + std::to_string(matrix->getDimensionCount()) + "," + "[" + dimensionSizes + "],"
+ "{" + labelList + "});";
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::setFeatureVectorInputHeader(const size_t index, CMatrix* matrix) const
{
const std::string labelList = genLabelsList(matrix, 0);
//function box_out = OV_setStreamedMatrixInputInputHeader(box_in, input_index, dimension_count, dimension_sizes, dimension_labels)
//box_out = OV_setFeatureVectorInputHeader(box_in, input_index, nb_features, labels)
const std::string cmd = std::string(m_boxInstanceVariableName.toASCIIString()) + " = OV_setFeatureVectorInputHeader("
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ","
+ std::to_string(matrix->getDimensionSize(0)) + "," + "{" + labelList + "});";
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::setSignalInputHeader(const size_t index, CMatrix* matrix, const uint64_t frequency) const
{
const std::string labelList = genLabelsList(matrix, 0);
//function box_out = ov_set_signal_input_header(box_in, input_index, nb_channel, nb_samples_per_buffer, channel_names, sampling_rate)
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_setSignalInputHeader(" + m_boxInstanceVariableName.toASCIIString() + ","
+ std::to_string(index + 1) + "," + std::to_string(matrix->getDimensionSize(0)) + ","
+ std::to_string(matrix->getDimensionSize(1)) + "," + "{" + labelList + "}," + std::to_string(frequency) + ");";
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::setChannelLocalisationInputHeader(const size_t index, CMatrix* matrix, const bool dynamic) const
{
const std::string labelList = genLabelsList(matrix, 0);
//function box_out = OV_setChannelLocalisationInputHeader(box_in, input_index, nb_channels, channel_names, dynamic)
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_setChannelLocalisationInputHeader(" + m_boxInstanceVariableName.toASCIIString()
+ "," + std::to_string(index + 1) + "," + std::to_string(matrix->getDimensionSize(0)) + "," + "{" + labelList + "},"
+ (dynamic ? "true" : "false") + ");";
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::setSpectrumInputHeader(const size_t index, CMatrix* matrix, CMatrix* frequencyAbscissa, const uint64_t frequency) const
{
const std::string labelList = genLabelsList(matrix, 0);
const std::string abscissaList = genLabelsList(matrix, 1);
// @FIXME CERT this is now one dim array
mxArray* matlabMatrix = ::mxCreateDoubleMatrix(frequencyAbscissa->getDimensionSize(0), 1, mxREAL);
memcpy(mxGetPr(matlabMatrix), frequencyAbscissa->getBuffer(), frequencyAbscissa->getBufferElementCount() * sizeof(double));
engPutVariable(m_matlabEngine, "OV_MATRIX_TMP", matlabMatrix);
mxDestroyArray(matlabMatrix);
//box_out = OV_setSpectrumInputHeader(box_in, input_index, nb_channels, channel_names, nb_bands, band_names, bands, sampling_rate)
const std::string cmd = std::string(m_boxInstanceVariableName.toASCIIString()) + " = OV_setSpectrumInputHeader("
+ std::string(m_boxInstanceVariableName.toASCIIString()) + ","
+ std::to_string(index + 1) + "," // input_index
+ std::to_string(matrix->getDimensionSize(0)) + "," // nb_channels
+ "{" + labelList + "}," // channel_names
+ std::to_string(matrix->getDimensionSize(1)) + "," // nb_freq abscissa
+ "{" + abscissaList + "}," //freq abscissa names
+ "OV_MATRIX_TMP," //bands
+ std::to_string(frequency) + ");"; //sampling rate
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::setStimulationsInputHeader(const size_t index) const
{
//box_out = OV_setStimulationInputHeader(box_in, input_index)
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_setStimulationsInputHeader(" + m_boxInstanceVariableName.toASCIIString() + "," +
std::to_string(index + 1) + ");";
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
//bool setExperimentInfoInputHeader(CMatrix * pMatrix);
//---------------------------------------------------------------------------------------------------------------
bool CMatlabHelper::addStreamedMatrixInputBuffer(const size_t index, CMatrix* matrix, const uint64_t startTime, const uint64_t endTime) const
{
mwSize* dims = new mwSize[matrix->getDimensionCount()];
size_t j = matrix->getDimensionCount() - 1;
for (size_t i = 0; i < matrix->getDimensionCount(); ++i)
{
dims[i] = matrix->getDimensionSize(j);
j--;
}
mxArray* matlabMatrix = ::mxCreateNumericArray(matrix->getDimensionCount(), dims, mxDOUBLE_CLASS, mxREAL);
//test : channel 1 samples to '10'
//for (size_t i = 0; i < 32;i++) matrix->getBuffer()[i] = 10;
memcpy(mxGetPr(matlabMatrix), matrix->getBuffer(), matrix->getBufferElementCount() * sizeof(double));
engPutVariable(m_matlabEngine, "OV_MATRIX_TMP", matlabMatrix);
mxDestroyArray(matlabMatrix);
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_addInputBuffer(" + m_boxInstanceVariableName.toASCIIString() + ","
+ std::to_string(index + 1) + ","
+ std::to_string(CTime(startTime).toSeconds()) + ","
+ std::to_string(CTime(endTime).toSeconds()) + ",OV_MATRIX_TMP');";
// please note the transpose operator ' to put the matrix with 1 channel per line
delete[] dims;
return engEvalString(m_matlabEngine, cmd.c_str()) == 0;
}
bool CMatlabHelper::addStimulationsInputBuffer(const size_t index, IStimulationSet* stimSet, const uint64_t startTime, const uint64_t endTime) const
{
if (stimSet->getStimulationCount() == 0 && engEvalString(m_matlabEngine, "OV_MATRIX_TMP = 0") != 0) { return false; }
// we create a 3xN matrix for N stims (access is easier in that order)
mxArray* matrix = ::mxCreateDoubleMatrix(3, size_t(stimSet->getStimulationCount()), mxREAL);
for (size_t i = 0; i < stimSet->getStimulationCount(); ++i)
{
::mxGetPr(matrix)[i * 3] = double(stimSet->getStimulationIdentifier(i));
::mxGetPr(matrix)[i * 3 + 1] = CTime(stimSet->getStimulationDate(i)).toSeconds();
::mxGetPr(matrix)[i * 3 + 2] = CTime(stimSet->getStimulationDuration(i)).toSeconds();
}
engPutVariable(m_matlabEngine, "OV_MATRIX_TMP", matrix);
mxDestroyArray(matrix);
const std::string cmd = std::string(m_boxInstanceVariableName) + " = OV_addInputBuffer(" + m_boxInstanceVariableName.toASCIIString() + ","
+ std::to_string(index + 1) + ","
+ std::to_string(CTime(startTime).toSeconds()) + ","
+ std::to_string(CTime(endTime).toSeconds()) + ",OV_MATRIX_TMP');";
return (engEvalString(m_matlabEngine, cmd.c_str()) == 0);
}
//---------------------------------------------------------------------------------------------------------------
bool CMatlabHelper::getStreamedMatrixOutputHeader(const size_t index, CMatrix* matrix) const
{
const std::string cmd = std::string("[OV_ERRNO, OV_NB_DIMENSIONS, OV_DIMENSION_SIZES, OV_DIMENSION_LABELS] = OV_getStreamedMatrixOutputHeader(")
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ");";
OV_ERROR_UNLESS_KRF(engEvalString(m_matlabEngine, cmd.c_str()) == 0 && getUi32FromEnv("OV_ERRNO") == 0,
"Could not get Streamed matrix output header", Kernel::ErrorType::BadProcessing);
const size_t nDimension = getUi32FromEnv("OV_NB_DIMENSIONS");
mxArray* dimensionSizes = engGetVariable(m_matlabEngine, "OV_DIMENSION_SIZES");
if (!dimensionSizes)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [OV_DIMENSION_SIZES]\n";
return false;
}
std::vector<CString> nameList = getNamelist("OV_DIMENSION_LABELS");
matrix->setDimensionCount(nDimension);
size_t idx = 0;
for (size_t i = 0; i < nDimension; ++i)
{
matrix->setDimensionSize(i, size_t(mxGetPr(dimensionSizes)[i]));
for (size_t x = 0; x < matrix->getDimensionSize(i) && idx < nameList.size(); ++x)
{
matrix->setDimensionLabel(i, x, escapeMatlabString(nameList[idx].toASCIIString()).c_str());
idx++;
}
}
mxDestroyArray(dimensionSizes);
return true;
}
bool CMatlabHelper::getFeatureVectorOutputHeader(const size_t index, CMatrix* matrix) const
{
const std::string cmd = std::string("[OV_ERRNO, OV_NB_FEATURES, OV_LABELS] = OV_getFeatureVectorOutputHeader(")
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ");";
OV_ERROR_UNLESS_KRF(engEvalString(m_matlabEngine, cmd.c_str()) == 0 && getUi32FromEnv("OV_ERRNO") == 0,
"Could not get Feature Vector output header",
Kernel::ErrorType::BadProcessing);
const size_t nbFeatures = getUi32FromEnv("OV_NB_FEATURES");
std::vector<CString> nameList = getNamelist("OV_LABELS");
// Check nb features == nb names ?
matrix->resize(nbFeatures);
for (size_t x = 0; x < nbFeatures; ++x) { matrix->setDimensionLabel(0, x, escapeMatlabString(nameList[x].toASCIIString()).c_str()); }
return true;
}
bool CMatlabHelper::getSignalOutputHeader(const size_t index, CMatrix* matrix, uint64_t& frequency) const
{
const std::string cmd = std::string("[OV_ERRNO, OV_NB_CHANNELS, OV_NB_SAMPLES_PER_BUFFER, OV_CHANNEL_NAMES, OV_SAMPLING_RATE] = OV_getSignalOutputHeader(")
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ");";
OV_ERROR_UNLESS_KRF(engEvalString(m_matlabEngine, cmd.c_str()) == 0 && getUi32FromEnv("OV_ERRNO") == 0, "Could not get Signal output header",
Kernel::ErrorType::BadProcessing);
const size_t nbChannels = getUi32FromEnv("OV_NB_CHANNELS");
const size_t nbSamples = getUi32FromEnv("OV_NB_SAMPLES_PER_BUFFER");
std::vector<CString> nameList = getNamelist("OV_CHANNEL_NAMES");
const size_t rate = getUi32FromEnv("OV_SAMPLING_RATE");
if (nameList.size() != nbChannels) { return false; }
matrix->resize(nbChannels, nbSamples);
frequency = rate;
for (size_t x = 0; x < nbChannels; ++x) { matrix->setDimensionLabel(0, x, escapeMatlabString(nameList[x].toASCIIString()).c_str()); }
return true;
}
bool CMatlabHelper::getChannelLocalisationOutputHeader(const size_t index, CMatrix* matrix, bool& /*dynamic*/) const
{
const std::string cmd = std::string("[OV_ERRNO, OV_NB_CHANNELS, OV_CHANNEL_NAMES, OV_DYNAMIC] = OV_getChannelLocalisationOutputHeader(")
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ");";
OV_ERROR_UNLESS_KRF(engEvalString(m_matlabEngine, cmd.c_str()) == 0 && getUi32FromEnv("OV_ERRNO") == 0,
"Could not get Channel Localisation output header", Kernel::ErrorType::BadProcessing);
const size_t nChannel = getUi32FromEnv("OV_NB_CHANNELS");
std::vector<CString> nameList = getNamelist("OV_CHANNEL_NAMES");
mxArray* dynamic = engGetVariable(m_matlabEngine, "OV_DYNAMIC");
if (!dynamic)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [OV_DYNAMIC]\n";
return false;
}
if (nameList.size() != nChannel) { return false; }
matrix->resize(nChannel, 3);
for (size_t x = 0; x < nChannel; ++x) { matrix->setDimensionLabel(0, x, escapeMatlabString(nameList[x].toASCIIString()).c_str()); }
mxDestroyArray(dynamic);
return true;
}
bool CMatlabHelper::getSpectrumOutputHeader(const size_t index, CMatrix* matrix, CMatrix* frequencyAbscissa, uint64_t& frequency) const
{
const std::string command = std::string(
"[OV_ERRNO, OV_NB_CHANNELS, OV_CHANNEL_NAMES, OV_NB_BANDS, OV_BANDS_NAME, OV_BANDS_LINEAR, OV_SAMPLING_RATE] = OV_getSpectrumOutputHeader(")
+ m_boxInstanceVariableName.toASCIIString() + "," + std::to_string(index + 1) + ");";
OV_ERROR_UNLESS_KRF(engEvalString(m_matlabEngine, command.c_str()) == 0 && getUi32FromEnv("OV_ERRNO") == 0,
"Could not get Spectrum output header", Kernel::ErrorType::BadProcessing);
const size_t nChannel = getUi32FromEnv("OV_NB_CHANNELS");
std::vector<CString> nameList = getNamelist("OV_CHANNEL_NAMES");
const size_t nAbscissa = getUi32FromEnv("OV_NB_ABSCISSAS");
std::vector<CString> freqAbscissaNames = getNamelist("OV_ABSCISSAS_NAME");
mxArray* freqAbscissa = engGetVariable(m_matlabEngine, "OV_ABSCISSAS_LINEAR");
if (!freqAbscissa)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [OV_ABSCISSAS_LINEAR]\n";
return false;
}
frequency = getUi64FromEnv("OV_SAMPLING_RATE");
//The Frequency abscissa list has dimensions nb_bands
frequencyAbscissa->resize(nAbscissa);
for (size_t x = 0; x < nAbscissa; ++x) { frequencyAbscissa->setDimensionLabel(0, x, escapeMatlabString(freqAbscissaNames[x].toASCIIString()).c_str()); }
// Adding the bands:
memcpy(frequencyAbscissa->getBuffer(), ::mxGetPr(freqAbscissa), nAbscissa * sizeof(double));
matrix->resize(nChannel, nAbscissa);
for (size_t x = 0; x < nChannel; ++x) { matrix->setDimensionLabel(0, x, escapeMatlabString(nameList[x].toASCIIString()).c_str()); }
for (size_t x = 0; x < nAbscissa; ++x) { matrix->setDimensionLabel(1, x, escapeMatlabString(freqAbscissaNames[x].toASCIIString()).c_str()); }
// @FIXME CERT is it me or it never copy data to matrix ?
mxDestroyArray(freqAbscissa);
return true;
}
bool CMatlabHelper::getStimulationsOutputHeader(size_t /*index*/, IStimulationSet* /*stimulationSet*/)
{
// Nothing to do, the stimulation header is empty.
return true;
}
//---------------------------------------------------------------------------------------------------------------
bool CMatlabHelper::popStreamedMatrixOutputBuffer(const size_t index, CMatrix* matrix, uint64_t& startTime, uint64_t& endTime) const
{
const std::string buf = std::string("[") + m_boxInstanceVariableName.toASCIIString()
+ ", OV_START_TIME, OV_END_TIME, OV_LINEAR_DATA_SIZE, OV_LINEAR_DATA] = OV_popOutputBufferReshape("
+ m_boxInstanceVariableName.toASCIIString() + ", " + std::to_string(index + 1) + ");";
const size_t res = engEvalString(m_matlabEngine, buf.c_str());
if (res != 0) { return false; }
startTime = genUi64FromEnvConverted("OV_START_TIME");
endTime = genUi64FromEnvConverted("OV_END_TIME");
const uint64_t size = getUi64FromEnv("OV_LINEAR_DATA_SIZE");
mxArray* data = engGetVariable(m_matlabEngine, "OV_LINEAR_DATA");
if (!data)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [OV_LINEAR_DATA]\n";
return false;
}
// ti be copied direclty in openvibe buffer, the linear matrix must be ordered line by line
memcpy(matrix->getBuffer(), ::mxGetPr(data), size * sizeof(double));
mxDestroyArray(data);
return true;
}
bool CMatlabHelper::popStimulationsOutputBuffer(const size_t index, IStimulationSet* stimSet, uint64_t& startTime, uint64_t& endTime) const
{
const std::string buf = std::string("[") + m_boxInstanceVariableName.toASCIIString()
+ ", OV_START_TIME, OV_END_TIME, OV_LINEAR_MATRIX_SIZE, OV_LINEAR_DATA] = OV_popOutputBuffer("
+ m_boxInstanceVariableName.toASCIIString() + ", " + std::to_string(index + 1) + ");";
const size_t res = engEvalString(m_matlabEngine, buf.c_str());
if (res != 0) { return false; }
startTime = genUi64FromEnvConverted("OV_START_TIME");
endTime = genUi64FromEnvConverted("OV_END_TIME");
const size_t size = getUi32FromEnv("OV_LINEAR_MATRIX_SIZE");
mxArray* data = engGetVariable(m_matlabEngine, "OV_LINEAR_DATA");
if (!data)
{
this->getLogManager() << Kernel::LogLevel_Error << "Nonexisting variable [OV_LINEAR_DATA]\n";
return false;
}
for (size_t i = 0; i < size; i += 3)
{
const uint64_t id = castFromMArray(data, i + 0);
const uint64_t date = convertFromMArray(data, i + 1);
const uint64_t duration = convertFromMArray(data, i + 2);
stimSet->appendStimulation(id, date, duration);
}
mxDestroyArray(data);
return true;
}
//--------------------------------------------------------------------------------------------------------------
} // namespace Matlab
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyMatlab
@@ -0,0 +1,74 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyMatlab
#include "ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <engine.h>
namespace OpenViBE {
namespace Plugins {
namespace Matlab {
class CMatlabHelper
{
public:
CMatlabHelper(Kernel::ILogManager& /*logManager*/, Kernel::IErrorManager& /*errorManager*/) { }
bool setManagers(Kernel::ILogManager* logManager, Kernel::IErrorManager* errorManager)
{
m_logManager = logManager;
m_errorManager = errorManager;
return true;
}
bool setStreamedMatrixInputHeader(size_t index, CMatrix* matrix) const;
bool setFeatureVectorInputHeader(size_t index, CMatrix* matrix) const;
bool setSignalInputHeader(size_t index, CMatrix* matrix, uint64_t frequency) const;
bool setChannelLocalisationInputHeader(size_t index, CMatrix* matrix, bool dynamic) const;
bool setSpectrumInputHeader(size_t index, CMatrix* matrix, CMatrix* frequencyAbscissa, uint64_t frequency) const;
bool setStimulationsInputHeader(size_t index) const;
// The input buffers for streamed matrix and its children streams are the same.
bool addStreamedMatrixInputBuffer(size_t index, CMatrix* matrix, uint64_t startTime, uint64_t endTime) const;
bool addStimulationsInputBuffer(size_t index, IStimulationSet* stimSet, uint64_t startTime, uint64_t endTime) const;
bool getStreamedMatrixOutputHeader(size_t index, CMatrix* matrix) const;
bool getFeatureVectorOutputHeader(size_t index, CMatrix* matrix) const;
bool getSignalOutputHeader(size_t index, CMatrix* matrix, uint64_t& frequency) const;
bool getChannelLocalisationOutputHeader(size_t index, CMatrix* matrix, bool& dynamic) const;
bool getSpectrumOutputHeader(size_t index, CMatrix* matrix, CMatrix* frequencyAbscissa, uint64_t& frequency) const;
static bool getStimulationsOutputHeader(size_t index, IStimulationSet* stimSet);
// The output buffers for streamed matrix and its children streams are the same.
bool popStreamedMatrixOutputBuffer(size_t index, CMatrix* matrix, uint64_t& startTime, uint64_t& endTime) const;
bool popStimulationsOutputBuffer(size_t index, IStimulationSet* stimSet, uint64_t& startTime, uint64_t& endTime) const;
void setMatlabEngine(Engine* engine) { m_matlabEngine = engine; }
void setBoxInstanceVariableName(const CString& name) { m_boxInstanceVariableName = name; }
uint32_t getUi32FromEnv(const char* name) const;
uint64_t getUi64FromEnv(const char* name) const;
uint64_t genUi64FromEnvConverted(const char* name) const;
std::vector<CString> getNamelist(const char* name) const;
Kernel::ILogManager& getLogManager() const { return *m_logManager; }
Kernel::IErrorManager& getErrorManager() const { return *m_errorManager; }
private:
Engine* m_matlabEngine = nullptr;
// Need to be very careful that these pointers are still valid when the Helper will try to use them
Kernel::ILogManager* m_logManager = nullptr;
Kernel::IErrorManager* m_errorManager = nullptr;
CString m_boxInstanceVariableName; //must be unique
};
} // namespace Matlab
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyMatlab
@@ -0,0 +1,14 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_MatlabScripting OpenViBE::CIdentifier(0x03303E0E, 0x39FE10DF)
#define OVP_ClassId_BoxAlgorithm_MatlabScriptingDesc OpenViBE::CIdentifier(0x46130E2F, 0x34F90BA1)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
@@ -0,0 +1,26 @@
#include <openvibe/ov_all.h>
#include "ovp_defines.h"
#include "box-algorithms/ovpCBoxAlgorithmMatlabScripting.h"
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace Matlab {
OVP_Declare_Begin()
#if defined TARGET_HAS_ThirdPartyMatlab
OVP_Declare_New(CBoxAlgorithmMatlabScriptingDesc);
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
#endif // TARGET_HAS_ThirdPartyMatlab
OVP_Declare_End()
} // namespace Matlab
} // namespace Plugins
} // namespace OpenViBE