Commit 08009065 authored by Jussi Lindgren's avatar Jussi Lindgren

Plugins: P300 Visualisation should now send stimuli to AS after rendering

- Updated documentation
- Small clean up
parent 0bed7265
......@@ -23,7 +23,9 @@ INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindThirdPartyGTK")
INCLUDE("FindThirdPartyBoost")#need boost for erp plot
INCLUDE("FindThirdPartyBoost") # need boost for erp plot
INCLUDE("FindThirdPartyBoost_System") # P300 visualizer ASIO part needs this
# ---------------------------------
# Target macros
# Defines target operating system
......
......@@ -7,9 +7,9 @@ __________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_P300SpellerVisualisation_Description|
This box can be used with the \ref Doc_BoxAlgorithm_P300SpellerStimulator box in order to
establish a P300 speller application. The visualisation consists in a matrix of 6 lines and
implement a P300 speller application. The visualisation consists in a matrix of 6 lines and
columns containing 26 characters and 10 numbers. The lines and columns can be flashed sequentially
resulting in an evoked potential in the user's brain activity. This evoked potential can be
to cause an evoked potential in the user's brain activity. This evoked potential can be
detected and used to find which line and which column the user was focused on, thus resulting
in the ability to write text.
......@@ -69,10 +69,12 @@ __________________________________________________________________
* |OVP_DocEnd_BoxAlgorithm_P300SpellerVisualisation_Outputs|
* |OVP_DocBegin_BoxAlgorithm_P300SpellerVisualisation_Output1|
DEPRECATED. This output should not be used anymore. See Miscellaneous Description below.
This output is used to translate incoming flash stimulations depending if they are target or not.
The actual target is determined with the second input. As soon as the target is known, each flash
can be considered as a target flash or not. This can be later use for selecting evoked response
potentials against other responses.
potentials against other responses.
* |OVP_DocEnd_BoxAlgorithm_P300SpellerVisualisation_Output1|
__________________________________________________________________
......@@ -170,5 +172,10 @@ Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_P300SpellerVisualisation_Miscellaneous|
The box relies on the TCP Tagging plugin of the OpenViBE Acquisition Server. A stimulation stream including target/nontarget
stimulations will be sent to the Acquisition Server directly after rendering. This 'software tagging' approach tries to
align the stimulations as well as possible with relation to the underlying EEG stream. It should be more time-accurate than using
the stimulations from the output socket of the box (deprecated). An even better option would be 'hardware tagging', i.e. to
send the stimulations to the amplifier. Hardware tagging is currently not supported due to a lack of a standard for such.
* |OVP_DocEnd_BoxAlgorithm_P300SpellerVisualisation_Miscellaneous|
*/
......@@ -141,6 +141,15 @@ namespace
}
};
// This callback flushes all accumulated stimulations to the TCP Tagging
// after the rendering has completed.
gboolean flush_callback(gpointer pUserData)
{
((CBoxAlgorithmP300SpellerVisualisation*)pUserData)->flushQueue();
return false; // Only run once
}
boolean CBoxAlgorithmP300SpellerVisualisation::initialize(void)
{
IBox& l_rStaticBoxContext=this->getStaticBoxContext();
......@@ -199,6 +208,11 @@ boolean CBoxAlgorithmP300SpellerVisualisation::initialize(void)
m_ui64LastTime=0;
m_pStimulusSender = NULL;
m_uiIdleFuncTag = 0;
m_vStimuliQueue.clear();
m_pMainWidgetInterface=gtk_builder_new(); // glade_xml_new(m_sInterfaceFilename.toASCIIString(), "p300-speller-main", NULL);
if(!gtk_builder_add_from_file(m_pMainWidgetInterface, m_sInterfaceFilename.toASCIIString(), NULL))
{
......@@ -274,7 +288,6 @@ boolean CBoxAlgorithmP300SpellerVisualisation::initialize(void)
this->getLogManager() << LogLevel_Warning << "Unable to connect to AS TCP Tagging, stimuli wont be forwarded.\n";
}
m_bTableInitialized=false;
return true;
......@@ -282,6 +295,16 @@ boolean CBoxAlgorithmP300SpellerVisualisation::initialize(void)
boolean CBoxAlgorithmP300SpellerVisualisation::uninitialize(void)
{
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
if(m_uiIdleFuncTag)
{
m_vStimuliQueue.clear();
g_source_remove(m_uiIdleFuncTag);
m_uiIdleFuncTag = 0;
}
}
if(m_pStimulusSender)
{
delete m_pStimulusSender;
......@@ -388,6 +411,17 @@ boolean CBoxAlgorithmP300SpellerVisualisation::processInput(uint32 ui32Index)
boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
{
// Remove possibly dangling idle func, this construct makes sure only one idle func is registered at a time.
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
if(m_uiIdleFuncTag)
{
g_source_remove(m_uiIdleFuncTag);
m_uiIdleFuncTag = 0;
}
}
// IBox& l_rStaticBoxContext=this->getStaticBoxContext();
IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();
......@@ -423,9 +457,6 @@ boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
int l_iColumn=-1;
boolean l_bIsTarget = false;
// Pass the stimulation to AS in any case
m_pStimulusSender->sendStimuli(l_ui64StimulationIdentifier);
if(l_ui64StimulationIdentifier >= m_ui64RowStimulationBase && l_ui64StimulationIdentifier < m_ui64RowStimulationBase+m_ui64RowCount)
{
l_iRow=(int)(l_ui64StimulationIdentifier-m_ui64RowStimulationBase);
......@@ -475,17 +506,26 @@ boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
m_pFlashFontDescription,
m_pNoFlashFontDescription);
// We now know if this flash corresponds to the current target or not, merge this to the outgoing stimulation stream
if(l_bIsTarget)
{
m_pStimulusSender->sendStimuli(OVTK_StimulationId_Target);
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
m_vStimuliQueue.push_back(OVTK_StimulationId_Target);
l_oFlaggingStimulationSet.appendStimulation(OVTK_StimulationId_Target, l_pStimulationSet->getStimulationDate(j), 0);
}
else
{
m_pStimulusSender->sendStimuli(OVTK_StimulationId_NonTarget);
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
m_vStimuliQueue.push_back(OVTK_StimulationId_NonTarget);
l_oFlaggingStimulationSet.appendStimulation(OVTK_StimulationId_NonTarget, l_pStimulationSet->getStimulationDate(j), 0);
}
}
// Pass the stimulation to the server also as-is. If its a flash, it can be differentiated from a 'target' spec because
// its NOT between OVTK_StimulationId_RestStart and OVTK_StimulationId_RestStop stimuli in the generated P300 timeline.
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
m_vStimuliQueue.push_back(l_ui64StimulationIdentifier);
}
}
m_pTargetFlaggingStimulationEncoder->process(OVP_GD_Algorithm_StimulationStreamEncoder_InputTriggerId_EncodeBuffer);
......@@ -567,10 +607,14 @@ boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
&l_vWidgets,
NULL);
m_pStimulusSender->sendStimuli(OVTK_StimulationId_BaselineStart);
m_pStimulusSender->sendStimuli(m_iTargetRow + m_ui64RowStimulationBase);
m_pStimulusSender->sendStimuli(m_iTargetColumn + m_ui64ColumnStimulationBase);
m_pStimulusSender->sendStimuli(OVTK_StimulationId_BaselineStop);
// Merge the current target into the stimulation stream. It can be differentiated
// from a 'flash' spec because it IS between OVTK_StimulationId_RestStart and
// OVTK_StimulationId_RestStop stimulations in the P300 timeline.
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
m_vStimuliQueue.push_back(m_iTargetRow + m_ui64RowStimulationBase);
m_vStimuliQueue.push_back(m_iTargetColumn + m_ui64ColumnStimulationBase);
}
if(l_vWidgets.size() == 1)
{
......@@ -708,6 +752,10 @@ boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
{
l_sString="<span color=\"darkorange\">" + l_sString + "</span>";
}
else
{
l_sString="<span color=\"darkred\">" + l_sString + "</span>";
}
}
l_sString=std::string(gtk_label_get_label(m_pResult))+l_sString;
gtk_label_set_markup(m_pResult, l_sString.c_str());
......@@ -746,6 +794,12 @@ boolean CBoxAlgorithmP300SpellerVisualisation::process(void)
}
}
// After any possible rendering, we flush the accumulated stimuli. The default idle func is low priority, so it should be run after rendering by gtk.
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
m_uiIdleFuncTag = g_idle_add(flush_callback, this);
}
return true;
}
......@@ -892,3 +946,17 @@ void CBoxAlgorithmP300SpellerVisualisation::_cache_collect_child_widget_cb_(CBox
((std::vector < ::GtkWidget* >*)pUserData)->push_back(rWidgetStyle.pChildWidget);
}
}
void CBoxAlgorithmP300SpellerVisualisation::flushQueue(void)
{
boost::mutex::scoped_lock lock(m_oIdleFuncMutex);
for(size_t i=0;i<m_vStimuliQueue.size();i++)
{
m_pStimulusSender->sendStimuli(m_vStimuliQueue[i]);
}
m_vStimuliQueue.clear();
// This function will be automatically removed after completion, so set to 0
m_uiIdleFuncTag = 0;
}
\ No newline at end of file
......@@ -9,6 +9,10 @@
#include <map>
#include <list>
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/version.hpp>
// TODO:
// - please move the identifier definitions in ovp_defines.h
// - please include your desciptor in ovp_main.cpp
......@@ -58,6 +62,10 @@ namespace OpenViBEPlugins
void _cache_collect_widget_cb_(CBoxAlgorithmP300SpellerVisualisation::SWidgetStyle& rWidgetStyle, void* pUserData);
void _cache_collect_child_widget_cb_(CBoxAlgorithmP300SpellerVisualisation::SWidgetStyle& rWidgetStyle, void* pUserData);
public:
void flushQueue(void); // Sends all accumulated stimuli to the TCP Tagging
protected:
OpenViBE::CString m_sInterfaceFilename;
......@@ -115,9 +123,14 @@ namespace OpenViBEPlugins
OpenViBE::boolean m_bTableInitialized;
// @todo refactor to std::pair<long,long> ?
std::map < unsigned long, std::map < unsigned long, CBoxAlgorithmP300SpellerVisualisation::SWidgetStyle > > m_vCache;
std::list < std::pair < int, int > > m_vTargetHistory;
std::vector< OpenViBE::uint64 > m_vStimuliQueue;
guint m_uiIdleFuncTag;
boost::mutex m_oIdleFuncMutex;
StimulusSender* m_pStimulusSender;
};
......@@ -148,7 +161,7 @@ namespace OpenViBEPlugins
rBoxAlgorithmPrototype.addInput ("Row selection stimulations", OV_TypeId_Stimulations);
rBoxAlgorithmPrototype.addInput ("Column selection stimulations", OV_TypeId_Stimulations);
rBoxAlgorithmPrototype.addOutput("Target / Non target flagging", OV_TypeId_Stimulations);
rBoxAlgorithmPrototype.addOutput("Target / Non target flagging (deprecated)", OV_TypeId_Stimulations);
rBoxAlgorithmPrototype.addSetting("Interface filename", OV_TypeId_Filename, "${Path_Data}/plugins/simple-visualisation/p300-speller.ui");
rBoxAlgorithmPrototype.addSetting("Row stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment