@@ -0,0 +1,200 @@ | |||
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 | |||
@@ -0,0 +1,61 @@ | |||
-- 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 |
@@ -0,0 +1,61 @@ | |||
-- 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 |
@@ -0,0 +1,21 @@ | |||
-- 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 |
@@ -0,0 +1,138 @@ | |||
-- 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 |