BrainStreamExampleSpellerPart5
Example 2: Visual Speller
Part 5
<- Go back to Part 4 - Continue with Part 6 ->
In the final part of this visual speller example, we will show how to use a looptick function for stimulus presentation. The advantage of a looptick function over a normal loop function is that BrainStream can modify the loop while it is running. For example, stimulus presentation could be dynamically adapted or stopped early, based on BrainStream's estimates about which stimulus is the target.
Structure of the looptick function
The function for stimulus presentation, runSequence, is now implemented as a looptick function. Below is a schematic overview of this function:
Figure 1: Flowchart of the runSequence looptick function. Actions indicated in orange are executed at the BrainStream Matlab session, whereas actions indicated in blue are executed at the client.
As can be seen from the flowchart, the runSequence function is executed at the client. However, functions from the BrainStream Matlab session can send information to the client to influence the behavior of the loop.
Looptick function
This is the runSequence looptick function (together with explanations in italics):
function [ev, stoploop, waittime] = runSequence(ev, tick_count, abort_loop) persistent uservars; waittime = 0; % init isi = 0.2; % inter-stimulus-interval if tick_count == 1 % first tick init_loop; end toc;
The first time the runSequence looptick function is called (e.g. if tick_count is 1), the init_loop function (see [.DocsSectionsExampleSpellerPart5#InitLoop below]) will be executed. This function initializes some variables and starts a stopwatch.
if abort_loop disp('New processing pipeline received from [[BrainStream|Brain Stream]]'); exit_loop; return end
If an error occurs on the BrainStream side or a new job (processing pipeline or event) was sent, BrainStream will set abort_loop to 1 during the next call to the looptick function to finish the current loop. You can use this feature to define some steps to finalize your loop before BrainStream quits. In this case, a message will be displayed on the command screen and the exit_loop function (see below) will be executed. This function sends a hardware marker to the data source to indicate the end of processing.
try [newvars, success] = bs_recv_user_brainstream(ev, 'stim',0,0); % check for new information from [[BrainStream|Brain Stream]] if ~isempty(newvars) uservars = [uservars, newvars]; end if ~isempty(uservars) && isempty(uservars{1}) disp('Exiting loop by user request') exit_loop; return; end if success >= 0 || ~isempty(uservars) % get feedback info for n = 1 : numel(uservars) disp(uservars{n}); ev.flashStyle = uservars{n}; end uservars = {}; end
The function now checks if there is new information sent to the client from the BrainStream side. If there are new variables available, they will be added to the uservars variable. If the variable that was sent is empty, this means that the loop should be ended. Therefore, the exit_loop function will be executed.
if ~ev.flashed ev = highlightGrid(ev); % randomly highlight row or column and send stim_## marker else ev = showGrid(ev); % dim higlighted row or column ev = bs_send_hardware_marker(ev,'highlightOff','eeg'); % also mark this moment end ev.flashed = ~ev.flashed;
Every time the looptick function is called, rows and columns on the screen should be highlighted or dimmed alternately. The field of the event structure called 'flashed' keeps track of whether the last stimulus was a flash or the dimmed grid. This is possible because all calls to the looptick function belong to the same event. If the last stimulus was a flash, the grid will be dimmed, and if the last stimulus was the dimmed grid, a row or column will be highlighted. Afterwards, the logical value of the ev.flashed variable is inverted.
if tick_count >= 2*bs_get_blockvalue(ev,'Experiment','nRep',10) % check if at end of sequence (NB: tick_count adds 2 per stimulus cycle) exit_loop(); else stoploop = 0; end
The function now checks if the sequence is finished. If all stimuli have been presented, the [.DocsSectionsExampleSpellerPart5#ExitLoop exit_loop] function will be executed. Otherwise, the stoploop variable will remain 0, which ensures the looptick function is called again.
waittime = max(0, tick_count*(isi/2)-toc); % determine time to wait before calling tick function again
A change in the grid should occur every 100 ms (isi/2). In order to achieve this, we can set the waittime variable. When the loop is initialized, the tic command starts the stopwatch. Each time the loop is called again, the toc command measures the time between the first looptick call and the present time. To determine the value of waittime, we take the time at which the stimulus should occur (tick_count*(isi/2)) and subtract from it the time that has passed already (toc). If the time at which the stimulus was supposed to occur has already passed, waittime is set to 0.
catch err = lasterror; exit_loop; rethrow(err); end
If an error occurs during the execution of the looptick function, the [.DocsSectionsExampleSpellerPart5#ExitLoop exit_loop] function will be executed.
This is the init_loop function:
function init_loop [uservars] = bs_recv_user_brainstream(ev, 'stim'); % read user socket, just to flush uservars = {}; ev.flashed = 0; tic; end
This is the exit_loop function:
function exit_loop ev = bs_send_hardware_marker(ev,'endSequence','eeg'); stoploop = 1; end end
Other changes to the speller
If you test this example, you will notice that the speller grid has changed from the previous examples. It now contains 36 elements: all letters of the alphabet and the numbers 0-9. This change has been accomplished by changing the key 'symbols' under topic 'Experiment' in the speller_common blockfile. By changing the blockfile, you can easily change the contents of the speller matrix.
Evaluation
You can find a log file of this experiment here. From this log file, we can determine the timing accuracy of the stimuli. Below are log file entries for the first and last five stimuli of one sequence:
10Jan12|11:09:46.701 INFO > '''***''' ( stim_11) '''*************************************************************''' 10Jan12|11:09:46.702 INFO > 225.95 (eeg:#40127.00) 225.95 226.05 act: modvar (t=EVENT) stim_11 10Jan12|11:09:46.901 INFO > '''***''' ( stim_4) '''*************************************************************''' 10Jan12|11:09:46.901 INFO > 226.15 (eeg:#40178.00) 226.15 226.25 act: modvar (t=EVENT) stim_4 10Jan12|11:09:47.101 INFO > '''***''' ( stim_12) '''*************************************************************''' 10Jan12|11:09:47.101 INFO > 226.34 (eeg:#40229.00) 226.34 226.45 act: modvar (t=EVENT) stim_1 10Jan12|11:09:47.307 INFO > '''***''' ( stim_5) '''*************************************************************''' 10Jan12|11:09:47.308 INFO > 226.55 (eeg:#40281.00) 226.55 226.65 act: modvar (t=EVENT) stim_5 10Jan12|11:09:47.512 INFO > '''***''' ( stim_10) '''*************************************************************''' 10Jan12|11:09:47.512 INFO > 226.75 (eeg:#40332.00) 226.75 226.86 act: modvar (t=EVENT) stim_10 . . . 10Jan12|11:10:05.706 INFO > '''***''' ( stim_2) '''*************************************************************''' 10Jan12|11:10:05.706 INFO > 244.95 (eeg:#44991.00) 244.95 245.05 act: modvar (t=EVENT) stim_2 10Jan12|11:10:05.908 INFO > '''***''' ( stim_9) '''*************************************************************''' 10Jan12|11:10:05.908 INFO > 245.15 (eeg:#45042.00) 245.15 245.25 act: modvar (t=EVENT) stim_9 10Jan12|11:10:06.107 INFO > '''***''' ( stim_4) '''*************************************************************''' 10Jan12|11:10:06.108 INFO > 245.34 (eeg:#45093.00) 245.34 245.45 act: modvar (t=EVENT) stim_4 10Jan12|11:10:06.314 INFO > '''***''' ( stim_7) '''*************************************************************''' 10Jan12|11:10:06.315 INFO > 245.55 (eeg:#45145.00) 245.55 245.66 act: modvar (t=EVENT) stim_7 10Jan12|11:10:06.507 INFO > '''***''' ( stim_6) '''*************************************************************''' 10Jan12|11:10:06.508 INFO > 245.75 (eeg:#45196.00) 245.75 245.85 act: modvar (t=EVENT) stim_6
The stim_# markers are sent each time a row or column of the matrix is flashed, so every 200 ms one of these markers should arrive. In the log file, the first number in each line (after the date and time stamp) indicates the time at which the marker arrived. This indeed happens every 0.2 seconds. The time between two successive stimuli also remains stable over the sequence.
Nevertheless, the timing accuracy is not perfect, sometimes jitters occur of approximately 10 ms. If you need even more accurate timing, you should use BrainStream in combination with a toolbox that is specifically designed for stimulus presentation, such as Psychtoolbox.