init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Linearly superposes a simple phase-locked template around 300ms after OVTK_StimulationId_Target
|
||||
*
|
||||
* Fiddler can be used to debug P300 scenarios. Note that the same effect could be
|
||||
* achieved with a box that tampers the signal after specific stimulation markers.
|
||||
* It can also be used as an example of how to manipulate the signal on the
|
||||
* acquisition server side using a plugin.
|
||||
*
|
||||
* Known limitations: Overlapping patterns not handled
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ovasCPluginFiddler.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <system/ovCMath.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
namespace Plugins {
|
||||
|
||||
CPluginFiddler::CPluginFiddler(const Kernel::IKernelContext& ctx) : IAcquisitionServerPlugin(ctx, CString("AcquisitionServer_Plugin_Fiddler"))
|
||||
{
|
||||
m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "Loading plugin: Fiddler\n";
|
||||
|
||||
m_settings.add("Fiddler_Strength", &m_BCI2000VersionFiddlerStrength); // The amplitude of the pattern, 0 = disable
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
// Hooks
|
||||
|
||||
|
||||
bool CPluginFiddler::startHook(const std::vector<CString>& /*selectedChannelNames*/, const size_t sampling, const size_t /*nChannel*/, const size_t nSamplePerSentBlock)
|
||||
{
|
||||
if (m_BCI2000VersionFiddlerStrength > 10e-06)
|
||||
{
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
m_StartSample = std::numeric_limits<uint64_t>::max();
|
||||
m_EndSample = 0;
|
||||
m_NProcessedSample = 0;
|
||||
m_Counter = 0;
|
||||
|
||||
m_Sampling = sampling;
|
||||
|
||||
m_LastBufferSize = 0;
|
||||
|
||||
m_kernelCtx.getLogManager() << Kernel::LogLevel_Warning << "Fiddler is enabled! Only use for debug purposes. Set strength=0 to disable.\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CPluginFiddler::loopHook(std::deque<std::vector<float>>& buffers, CStimulationSet& stimSet, const uint64_t /*start*/, const uint64_t /*end*/, const uint64_t /* sampleTime */)
|
||||
{
|
||||
if (m_BCI2000VersionFiddlerStrength > 10e-06)
|
||||
{
|
||||
// We need to make sure we don't process the same samples twice
|
||||
uint64_t nProcessed = 0;
|
||||
if (m_LastBufferSize > m_nSamplePerSentBlock) { nProcessed = m_LastBufferSize - m_nSamplePerSentBlock; }
|
||||
|
||||
// Loop over the stimulations
|
||||
for (size_t i = 0; i < stimSet.getStimulationCount(); ++i)
|
||||
{
|
||||
const uint64_t id = stimSet.getStimulationIdentifier(i);
|
||||
const uint64_t time = stimSet.getStimulationDate(i);
|
||||
const uint64_t nSample = CTime(time).toSampleCount(m_Sampling);
|
||||
|
||||
if (id == OVTK_StimulationId_Target && nSample > m_NProcessedSample)
|
||||
{
|
||||
m_StartSample = nSample + CTime(0.0).toSampleCount(m_Sampling);
|
||||
m_EndSample = nSample + CTime(0.5).toSampleCount(m_Sampling);
|
||||
m_Counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = size_t(nProcessed); i < buffers.size(); ++i)
|
||||
{
|
||||
// std::cout << "Sample " << CTime(l_SampleTime).toSeconds() << " range " << CTime(m_startTime).toSeconds() << " stop " << CTime(m_endTime).toSeconds();
|
||||
|
||||
if (m_NProcessedSample > m_StartSample && m_NProcessedSample <= m_EndSample)
|
||||
{
|
||||
const float lobe1 = 0.25F; // Position, in seconds
|
||||
const float lobe2 = 0.30F;
|
||||
const float spread1 = 0.008F; // Width
|
||||
const float spread2 = 0.004F;
|
||||
|
||||
// Two beta functions weighted by gaussians; the beta parameters are ^1 and ^4
|
||||
// n.b. not terribly efficient as the same pattern is created every time anew. In practice this is of little importance.
|
||||
const float st = float(CTime(m_Sampling, m_Counter).toSeconds());
|
||||
const float bump1 = std::exp(-std::pow(st - lobe1, 2) / spread1) * (st * std::pow(1 - st, 4));
|
||||
const float bump2 = std::exp(-std::pow(st - lobe2, 2) / spread2) * (st * std::pow(1 - st, 4));
|
||||
const float value = (-0.5F * bump1 + 0.9F * bump2) * 40.0F;
|
||||
|
||||
std::vector<float>& sample = buffers[i];
|
||||
float* tmp = &sample[0];
|
||||
for (size_t j = 0; j < sample.size(); ++j) { tmp[j] += value * m_BCI2000VersionFiddlerStrength; }
|
||||
m_Counter++;
|
||||
}
|
||||
|
||||
m_NProcessedSample++;
|
||||
}
|
||||
|
||||
m_LastBufferSize = buffers.size();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Plugins
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* \brief Acquisition Server plugin that tinkers with the signal for P300 debugging purposes
|
||||
* \version 0.1
|
||||
* \author Jussi T. Lindgren / Inria
|
||||
*/
|
||||
|
||||
#include "ovasIAcquisitionServerPlugin.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CAcquisitionServer;
|
||||
|
||||
namespace Plugins {
|
||||
class CPluginFiddler final : public IAcquisitionServerPlugin
|
||||
{
|
||||
// Plugin interface
|
||||
public:
|
||||
explicit CPluginFiddler(const Kernel::IKernelContext& ctx);
|
||||
~CPluginFiddler() override {}
|
||||
|
||||
bool startHook(const std::vector<CString>& selectedChannelNames, const size_t sampling, const size_t nChannel, const size_t nSamplePerSentBlock) override;
|
||||
void stopHook() override {}
|
||||
void loopHook(std::deque<std::vector<float>>& buffers, CStimulationSet& stimSet, const uint64_t start, const uint64_t end,
|
||||
const uint64_t sampleTime) override;
|
||||
|
||||
// Plugin implementation
|
||||
float m_BCI2000VersionFiddlerStrength = 0;
|
||||
uint64_t m_StartSample = 0;
|
||||
uint64_t m_EndSample = 0;
|
||||
|
||||
size_t m_Sampling = 0;
|
||||
size_t m_NProcessedSample = 0;
|
||||
size_t m_LastBufferSize = 0;
|
||||
size_t m_Counter = 0;
|
||||
|
||||
private:
|
||||
|
||||
size_t m_nSamplePerSentBlock = 0;
|
||||
};
|
||||
} // namespace Plugins
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#if defined TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
/*
|
||||
* Notes: This code should be kept compatible with changes to LSL Input Driver in OpenViBE Acquisition Server,
|
||||
* and LSL Export box in Designer.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ovasCPluginLSLOutput.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
#include <labstreamlayer/Utils.hpp>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
namespace Plugins {
|
||||
|
||||
CPluginLSLOutput::CPluginLSLOutput(const Kernel::IKernelContext& ctx)
|
||||
: IAcquisitionServerPlugin(ctx, CString("AcquisitionServer_Plugin_LabStreamingLayerOutput"))
|
||||
{
|
||||
m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "Loading plugin: LSL Output\n";
|
||||
|
||||
m_settings.add("LSL_EnableLSLOutput", &m_IsLSLOutputEnabled);
|
||||
m_settings.add("LSL_SignalStreamName", &m_SignalStreamName);
|
||||
m_settings.add("LSL_MarkerStreamName", &m_MarkerStreamName);
|
||||
m_settings.load();
|
||||
|
||||
// These are not saved or loaded from .conf as they are supposed to be unique
|
||||
m_SignalStreamID = CIdentifier::random().str();
|
||||
m_MarkerStreamID = CIdentifier::random().str();
|
||||
|
||||
while (m_MarkerStreamID == m_SignalStreamID) { m_MarkerStreamID = CIdentifier::random().str(); } // very unlikely
|
||||
}
|
||||
|
||||
CPluginLSLOutput::~CPluginLSLOutput()
|
||||
{
|
||||
if (m_signalOutlet)
|
||||
{
|
||||
delete m_signalOutlet;
|
||||
m_signalOutlet = nullptr;
|
||||
}
|
||||
if (m_stimulusOutlet)
|
||||
{
|
||||
delete m_stimulusOutlet;
|
||||
m_stimulusOutlet = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Hooks
|
||||
|
||||
|
||||
bool CPluginLSLOutput::startHook(const std::vector<CString>& selectedChannelNames, const size_t sampling, const size_t nChannel,
|
||||
const size_t nSamplePerSentBlock)
|
||||
{
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
m_useOVTimestamps = m_kernelCtx.getConfigurationManager().expandAsBoolean("${LSL_UseOVTimestamps}", m_useOVTimestamps);
|
||||
m_startTime = System::Time::zgetTime();
|
||||
|
||||
if (m_IsLSLOutputEnabled)
|
||||
{
|
||||
m_kernelCtx.getLogManager() << Kernel::LogLevel_Trace << "Will create streams [" << m_SignalStreamName << ", id " << m_SignalStreamID << "] and ["
|
||||
<< m_MarkerStreamName << ", id " << m_MarkerStreamID << "]\n";
|
||||
|
||||
// Open a signal stream
|
||||
lsl::stream_info signalInfo(m_SignalStreamName, "signal", int(nChannel), double(sampling), lsl::cf_float32, m_SignalStreamID);
|
||||
|
||||
lsl::xml_element channels = signalInfo.desc().append_child("channels");
|
||||
|
||||
for (size_t i = 0; i < nChannel; ++i)
|
||||
{
|
||||
channels.append_child("channel").append_child_value("label", selectedChannelNames[i].toASCIIString()).append_child_value("unit", "unknown").
|
||||
append_child_value("type", "signal");
|
||||
}
|
||||
|
||||
// make a new outlet
|
||||
m_signalOutlet = new lsl::stream_outlet(signalInfo, int(m_nSamplePerSentBlock));
|
||||
|
||||
// Open a stimulus stream
|
||||
lsl::stream_info stimulusInfo(m_MarkerStreamName, "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_int32, m_MarkerStreamID);
|
||||
|
||||
stimulusInfo.desc().append_child("channels").append_child("channel").append_child_value("label", "Stimulations").append_child_value("type", "marker");
|
||||
|
||||
m_stimulusOutlet = new lsl::stream_outlet(stimulusInfo);
|
||||
|
||||
m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "LSL Output activated...\n";
|
||||
}
|
||||
|
||||
// m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "Step from sampling rate is " << 1.0 / double(sampling) << "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CPluginLSLOutput::loopHook(std::deque<std::vector<float>>& buffers, CStimulationSet& stimSet, const uint64_t start, const uint64_t end,
|
||||
const uint64_t /*sampleTime*/)
|
||||
{
|
||||
if (m_IsLSLOutputEnabled)
|
||||
{
|
||||
// Output signal
|
||||
if (m_signalOutlet->have_consumers())
|
||||
{
|
||||
const uint64_t sampleStep = (end - start) / static_cast<uint64_t>(m_nSamplePerSentBlock);
|
||||
|
||||
if (m_useOVTimestamps)
|
||||
{
|
||||
const double sampleStepInSec = CTime(sampleStep).toSeconds();
|
||||
const double chunkStartInSec = CTime(start).toSeconds();
|
||||
for (size_t i = 0; i < m_nSamplePerSentBlock; ++i) { m_signalOutlet->push_sample(buffers[i], chunkStartInSec + double(i) * sampleStepInSec); }
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < m_nSamplePerSentBlock; ++i)
|
||||
{
|
||||
const double lslTime = OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + CTime(start + i * sampleStep));
|
||||
m_signalOutlet->push_sample(buffers[i], lslTime);
|
||||
}
|
||||
}
|
||||
|
||||
// m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "Pushed first signal at " << start << "\n";
|
||||
// m_kernelCtx.getLogManager() << Kernel::LogLevel_Info << "Step is " << step << "\n";
|
||||
}
|
||||
|
||||
// Output stimuli
|
||||
if (m_stimulusOutlet->have_consumers())
|
||||
{
|
||||
for (size_t i = 0; i < stimSet.getStimulationCount(); ++i)
|
||||
{
|
||||
if (stimSet.getStimulationDate(i) >= start && stimSet.getStimulationDate(i) < end)
|
||||
{
|
||||
const int code = int(stimSet.getStimulationIdentifier(i));
|
||||
const double date = m_useOVTimestamps ? CTime(stimSet.getStimulationDate(i)).toSeconds()
|
||||
: OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + CTime(stimSet.getStimulationDate(i)));
|
||||
m_stimulusOutlet->push_sample(&code, date);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPluginLSLOutput::stopHook()
|
||||
{
|
||||
if (m_IsLSLOutputEnabled)
|
||||
{
|
||||
if (m_signalOutlet)
|
||||
{
|
||||
delete m_signalOutlet;
|
||||
m_signalOutlet = nullptr;
|
||||
}
|
||||
if (m_stimulusOutlet)
|
||||
{
|
||||
delete m_stimulusOutlet;
|
||||
m_stimulusOutlet = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Plugins
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* \brief Acquisition Server plugin outputting signals and stimulations to LabStreamingLayer (LSL) streams
|
||||
* \version 0.1
|
||||
* \author Jussi T. Lindgren / Inria
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
#include "ovasIAcquisitionServerPlugin.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CAcquisitionServer;
|
||||
|
||||
namespace Plugins {
|
||||
class CPluginLSLOutput final : public IAcquisitionServerPlugin
|
||||
{
|
||||
// Plugin interface
|
||||
public:
|
||||
explicit CPluginLSLOutput(const Kernel::IKernelContext& ctx);
|
||||
~CPluginLSLOutput() override;
|
||||
|
||||
bool startHook(const std::vector<CString>& selectedChannelNames, const size_t sampling, const size_t nChannel, const size_t nSamplePerSentBlock) override;
|
||||
void stopHook() override;
|
||||
void loopHook(std::deque<std::vector<float>>& buffers, CStimulationSet& stimSet, const uint64_t start, const uint64_t end,
|
||||
const uint64_t sampleTime) override;
|
||||
|
||||
// Plugin implementation
|
||||
bool m_IsLSLOutputEnabled = false;
|
||||
std::string m_SignalStreamName = "openvibeSignal";
|
||||
std::string m_SignalStreamID;
|
||||
std::string m_MarkerStreamName = "openvibeMarkers";
|
||||
std::string m_MarkerStreamID;
|
||||
|
||||
private:
|
||||
lsl::stream_outlet* m_signalOutlet = nullptr;
|
||||
lsl::stream_outlet* m_stimulusOutlet = nullptr;
|
||||
|
||||
size_t m_nSamplePerSentBlock = 0;
|
||||
|
||||
bool m_useOVTimestamps = false;
|
||||
CTime m_startTime = CTime(0);
|
||||
};
|
||||
} // namespace Plugins
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
Reference in New Issue
Block a user