BrainStreamExampleSpellerPart5

From TSG Doc
Jump to navigation Jump to search

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:

343x

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.

<- Go back to Part 4 - Continue with Part 6 ->