...
 
Commits (1)
  • Jussi Lindgren's avatar
    Plugins: Added Muse file reader · 7891d713
    Jussi Lindgren authored
    - This requires Google Protocol Buffers installed and possibly tweaks to
      the associated Find scripts. The protobuf dependency is not currently
      installed by the openvibe dependency installers.
    
    Code contributed by Karl Semich
    7891d713
# ---------------------------------
# Finds Google Protocol Buffers Library
#
# Sets PROTOBUF_FOUND
# Sets PROTOBUF_INCLUDE_DIRS
# Sets PROTOBUF_LIBRARIES
# ---------------------------------
IF(UNIX)
INCLUDE(FindProtobuf)
FIND_PACKAGE(Protobuf)
IF(PROTOBUF_FOUND)
MESSAGE(STATUS " Found Protobuf...")
INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIRS})
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${PROTOBUF_LIBRARIES})
ADD_DEFINITIONS(-DTARGET_HAS_Protobuf)
ELSE(PROTOBUF_FOUND)
MESSAGE(STATUS " Failed to find Protobuf... (optional, for muse reader box)")
ENDIF(PROTOBUF_FOUND)
ENDIF(UNIX)
IF(WIN32)
MESSAGE(STATUS " FAILED to find Protobuf (optional, for muse reader box)")
ENDIF(WIN32)
......@@ -18,6 +18,7 @@ INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyProtobuf")
# ---------------------------------
# Target macros
......
#ifdef TARGET_HAS_Protobuf
#include "ovpCBoxAlgorithmMuseFileReader.h"
#include <openvibe/ovITimeArithmetics.h>
#include <cassert>
using namespace OpenViBE;
using namespace OpenViBE::Kernel;
using namespace OpenViBE::Plugins;
using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::FileIO;
using namespace Muse;
#include "ovpCMuseReaderHelper.h"
boolean CBoxAlgorithmMuseFileReader::initialize(void)
{
m_oSignalEncoder.initialize(*this, 0);
m_oStimulationEncoder.initialize(*this, 1);
m_pMuseReaderHelper = NULL;
m_ui32SampleCountPerBuffer = static_cast<uint32>((uint64)FSettingValueAutoCast(*getBoxAlgorithmContext(), 1));
if (m_ui32SampleCountPerBuffer == 0)
{
getLogManager() << LogLevel_Error << "SampleCountPerBuffer is 0, this will not work\n";
return false;
}
uint64 l_ui64DefaultEEGSampleRate = FSettingValueAutoCast(*getBoxAlgorithmContext(), 2);
if (l_ui64DefaultEEGSampleRate == 0)
{
getLogManager() << LogLevel_Error << "DefaultEEGSampleRate is 0, this will not work\n";
return false;
}
float64 l_f64MaxClockSkew = FSettingValueAutoCast(*getBoxAlgorithmContext(), 3);
if (l_f64MaxClockSkew <= 0)
{
getLogManager() << LogLevel_Error << "MaxClockSkew is nonpositive, this will not work\n";
return false;
}
CString l_sFilename = FSettingValueAutoCast(*getBoxAlgorithmContext(), 0);
m_oFile.open(l_sFilename);
if (!m_oFile.is_open()) {
getLogManager() << LogLevel_Error << "Could not open file [" << l_sFilename << "]\n";
return false;
}
try
{
m_pMuseReaderHelper = new CMuseReaderHelper(m_oFile, l_ui64DefaultEEGSampleRate, ITimeArithmetics::secondsToTime(l_f64MaxClockSkew));
}
catch(CMuseReaderHelper::ParseError e)
{
getLogManager() << LogLevel_Error << "Parse error reading [" << l_sFilename << "]: " << e.what() << "\n";
return false;
}
m_oSignalEncoder.getInputSamplingRate() = m_pMuseReaderHelper->getEEGSampleRate();
m_oSignalEncoder.getInputMatrix()->setDimensionCount(2);
m_oSignalEncoder.getInputMatrix()->setDimensionSize(1, m_ui32SampleCountPerBuffer);
m_oStimulationEncoder.encodeHeader();
getDynamicBoxContext().markOutputAsReadyToSend(1, 0, 0);
return true;
}
/*******************************************************************************/
boolean CBoxAlgorithmMuseFileReader::uninitialize(void)
{
delete m_pMuseReaderHelper;
m_oFile.close();
m_oSignalEncoder.uninitialize();
m_oStimulationEncoder.uninitialize();
return true;
}
/*******************************************************************************/
boolean CBoxAlgorithmMuseFileReader::processClock(IMessageClock& rMessageClock)
{
// some pre-processing code if needed...
// ready to process !
if (m_pMuseReaderHelper->messageAvailable())
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
}
return true;
}
/*******************************************************************************/
uint64 CBoxAlgorithmMuseFileReader::getClockFrequency(void)
{
// Note that the time is coded on a 64 bits unsigned integer, fixed decimal point (32:32)
// arguments in reverse order inverts from period to frequency
return ITimeArithmetics::sampleCountToTime(m_ui32SampleCountPerBuffer, m_pMuseReaderHelper->getEEGSampleRate());
}
/*******************************************************************************/
boolean CBoxAlgorithmMuseFileReader::process(void)
{
// the static box context describes the box inputs, outputs, settings structures
//IBox& l_rStaticBoxContext=this->getStaticBoxContext();
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();
// here is some useful functions:
// - To send an output chunk :
// l_rDynamicBoxContext.markOutputAsReadyToSend(output_index, chunk_start_time, chunk_end_time);
try
{
m_pMuseReaderHelper->parseSamples(m_ui32SampleCountPerBuffer, getLogManager());
if (m_pMuseReaderHelper->getEEGSampleCount() != m_ui32SampleCountPerBuffer)
{
getLogManager() << LogLevel_Error << "Asked for " << m_ui32SampleCountPerBuffer << " samples but provided with " << m_pMuseReaderHelper->getEEGSampleCount() << "\n";
return false;
}
}
catch(CMuseReaderHelper::ParseError e)
{
getLogManager() << LogLevel_Error << ".muse parse error: " << e.what() << "\n";
return false;
}
uint64 l_ui64SignalTime = m_pMuseReaderHelper->getEEGTime();
uint32 l_ui32EEGChannelCount = m_pMuseReaderHelper->getEEGChannelCount();
if (l_ui32EEGChannelCount != m_oSignalEncoder.getInputMatrix()->getDimensionSize(0))
{
m_oSignalEncoder.getInputMatrix()->setDimensionSize(0, l_ui32EEGChannelCount);
m_oSignalEncoder.encodeHeader();
l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_ui64SignalTime, l_ui64SignalTime);
}
float64 * l_pBuffer = m_oSignalEncoder.getInputMatrix()->getBuffer();
float64 * l_pBufferTail = l_pBuffer + m_oSignalEncoder.getInputMatrix()->getBufferElementCount();
for (uint32 l_ui32Sample = 0; l_ui32Sample < m_ui32SampleCountPerBuffer; ++ l_ui32Sample)
{
float32 const * l_pSample = m_pMuseReaderHelper->getEEGSample(l_ui32Sample);
for (uint32 l_ui32Channel = 0; l_ui32Channel < l_ui32EEGChannelCount; ++ l_ui32Channel)
{
*(l_pBuffer++) = l_pSample[l_ui32Channel];
}
}
while (l_pBuffer < l_pBufferTail)
{
*(l_pBuffer++) = 0;
}
m_oSignalEncoder.encodeBuffer();
uint64 l_ui64SignalPeriod = m_pMuseReaderHelper->getEEGSamplePeriod() * m_pMuseReaderHelper->getEEGSampleCount();
l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_ui64SignalTime, l_ui64SignalTime + l_ui64SignalPeriod);
if (m_pMuseReaderHelper->getStimulationCount() > 0) {
IStimulationSet * l_pStimulationSet = m_oStimulationEncoder.getInputStimulationSet();
uint64 l_ui64StartTime = l_ui64SignalTime;
uint64 l_ui64EndTime = l_ui64SignalTime + l_ui64SignalPeriod;
l_pStimulationSet->setStimulationCount(m_pMuseReaderHelper->getStimulationCount());
for (uint32 l_ui32Index = 0; l_ui32Index < m_pMuseReaderHelper->getStimulationCount(); ++ l_ui32Index)
{
uint64 l_ui64Stimulation = m_pMuseReaderHelper->getStimulation(l_ui32Index);
uint64 l_ui64Time = m_pMuseReaderHelper->getStimulationTime(l_ui32Index);
l_pStimulationSet->insertStimulation(l_ui32Index, l_ui64Stimulation, l_ui64Time, 0);
if (l_ui64Time < l_ui64StartTime)
{
l_ui64StartTime = l_ui64Time;
}
if (l_ui64Time > l_ui64EndTime)
{
l_ui64EndTime = l_ui64Time;
}
}
m_oStimulationEncoder.encodeBuffer();
l_rDynamicBoxContext.markOutputAsReadyToSend(1, l_ui64StartTime, l_ui64EndTime);
}
// A typical process iteration may look like this.
// This example only iterate over the first input of type Signal, and output a modified Signal.
// thus, the box uses 1 decoder (m_oSignalDecoder) and 1 encoder (m_oSignalEncoder)
/*
IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();
//iterate over all chunk on input 0
for(uint32 i=0; i<l_rDynamicBoxContext.getInputChunkCount(0); i++)
{
// decode the chunk i
m_oSignalDecoder.decode(i);
// the decoder may have decoded 3 different parts : the header, a buffer or the end of stream.
if(m_oSignalDecoder.isHeaderReceived())
{
// Header received. This happens only once when pressing "play". For example with a StreamedMatrix input, you now know the dimension count, sizes, and labels of the matrix
// ... maybe do some process ...
// Pass the header to the next boxes, by encoding a header on the output 0:
m_oSignalEncoder.encodeHeader(0);
// send the output chunk containing the header. The dates are the same as the input chunk:
l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
}
if(m_oSignalDecoder.isBufferReceived())
{
// Buffer received. For example the signal values
// Access to the buffer can be done thanks to :
IMatrix* l_pMatrix = m_oSignalDecoder.getOutputMatrix(); // the StreamedMatrix of samples.
uint64 l_uiSamplingFrequency = m_oSignalDecoder.getOutputSamplingRate(); // the sampling rate of the signal
// ... do some process on the matrix ...
// Encode the output buffer :
m_oSignalEncoder.encodeBuffer(0);
// and send it to the next boxes :
l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
}
if(m_oSignalDecoder.isEndReceived())
{
// End of stream received. This happens only once when pressing "stop". Just pass it to the next boxes so they receive the message :
m_oSignalEncoder.encodeEnd(0);
l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
}
// The current input chunk has been processed, and automaticcaly discarded.
// you don't need to call "l_rDynamicBoxContext.markInputAsDeprecated(0, i);"
}
*/
// check the official developer documentation webpage for more example and information :
// Tutorials:
// http://openvibe.inria.fr/documentation/#Developer+Documentation
// Codec Toolkit page :
// http://openvibe.inria.fr/codec-toolkit-references/
// Feel free to ask experienced developers on the forum (http://openvibe.inria.fr/forum) and IRC (#openvibe on irc.freenode.net).
return true;
}
#endif
#ifndef __OpenViBEPlugins_BoxAlgorithm_MuseFileReader_H__
#define __OpenViBEPlugins_BoxAlgorithm_MuseFileReader_H__
//You may have to change this path to match your folder organisation
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <fstream>
// The unique identifiers for the box and its descriptor.
// Identifier are randomly chosen by the skeleton-generator.
#define OVP_ClassId_BoxAlgorithm_MuseFileReader OpenViBE::CIdentifier(0xB2F0D658, 0xB4455CA1)
#define OVP_ClassId_BoxAlgorithm_MuseFileReaderDesc OpenViBE::CIdentifier(0x7D0D0C20, 0xF160B26D)
namespace Muse { class CMuseReaderHelper; }
namespace OpenViBEPlugins
{
namespace FileIO
{
/**
* \class CBoxAlgorithmMuseFileReader
* \author Karl Semich
* \date Fri Jul 1 10:53:23 2016
* \brief The class CBoxAlgorithmMuseFileReader describes the box Muse file reader.
*
*/
class CBoxAlgorithmMuseFileReader : virtual public OpenViBEToolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >
{
public:
virtual void release(void) { delete this; }
virtual OpenViBE::boolean initialize(void);
virtual OpenViBE::boolean uninitialize(void);
//Here is the different process callbacks possible
// - On clock ticks :
virtual OpenViBE::boolean processClock(OpenViBE::CMessageClock& rMessageClock);
// - On new input received (the most common behaviour for signal processing) :
//virtual OpenViBE::boolean processInput(OpenViBE::uint32 ui32InputIndex);
// - On message received :
//virtual OpenViBE::boolean processMessage(const OpenViBE::Kernel::IMessageWithData& msg, OpenViBE::uint32 inputIndex);
// If you want to use processClock, you must provide the clock frequency.
virtual OpenViBE::uint64 getClockFrequency(void);
virtual OpenViBE::boolean process(void);
_IsDerivedFromClass_Final_(OpenViBEToolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_MuseFileReader);
protected:
// No Input decoder.
// No Output decoder.
OpenViBEToolkit::TSignalEncoder < CBoxAlgorithmMuseFileReader> m_oSignalEncoder;
OpenViBEToolkit::TStimulationEncoder < CBoxAlgorithmMuseFileReader> m_oStimulationEncoder;
OpenViBE::uint32 m_ui32SampleCountPerBuffer;
Muse::CMuseReaderHelper *m_pMuseReaderHelper;
std::ifstream m_oFile;
};
// If you need to implement a box Listener, here is a sekeleton for you.
// Use only the callbacks you need.
// For example, if your box has a variable number of input, but all of them must be stimulation inputs.
// The following listener callback will ensure that any newly added input is stimulations :
/*
virtual OpenViBE::boolean onInputAdded(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index)
{
rBox.setInputType(ui32Index, OV_TypeId_Stimulations);
};
*/
/*
// The box listener can be used to call specific callbacks whenever the box structure changes : input added, name changed, etc.
// Please uncomment below the callbacks you want to use.
class CBoxAlgorithmMuseFileReaderListener : public OpenViBEToolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >
{
public:
//virtual OpenViBE::boolean onInitialized(OpenViBE::Kernel::IBox& rBox) { return true; };
//virtual OpenViBE::boolean onNameChanged(OpenViBE::Kernel::IBox& rBox) { return true; };
//virtual OpenViBE::boolean onInputConnected(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onInputDisconnected(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onInputAdded(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onInputRemoved(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onInputTypeChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onInputNameChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputConnected(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputDisconnected(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputAdded(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputRemoved(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputTypeChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onOutputNameChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingAdded(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingRemoved(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingTypeChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingNameChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingDefaultValueChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
//virtual OpenViBE::boolean onSettingValueChanged(OpenViBE::Kernel::IBox& rBox, const OpenViBE::uint32 ui32Index) { return true; };
_IsDerivedFromClass_Final_(OpenViBEToolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >, OV_UndefinedIdentifier);
};
*/
/**
* \class CBoxAlgorithmMuseFileReaderDesc
* \author Karl Semich ()
* \date Fri Jul 1 10:53:23 2016
* \brief Descriptor of the box Muse file reader.
*
*/
class CBoxAlgorithmMuseFileReaderDesc : virtual public OpenViBE::Plugins::IBoxAlgorithmDesc
{
public:
virtual void release(void) { }
virtual OpenViBE::CString getName(void) const { return OpenViBE::CString("Muse file reader"); }
virtual OpenViBE::CString getAuthorName(void) const { return OpenViBE::CString("Karl Semich"); }
virtual OpenViBE::CString getAuthorCompanyName(void) const { return OpenViBE::CString(""); }
virtual OpenViBE::CString getShortDescription(void) const { return OpenViBE::CString("Reads .muse files"); }
virtual OpenViBE::CString getDetailedDescription(void) const { return OpenViBE::CString("Reads .muse files produced by software associated with the Muse headband"); }
virtual OpenViBE::CString getCategory(void) const { return OpenViBE::CString("File reading and writing/Muse"); }
virtual OpenViBE::CString getVersion(void) const { return OpenViBE::CString("0.1"); }
virtual OpenViBE::CString getStockItemName(void) const { return OpenViBE::CString("gtk-open"); }
virtual OpenViBE::CIdentifier getCreatedClass(void) const { return OVP_ClassId_BoxAlgorithm_MuseFileReader; }
virtual OpenViBE::Plugins::IPluginObject* create(void) { return new OpenViBEPlugins::FileIO::CBoxAlgorithmMuseFileReader; }
/*
virtual OpenViBE::Plugins::IBoxListener* createBoxListener(void) const { return new CBoxAlgorithmMuseFileReaderListener; }
virtual void releaseBoxListener(OpenViBE::Plugins::IBoxListener* pBoxListener) const { delete pBoxListener; }
*/
virtual OpenViBE::boolean getBoxPrototype(
OpenViBE::Kernel::IBoxProto& rBoxAlgorithmPrototype) const
{
//No input specified.To add inputs use :
//rBoxAlgorithmPrototype.addInput("Input Name",OV_TypeId_XXXX);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyInput);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddInput);
rBoxAlgorithmPrototype.addOutput("EEG stream",OV_TypeId_Signal);
rBoxAlgorithmPrototype.addOutput("Markers",OV_TypeId_Stimulations);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyOutput);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddOutput);
//No Message input specified.To add Message inputs use :
//rBoxAlgorithmPrototype.addMessageInput("Input Name");
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyMessageInput);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddMessageInput);
//No Message output specified.To add Message outputs use :
//rBoxAlgorithmPrototype.addMessageOutput("Output Name");
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyMessageOutput);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddMessageOutput);
rBoxAlgorithmPrototype.addSetting("File name",OV_TypeId_Filename,"");
rBoxAlgorithmPrototype.addSetting("Samples per buffer",OV_TypeId_Integer,"16");
rBoxAlgorithmPrototype.addSetting("Default EEG sample rate",OV_TypeId_Integer,"220");
rBoxAlgorithmPrototype.addSetting("Clock seconds accuracy",OV_TypeId_Float,"0.2");
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifySetting);
//rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddSetting);
rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_IsUnstable);
return true;
}
_IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_MuseFileReaderDesc);
};
};
};
#endif // __OpenViBEPlugins_BoxAlgorithm_MuseFileReader_H__
#ifdef TARGET_HAS_Protobuf
#include "ovpCMuseReaderHelper.h"
#include <openvibe/ovITimeArithmetics.h>
#include <toolkit/ovtk_stimulations.h>
#include <iostream>
#include <sstream>
#include <vector>
#include <boost/regex.hpp>
using namespace std;
using namespace OpenViBE;
using namespace OpenViBE::Kernel;
using namespace Muse;
using namespace interaxon::muse_data;
/**
* Parse the header information for the next message. This is called prior to the request
* for the next message, so that EOF and stream validity may be detected in advance.
**/
void CMuseReaderHelper::parseMessageHeader()
{
// message length
int32 l_i32MsgLength;
m_rMuseStream.read(reinterpret_cast<char*>(&l_i32MsgLength), 4);
// message type
int16 l_i16MsgType;
m_rMuseStream.read(reinterpret_cast<char*>(&l_i16MsgType), 2);
// validate
if (!messageAvailable()) {
// read failed, EOF
m_oMsgBuffer.clear();
return;
}
if (l_i16MsgType != 2)
throw ParseError("Only message type 2 supported.");
// store
m_oMsgBuffer.resize(l_i32MsgLength);
}
void CMuseReaderHelper::parseMessage()
{
if (!messageAvailable())
throw ParseError("No more messages.");
m_rMuseStream.read(m_oMsgBuffer.data(), m_oMsgBuffer.size());
bool l_bStatus = m_oCollection.ParseFromArray(m_oMsgBuffer.data(), m_oMsgBuffer.size());
if (l_bStatus == false)
throw ParseError("Failed to parse message body");
parseMessageHeader();
}
CMuseReaderHelper::CMuseReaderHelper(istream & rInputStream, uint64 ui64DefaultEEGSampleRate, int64 i64MaxClockSkew)
: m_rMuseStream(rInputStream),
m_ui64EEGTime(0),
m_ui64EEGSampleRate(ui64DefaultEEGSampleRate),
m_ui64EEGSamplePeriod(ITimeArithmetics::sampleCountToTime(ui64DefaultEEGSampleRate, 1)),
m_i64MaxClockSkew(i64MaxClockSkew),
m_ui32EEGChannelCount(0),
m_bCorrectState(true),
m_iCollectionIndex(-1)
{
parseMessageHeader();
}
CMuseReaderHelper::~CMuseReaderHelper()
{
}
bool CMuseReaderHelper::parseAnnotation(MuseData const & rAnnotationData, ILogManager & rLogManager)
{
Annotation l_oAnnotation = rAnnotationData.GetExtension(Annotation::museData);
string l_sEventData = l_oAnnotation.event_data();
istringstream l_oEventDataStream(l_sEventData);
string l_sEventName;
l_oEventDataStream >> l_sEventName;
// ignore hardware-generated metrics
string l_sIgnored[] =
{
"/muse/elements/alpha_absolute",
"/muse/elements/beta_absolute",
"/muse/elements/gamma_absolute",
"/muse/elements/delta_absolute",
"/muse/elements/theta_absolute",
"/muse/elements/mellow",
"/muse/elements/concentration"
};
for (size_t l_i = 0; l_i < sizeof(l_sIgnored) / sizeof(l_sIgnored[0]); ++ l_i)
if (l_sEventName == l_sIgnored[l_i])
return true;
// capture markers
string l_sEventNamePrefix = l_sEventName;
l_sEventNamePrefix.resize(8);
if (l_sEventNamePrefix == "/Marker/") {
uint64 stimulation = l_sEventName[8] - '0' + OVTK_StimulationId_Label_00;
m_oStimulations.push_back(stimulation);
m_oStimulationTimes.push_back(ITimeArithmetics::secondsToTime(rAnnotationData.timestamp()));
} else {
rLogManager << LogLevel_ImportantWarning << "Unrecognized .muse event: " << l_sEventData.c_str() << "\n";
}
return true;
}
bool CMuseReaderHelper::parseEEGSample(MuseData const & rEEGData, uint32 ui32Count, ILogManager & rLogManager)
{
EEG l_oEEG = rEEGData.GetExtension(EEG::museData);
uint32 l_ui32nValues = l_oEEG.values_size();
uint64 l_ui64PacketTime = ITimeArithmetics::secondsToTime(rEEGData.timestamp());
if (l_oEEG.has_drl())
++ l_ui32nValues;
if (l_oEEG.has_ref())
++ l_ui32nValues;
// Stored as a packed array, so this means this is the first sample in this buffer
if (m_oEEGSamples.empty())
{
m_ui32EEGChannelCount = l_ui32nValues;
if (m_ui64EEGTime == 0)
{
m_ui64EEGTime = l_ui64PacketTime - m_i64MaxClockSkew / 2;
}
}
else if (l_ui32nValues != m_ui32EEGChannelCount)
{
// channel count changes, close buffer
return false;
}
uint64 l_ui64PredictedTime = m_ui64EEGTime + getEEGSampleCount() * getEEGSamplePeriod();
int64 l_i64DeltaTime;
if (l_ui64PacketTime < l_ui64PredictedTime)
{
l_i64DeltaTime = - static_cast<int64>(l_ui64PredictedTime - l_ui64PacketTime);
}
else
{
l_i64DeltaTime = l_ui64PacketTime - l_ui64PredictedTime;
}
if (l_i64DeltaTime > m_i64MaxClockSkew)
{
// dropped packets, insert 0's
l_i64DeltaTime -= m_i64MaxClockSkew - getEEGSamplePeriod();
uint64 l_ui64DroppedSamples = l_i64DeltaTime / getEEGSamplePeriod();
if (m_bCorrectState) {
rLogManager << LogLevel_Warning << "Clock skew: missing " << l_ui64DroppedSamples << " sample(s)\n";
m_oStimulations.push_back(OVTK_GDF_Incorrect);
m_oStimulationTimes.push_back(l_ui64PredictedTime);
m_bCorrectState = false;
}
for (; l_ui64DroppedSamples > 0; -- l_ui64DroppedSamples)
{
for (uint32 l_ui32Channel = 0; l_ui32Channel < m_ui32EEGChannelCount; ++ l_ui32Channel)
{
m_oEEGSamples.push_back(0);
}
if (m_oEEGSamples.size() >= m_ui32EEGChannelCount * ui32Count)
{
return false;
}
}
l_ui64PredictedTime = m_ui64EEGTime + getEEGSampleCount() * getEEGSamplePeriod();
}
else if (l_i64DeltaTime < -m_i64MaxClockSkew)
{
// extra packets, drop them
l_i64DeltaTime += m_i64MaxClockSkew - getEEGSamplePeriod();
if (m_bCorrectState)
{
uint64 l_ui64ExtraSamples = -l_i64DeltaTime / getEEGSamplePeriod();
rLogManager << LogLevel_Warning << "Clock skew: dropping " << l_ui64ExtraSamples << " sample(s)\n";
m_oStimulations.push_back(OVTK_GDF_Incorrect);
m_oStimulationTimes.push_back(l_ui64PacketTime);
m_bCorrectState = false;
}
return true;
}
if (!m_bCorrectState)
{
m_oStimulations.push_back(OVTK_GDF_Correct);
m_oStimulationTimes.push_back(l_ui64PredictedTime);
m_bCorrectState = true;
}
for (int l_i = 0; l_i < l_oEEG.values_size(); ++ l_i)
{
m_oEEGSamples.push_back(l_oEEG.values(l_i));
}
if (l_oEEG.has_drl())
{
m_oEEGSamples.push_back(l_oEEG.drl());
}
if (l_oEEG.has_ref())
{
m_oEEGSamples.push_back(l_oEEG.ref());
}
return true;
}
void CMuseReaderHelper::parseSamples(uint32 ui32Count, ILogManager & rLogManager)
{
m_oStimulations.clear();
m_oStimulationTimes.clear();
if (m_ui64EEGTime != 0)
{
m_ui64EEGTime += getEEGSampleCount() * getEEGSamplePeriod();
}
m_oEEGSamples.clear();
// Handling of m_iCollectionIndex:
// -1 prior to reading any messages
// if an undesired sample is read, value is unchanged; sample will be re-read later
// when a sample is consumed, value is incremented
// when value is invalid (<-1,>size), new message is read, value is set to 0
while (m_ui32EEGChannelCount == 0 || m_oEEGSamples.size() / m_ui32EEGChannelCount < ui32Count)
{
if (m_iCollectionIndex < 0 || m_iCollectionIndex >= m_oCollection.collection_size()) {
if (!messageAvailable())
break;
parseMessage();
m_iCollectionIndex = 0;
continue;
}
MuseData const & l_rData = m_oCollection.collection(m_iCollectionIndex);
bool l_bParsed = true;
switch (l_rData.datatype()) {
case MuseData::ANNOTATION:
l_bParsed = parseAnnotation(l_rData, rLogManager);
break;
case MuseData::EEG:
l_bParsed = parseEEGSample(l_rData, ui32Count, rLogManager);
break;
case MuseData::QUANT:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'QUANT'\n"; break;
case MuseData::ACCEL:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'ACCEL'\n"; break;
case MuseData::BATTERY:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'BATTERY'\n"; break;
case MuseData::VERSION:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'VERSION'\n"; break;
case MuseData::CONFIG:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'CONFIG'\n"; break;
case MuseData::HISTOGRAM:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'HISTOGRAM'\n"; break;
case MuseData::ALGVALUE:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'ALGVALUE'\n"; break;
case MuseData::DSP:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'DSP'\n"; break;
case MuseData::COMPUTING_DEVICE:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'COMPUTING_DEVICE'\n"; break;
case MuseData::EEG_DROPPED:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'EEG_DROPPED'\n"; break;
case MuseData::ACC_DROPPED:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'ACC_DROPPED'\n"; break;
case MuseData::CALM_APP:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'CALM_APP'\n"; break;
case MuseData::CALM_ALG:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'CALM_ALG'\n"; break;
case MuseData::MUSE_ELEMENTS:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'MUSE_ELEMENTS'\n"; break;
case MuseData::GYRO:
rLogManager << LogLevel_ImportantWarning << "Unhandled Muse datatype 'GYRO'\n"; break;
default:
throw ParseError("Unrecognized MuseData type");
}
if (!l_bParsed)
break;
++ m_iCollectionIndex;
}
while (getEEGSampleCount() < ui32Count)
{
if (m_bCorrectState)
{
m_oStimulations.push_back(OVTK_GDF_Incorrect);
m_oStimulationTimes.push_back(m_ui64EEGTime + getEEGSampleCount() * getEEGSamplePeriod());
m_bCorrectState = false;
}
for (uint32 l_ui32Channel = 0; l_ui32Channel < getEEGChannelCount(); ++ l_ui32Channel)
{
m_oEEGSamples.push_back(0);
}
}
}
#endif
#ifndef MUSEREADERHELPER_H
#define MUSEREADERHELPER_H
#include <openvibe/ov_all.h>
#include <istream>
#include <stdexcept>
#include <vector>
#include "Muse_v2.pb.h"
namespace Muse
{
/**
* \class CMuseReaderHelper
* \author Karl Semich
* \brief Muse protobuf format parser and utilities.
**/
class CMuseReaderHelper
{
protected:
std::istream & m_rMuseStream;
interaxon::muse_data::MuseDataCollection m_oCollection;
OpenViBE::uint64 m_ui64EEGTime;
OpenViBE::uint64 m_ui64EEGSampleRate;
OpenViBE::uint64 m_ui64EEGSamplePeriod;
OpenViBE::int64 m_i64MaxClockSkew;
OpenViBE::uint32 m_ui32EEGChannelCount;
OpenViBE::boolean m_bCorrectState;
std::vector<OpenViBE::float32> m_oEEGSamples;
std::vector<OpenViBE::uint64> m_oStimulations;
std::vector<OpenViBE::uint64> m_oStimulationTimes;
/**
* Sizes m_oMsgBuffer
*/
void parseMessageHeader();
/**
* Populates m_oCollection
*/
void parseMessage();
private:
std::vector<char> m_oMsgBuffer;
int m_iCollectionIndex;
/**
* Populates m_oStimulations. Called by parseSamples().
*/
bool parseAnnotation(interaxon::muse_data::MuseData const & rAnnotationData, OpenViBE::Kernel::ILogManager & rLogManager);
/**
* Populates m_oEEGSamples. Called by parseSamples().
*/
bool parseEEGSample(interaxon::muse_data::MuseData const & rEEGData, OpenViBE::uint32 ui32Count, OpenViBE::Kernel::ILogManager & rLogManager);
public:
/**
* Construct from a .muse file stream.
* \param oInputStream stream to read from
**/
CMuseReaderHelper(std::istream & rInputStream, OpenViBE::uint64 ui64DefaultEEGSampleRate, OpenViBE::int64 i64MaxClockSkew);
~CMuseReaderHelper();
/**
* Read a contiguous block of samples.
* Data can be fetched via accessor methods.
*
* \param ui32Count number of EEG samples to read
**/
void parseSamples(OpenViBE::uint32 ui32Count, OpenViBE::Kernel::ILogManager & rLogManager);
/**
* EEG Accessors, populated by parseSamples()
**/
OpenViBE::uint64 getEEGTime() const { return m_ui64EEGTime; }
OpenViBE::uint32 getEEGChannelCount() const { return m_ui32EEGChannelCount; }
OpenViBE::uint32 getEEGSampleCount() const { return m_oEEGSamples.size() / getEEGChannelCount(); }
OpenViBE::uint64 getEEGSampleRate() const { return m_ui64EEGSampleRate; }
OpenViBE::uint64 getEEGSamplePeriod() const { return m_ui64EEGSamplePeriod; }
OpenViBE::float32 const * getEEGSample(OpenViBE::uint32 ui32which) const { return &m_oEEGSamples[ui32which * getEEGChannelCount()]; }
OpenViBE::float32 const * getEEGSamples() const { return &m_oEEGSamples[0]; }
/**
* Stimulation Accessors, populated by parseSamples()
**/
OpenViBE::uint32 getStimulationCount() const { return m_oStimulations.size(); }
OpenViBE::uint64 getStimulation(OpenViBE::uint32 ui32which) const { return m_oStimulations[ui32which]; }
OpenViBE::float64 getStimulationTime(OpenViBE::uint32 ui32which) const { return m_oStimulationTimes[ui32which]; }
/**
* Stream is valid, EOF not reached yet.
**/
bool messageAvailable() { return static_cast<bool>(m_rMuseStream); }
/**
* This exception is thrown for unreadable data.
**/
class ParseError : public std::runtime_error
{
public:
ParseError(std::string message = "Parse error") : std::runtime_error(message) {}
};
};
}
#endif
......@@ -10,6 +10,10 @@
#include "box-algorithms/ovpCBoxAlgorithmBrainampFileWriter.h"
// @END GIPSA
#ifdef TARGET_HAS_Protobuf
#include "box-algorithms/muse/ovpCBoxAlgorithmMuseFileReader.h"
#endif
OVP_Declare_Begin();
// @BEGIN CICIT-GARCHES
......@@ -26,5 +30,10 @@ OVP_Declare_Begin();
OVP_Declare_New(OpenViBEPlugins::FileIO::CBoxAlgorithmBrainampFileWriterDesc)
// @END GIPSA
#ifdef TARGET_HAS_Protobuf
OVP_Declare_New(OpenViBEPlugins::FileIO::CBoxAlgorithmMuseFileReaderDesc)
#endif
OVP_Declare_End();
\ No newline at end of file