How to use LabJack with Psychtoolbox

From SCCN
Jump to: navigation, search

Contents

Background

If you want to perform event-related potential study, but your EEG recorder is not supported by Christian Kothe's lab streaming layer (LSL), the following information may be useful. In fact, this solution I introduce here is from my collaboration with Dr. Stefan Keslacy and Jose Benavidez who use Biopac MP150. Here, I show an example of Stroop task for Psychtoolbox designed and published by Dr. Peter Scarfe, which I modified to be used with LabJack U3. The modified code is published here with permission of Dr. Peter Scarfe. With the setup introduced here, anyone can start to record event-related potential.

Conflict of Interest

I declare no conflict of interest with the LabJack manufacture.

Why do we need LabJack? (07/26/2019 update)

Here, LabJack U3 LV sends 'trigger' signal that indicates the timing of stimulus presentation and response made. A short pulse is sent to the EEG recorder so that later an analyst can identify the latency of the event. Note that LabJack U3 HV is a different product, and its first 4 channels AIN0-3 can NOT be assigned to be digital output (thanks Oren at LabJack support for pointing me to this info). So the example code below cannot be used straightly, but channel assignment needs to be changed which should not be too difficult anyways.

Is LabJack's timing accurate?

According to this source, as long as it is connected to USB 2.0, the typical delay is 0.6 ms at the best channel, 1.0 ms at the second best, and mostly < 2.4 ms (if below USB 2.0, the delay is still = 4 ms mostly). Considering the fact the EEG data is typically analyzed with sampling rate at 250 Hz, < 4 ms delay means maximum two frames of delay. This is sufficiently accurate.

Materials

  • Computer, monitor, keyboard, mouse, speaker, etc.
  • EEG recorder that has external input port.
  • Matlab
  • Psychtoolbox-3
  • LabJack U3-HV/LV (either will do, $119/115 respectively.)

Example MATLAB Code (07/26/2019 update)

% Clear the workspace
close all;
clearvars;
sca;
 
 
 
%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Initialize LabJack %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%
% Make the UD .NET assembly visible in MATLAB.
ljasm = NET.addAssembly('LJUDDotNet');
ljudObj = LabJack.LabJackUD.LJUD;
 
% Read and display the UD version.
disp(['UD Driver Version = ' num2str(ljudObj.GetDriverVersion())])
 
% Open the first found LabJack U3.
[errorInOpening, ljHandle] = ljudObj.OpenLabJackS('LJ_dtU3', 'LJ_ctUSB', '0', true, 0);
 
% Reset pin assignments to the factory default condition.
errorInResetting = ljudObj.ePutS(ljHandle, 'LJ_ioPIN_CONFIGURATION_RESET', 0, 0, 0);
 
 
 
% Setup PTB with some default values
PsychDefaultSetup(2);
 
% Seed the random number generator. Here we use the an older way to be
% compatible with older systems. Newer syntax would be rng('shuffle'). Look
% at the help function of rand "help rand" for more information
rand('seed', sum(100 * clock));
 
% Set the screen number to the external secondary monitor if there is one
% connected
screenNumber = max(Screen('Screens'));
 
% Define black, white and grey
white = WhiteIndex(screenNumber);
grey = white / 2;
black = BlackIndex(screenNumber);
 
% Open the screen
[window, windowRect] = PsychImaging('OpenWindow', screenNumber, grey, [], 32, 2);
 
% Flip to clear
Screen('Flip', window);
 
% Query the frame duration
ifi = Screen('GetFlipInterval', window);
 
% Set the text size
Screen('TextSize', window, 60);
 
% Query the maximum priority level
topPriorityLevel = MaxPriority(window);
 
% Get the centre coordinate of the window
[xCenter, yCenter] = RectCenter(windowRect);
 
% Set the blend funciton for the screen
Screen('BlendFunction', window, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA');
 
 
%----------------------------------------------------------------------
%                       Timing Information
%----------------------------------------------------------------------
 
% Interstimulus interval time in seconds and frames
isiTimeSecs = 1;
isiTimeFrames = round(isiTimeSecs / ifi);
 
% Numer of frames to wait before re-drawing
waitframes = 1;
 
 
%----------------------------------------------------------------------
%                       Keyboard information
%----------------------------------------------------------------------
 
% Define the keyboard keys that are listened for. We will be using the left
% and right arrow keys as response keys for the task and the escape key as
% a exit/reset key
escapeKey = KbName('ESCAPE');
leftKey = KbName('LeftArrow');
rightKey = KbName('RightArrow');
downKey = KbName('DownArrow');
 
 
%----------------------------------------------------------------------
%                     Colors in words and RGB
%----------------------------------------------------------------------
 
% We are going to use three colors for this demo. Red, Green and blue.
wordList = {'Red', 'Green', 'Blue'};
rgbColors = [1 0 0; 0 1 0; 0 0 1];
 
% Make the matrix which will determine our condition combinations
condMatrixBase = [sort(repmat([1 2 3], 1, 3)); repmat([1 2 3], 1, 3)];
 
% Number of trials per condition. We set this to one for this demo, to give
% us a total of 20 trials.
trialsPerCondition = 1;
 
% Duplicate the condition matrix to get the full number of trials
condMatrix = repmat(condMatrixBase, 1, trialsPerCondition);
 
% Get the size of the matrix
[~, numTrials] = size(condMatrix);
 
% Randomise the conditions
shuffler = Shuffle(1:numTrials);
condMatrixShuffled = condMatrix(:, shuffler);
 
 
%----------------------------------------------------------------------
%                     Make a response matrix
%----------------------------------------------------------------------
 
% respMat is a four row matrix that consists of:
% 1) the word (1-REG, 2-GREEN, 3-BLUE)
% 2) color of the word it is written in (1-REG, 2-GREEN, 3-BLUE)
% 3) the key they respond with (1-REG, 2-GREEN, 3-BLUE)
% 4) the time (in Sec) they took to make there response.
respMat = nan(4, numTrials);
 
%----------------------------------------------------------------------
%                       Experimental loop
%----------------------------------------------------------------------
 
% Animation loop: we loop for the total number of trials
for trial = 1:numTrials
 
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Set the both trigger signals to LOW. %%%
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 0, 0, 0, 0); % Set FIO_0 to output-high.
    errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 1, 0, 0, 0); % Set FIO_1 to output-high.
    errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 2, 0, 0, 0); % Set FIO_2 to output-high.
    errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 3, 0, 0, 0); % Set FIO_3 to output-high.
    errorInExecuting  = ljudObj.GoOne(ljHandle);
 
    % Word and color number
    wordNum = condMatrixShuffled(1, trial);
    colorNum = condMatrixShuffled(2, trial);
 
    % The color word and the color it is drawn in
    theWord = wordList(wordNum);
    theColor = rgbColors(colorNum, :);
 
    % Cue to determine whether a response has been made
    respToBeMade = true;
 
    % If this is the first trial we present a start screen and wait for a
    % key-press
    if trial == 1
        DrawFormattedText(window, 'Name the color \n\n Press Any Key To Begin',...
            'center', 'center', black);
        Screen('Flip', window);
        KbStrokeWait;
    end
 
    % Flip again to sync us to the vertical retrace at the same time as
    % drawing our fixation point
    Screen('DrawDots', window, [xCenter; yCenter], 10, black, [], 2);
    vbl = Screen('Flip', window);
 
    % Now we present the isi interval with fixation point minus one frame
    % because we presented the fixation point once already when getting a
    % time stamp
    for frame = 1:isiTimeFrames - 1
 
        % Draw the fixation point
        Screen('DrawDots', window, [xCenter; yCenter], 10, black, [], 2);
 
        % Flip to the screen
        vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);
    end
 
    % Now present the word in continuous loops until the person presses a
    % key to respond. We take a time stamp before and after to calculate
    % our reaction time. We could do this directly with the vbl time stamps,
    % but for the purposes of this introductory demo we will use GetSecs.
    %
    % The person should be asked to respond to either the written word or
    % the color the word is written in. They make thier response with the
    % three arrow key. They should press "Left" for "Red", "Down" for
    % "Green" and "Right" for "Blue".
 
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Send stimulus-onset trigger to HIGH. %%%
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 0, 1, 0, 0); % Set FIO_0 to output-high.
    errorInExecuting  = ljudObj.GoOne(ljHandle);
 
    tStart = GetSecs;
    while respToBeMade == true
 
        % Draw the word
        DrawFormattedText(window, char(theWord), 'center', 'center', theColor);
 
        % Check the keyboard. The person should press the
        [keyIsDown,secs, keyCode] = KbCheck;
        if keyCode(escapeKey)
            ShowCursor;
            sca;
            return
        elseif keyCode(leftKey)
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %%% Response RED: Send response-onset trigger to HIGH. %%%
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 1, 1, 0, 0); % Set FIO_1 to output-high.
            errorInExecuting  = ljudObj.GoOne(ljHandle);
            response = 1;
            respToBeMade = false;
        elseif keyCode(downKey)
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %%% Response GREEN: Send response-onset trigger to HIGH. %%%
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 2, 1, 0, 0); % Set FIO_2 to output-high.
            errorInExecuting  = ljudObj.GoOne(ljHandle);
            response = 2;
            respToBeMade = false;
        elseif keyCode(rightKey)
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %%% Response BLUE: Send response-onset trigger to HIGH. %%%
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 3, 1, 0, 0); % Set FIO_3 to output-high.
            errorInExecuting  = ljudObj.GoOne(ljHandle);
            response = 3;
            respToBeMade = false;
        end
 
        % Flip to the screen
        vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);
    end
    tEnd = GetSecs;
    rt = tEnd - tStart;
 
    % Record the trial data into out data matrix
    respMat(1, trial) = wordNum;
    respMat(2, trial) = colorNum;
    respMat(3, trial) = response;
    respMat(4, trial) = rt;
end
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Set the both trigger signals to LOW. %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 0, 0, 0, 0); % Set FIO_0 to output-high.
errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 1, 0, 0, 0); % Set FIO_1 to output-high.
errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 2, 0, 0, 0); % Set FIO_2 to output-high.
errorInRequesting = ljudObj.AddRequestS(ljHandle, 'LJ_ioPUT_DIGITAL_BIT', 3, 0, 0, 0); % Set FIO_3 to output-high.
errorInExecuting  = ljudObj.GoOne(ljHandle);
 
% End of experiment screen. We clear the screen once they have made their
% response
DrawFormattedText(window, 'Experiment Finished \n\n Press Any Key To Exit',...
    'center', 'center', black);
Screen('Flip', window);
KbStrokeWait;
sca;
 
date = clock;
 
% Saves the behavioral data at the current directory.
% The output size is 4 x numTrials,
% The four rows are: wordNum, colorNum, response, raction time.
save(sprintf('StroopData_%.0f_%.0f_%.0f_%.0f_%.0f', date(1), date(2),date(3),date(4),date(5)), 'respMat'); % Saves the behavior log as 'StroopData_Year-Month-Date-Hour-Minute.mat'

Reference

Dr. Peter Scarfe's website.


Author: Makoto Miyakoshi, Swartz Center for Computational Neuroscience, Institute for Neural Computation, UC San Diego.