Commit 7891d713 authored by Jussi Lindgren's avatar Jussi Lindgren

Plugins: Added Muse file reader

- 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
parent 04a24158
# ---------------------------------
# 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") ...@@ -18,6 +18,7 @@ INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML") INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSystem") INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindThirdPartyBoost") INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyProtobuf")
# --------------------------------- # ---------------------------------
# Target macros # 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;