Commit ef8fd55f authored by Christian Poth's avatar Christian Poth
Browse files

Add exp.

parent 13876b5a
% alt_xpc_221024.m
%
% Christian Poth, 2022, c.poth@uni-bielefeld.de
%
% The folder containing this script must be added to search path.
try
sca;
close all;
clear;
% Setup.
% --------------------------------------------------------------------------
cd /home/eeglabexpcontrol/Desktop/Christian/alt_xpc_211005
addpath('functions')
% Pre-heat functions.
GetSecs;
WaitSecs(0.1);
% Get participant info.
ptc = getPtc('alt_xpc_221024');
% Seed random number generator.
rStream = RandStream('mt19937ar', 'seed', sum(99*ptc.ptcNo + 100*ptc.session));
RandStream.setGlobalStream(rStream);
% Setup screen.
scr = setupScreen(85, [1024 768], 71, 360, 276, 32);
ListenChar(-1);
HideCursor;
% Setup basic visual stimuli.
vis = setupVis(scr, 0.5);
% Setup visual stimuli.
stim = setupStim(scr, vis, ptc); % also includes fixation radius.
% Setup keyboard.
key = setupKey({'C'}, {'C', 'V', 'O', 'A', 'UpArrow', 'DownArrow', 'LeftArrow', 'RightArrow', 'SPACE', 'RETURN', 'ESCAPE'});
% Determine validity of the alerting cue's prediction of the target.
if mod(ptc.ptcNo, 2) == 0 % For even participants...
if ptc.session <= 2 % in the first two sessions ...
acValid = 0.9; % the alerting cue predicts target appearance with .9 validity.
else
acValid = 0.1; % and with .1 validity in the last two sessions.
end
else
if ptc.session <= 2
acValid = 0.1;
else
acValid = 0.9;
end
end
% Setup design.
design = setupDesign(acValid);
% Setup waiting times.
wFrFix = csvread('wtfix.csv');
wFrTg = csvread('wttg.csv');
% Open data file.
saveFolder = fileparts('data/');
fileName = [ptc.expName '_p' num2str(ptc.ptcNo) '_s' num2str(ptc.session) '.dat'];
varNames = {...
'participant' 'session' 'trial' 'practice' 'cx' 'cy' 'tg_ecc' 'alert' 'tg_yes' 'ac_valid' 'resp' 'rt' 'correct'...
'vbl_start' 'vbl_fix' 'vbl_alert_on' 'vbl_alert_off' 'vbl_target' 'vbl_post_on' 'vbl_post_off' 'fix_error'...
};
dataFile = openLog(saveFolder, fileName, varNames);
% Show instructions.
showInst(scr, vis, 'mainTestInst.png');
% Setup Eyelink.
edfFile = ['p' num2str(ptc.ptcNo) 's' num2str(ptc.session)];
el = prepEyeLink(scr, vis, ptc, edfFile);
% Setup and test audio.
Snd('Close');
[aud, pahandle] = setupAud(scr, 1);
% Maximum priority.
Priority(MaxPriority(scr.win));
% Experiment start time.
expStartTime = GetSecs;
% Trial counter (trial id unique in session).
trial = 0;
% Practice trials.
% --------------------------------------------------------------------------
for tr = 1:size(design.practice, 1)
trial = trial + 1;
trialDesign = design.practice(tr, :);
isPractice = 1;
wFix = wFrFix(randi(length(wFrFix), 1, 1));
wTg = wFrTg(randi(length(wFrTg), 1, 1));
[correct, forceCalib, fixError] = runTrial(scr, vis, stim, key, ptc, trial, isPractice, trialDesign, el, aud, pahandle, dataFile, edfFile, saveFolder, wFix, wTg);
end
% Instructions after practice.
showInst(scr, vis, 'practiceInst.png');
% Experiment trials.
% --------------------------------------------------------------------------
nTrial = size(design.design, 1);
calib = 1; % Calibrate.
nTrialCalib = 200; % Calibrate after this number of trials.
fixErrors = [];
calibCnt = 0;
% Run trials.
tr = 0;
while tr < nTrial
% Trial parameters.
tr = tr + 1;
trial = trial + 1;
trialDesign = design.design(tr, :);
isPractice = 0;
% Calibrate eye tracker?
if calib
% Re-calibrate tracker.
PsychPortAudio('Close');
WaitSecs(1);
% Pause before calibration.
Screen('FillRect', scr.win, vis.bgColor);
DrawFormattedText(scr.win, 'PAUSE (weiter mit beliebiger Taste).', 'center', scr.cy-100, vis.black);
Screen(scr.win,'DrawingFinished');
Screen('Flip', scr.win);
KbWait();
EyelinkDoTrackerSetup(el);
Snd('Close');
[aud, pahandle] = setupAud(scr, 0);
calib = 0;
calibCnt = 0;
end
% Run trial.
wFix = wFrFix(randi(length(wFrFix), 1, 1));
wTg = wFrTg(randi(length(wFrTg), 1, 1));
[correct, forceCalib, fixError] = runTrial(scr, vis, stim, key, ptc, trial, isPractice, trialDesign, el, aud, pahandle, dataFile, edfFile, saveFolder, wFix, wTg);
% If fixation error, repeat trial later.
if fixError
nTrial = nTrial + 1;
randTr = randi([tr nTrial], 1);
design.design = [design.design(1:tr-1, :); design.design(tr:randTr-1, :); trialDesign; design.design(randTr:end, :)];
end
fixErrors = [fixErrors, fixError];
% Do we need to calibrate on the next trial?
calibCnt = calibCnt + 1;
if ((calibCnt == nTrialCalib) || (length(fixErrors) > 10 && sum(fixErrors(end-10:end)) == 10) || forceCalib)
calib = 1;
fixErrors = [];
end
end
% Finish and clean-up.
% --------------------------------------------------------------------------
% Normal priority.
Priority(0);
% Save workspace. The demographic data of the participant and the Quest data are here.
save(fullfile(saveFolder, edfFile));
% Experiment duration and thank you note.
expStopTime = GetSecs;
expDurationMin = (expStopTime - expStartTime) / 60;
thx = (['Danke!\n\n Dieses Experiment hat ' num2str(round(expDurationMin)) ' min gedauert (ohne Instruktionen).']);
DrawFormattedText(scr.win, thx, 'center', 'center', [200 0 200]);
Screen('Flip', scr.win);
KbStrokeWait;
% Shut down the experiment.
PsychPortAudio('Close');
finishEyeLink(edfFile, saveFolder);
fclose(dataFile);
ShowCursor;
sca;
home;
catch me
sca;
rethrow(me)
end
VOLKMANN201012
Table for gamma-linearization of monitor "VOLKMANN" (ViewSonic, G90fB).
Luminance measured in cd/m^2 using a Minolta-LS 110.
Christian Poth, 2020, c.poth@uni-bielefeld.de
function finishEyeLink(edfFile, saveFolder)
%function finishEyeLink
% Shut down Eyelink and clean up
%
% Christian Poth, 2019, c.poth@uni-bielefeld.de
Eyelink('Command', 'set_idle_mode');
WaitSecs(0.5);
Eyelink('CloseFile');
try
fprintf('Receiving data file ''%s''\n', edfFile);
status = Eyelink('ReceiveFile', [], saveFolder, 1);
if status > 0
fprintf('ReceiveFile status %d\n', status);
end
if 2 == exist(edfFile, 'file')
fprintf('Data file ''%s'' can be found in ''%s''\n', edfFile, pwd);
end
catch
fprintf('Problem receiving data file ''%s''\n', edfFile );
end
Eyelink('Shutdown');
\ No newline at end of file
seed(1996)
wtfix <- rgeom(n = 10000, prob = 1/3) * 17 + 85
wttg <- rgeom(n = 10000, prob = 2/5) * 3 + 8
hist(wtfix)
min(wtfix)
max(wtfix)
hist(wttg)
min(wttg)
max(wttg)
write.table(wtfix, file = "wtfix.csv", sep = ",", quote = FALSE, row.names = FALSE, col.names = FALSE)
write.table(wttg, file = "wttg.csv", sep = ",", quote = FALSE, row.names = FALSE, col.names = FALSE)
function ptc = getPtc(expName)
%function ptc = getPtc(expName)
% Collects participant and experiment information.
% INPUT:
% expName = name of experiment
% OUTPUT:
% ptc = struct with information on participant and experiment
%
% Christian Poth, 2021, c.poth@uni-bielefeld.de
while 1
ptc.expName = expName;
ptc.ptcNo = str2double(input('Versuchspersonennummer:', 's'));
ptc.code = input('Versuchspersonen-Code:', 's');
ptc.session = str2double(input('Sitzungsnummer:', 's'));
ptc.age = input('Alter der Versuchsperson (in Jahren):', 's');
ptc.gender = input('Geschlecht der Versuchsperson\n(w = weiblich, m = männlich, a = andere/keine Angabe):', 's');
ptc.hand = input('Händigkeit der Versuchsperson\n(l = linkshändig, r = rechtshändig, b = beidhändig):', 's');
ptc.visionOk = input('Ist das Sehen unbeeinträchtigt oder korrigiert? (u = unbeeinträchtigt, k = Kontaktlinsen, b = Brille):', 's');
ptc.bino = input('Sollen beide Augen getracked werden? (j = ja, n = nein)', 's');
ptc
correct = input('Sind alle Angaben korrekt? (j = ja, n = nein): ', 's');
if strcmp(correct, 'j')
break;
end
end
function expLog = openLog(saveFolder, fileName, varNames)
%function expLog = openLog(saveFolder, fileName, varNames)
% OUTPUT: Log-file.
% INPUT:
% saveFolder = folder where data shall be saved
% fileName = desired file name
% varNames = cell with strings of variable names
%
% Christian Poth, 2020, c.poth@uni-bielefeld.de
if ~exist(fullfile(saveFolder, fileName))
expLog = fopen(fullfile(saveFolder, fileName), 'w');
else
clear all;
clear mex;
clear functions;
close all;
sca;
error(['Do not overwrite data file!'])
end
if expLog == -1
error(['Could not open ' fileName ' for writing'])
end
fprintf(expLog, [repmat('%s\t', 1, size(varNames, 2) - 1) '%s\n'], varNames{:});
function el = prepEyeLink(scr, vis, ptc, edfFile)
%function prepEyeLink
% Prepares EyeLink for experiment.
% INPUT:
% scr = struct with screen info
% vis = struct with visual info
% ptc = struct with participant info
% edfFile = edf file
% OUTPUT:
% el = struct with Eyelink info
%
% Christian Poth, 2021, c.poth@uni-bielefeld.de
% Load eyelink defaults and change calibration stimuli.
el = EyelinkInitDefaults(scr.win);
el.backgroundcolour = vis.bgColor;
el.msgfontcolour = vis.instructionColor;
el.msgfontsize = 20;
el.imgtitlecolour = vis.black;
el.targetbeep = 1;
el.calibrationtargetcolour = vis.white;
EyelinkUpdateDefaults(el);
% Initialization of the connection with the Eyelink.
% Exit program if this fails.
dummy = 0;
if ~EyelinkInit(dummy, 1)
fprintf('Eyelink init aborted.\n');
Eyelink('Shutdown');
sca;
return;
end
% Open file to record data to.
edf = Eyelink('Openfile', edfFile);
if edf ~= 0
fprintf('Cannot create EDF file!');
Eyelink('Shutdown');
sca;
return;
end
% Check connection.
if Eyelink('IsConnected') ~= 1 && ~dummy
Eyelink('Shutdown');
sca;
return;
end
% Set up tracker configuration.
% ------------------------------------------------------------------------------
Eyelink('command', 'add_file_preamble_text ''Christian Poth, c.poth@uni-bielefeld.de''');
% Map gaze to px.
Eyelink('command','screen_pixel_coords = %ld %ld %ld %ld', 0, 0, scr.xRes-1, scr.yRes-1);
Eyelink('message', 'DISPLAY_COORDS %ld %ld %ld %ld', 0, 0, scr.xRes-1, scr.yRes-1);
% Binocular eye tracking.
if strcmp(ptc.bino, 'j')
Eyelink('command', 'binocular_enabled = YES');
Eyelink('command', 'select_eye_after_validation = NO');
end
% Calibration type.
Eyelink('command', 'calibration_type = HV9');
Eyelink('command', 'generate_default_targets = YES');
% Set parser (conservative saccade thresholds).
Eyelink('command', 'saccade_velocity_threshold = 35');
Eyelink('command', 'saccade_acceleration_threshold = 9500');
% EDF file contents.
Eyelink('command', 'file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE');
Eyelink('command', 'file_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA');
Eyelink('command', 'link_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK');
Eyelink('command', 'link_sample_data = LEFT,RIGHT,GAZE,AREA');
% Camera setup.
Eyelink('command', 'sample_rate = %d', 1000);
% Enter Eyetracker camera setup mode, calibration and validation.
EyelinkDoTrackerSetup(el);
\ No newline at end of file
function [correct, forceCalib, fixError] = runTrial(scr, vis, stim, key, ptc, trial, isPractice, trialDesign, el, aud, pahandle, dataFile, edfFile, saveFolder, wFix, wTg)
%function [correct, forceCalib, fixError] = runTrial(scr, vis, stim, key, ptc, trial, isPractice, trialDesign, el, aud, pahandle, dataFile, edfFile, saveFolder, wFix, wTg)
%
% Christian Poth, 2021, c.poth@uni-bielefeld.de
% Prepare trial.
% ------------------------------------------------------------------------------
% Reset eye for online monitoring.
eyeUsed = -1;
% Set variables.
fixError = 0;
forceCalib = 0;
vblStart = 0;
vblFix = 0;
vblAlertOn = 0;
vblAlertOff = 0;
vblTarget = 0;
respTime = 0;
vblPostOn = 0;
vblPostOff = 0;
resp = 0;
correct = 0;
% Fixation duration.
fixDur = (wFix-0.5)*scr.flipInt;
% Visual stimuli for this trial.
% ------------------------------------------------------------------------------
% Gaze stimulus.
gazeTex = Screen('MakeTexture', scr.win, stim.matGazeStim);
fixPos = CenterRectOnPoint([1 1 size(stim.matGazeStim(:, :, 1))], scr.cx, scr.cy);
Screen('DrawTexture', scr.win, gazeTex, [], fixPos);
tx = scr.cx + va2pix(trialDesign(2), scr.distance, scr.width, scr.xRes);
ty = scr.cy;
if trialDesign(4) % If target should be presented...
tgTex = Screen('MakeTexture', scr.win, stim.matTgStim); ...load the target texture.
else % if no target should be presented...
tgTex = Screen('MakeTexture', scr.win, stim.matNoTgStim); ...load the no-target texture that is blank.
end
tgPos = CenterRectOnPoint([1 1 size(stim.matTgStim(:, :, 1))], tx, ty);
Screen('DrawTexture', scr.win, tgTex, [], tgPos);
% Auditory stimulus for this trial.
toneFreq = randi([700, 900], 1, 1);
if trialDesign(3)
tone(1, :) = MakeBeep(toneFreq, 0.0471, aud.freq) * 0.2; % ~ dB(A), TACKLIFE SLM01 sound level meter.
else
tone(1, :) = MakeBeep(toneFreq, 0.0471, aud.freq) * 0; % Silent.
end
tone(2, :) = tone(1, :);
PsychPortAudio('FillBuffer', pahandle, tone);
Screen('FillRect', scr.win, vis.bgColor);
Screen('Flip', scr.win);
% Stimulus stream and eye recording.
% ------------------------------------------------------------------------------
% Clear keyboard buffer.
FlushEvents('KeyDown');
Eyelink('Message', 'TRIALID %d', trial);
% Start recording eye position.
Eyelink('Command', 'set_idle_mode');
WaitSecs(0.05);
Eyelink('StartRecording');
% Record few samples before start displaying.
WaitSecs(0.1);
% Start screen.
Screen('FillRect', scr.win, vis.bgColor);
Screen('DrawingFinished', scr.win);
vblStart = Screen('Flip', scr.win);
Eyelink('Message', 'SYNCTIME');
% Fixation stimulus.
Screen('FillRect', scr.win, vis.bgColor);
Screen('DrawTexture', scr.win, gazeTex, [], fixPos);
Screen('DrawingFinished', scr.win);
vblFix = Screen('Flip', scr.win, vblStart + (17-0.5)*scr.flipInt);
Eyelink('Message', 'FIX_ON');
while (GetSecs - vblFix) < fixDur
if Eyelink( 'NewFloatSampleAvailable') > 0
evt = Eyelink('NewestFloatSample');
if eyeUsed ~= -1
x = evt.gx(eyeUsed+1);
y = evt.gy(eyeUsed+1);
% Valid data and pupil visible?
if x ~= el.MISSING_DATA && y ~= el.MISSING_DATA && evt.pa(eyeUsed+1) > 0
% Does participant fixate fixation stimulus?
if sqrt( (x-scr.cx)^2 + (y-scr.cy)^2 ) > stim.fixRad
fixError = 1;
Eyelink('Message', 'FIXATION_ABORTED');
break;
end
end
else % Find used eye.
eyeUsed = Eyelink('EyeAvailable');
if eyeUsed == el.BINOCULAR; % If both eyes are tracked.
eyeUsed = el.LEFT_EYE; % Use left eye.
end
end
end
end
% Alert or no alert. Lasts 8 screen refreshes, i.e. 94 ms at 85 Hz.
Screen('FillRect', scr.win, vis.bgColor);
Screen('DrawTexture', scr.win, gazeTex, [], fixPos);
Screen('DrawingFinished', scr.win);
vblAlertOn = Screen('Flip', scr.win);
Eyelink('Message', 'ALERT_ON');
PsychPortAudio('Start', pahandle, 1, 0, 0);
Screen('DrawTexture', scr.win, gazeTex, [], fixPos);
Screen('DrawingFinished', scr.win);
vblAlertOff = Screen('Flip', scr.win, vblAlertOn + (8-0.5) * scr.flipInt);
PsychPortAudio('Stop', pahandle, 1, 0, 0);
Eyelink('Message', 'ALERT_OFF');
% Target or no target and start of response collection.
Screen('FillRect', scr.win, vis.bgColor);
Screen('DrawTexture', scr.win, gazeTex, [], fixPos);
Screen('DrawTexture', scr.win, tgTex, [], tgPos);
Screen('DrawingFinished', scr.win);
vblTarget = Screen('Flip', scr.win, vblAlertOn + (wTg-0.5)*scr.flipInt);
Eyelink('Message', 'TARGET_ON');
% Response collection.
while (GetSecs - vblTarget) < 0.8 % Response deadline = 800 ms.
[x, y, buttons] = GetMouse(scr.win);
if sum(buttons) ~= 0
respTime = GetSecs;
Eyelink('Message', 'RESP_MADE');
break
end
end
% Post-response interval.
Screen('FillRect', scr.win, vis.bgColor);
vblPostOn = Screen('Flip', scr.win);
Eyelink('Message', 'POST_ON');
% Check keyboard in case experiment is aborted or calibration is forced.
while (GetSecs - vblPostOn) < 1 - 0.5*scr.flipInt
[~, ~, keyCode] = KbCheck;
pressedKey = find(keyCode);
% Escape quits the experiment.
if keyCode(KbName('ESCAPE'))
Screen('FillRect', scr.win, vis.bgColor);
DrawFormattedText(scr.win, 'Experiment abgebrochen (via Escape).', 'center', 'center', [255 0 0]);
Screen('DrawingFinished', scr.win);
Screen('Flip', scr.win);
KbStrokeWait;
WaitSecs(0.1);
Eyelink('StopRecording');
PsychPortAudio('Close');
finishEyeLink(edfFile, saveFolder);
fclose(dataFile);
ShowCursor;
sca;
home;
end
% C forces calibration on the next trial.
if keyCode(KbName('C'))
forceCalib = 1;
end
end
vblPostOff = Screen('Flip', scr.win);
Eyelink('Message', 'POST_OFF');
while KbCheck; end % Wait until KbCheck finished.
% Score response.
if buttons(1) && ~buttons(3)
resp = -1;
elseif ~buttons(1) && buttons(3)
resp = 1;
else
resp = 0;
end
if trialDesign(2) < 0
correct = (resp == -1)
else
correct = (resp == 1)
end
% Stop recording.