MR volume triggers & PTB3

Notes about using PTB3 to register MRI volume triggers

Posted by dr. Etienne B. Roesch on July 13, 2020

You may find interesting:


New Matlab Script

Some Short Description of the Script

[Recipes] Registering fMRI volume triggers with PTB3

The problem

There are several ways one can go about reading triggers from a MRI scanner, with the PsychToolbox, (see also Peter Scarfe’s tutorials), and they can be slightly confusing. Below is a cookbook of a few ways that have worked for me.

The solution

Timing with MEX files

The first time you access any MEX function or M file, Matlab takes several hundred milliseconds to load it from disk. Allocating a variable takes time too. Usually you’ll want to omit those delays from your timing measurements by making sure all the functions you use are loaded and that all the variables you use are allocated, before you start timing. MEX files stay loaded until you flush the MEX files (e.g. by changing directory or calling CLEAR MEX). M files and variables stay in memory until you clear them.

This basically means that any function call will require matlab to load a series of files, and this operation takes time, before you’ll even start using the function. This may or may not be a problem. If you require high precision timing, you will need to assert timing for the important portions of your code (e.g. by using the GetSecs function). Situations that may require this extra step may be when you are interested in the time delta between triggers, want to compute some average over time (think EEG/ERP), etc. If you need to use a PTB function during a time-critical period, then you’ll have to preload the associated MEX file, by simply calling the function once, before the portion of code that is time-critical.

See:

with GetChar

References

% PTB Clear the workspace
close all;
clear all;
sca;

% Keeping recording of some parameters (expt is then dumbed into a file)
expt.inTheScanner                   = 1; % Are we using the scanner or just playing?
expt.numberOfVolumesToBeSkipped     = 3;
expt.mriTrigger                     = 't'; % As sent by the FORP device in CINN

% Wait for MR pulse
if expt.inTheScanner   % We are not trying out stuff on the laptop

    numberSkipped = 0;
    while numberSkipped ~= expt.numberOfVolumesToBeSkipped   % Skip 3 volumes
        char = 'z';
        while char ~= expt.mriTrigger
            [char,when]=GetChar;
        end
        numberSkipped = numberSkipped + 1;
    end
end

with KbCheck

References

% PTB Clear the workspace
close all;
clear all;
sca;

% Keeping recording of some parameters (expt is then dumbed into a file)
expt.inTheScanner                   = 1;
expt.numberOfVolumesToBeSkipped     = 3;
expt.mriTrigger                     = KbName('t');

startSecs = GetSecs

while KbCheck; end % Wait until all keys are released.

% Wait for MR pulse
if expt.inTheScanner   % We are not trying out stuff on the laptop

    numberSkipped = 0;
    while numberSkipped ~= expt.numberOfVolumesToBeSkipped

        [ keyIsDown, timeSecs, keyCode ] = KbCheck;
        if keyIsDown
            fprintf('Volume trigger at %d sec\n', timeSecs - startSecs);
            if keyCode(expt.mriTrigger)
                numberSkipped = numberSkipped + 1;
            end

            while KbCheck; end % Wait until all keys are released.

        end
    end
end

with KbQueues (a.k.a. KbQueueCreate–KbQueueStart–KbQueueWait)

References

% PTB Clear the workspace
close all;
clear all;
sca;

% Keeping recording of some parameters (expt is then dumbed into a file)
expt.inTheScanner                   = 1;
expt.numberOfVolumesToBeSkipped     = 3;
expt.mriTrigger                     = KbName('t');

% List of vendor IDs for valid FORP devices:
vendorIDs = [1240 6171];

psychtoolbox_forp_id=-1;

Devices = PsychHID('Devices');
% Loop through all KEYBOARD devices with the vendorID of FORP's vendor:
for i=1:size(Devices,2)
    if (strcmp(Devices(i).usageName,'Keyboard')|strcmp(Devices(i).usageName,'Keypad')) & ...
        ismember(Devices(i).vendorID, vendorIDs)

        psychtoolbox_forp_id=i;
        break;
    end
end

% Throws an exception that you should/could catch
if psychtoolbox_forp_id==-1;
    error('No FORP-Device detected on your system');
end

% Wait for MR pulse
if expt.inTheScanner   % We are not trying out stuff on the laptop

    keysOfInterest=zeros(1,256);
    keysOfInterest(expt.mriTrigger)=1;
    KbQueueCreate(psychtoolbox_forp_id, keysOfInterest);	% Create event queue on FORP
    KbQueueStart;   % Start listening

    numberSkipped = 0;
    while numberSkipped ~= expt.numberOfVolumesToBeSkipped
        secs = KbQueueWait; % Wait until the 't' key signal is sent
        numberSkipped = numberSkipped + 1;
    end
    KbQueueRelease;

end

with KbTriggerWait (without persistent KbQueues)

References

% PTB Clear the workspace
close all;
clear all;
sca;

% Keeping recording of some parameters (expt is then dumbed into a file)
expt.inTheScanner                   = 1;
expt.numberOfVolumesToBeSkipped     = 3;
expt.mriTrigger                     = KbName('t');

% List of vendor IDs for valid FORP devices:
vendorIDs = [1240 6171];

psychtoolbox_forp_id=-1;

Devices = PsychHID('Devices');
% Loop through all KEYBOARD devices with the vendorID of FORP's vendor:
for i=1:size(Devices,2)
    if (strcmp(Devices(i).usageName,'Keyboard')|strcmp(Devices(i).usageName,'Keypad')) & ...
        ismember(Devices(i).vendorID, vendorIDs)
        psychtoolbox_forp_id=i;
        break;
    end
end

if psychtoolbox_forp_id==-1;
    error('No FORP-Device detected on your system');
end

numberSkipped = 0;
while numberSkipped ~= expt.numberOfVolumesToBeSkipped
    % Note that KbTriggerWait will not respond to interrupts reliably--
    % if no trigger is generated, you may have to kill Matlab to break out
    % of the call to KbTriggerWait
    secs = KbTriggerWait(expt.mriTrigger, psychtoolbox_forp_id);
    % Trigger has been detected, proceed with other tasks
    numberSkipped = numberSkipped + 1;
end