Commit 465d069d authored by Jussi Lindgren's avatar Jussi Lindgren

Demos: Various changes to the basic & xDAWN P300 spellers

- Changed the scenarios to use TCP Tagging (software tagging)
- The recorded files will now have only one stimulation stream
- Simplified the scenarios, especially the classification part
- Added lua scripts to filter targets and flashes from P300 timelines
- Added a lua script to accumulate P300 row/column votes
- Added some comments to the scenarios
- Added a replay scenario to the basic P300 speller
parent 08009065
function arrayMax(a)
if #a == 0 then return nil, nil end
local maxIdx, maxValue = 0, a[0]
for i = 1, (#a -1 ) do
if maxValue < a[i] then
maxIdx, maxValue = i, a[i]
end
end
return maxIdx, maxValue
end
-- For handling target fifo
List = {}
function List.new ()
return {first = 0, last = -1}
end
function List.pushright (list, value)
local last = list.last + 1
list.last = last
list[last] = value
end
function List.popleft (list)
local first = list.first
if first > list.last then
error("list is empty")
end
local value = list[first]
list[first] = nil -- to allow garbage collection
list.first = first + 1
return value
end
function List.isempty (list)
if list.first > list.last then
return true
else
return false
end
end
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
row_base = _G[box:get_setting(2)]
col_base = _G[box:get_setting(3)]
segment_start = _G[box:get_setting(4)]
segment_stop = _G[box:get_setting(5)]
-- 0 inactive, 1 segment started, 2 segment stopped (can vote)
segment_status = 0
-- the idea is to push the flash states to the fifo, and when predictions arrive (with some delay), they are matched in oldest-first fashion.
target_fifo = List.new()
-- box:log("Info", string.format("pop %d %d", id[1], id[2]))
row_votes = {}
col_votes = {}
do_debug = false
end
-- this function is called when the box is uninitialized
function uninitialize(box)
end
function process(box)
-- loops until box is stopped
while box:keep_processing() do
-- first, parse the timeline stream
for stimulation = 1, box:get_stimulation_count(2) do
-- gets the received stimulation
local identifier, date, duration = box:get_stimulation(2, 1)
-- discards it
box:remove_stimulation(2, 1)
if identifier == segment_start then
if do_debug then
box:log("Info", string.format("Trial start"))
box:log("Info", string.format("Clear votes"))
end
-- zero the votes
col_votes = {}
row_votes = {}
target_fifo = List.new()
-- fixme fixed 20
for i = 0,20 do
col_votes[i] = 0
row_votes[i] = 0
end
segment_status = 1
end
-- Does the identifier code a flash? if so, put into fifo
if segment_status == 1 and identifier >= row_base and identifier <= OVTK_StimulationId_LabelEnd then
-- assume rows before cols
if identifier < col_base then
local t = {"row", identifier - row_base}
List.pushright(target_fifo,t)
if do_debug then
box:log("Info", string.format("Push row target %d", identifier - row_base ))
end
else
local t = {"col", identifier - col_base}
List.pushright(target_fifo,t)
if do_debug then
box:log("Info", string.format("Push col target %d", identifier - col_base ))
end
end
end
if identifier == segment_stop then
if do_debug then
box:log("Info", string.format("Trial stop"))
end
segment_status = 2
end
end
-- then parse the classifications
for stimulation = 1, box:get_stimulation_count(1) do
-- gets the received stimulation
local identifier, date, duration = box:get_stimulation(1, 1)
-- discards it
box:remove_stimulation(1, 1)
-- Is it an in-class prediction?
if identifier == OVTK_StimulationId_Target then
local t = List.popleft(target_fifo)
if do_debug then
box:log("Info", string.format("Pred fifo %s %d is target", t[1], t[2]))
end
if t[1]=="row" then
row_votes[t[2]] = row_votes[t[2]] + 1
else
col_votes[t[2]] = col_votes[t[2]] + 1
end
end
if identifier == OVTK_StimulationId_NonTarget then
local t = List.popleft(target_fifo)
if do_debug then
box:log("Info", string.format("Pred fifo %s %d is nontarget", t[1], t[2]))
end
end
end
if segment_status == 2 and List.isempty(target_fifo) then
-- output the vote after the segment end when we've matched all predictions
local maxRowIdx, maxRowValue = arrayMax(row_votes)
local maxColIdx, maxColValue = arrayMax(col_votes)
local rowVotes = 0
local colVotes = 0
for ir, val in pairs(row_votes) do
rowVotes = rowVotes + val
end
for ir, val in pairs(col_votes) do
colVotes = colVotes + val
end
if do_debug then
box:log("Info", string.format("Vote [%d %d] wt [%d,%d]", maxRowIdx+row_base, maxColIdx+col_base, maxRowValue, maxColValue))
box:log("Info", string.format(" Total [%d %d]", rowVotes, colVotes))
end
local now = box:get_current_time()
box:send_stimulation(1, maxRowIdx + row_base, now, 0)
box:send_stimulation(2, maxColIdx + col_base, now, 0)
segment_status = 0
end
box:sleep()
end
end
-- Picks out 'flashes' from a stimulation stream
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
box:set_filter_mode(1);
state = 0
do_debug = false
end
-- this function is called when the box is uninitialized
function uninitialize(box)
end
-- this function is called once by the box
function process(box)
-- loop until box:keep_processing() returns zero
-- cpu will be released with a call to sleep
-- at the end of the loop
while box:keep_processing() do
-- gets current simulated time
t = box:get_current_time()
-- loops on every received stimulation for a given input
for stimulation = 1, box:get_stimulation_count(1) do
-- gets stimulation
stimulation_id, stimulation_time, stimulation_duration = box:get_stimulation(1, 1)
if stimulation_id == OVTK_StimulationId_SegmentStart then
state = 1
elseif stimulation_id == OVTK_StimulationId_SegmentStop then
state = 0
end
-- If we're between 'rest start' and 'rest_stop', this specifies a target
if state == 1 and stimulation_id >= OVTK_StimulationId_LabelStart and stimulation_id <= OVTK_StimulationId_LabelEnd then
box:send_stimulation(1, stimulation_id, stimulation_time, 0)
if do_debug then
box:log("Info", string.format("Push a target %d at %f (now %f)", stimulation_id, stimulation_time, t))
end
end
-- discards it
box:remove_stimulation(1, 1)
end
-- releases cpu
box:sleep()
end
end
-- Picks out 'targets' from a stimulation stream
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
state = 0
box:set_filter_mode(1);
do_debug = false
end
-- this function is called when the box is uninitialized
function uninitialize(box)
end
-- this function is called once by the box
function process(box)
-- loop until box:keep_processing() returns zero
-- cpu will be released with a call to sleep
-- at the end of the loop
while box:keep_processing() do
-- gets current simulated time
t = box:get_current_time()
-- loops on every received stimulation for a given input
for stimulation = 1, box:get_stimulation_count(1) do
-- gets stimulation
stimulation_id, stimulation_time, stimulation_duration = box:get_stimulation(1, 1)
if stimulation_id == OVTK_StimulationId_RestStart then
state = 1
elseif stimulation_id == OVTK_StimulationId_RestStop then
state = 0
end
-- If we're between 'rest start' and 'rest_stop', this specifies a target
if state == 1 and stimulation_id >= OVTK_StimulationId_LabelStart and stimulation_id <= OVTK_StimulationId_LabelEnd then
box:send_stimulation(1, stimulation_id, stimulation_time, 0)
if do_debug then
box:log("Info", string.format("Push a target %d at %f (now = %f)", stimulation_id, stimulation_time, t))
end
end
-- discards it
box:remove_stimulation(1, 1)
end
-- releases cpu
box:sleep()
end
end
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
stim = _G[box:get_setting(2)]
launchTime = box:get_setting(3)
end
-- this function is called when the box is uninitialized
function uninitialize(box)
end
-- this function is called once by the box
function process(box)
box:send_stimulation(1, stim, launchTime, 0)
end
......@@ -29,6 +29,7 @@ end
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
math.randomseed(os.time())
......
<OpenViBE-Scenario>
<Creator>OpenViBE</Creator>
<CreatorVersion>1.1.0+git</CreatorVersion>
<Boxes>
<Box>
<Identifier>(0x00000fa6, 0x00006e68)</Identifier>
......@@ -93,68 +91,6 @@
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x0000191b, 0x00004ce6)</Identifier>
<Name>Stimulation listener</Name>
<AlgorithmClassIdentifier>(0x65731e1d, 0x47de5276)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulation stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Debug</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>-80.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa963f5, 0x1a638cd4)</Identifier>
<Value>43</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>640.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xf451ad91, 0x14c75f86)</Value>
</Attribute>
<Attribute>
<Identifier>(0xad100179, 0xa3c984ab)</Identifier>
<Value>136</Value>
</Attribute>
<Attribute>
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
<Value>(0x00000000, 0x0003ba9a)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc73e83ec, 0xf855c5bc)</Identifier>
<Value>true</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000020f0, 0x000020aa)</Identifier>
<Name>Signal Decimation</Name>
......@@ -211,76 +147,6 @@
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00003fd1, 0x000009e3)</Identifier>
<Name>Generic stream reader</Name>
<AlgorithmClassIdentifier>(0x6468099f, 0x0370095a)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output stream 1</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output stream 2</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Filename</Name>
<DefaultValue></DefaultValue>
<Value>C:/jl/p300-speller/signals/p300-train-[2016.02.10-16.25.33].ov</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>-288.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa963f5, 0x1a638cd4)</Identifier>
<Value>43</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>688.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xf37b8e7a, 0x1bc33e4e)</Value>
</Attribute>
<Attribute>
<Identifier>(0xad100179, 0xa3c984ab)</Identifier>
<Value>153</Value>
</Attribute>
<Attribute>
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
<Value>(0x00000000, 0x00035714)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc73e83ec, 0xf855c5bc)</Identifier>
<Value>true</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x0000599b, 0x000026ba)</Identifier>
<Name>Acquisition client</Name>
......@@ -580,35 +446,6 @@
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00003f78, 0x0000691b)</Identifier>
<Source>
<BoxIdentifier>(0x00003fd1, 0x000009e3)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x0000191b, 0x00004ce6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
<Attributes>
<Attribute>
<Identifier>(0x1b32c44c, 0x1905e0e9)</Identifier>
<Value>-260</Value>
</Attribute>
<Attribute>
<Identifier>(0x358ae8b5, 0x0f8bacd1)</Identifier>
<Value>695</Value>
</Attribute>
<Attribute>
<Identifier>(0x3f0a3b27, 0x570913d2)</Identifier>
<Value>-106</Value>
</Attribute>
<Attribute>
<Identifier>(0x6267b5c5, 0x676e3e42)</Identifier>
<Value>640</Value>
</Attribute>
</Attributes>
</Link>
<Link>
<Identifier>(0x00004126, 0x00004b0d)</Identifier>
<Source>
......@@ -757,6 +594,50 @@
</Links>
<MessageLinks></MessageLinks>
<Comments>
<Comment>
<Identifier>(0x00001653, 0x00002964)</Identifier>
<Text>&lt;u&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;/u&gt; be sure that the
&lt;i&gt;sampling rate&lt;/i&gt; and &lt;i&gt;sample count
per buffer&lt;/i&gt; you use in the &lt;u&gt;acquisition
server&lt;/u&gt; are compatible with the actual
&lt;i&gt;signal decimation factor&lt;/i&gt;</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>208.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>0.000000</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x000028e8, 0x00003dee)</Identifier>
<Text>&lt;u&gt;&lt;b&gt;&lt;big&gt;Overview&lt;/big&gt;&lt;/b&gt;&lt;/u&gt;
This scenario can be used in order
to check the quality of the signals
before starting an experiment.
One should &lt;u&gt;definitely&lt;/u&gt;
check the quality of the signals
and ensure that :
- &lt;b&gt;eye blinks&lt;/b&gt; are visible
- &lt;b&gt;jaw clenching&lt;/b&gt; are visible
- &lt;b&gt;alpha waves&lt;/b&gt; are visible when closing eyes</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>816.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>-112.000000</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x00004785, 0x00007f9c)</Identifier>
<Text>You can browse each box' documentation by selecting the box and pressing &lt;b&gt;F1&lt;/b&gt;</Text>
......@@ -771,6 +652,24 @@
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x000059b5, 0x00001524)</Identifier>
<Text>The &lt;i&gt;Temporal Filter&lt;/i&gt;
and &lt;i&gt;Signal Decimation&lt;/i&gt;
boxes transform the signal
so you can see what is actually
used online.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>208.000000</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>-128.000000</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<VisualisationTree>
<VisualisationWidget>
......@@ -820,7 +719,7 @@
<Attributes>
<Attribute>
<Identifier>(0x790d75b8, 0x3bb90c33)</Identifier>
<Value></Value>
<Value>Jussi T. Lindgren</Value>
</Attribute>
<Attribute>
<Identifier>(0x8c1fc55b, 0x7b433dc2)</Identifier>
......@@ -828,7 +727,7 @@
</Attribute>
<Attribute>
<Identifier>(0x9f5c4075, 0x4a0d3666)</Identifier>
<Value></Value>
<Value>Basic P300 0/4</Value>
</Attribute>
<Attribute>
<Identifier>(0xf36a1567, 0xd13c53da)</Identifier>
......@@ -840,7 +739,7 @@
</Attribute>