Commit 9dd3b1c8 authored by nfoy's avatar nfoy
Browse files

Merge remote-tracking branch 'origin/wip-jlindgre-motor-imagery-improvements' into integration-1.2

parents 91217407 7cbbc34f
......@@ -12,7 +12,7 @@ if(POLICY CMP0048)
cmake_policy(SET CMP0048 OLD)
endif()
PROJECT(OpenVIBE)
PROJECT(OpenViBE)
# These versions are used by the subprojects by default.
# If you wish to maintain specific version numbers for a subproject, please do so in the projects CMakeLists.txt
......@@ -34,6 +34,11 @@ IF(OPENMP_FOUND)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
ENDIF(OPENMP_FOUND)
ADD_DEFINITIONS("-DOV_PROJECT_NAME=\"${PROJECT_NAME}\"")
ADD_DEFINITIONS("-DOV_VERSION_MAJOR=\"${OV_GLOBAL_VERSION_MAJOR}\"")
ADD_DEFINITIONS("-DOV_VERSION_MINOR=\"${OV_GLOBAL_VERSION_MINOR}\"")
ADD_DEFINITIONS("-DOV_VERSION_PATCH=\"${OV_GLOBAL_VERSION_PATCH}\"")
IF(WIN32)
ADD_DEFINITIONS("-DNOMINMAX -DBOOST_ALL_NO_LIB")
# Switch /arch:SSE2 enables vectorization. Remove if your CPU/compiler doesn't support it.
......
<OpenViBE-Classifier-Box XMLVersion="3">
<Strategy-Identifier class-id="(0xffffffff, 0xffffffff)">Native</Strategy-Identifier>
<Algorithm-Identifier class-id="(0x2ba17a3c, 0x1bd46d84)">Linear Discrimimant Analysis (LDA)</Algorithm-Identifier>
<Stimulations>
<Class-Stimulation class-id="0">OVTK_StimulationId_Label_01</Class-Stimulation>
<Class-Stimulation class-id="1">OVTK_StimulationId_Label_02</Class-Stimulation>
</Stimulations>
<OpenViBE-Classifier>
<LDA version="1">
<Classes>0 1 </Classes>
<Class-config-list>
<Class-config>
<Weights> 5.482270e+001 2.478968e+001</Weights>
<Bias>-66.7812</Bias>
</Class-config>
<Class-config>
<Weights> 5.275779e+001 2.744076e+001</Weights>
<Bias>-68.0094</Bias>
</Class-config>
</Class-config-list>
</LDA>
</OpenViBE-Classifier>
</OpenViBE-Classifier-Box>
\ No newline at end of file
......@@ -43,7 +43,8 @@ namespace OpenViBEAcquisitionServer
virtual void loopHook(std::vector < std::vector < OpenViBE::float32 > >& vPendingBuffer,
OpenViBE::CStimulationSet& oStimulationSet,
OpenViBE::uint64 start,
OpenViBE::uint64 end) {}
OpenViBE::uint64 end,
OpenViBE::uint64 sampleTime) {}
/// Hook called at the end of the acceptNewConnection() function of AcquisitionServer
virtual void acceptNewConnectionHook() {}
......
......@@ -543,11 +543,12 @@ boolean CAcquisitionServer::loop(void)
const uint64 l_ui64BufferEndSamples = m_ui64SampleCount-m_vPendingBuffer.size()+m_ui32SampleCountPerSentBlock;
const uint64 l_ui64BufferStartTime = ITimeArithmetics::sampleCountToTime(m_ui32SamplingFrequency, l_ui64BufferStartSamples);
const uint64 l_ui64BufferEndTime = ITimeArithmetics::sampleCountToTime(m_ui32SamplingFrequency, l_ui64BufferEndSamples);
const uint64 l_ui64SampleTime = ITimeArithmetics::sampleCountToTime(m_ui32SamplingFrequency, m_ui64SampleCount);
// Pass the stimuli and buffer to all plugins; note that they may modify them
for(std::vector<IAcquisitionServerPlugin*>::iterator itp = m_vPlugins.begin(); itp != m_vPlugins.end(); ++itp)
{
(*itp)->loopHook(m_vPendingBuffer, m_oPendingStimulationSet, l_ui64BufferStartTime, l_ui64BufferEndTime);
(*itp)->loopHook(m_vPendingBuffer, m_oPendingStimulationSet, l_ui64BufferStartTime, l_ui64BufferEndTime, l_ui64SampleTime);
}
// Handle connections
......
......@@ -2229,7 +2229,7 @@ Do you want to continue the execution?&lt;/b&gt;</property>
<property name="height_request">240</property>
<property name="can_focus">False</property>
<property name="events">GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK</property>
<property name="title" translatable="yes">OpenViBE Designer</property>
<property name="title" translatable="yes">OpenViBE Designer v${PROJECT_VERSION}</property>
<property name="window_position">center</property>
<property name="default_width">1024</property>
<property name="default_height">768</property>
......
......@@ -5,6 +5,10 @@ INCLUDE_DIRECTORIES(${ADDITIONAL_PATH})
FILE(GLOB_RECURSE additional_source_files ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h)
SET(source_files "${source_files};${additional_source_files}")
SET(ADDITIONAL_PATH "${CMAKE_SOURCE_DIR}/contrib/plugins/server-extensions/tcp-tagging/")
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH})
FILE(GLOB_RECURSE additional_source_files ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h)
SET(source_files "${source_files};${additional_source_files}")
FUNCTION(OV_ADD_CONTRIB_DRIVER DRIVER_PATH)
......
......@@ -5,6 +5,7 @@
*/
#include "ovasCPluginExternalStimulations.h"
#include "ovasCPluginTCPTagging.h"
#include "ovasCDriverBrainmasterDiscovery.h"
#include "ovasCDriverBrainProductsBrainVisionRecorder.h"
......@@ -67,6 +68,9 @@ namespace OpenViBEContributions {
#endif
pGUI->registerPlugin(new OpenViBEAcquisitionServer::OpenViBEAcquisitionServerPlugins::CPluginExternalStimulations(rKernelContext));
// register tcp-tagging plugin
pGUI->registerPlugin(new OpenViBEAcquisitionServer::OpenViBEAcquisitionServerPlugins::CPluginTCPTagging(rKernelContext));
}
}
......@@ -61,7 +61,7 @@ void CPluginExternalStimulations::startHook(const std::vector<OpenViBE::CString>
}
void CPluginExternalStimulations::loopHook(std::vector < std::vector < OpenViBE::float32 > >& /* vPendingBuffer */, CStimulationSet &stimulationSet, uint64 start, uint64 end)
void CPluginExternalStimulations::loopHook(std::vector < std::vector < OpenViBE::float32 > >& /* vPendingBuffer */, CStimulationSet &stimulationSet, uint64 start, uint64 end, uint64 /* sampleTime */)
{
if (m_bIsExternalStimulationsEnabled)
{
......
......@@ -32,7 +32,7 @@ namespace OpenViBEAcquisitionServer
virtual void startHook(const std::vector<OpenViBE::CString>&, OpenViBE::uint32 ui32SamplingFrequency, OpenViBE::uint32 ui32ChannelCount, OpenViBE::uint32 ui32SampleCountPerSentBlock);
virtual void stopHook();
virtual void loopHook(std::vector < std::vector < OpenViBE::float32 > >& vPendingBuffer,
OpenViBE::CStimulationSet& stimulationSet, OpenViBE::uint64 start, OpenViBE::uint64 end);
OpenViBE::CStimulationSet& stimulationSet, OpenViBE::uint64 start, OpenViBE::uint64 end, OpenViBE::uint64 sampleTime);
virtual void acceptNewConnectionHook();
......
#!/usr/bin/python3
# Example of tcp tagging client
import sys
import socket
from time import time, sleep
# host and port of tcp tagging server
HOST = '127.0.0.1'
PORT = 15361
# Event identifier
EVENT_ID = 5+0x8100
# Artificial delay (ms). It may need to be increased if the time to send the tag is too long and causes tag loss.
DELAY=0
# transform a value into an array of byte values in little-endian order.
def to_byte(value, length):
for x in range(length):
yield value%256
value//=256
# connect
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
for i in range(100):
# create the three pieces of the tag, padding, event_id and timestamp
padding=[0]*8
event_id=list(to_byte(EVENT_ID, 8))
# timestamp can be either the posix time in ms, or 0 to let the acquisition server timestamp the tag itself.
timestamp=list(to_byte(int(time()*1000)+DELAY, 8))
# send tag and sleep
s.sendall(bytes(padding+event_id+timestamp))
sleep(1)
s.close()
#include "ovasCPluginTCPTagging.h"
#include <set>
#include <queue>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include <sys/timeb.h>
// Implementation of PluginTCPTagging.
// The plugin relies on four auxilliary classes: TagQueue, TagSession, TagServer and TagStream.
// TagQueue implements a trivial queue to store tags with exclusive locking.
// TagServer implements a server that simply binds to a port and waits for incoming connections.
// TagSession represents an individual connection with a client and holds a connection handle (socket)
// and a data buffer to store incoming data.
// The use of shared pointers is instrumental to ensure that instances are still alive when call-backs are
// called and avoid memory corruption.
// The TagStream class implements a stream to allow to collect tags. Upon instantiation, it creates an instance
// of TagServer and starts the server in an auxilliary thread.
// The exchange of data between the main tread and the auxilliary thread is performed via a lockfree queue (boost).
using boost::asio::ip::tcp;
using namespace OpenViBE;
using namespace OpenViBEAcquisitionServer;
using namespace OpenViBEAcquisitionServerPlugins;
// A Tag consists of an identifier to inform about the type of event
// and a timestamp corresponding to the time at which the event occurrs.
struct Tag
{
uint64 padding, identifier, timestamp;
};
class TagSession; // forward declaration of TagSession to define SharedSessionPtr
class TagQueue; // forward declaration of TagQueue to define SharedQueuePtr
typedef boost::shared_ptr<TagQueue> SharedQueuePtr;
typedef boost::shared_ptr<TagSession> SharedSessionPtr;
// A trivial implementation of a queue to store Tags with exclusive locking
class TagQueue
{
public:
TagQueue()
{
}
void push(const Tag& tag)
{
boost::lock_guard<boost::mutex> guard(m_mutex);
m_queue.push(tag);
}
bool pop(Tag& tag)
{
boost::lock_guard<boost::mutex> guard(m_mutex);
if (m_queue.size()==0) return false;
else {
tag = m_queue.front();
m_queue.pop();
return true;
}
}
private:
std::queue<Tag> m_queue;
boost::mutex m_mutex;
};
// An instance of TagSession is associated to every client connecting to the Tagging Server.
// It contains a connection handle and data buffer.
class TagSession : public boost::enable_shared_from_this<TagSession>
{
public:
TagSession(boost::asio::io_service& io_service, const SharedQueuePtr& queue)
: m_socket(io_service), m_queuePtr(queue)
{
}
tcp::socket& socket()
{
return m_socket;
}
void start()
{
startRead();
}
void startRead()
{
// Caveat: a shared pointer is used (instead of simply using this) to ensure that this instance of TagSession is still alive when the call-back is called.
boost::asio::async_read(m_socket, boost::asio::buffer((void *) &m_tag, sizeof(Tag)), boost::bind(&TagSession::handleRead, shared_from_this(), _1));
}
void handleRead(const boost::system::error_code& error)
{
if (!error) {
// If the timestamp is 0, set timestamp to current posix time.
if (m_tag.timestamp==0) {
// Get POSIX time (number of milliseconds since epoch)
timeb time_buffer;
ftime(&time_buffer);
uint64 posixTime = time_buffer.time*1000ULL + time_buffer.millitm;
// edit timestamp
m_tag.timestamp=posixTime;
}
// Push tag to the queue.
m_queuePtr->push(m_tag);
// Continue reading.
startRead();
}
}
private:
Tag m_tag;
tcp::socket m_socket;
SharedQueuePtr m_queuePtr;
};
// TagServer implements a server that binds to a port and accepts new connections.
// It also has a field sessionSet that holds shared pointers to all exisiting sessions
class TagServer
{
public:
// Server port.
enum {PORT = 15361};
TagServer(const SharedQueuePtr& queue)
: m_ioService(), m_acceptor(m_ioService, tcp::endpoint(tcp::v4(), PORT)), m_queuePtr(queue)
{
}
void run()
{
try {
startAccept();
m_ioService.run();
}
catch(std::exception& e) {
// TODO: log error message
}
}
private:
void startAccept()
{
SharedSessionPtr newSession (new TagSession(m_ioService, m_queuePtr));
// Note: if this instance of TaggingSever is destroyed then the associated io_service is destroyed as well.
// Therefore the call-back will never be called if this instance is destroyed and it is safe to use this instead of shared pointer.
m_acceptor.async_accept(newSession->socket(), boost::bind(&TagServer::handleAccept, this, newSession, _1));
}
void handleAccept(SharedSessionPtr session, const boost::system::error_code& error)
{
if (!error) {
session->start();
}
startAccept();
}
private:
boost::asio::io_service m_ioService;
tcp::acceptor m_acceptor;
const SharedQueuePtr& m_queuePtr;
};
// TagStream allows to collect tags received via TCP.
class TagStream
{
// Initial memory allocation of lockfree queue.
enum {ALLOCATE = 128};
public:
TagStream()
: m_queuePtr(new TagQueue)
{
boost::thread thread (&TagStream::startServer, this);
}
bool pop(Tag& tag)
{
return m_queuePtr->pop(tag);
}
private:
void startServer()
{
TagServer server(m_queuePtr);
server.run();
}
private:
SharedQueuePtr m_queuePtr;
};
static TagStream tagStream;
// CPluginTCPTagging implementation
CPluginTCPTagging::CPluginTCPTagging(const OpenViBE::Kernel::IKernelContext& rKernelContext)
: IAcquisitionServerPlugin(rKernelContext, CString("AcquisitionServer_Plugin_TCPTagging"))
{
m_rKernelContext.getLogManager() << Kernel::LogLevel_Info << "Loading plugin: TCP Tagging\n";
}
CPluginTCPTagging::~CPluginTCPTagging()
{
}
void CPluginTCPTagging::startHook(const std::vector<OpenViBE::CString>& vSelectedChannelNames,
OpenViBE::uint32 ui32SamplingFrequency, OpenViBE::uint32 ui32ChannelCount, OpenViBE::uint32 ui32SampleCountPerSentBlock)
{
// Get POSIX time (number of milliseconds since epoch)
timeb time_buffer;
ftime(&time_buffer);
uint64 posixTime = time_buffer.time*1000ULL + time_buffer.millitm;
// Initialize time counters.
m_previousPosixTime = posixTime;
m_previousSampleTime = 0;
// Clear Tag stream
Tag tag;
while(tagStream.pop(tag));
}
void CPluginTCPTagging::loopHook(std::vector < std::vector < OpenViBE::float32 > >& /*vPendingBuffer*/,
OpenViBE::CStimulationSet& stimulationSet, uint64 start, uint64 end, uint64 sampleTime)
{
// Get POSIX time (number of milliseconds since epoch)
timeb time_buffer;
ftime(&time_buffer);
uint64 posixTime = time_buffer.time*1000ULL + time_buffer.millitm;
Tag tag;
// Collect tags from the stream until exhaustion.
while(tagStream.pop(tag)) {
m_rKernelContext.getLogManager() << Kernel::LogLevel_Info << "New Tag received (" << tag.padding << ", " << tag.identifier << ", " << tag.timestamp << ") at " << posixTime << " (posix time in ms)\n";
// Check that the timestamp fits the current chunk.
if (tag.timestamp < m_previousPosixTime) {
m_rKernelContext.getLogManager() << Kernel::LogLevel_Warning << "The Tag has arrived before the beginning of the current chunk and will be inserted at the beginning of this chunk\n";
tag.timestamp = m_previousPosixTime;
}
// Marker time correction (simple local linear interpolation).
if (m_previousPosixTime != posixTime) {
tag.timestamp = m_previousSampleTime + (tag.timestamp - m_previousPosixTime)*((sampleTime - m_previousSampleTime) / (posixTime - m_previousPosixTime));
}
// Insert tag into the stimulation set.
stimulationSet.appendStimulation(tag.identifier, tag.timestamp, 0 /* duration of tag (ms) */);
}
// Update time counters.
m_previousPosixTime = posixTime;
m_previousSampleTime = sampleTime;
}
#ifndef __OpenViBE_AcquisitionServer_TCPTagging_H__
#define __OpenViBE_AcquisitionServer_TCPTagging_H__
/**
* \brief Acquisition Server plugin adding the capability to receive stimulations from external sources
* via TCP.
*/
#include "ovasIAcquisitionServerPlugin.h"
namespace OpenViBEAcquisitionServer
{
namespace OpenViBEAcquisitionServerPlugins
{
class CPluginTCPTagging : public IAcquisitionServerPlugin
{
public:
CPluginTCPTagging(const OpenViBE::Kernel::IKernelContext& rKernelContext);
~CPluginTCPTagging();
// Overrides virtual method startHook inherited from class IAcquisitionServerPlugin.
void startHook(const std::vector<OpenViBE::CString>& vSelectedChannelNames, OpenViBE::uint32 ui32SamplingFrequency,
OpenViBE::uint32 ui32ChannelCount, OpenViBE::uint32 ui32SampleCountPerSentBlock);
// Overrides virtual method loopHook inherited from class IAcquisitionServerPlugin.
void loopHook(std::vector < std::vector < OpenViBE::float32 > >& vPendingBuffer,
OpenViBE::CStimulationSet& stimulationSet, OpenViBE::uint64 start, OpenViBE::uint64 end, OpenViBE::uint64 sampleTime);
private:
OpenViBE::uint64 m_previousPosixTime;
OpenViBE::uint64 m_previousSampleTime;
};
}
}
#endif // __OpenViBE_AcquisitionServer_TCPTagging_H__
<OpenViBE-Classifier-Box FormatVersion="4">
<Strategy-Identifier class-id="(0xffffffff, 0xffffffff)">Native</Strategy-Identifier>
<Algorithm-Identifier class-id="(0x2ba17a3c, 0x1bd46d84)">Linear Discrimimant Analysis (LDA)</Algorithm-Identifier>
<Stimulations>
<Class-Stimulation class-id="0">OVTK_StimulationId_Label_01</Class-Stimulation>
<Class-Stimulation class-id="1">OVTK_StimulationId_Label_02</Class-Stimulation>
<Class-Stimulation class-id="2">OVTK_StimulationId_Label_03</Class-Stimulation>
</Stimulations>
<OpenViBE-Classifier>
<LDA version="1">
<Classes>0 1 2 </Classes>
<Class-config-list>
<Class-config>
<Weights> 1.420580e+002 1.407747e+002 1.515542e+002 1.064545e+002</Weights>
<Bias>-3949.05</Bias>
</Class-config>
<Class-config>
<Weights> 1.396979e+002 1.432478e+002 1.514010e+002 1.063725e+002</Weights>
<Bias>-3947.23</Bias>
</Class-config>
<Class-config>
<Weights> 1.396863e+002 1.410456e+002 1.539364e+002 1.070348e+002</Weights>
<Bias>-3961.82</Bias>
</Class-config>
</Class-config-list>
</LDA>
</OpenViBE-Classifier>
</OpenViBE-Classifier-Box>
\ No newline at end of file
sent = false
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
sent = false;
end
function uninitialize(box)
end
function process(box)
while box:keep_processing() and sent == false do
current_time = box:get_current_time() + 1
box:send_stimulation(1, OVTK_StimulationId_Label_01, current_time, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_02, current_time+4, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_03, current_time+8, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_02, current_time+12, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_01, current_time+16, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_03, current_time+20, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_01, current_time+24, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_02, current_time+28, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_03, current_time+32, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_03, current_time+36, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_02, current_time+40, 0)
box:send_stimulation(1, OVTK_StimulationId_Label_01, current_time+44, 0)
box:send_stimulation(1, OVTK_StimulationId_ExperimentStop, current_time+48, 0)
sent = true
box:sleep()
end
end
/**
* \page BoxAlgorithm_OutlierRemoval Outlier Removal
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OutlierRemoval_Description|
The outlier removal box discards extremal feature vectors. The user can specify the desired quantile limits [min,max].
The algorithm loops through the feature dimensions and computes range r(j)=[quantile(min),quantile(max)] for each dimension j.
If each feature j of example i is inside r(j), the example i is kept. Otherwise it is discarded. The box is intended to
be sent all the vectors of interest before being given the stimulation to start the removal.
* |OVP_DocEnd_BoxAlgorithm_OutlierRemoval_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_OutlierRemoval_Inputs|
* |OVP_DocEnd_BoxAlgorithm_OutlierRemoval_Inputs|