Supplementary material
Recommended hardware, software, and settings
Recommended hardware: ASF runs on the shoulders of the Psychophysics toolbox. Check http://psychtoolbox.org/wikka.php?wakka=SystemRequirements for recommendations.
Software: ASF requires MATLABand the Psychophysics Toolbox (http://psychtoolbox.org) to be installed. Currently, the Psychophysics Toolbox needs the 32-bit version of MATLAB to run. Please refer to the PTBWebsite for updates on this.
Settings: In order to achieve optimal accuracy of timing, disabling all network adapters during an experiment is recommended; also refer to PsychToolbox’s suggestions (help SyncTrouble).
Listings for example project
Stimulus definition file example.std
Listing 1:
S01_empty.bmp
S02_fix.bmp
S03_prime_left.bmp
S04_prime_right.bmp
S05_mask_left.bmp
S06_mask_right.bmp
Trial definition file example.trd
Listing 2:
2 2
1 0 2 30 3 1 2 2 5 6 1 90 4 5 1
2 0 2 30 4 1 2 2 5 6 1 90 4 5 1
1 0 2 30 4 1 2 2 6 6 1 90 4 5 3
2 0 2 30 3 1 2 2 6 6 1 90 4 53
3 0 2 30 3 1 2 5 5 6 1 90 4 51
4 0 2 30 4 1 2 5 5 6 1 90 4 51
3 0 2 30 4 1 2 5 6 6 1 90 4 53
Configurable MATLAB script, which generates a trd-file
Listing 3:
function makeExampleTrd(strProjectName)
%function makeExampleTrd(strProjectName)
%%Creates a .trd file for a masked priming experiment in ASF
%
%Example call to create 'exampleScripted.trd':
%makeExampleTrd('exampleScripted')
%
%%ASF (C) Jens Schwarzbach
%------
%DESIGN ASPECTS
%------
Design.soaLevels = [3, 6];
Design.nSoaLevels = length(Design.soaLevels);
Design.primeDirectionLevels = [0, 1]; %LEFT, RIGHT
Design.nPrimeDirectionLevels = length(Design.primeDirectionLevels);
Design.maskDirectionLevels = [0, 1]; %LEFT, RIGHT
Design.nMaskDirectionLevels = length(Design.maskDirectionLevels);
Design.primeDuration = 1; %PRIMES WILL BE SHOWN FOR ONE FRAME
Design.maskDuration = 6; %MASK WILL BE SHOWN FOR SIX FRAMES
Design.responseWindow = 90; %PARTICIPANTS HAVE NINETY FRAMES TO RESPOND
Design.factorNames = {'Congruence', 'SOA'};
Design.nFactors = length(Design.factorNames);
Design.factorLevels = [2, Design.nSoaLevels];
Design.levelNames{1, 1} = 'congruent';
Design.levelNames{1, 2} = 'incongruent';
for i = 1:Design.factorLevels(2)
Design.levelNames{2, i} = sprintf('SOA%d', Design.soaLevels(i));
end
Design.nReplicationsPerCondition = 10;
%SIMPLE VARIATION OF TRIAL ONSET ASYNCHRONIES FOR fMRI
%WILL ONLY BECOME EFFECTIVE WHEN CALLING ASF WITH
%Cfg.useTrialOnsetTimes = 1
Design.jitteredTrialOnsetAsynchronies = [2:0.5:7]; %MIN, STEP, MAX
%PICTURE CODES MUST REFLECT ORDER IN STD FILE
PictureCodes.empty = 1;
PictureCodes.fix = 2;
PictureCodes.primeLeft = 3;
PictureCodes.primeRight = 4;
PictureCodes.maskLeft = 5;
PictureCodes.maskRight = 6;
%------
%HARDWARE ASPECTS
%------
ResponseKey.left = 1; %LEFT MOUSE BUTTON
ResponseKey.right = 3; %RIGHT MOUSE BUTTON
%------
%LOOP THROUGH ENTIRE DESIGN TO CREATE A LIST CONTAINING ALL TRIALS
%------
%SAME FOR ALL TRIALS
ThisTrial.tOnset = 0;%UNUSED PARAMETER
ThisTrial.primeDuration = Design.primeDuration;
ThisTrial.maskDuration = Design.maskDuration;
ThisTrial.responseWindow = Design.responseWindow;
ThisTrial.startRTmeasurementOnPage = 3;
ThisTrial.endRTmeasurementOnPage = 4;
ThisTrial.empty = PictureCodes.empty;
ThisTrial.fix = PictureCodes.fix;
counter = 0;
for iPrimeDirection = 1:Design.nPrimeDirectionLevels
for iMaskDirection = 1:Design.nMaskDirectionLevels
for iSoa = 1:Design.nSoaLevels
for iReplication = 1:Design.nReplicationsPerCondition
%FILL STRUCTURE ThisTrial WITH ALL PARAMETERS THAT
%ARE RELEVANT FOR THIS TRIAL
%VARIABLE PRESTIMULUS PERIOD BETWEEN 15 AND 45 FRAMES
ThisTrial.preStimPeriod = round(rand*30)+15;
%PRIME
if iPrimeDirection == 1
ThisTrial.prime = PictureCodes.primeLeft;
else
ThisTrial.prime = PictureCodes.primeRight;
end
%MASK
if iMaskDirection == 1
ThisTrial.mask = PictureCodes.maskLeft;
ThisTrial.correctResponse = ResponseKey.left;
else
ThisTrial.mask = PictureCodes.maskRight;
ThisTrial.correctResponse = ResponseKey.right;
end
%SOA-prime-mask
ThisTrial.Soa = Design.soaLevels( iSoa );
%CONGRUENCE (CONGRUENT = 0, INCONGRUENT = 1)
if iPrimeDirection == iMaskDirection
ThisTrial.congruence = 0; %congruent
else
ThisTrial.congruence = 1; %incongruent
end
%WE CODE CONGRUENCE AND SOA-LEVEL
%MAKE SURE THIS HAS THE SAME ORRDER AS FACTORIAL INFO
ThisTrial.code = ASF_encode(...
[ThisTrial.congruence, iSoa-1],...
[2, Design.nSoaLevels] );
%CREATE A TRIALDEFINITION FOR THIS TRIAL
counter = counter + 1;
TrialVec( counter ) = ThisTrial;
end
end
end
end
Design.nTrials = counter;
%------
%RANDOMIZE TRIALS
%------
TrialVec = TrialVec(randperm(Design.nTrials));
%------
%JITTER TRIAL ONSET TIMES (for fMRI DEMO)
%------
TrialVec(1).tOnset = 12; %LET SCANNER ACQUIRE A COUPLE OF VOLUMES BEFORE
%STARTING
jitterVec = Design.jitteredTrialOnsetAsynchronies;
for iTrial = 2:Design.nTrials
%PERMUTE
jitterVec = jitterVec( randperm( length( jitterVec ) ) );
%PICK FIRST ELEMENT OF PERMUTED JITTERVEC AS THIS TRIAL'S JITTER
jitter = jitterVec( 1 );
%NEW ONSET TIME IS ONSET TIME OF PREVIOUS TRIAL PLUS JITTER
TrialVec(iTrial).tOnset = TrialVec( iTrial-1 ).tOnset + jitter;
end
%YOU MAY WANT TO ADD AN EMPTY TRIAL AT THE VERY END OF YOUR EXPERIMENT
%IN ORDER TO LET THE BOLD RESPONSE RELAX. NOT COVERED HERE.
%------
%WRITE TRD FILE
%------
writeTrdFile( strProjectName, Design, TrialVec)
function writeTrdFile( strProjectName, Design, TrialVec)
%function writeTrdFile( strProjectName, Design, TrialVec)
%PUTS TOGETHER ALL INFORMATION IN TRIALDEFINITION TO GENERATE A TRD FILE
fName = [strProjectName, '.trd'];
fid = fopen( fName, 'w');
if fid
fprintf( 1, 'WRITING %s ...', fName);
else
error( 'Error: %s cannot be created.', fName);
end
%WRITE DESIGN INFO
fprintf(fid, '%3d ', Design.factorLevels);
fprintf(fid, '%s ', Design.factorNames{:});
for i = 1:Design.nFactors
for j = 1:Design.factorLevels(i)
fprintf(fid, '%s ', Design.levelNames{i, j});
end
end
%WRITE TRIAL INFO, ONE LINE PER TRIAL
for i = 1:length( TrialVec )
ThisTrial = TrialVec( i ); %PICK CURRENT TRIAL
fprintf( fid, '\n' ); %NEW LINE
fprintf( fid, '%3d\t', ThisTrial.code );
fprintf( fid, '%8.3f\t', ThisTrial.tOnset );
fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, ThisTrial.preStimPeriod );
fprintf( fid, '%3d %3d\t\t', ThisTrial.prime, ThisTrial.primeDuration);
interStimulusInterval = ThisTrial.Soa - ThisTrial.primeDuration;
fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, interStimulusInterval );
fprintf( fid, '%3d %3d\t\t', ThisTrial.mask, ThisTrial.maskDuration );
fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, ThisTrial.responseWindow );
fprintf( fid, '%3d\t', ThisTrial.startRTmeasurementOnPage );
fprintf( fid, '%3d\t', ThisTrial.endRTmeasurementOnPage );
fprintf( fid, '%3d', ThisTrial.correctResponse );
end
fclose(fid);
Basic analysis script
Listing4:
function analyzeExample(projectName)
%function analyzeExample(projectName)
%
%%EXAMPLE CALL
%analyzeExample('example')
%
%%ASF (C) Jens Schwarzbach
%LOAD STORED LOG FILE
load( projectName )
%res contains the following information extracted from the log
%COL 1: CODE
%COL 2: RT
%COL 3: KEY
%COL 4: CORRECTRESPONSE
res = ASF_readExpInfo( ExpInfo );
dat.code = ASF_decode(res(:, 1), [2, 2]);
congruenceVec = ExpInfo.factorinfo.levelNames(1,:);
soaVec = ExpInfo.factorinfo.levelNames(2,:);
for iCongruence = 1:2
for iSoa = 1:2
%SELECT TRIALS FOR THIS FACTORIAL COMBINATION, IN WHICH
%THE PARTICIPANT RESPONDED CORRECTLY
casesCorrect = find(...
(dat.code(:, 1)+1 == iCongruence) &...
(dat.code(:, 2)+1 == iSoa) &...
(res(:, 3) == res(:, 4)) );
%CALCULATE MEAN REACTION TIME AND STANDARD ERROR OF THE MEAN
%FOR THIS FACTORIAL COMBINATION
meanRT( iCongruence, iSoa ) = mean( res(casesCorrect, 2) );
seRT( iCongruence, iSoa ) =...
std( res(casesCorrect, 2) )/sqrt(length(casesCorrect));
end
end
%CREATE A LINEPLOT WITH ERRORBARS (SEM)
figure
ebh = errorbar(meanRT', seRT');
set(ebh, 'LineWidth', 2)
set(gca, 'xtick', [1, 2], 'xlim', [0.5, 2.5], 'xtickLabel', soaVec)
legend(congruenceVec, 'Location', 'NorthWest')
ylabel('RT [ms]')
xlabel('SOA [frames]')
Example session
%STEP 1: prepare experiment
makeExampleTrd('exampleScripted')
%STEP 2: run experiment
%WITHOUT JITTER
ExpInfo = ASF('example.std', 'exampleScripted.trd', 'example', [])
%WITH JITTER
Cfg.useTrialOnsetTimes = 1;
ExpInfo = ASF('example.std', 'exampleScripted.trd', 'example', Cfg)
%STEP 3: analyze experiment
analyzeExample('example')
Supplementary Fig.1 Output of the analysis script analyzeExample. Reaction times are shorter on congruent than on incongruent trials. The effect is stronger for longer SOAs than for shorter SOAs, indicating that primes produce a covert preparation of motor responses, as predicted by an accumulator model of response priming (Vorberg, Mattler, Heinecke, Schmidt, & Schwarzbach, 2003)
Inside the black box: The logic of the runtime module
General structure of ASF’s runtime engine
ASF’s runtime engine consists of three major parts:(1) calls to initialization routines based on functions of the Psychophysics Toolbox to create an OpenGLwindow and custom routines that initialize hardware, such as response devices and routines that read in the stimulus and trialdefinition files;(2) a loop that calls, for each trial of the experiment, the function ASF_ShowTrial() or a user-written trial presentation code or plugin;(3) shutdown routines that write data to a file and that close files and hardwaredevices.
ASF_ShowTrial
A trial is defined by a trial code, a trial onset time (in seconds with respect to the start of the experiment), a vector of stimulus numbers, a vector of respective stimulus durations (in refresh frames), a page number at which response collection shall start, and a code for the correct response.
This function splits a trial into five phases.
· Phase 1: Wait for the right time to start with the trial presentation. This may be the defined trial onset time (immediately, if the trial onset time is 0) or a specific event—for example, a synchronization pulse from the MR scanner (this depends on the Cfg settings).
· Phase 2: Loop through page presentations without response collection.
· Phase 3: Loop through page presentations while checking for a response
· Phase 4: After a response has been given, loop through any remaining page presentations without response collection.
· Phase 5: After all pages have been presented, the program can give a feedback for this trial (depending on Cfg settings).
This separation has been chosen in order to maximize timing accuracy, while keeping flexibility for the programmer. For example, page presentation with and without response collection are conceptually separated because the former can be hooked to the highly accurate refresh signal of a graphics card, while the latter requires polling of the status of response devices, which can introduce unwanted delays. While this separation of phases is suitable for most experiments, the whole sequence of operations and inner structure of this function can be changed to meet the experimenters’ needs and can be saved as a user-supplied trial function or plugin. The listing below is a copy of ASF’s internal function ASF_showTrial() and can be used as a template for creating new plugins.
Listing 5:
function TrialInfo = ASF_showTrialSample(atrial, windowPtr, Stimuli, Cfg)
%function TrialInfo = ASF_showTrialSample(atrial, windowPtr, Stimuli, Cfg)
%
%%SAVE A COPY OF THIS FILE UNDER A DIFFERENT NAME E.G. myPlugin.m
%%TO USE IT AS A STARTING POINT FOR YOUR PLUGIN-DEVELOPMENTS
%% written by: jens schwarzbach
% VBLTimestamp system time (in seconds) when the actual flip has happened
% StimulusOnsetTime An estimate of Stimulus-onset time
% FlipTimestamp is a timestamp taken at the end of Flip's execution
VBLTimestamp = 0; StimulusOnsetTime = 0; FlipTimestamp = 0; Missed = 0;
Bpos = 0;
StartRTMeasurement = 0; EndRTMeasurement = 0;
timing = [0, VBLTimestamp, StimulusOnsetTime, FlipTimestamp, Missed, Bpos];
nPages = length(atrial.pageNumber);
timing(nPages, end) = 0;
this_response = [];
%ON PAGES WITH WITH RESPONSE COLLECTION MAKE SURE THE CODE RETURNS IN TIME
%BEFORE THE NEXT VERTICAL BLANK. FOR EXAMPLE IF THE RESPONSE WINDOW IS 1000
%ms TOLERANCE MAKES THE RESPONSE COLLECTION CODE RETURN AFTER 1000ms-0.3
%FRAMES, I.E. AFTER 995 ms AT 60Hz
toleranceSec = Cfg.Screen.monitorFlipInterval*0.3;
%HOWEVER, THIS MUST NOT BE LONGER THAN ONE FRAME
%DURATION. EXPERIMENTING WITH ONE QUARTER OF A FRAME
responseGiven = 0;
this_response.key = [];
this_response.RT = [];
%------
%TRIAL PRESENTATION HAS SEVERAL PHASES
% 1) WAIT FOR THE RIGHT TIME TO START TRIAL PRESENTATION. THIS MAY BE
% IMMEDIATELY OR USER DEFINED (E.G. IN fMRI EXPERIMENTS)
%
% 2) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION
%
% 3) LOOP THROUGH PAGE PRESENTATIONS WHILE CHECKING FOR USER INPUT/RESPONSES
%
% 4) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION
% (AFTER RESPONSE HAS BEEN GIVEN)
%
% 5) FEEDBACK
%------
%IF YOU WANT TO DO ANY OFFLINE STIMULUS RENDERING (I.E. BEFORE THE TRIAL
%STARTS), PUT THAT CODE HERE
%LOG DATE AND TIME OF TRIAL
strDate = datestr(now); %store when trial was presented
%------
% PHASE 1) WAIT FOR THE RIGHT TIME TO START TRIAL PRESENTATION. THIS MAY BE
% IMMEDIATELY OR USER DEFINED (E.G. IN fMRI EXPERIMENTS)
%------
%IF EXTERNAL TIMING REQUESTED (e.g. fMRI JITTERING)
if Cfg.useTrialOnsetTimes
wakeupTime = WaitSecs('UntilTime', Cfg.experimentStart + atrial.tOnset);
else
wakeupTime = GetSecs;
end
%LOG TIME OF TRIAL ONSET WITH RESPECT TO START OF THE EXPERIMENT
%USEFUL FOR DATA ANALYSIS IN fMRI
tStart = wakeupTime - Cfg.experimentStart;
if Cfg.Eyetracking.doDriftCorrection
EyelinkDoDriftCorrect(Cfg.el);
end
%------
%END OF PHASE 1
%------
%MESSAGE TO EYELINK
Cfg = ASF_sendMessageToEyelink(Cfg, 'TRIALSTART');
%------
% PHASE 2) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION
%------
%CYCLE THROUGH PAGES FOR THIS TRIAL
atrial.nPages = length(atrial.pageNumber);
for i = 1:atrial.startRTonPage-1
if (i > atrial.nPages)
break;
else
%PUT THE APPROPRIATE TEXTURE ON THE BACK BUFFER
Screen('DrawTexture', windowPtr, Stimuli.tex(atrial.pageNumber(i)));
%PRESERVE BACK BUFFER IF THIS TEXTURE IS TO BE SHOWN
%AGAIN AT THE NEXT FLIP
bPreserveBackBuffer = atrial.pageDuration(i) > 1;
%FLIP THE CONTENT OF THIS PAGE TO THE DISPLAY AND PRESERVE IT IN
%THE BACKBUFFER IN CASE THE SAME IMAGE IS TO BE FLIPPED AGAIN TO
%SCREEN
[VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Bpos] =...
ASF_xFlip(windowPtr, Stimuli.tex(atrial.pageNumber(i)),...
Cfg, bPreserveBackBuffer);
%SET TRIGGER (PARALLEL PORT AND EYELINK)
ASF_setTrigger(Cfg, atrial.pageNumber(i));
%LOG WHEN THIS PAGE APPEARED
timing(i, 1:6) = [atrial.pageDuration(i), VBLTimestamp,...
StimulusOnsetTime FlipTimestamp Missed Bpos];
%WAIT OUT STIMULUS DURATION IN FRAMES. WE USE PAGE FLIPPING
%RATHER THAN A TIMER WHENEVER POSSIBLE BECAUSE GRAPHICS BOARDS
%PROVIDE EXCELLENT TIMING; THIS IS THE REASON WHY WE MAY WANT TO
%KEEP A STIMULUS IN THE BACKBUFFER (NONDESTRUCTIVE PAGE FLIPPING)
%NOT ALL GRAPHICS CARDS CAN DO THIS. FOR CARDS WITHOUT AUXILIARY
%BACKBUFFERS WE COPY THE TEXTURE EXPLICITLY ON THE BACKBUFFER
%AFTER IT HAS BEEN DESTROYED BY FLIPPING
nFlips = atrial.pageDuration(i) - 1; %WE ALREADY FLIPPED ONCE
for FlipNumber = 1:nFlips
%PRESERVE BACK BUFFER IF THIS TEXTURE IS TO BE SHOWN
%AGAIN AT THE NEXT FLIP
bPreserveBackBuffer = FlipNumber < nFlips;
%FLIP THE CONTENT OF THIS PAGE TO THE DISPLAY AND PRESERVE
%IT IN THE BACKBUFFER IN CASE THE SAME IMAGE IS TO BE
%FLIPPED AGAIN TO THE SCREEN
ASF_xFlip(windowPtr, Stimuli.tex(atrial.pageNumber(i)),...
Cfg, bPreserveBackBuffer);
end
end
end
%------
%END OF PHASE 2
%------
%------
% PHASE 3) LOOP THROUGH PAGE PRESENTATIONS WHILE CHECKING FOR USER
% INPUT/RESPONSES
%------
%SPECIAL TREATMENT FOR THE DISPLAY PAGES ON WHICH WE ALLOW REACTIONS