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) | |||||
if maxRowValue == 0 and maxColValue == 0 then | |||||
box:log("Warning", string.format("Classifier predicted 'no p300' for all flashes of the trial")); | |||||
end | |||||
if do_debug then | |||||
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 | |||||
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 |
-- This Lua script sends Stimulations to the speller visualization box for a tactile p300-System with 6 stimulators | |||||
-- | |||||
-- Author : Tobias Baumann | |||||
-- Date : 2021-12-06 | |||||
-- Revised: 2021-19-11 | |||||
--This function lets the box sleep until a fixed moment | |||||
function wait_until(box, time) | |||||
while box:get_current_time() < time do | |||||
box:sleep() | |||||
end | |||||
end | |||||
--This function lets the box wait for a fixed duration | |||||
function wait_for(box, duration) | |||||
wait_until(box, box:get_current_time() + duration) | |||||
end | |||||
--this function checks, wether value already is an element of the given stim_matrix | |||||
function is_element(matrix, value) | |||||
for i = 1, #matrix do | |||||
if #matrix == 0 then | |||||
return(false) | |||||
elseif value == matrix[i] then | |||||
return(true) | |||||
end | |||||
end | |||||
return(false) | |||||
end | |||||
--this function creates a sequence of stimulations by shuffeling the values of the given stim_matrix | |||||
function create_sequence(matrix) | |||||
math.randomseed(os.time()) | |||||
local stim_matrix = {} | |||||
local stim_code = 0 | |||||
local i = 1 | |||||
while i <= #matrix do | |||||
stim_code = matrix[math.random(1,#matrix)] | |||||
if is_element(stim_matrix, stim_code) == false then | |||||
stim_matrix[i] = stim_code | |||||
i = i + 1 | |||||
end | |||||
end | |||||
return(stim_matrix) | |||||
end | |||||
-- this function is called when the box is initialized | |||||
function initialize(box) | |||||
--load stimulation codes | |||||
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua") | |||||
--load box settings | |||||
row_base = _G[box:get_setting(2)] | |||||
col_base = _G[box:get_setting(3)] | |||||
n_rows = box:get_setting(4) | |||||
n_repetitions = box:get_setting(5) | |||||
flash_duration = box:get_setting(7) | |||||
noflash_duration = box:get_setting(8) | |||||
inter_repetition_delay = box:get_setting(9) | |||||
inter_trial_delay = box:get_setting(10) | |||||
send_toggle = _G[box:get_setting(11)] | |||||
time_to_send = box:get_setting(13) | |||||
if box:get_setting(12) == 'true' then | |||||
free_spelling = true | |||||
n_trials = 1 | |||||
else | |||||
free_spelling = false | |||||
n_trials = box:get_setting(6) | |||||
end | |||||
--Lua variables | |||||
send = false | |||||
experiment_end = false | |||||
tactilo_stimcodes = {} | |||||
for x = 1, n_rows do | |||||
tactilo_stimcodes[x] = row_base + x - 1 | |||||
end | |||||
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) | |||||
while box:keep_processing() do | |||||
if send and not experiment_end then | |||||
-- iterate over the number of trials | |||||
for trial = 1, n_trials do | |||||
box:send_stimulation(1, OVTK_StimulationId_RestStart, box:get_current_time() , 0) | |||||
wait_for(box, inter_trial_delay) | |||||
box:send_stimulation(1, OVTK_StimulationId_RestStop, box:get_current_time() , 0) | |||||
box:send_stimulation(1, OVTK_StimulationId_TrialStart ,box:get_current_time() , 0) | |||||
-- iterate over the number of repetitions | |||||
for segment = 1, n_repetitions do | |||||
tactilo_stimcodes = create_sequence(tactilo_stimcodes) | |||||
box:send_stimulation(1, OVTK_StimulationId_SegmentStart ,box:get_current_time() , 0) | |||||
-- iterate over the number of tactilos | |||||
for i = 1, #tactilo_stimcodes do | |||||
box:send_stimulation(1, tactilo_stimcodes[i] ,box:get_current_time() , 0) | |||||
box:send_stimulation(1, OVTK_StimulationId_VisualStimulationStart ,box:get_current_time() , 0) | |||||
wait_for(box, flash_duration) | |||||
box:send_stimulation(1, OVTK_StimulationId_VisualStimulationStop ,box:get_current_time() , 0) | |||||
wait_for(box, noflash_duration) | |||||
end | |||||
box:send_stimulation(1, OVTK_StimulationId_SegmentStop ,box:get_current_time() , 0) | |||||
wait_for(box, inter_repetition_delay) | |||||
end | |||||
box:send_stimulation(1, OVTK_StimulationId_TrialStop ,box:get_current_time() , 0) | |||||
end | |||||
if not free_spelling then | |||||
-- end experiment if set to copy spelling | |||||
box:send_stimulation(1, OVTK_StimulationId_ExperimentStop ,box:get_current_time() , 0) | |||||
send = false | |||||
experiment_end = true | |||||
end | |||||
else if not send and not experiment_end then | |||||
-- delay the start of the experiment by t = time_to_send | |||||
wait_for(box, time_to_send) | |||||
send = true | |||||
box:send_stimulation(1, OVTK_StimulationId_ExperimentStart ,box:get_current_time() , 0) | |||||
end | |||||
end | |||||
-- releases cpu | |||||
box:sleep() | |||||
end | |||||
end |