MATLAB
    
    function TPxYarbusTask(initRequired)
%
% This demo recreates the viewing task used by Yarbus, which demonstrated
% that the pattern of free-viewing of an image changes as a function of the
% task required of the viewer. 
%
% We present the same image three times, for 8 seconds each. Each time we
% pose a different question to the participant (question order is
% randomized). Gaze scan paths for the three trials are then plotted side
% by side for comparison.
%
% If initRequired is set to 1, the function first calls
% TPxTrackpixx3CalibrationTesting to connect to the TRACKPixx3 and
% calibrate the tracker.
% Most recently tested with:
% -- TRACKPixx3 firmware revision 18 
% -- DATAPixx3 firmware revision 19 
% -- MATLAB version 9.6.0.1150989 (R2019a) 
% -- Psychtoolbox verison 3.0.15 
% -- Datapixx Toolbox version 3.7.5735
% -- Windows 10 version 1903, 64bit
%References:
% DeAngelus, M., & Pelz, J. B. (2009). Top-down control of eye movements: Yarbus revisited. Visual Cognition, 17(6-7), 790-811.
% Yarbus, A. L. (1967). Eye movements during perception of complex objects. In Eye movements and vision (pp. 171-211). Springer, Boston, MA.
% Oct 15, 2019  lef     Written
% Mar 26  2020  lef     Updated
%% Step 1 - Initialize (if needed)
if nargin==0
    initRequired=1;
end
% Get some user input
fileName= input('Enter participant name: ', 's');
fileID = [fileName '.mat'];
%If a calibration is needed, call the calibration script
if initRequired
    fprintf('\nInitialization required\n\nCalibrating the device...');
    TPxTrackpixx3CalibrationTesting;
end
%Connect to TRACKPixx3
Datapixx('Open');
Datapixx('SetTPxAwake');
Datapixx('RegWrRd');
%% Step 2 - Set up the TRACKPixx recording schedule
Datapixx('SetupTPxSchedule');
%write all commands to the DATAPixx device register
Datapixx('RegWrRd');
%% Step 3 - Show our image and record eye position
AssertOpenGL;                                             
%set our ideal viewing time in seconds
viewingTime = 8;
%create a structure to store our data. This format allows us to easily add
%more trials and images if we want. For now we stick to 3
data = struct('Trial', [],...
              'Question', {'In the image, what time of day is it?',...
              'In the image, what is the average age of the group?',...
              'In the image, what is the overall mood of the event?'},...
              'Image', {'Renoir_Boating.jpg','Renoir_Boating.jpg', 'Renoir_Boating.jpg'},...
              'ViewingTime', [],...
              'EyeData', []);
          
%shuffle order of presentation
order = randperm(numel(data));
%open window
Screen('Preference', 'SkipSyncTests', 1 );
screenID = 2;                                              %change to switch display
[windowPtr, rect]=Screen('OpenWindow', screenID, [0,0,0]);
Screen('BlendFunction', windowPtr, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA');
Screen('Flip',windowPtr);
%show instructions to participant
text_to_draw = ['FREE VIEWING DEMO:\n\nYou will be asked a question about a painting. \nYou will have 8 seconds to view the painting and decide your answer.\n\nPress any key to start.'];
DrawFormattedText(windowPtr, text_to_draw, 'center', 700, 255);
Screen('Flip', windowPtr);
%wait for participant to continue
[~, ~, ~] = KbPressWait;
Screen('Flip', windowPtr);
WaitSecs(1);
start_time = Datapixx('GetTime');
for k = 1:numel(data)
    index = order(k);
    data(k).Trial = index;
    currentQuestion = data(index).Question;
    im = imread(data(index).Image);
    imTexture = Screen('MakeTexture', windowPtr, im); 
    
    text_to_draw = [currentQuestion '\n\nPress any key to start.'];
    DrawFormattedText(windowPtr, text_to_draw, 'center', 700, 255);
    Screen('Flip', windowPtr);
    
    %wait for a keypress
    [~, ~, ~] = KbPressWait;
    
    %set up recording to start on the same frame flip that shows the image.
    %We also get the time of the flip using a Marker which indcates the
    %frame flip on the DATAPixx clock
    Datapixx('StartTPxSchedule');
    Datapixx('SetMarker');
    Datapixx('RegWrRdVideoSync');
    
    %draw our image and flip
    Screen('DrawTexture', windowPtr, imTexture, [], rect);
    Screen('Flip', windowPtr);
    
    time1 = Datapixx('GetMarker');
    time2 = time1;
    
    %repeatedly check the device for current time, and break loop when our
    %8 seconds are done.
    while (time2 - time1) < viewingTime
        Datapixx('RegWrRd');
        time2 = Datapixx('GetTime');
    end
    
    %stop recording
    Datapixx('StopTPxSchedule');
    Datapixx('RegWrRd');
    
    %read in eye data
    Datapixx('RegWrRd');
    status = Datapixx('GetTPxStatus');
    toRead = status.newBufferFrames;
    [bufferData, ~, ~] = Datapixx('ReadTPxData', toRead);
    
    %bufferData is formatted as follows:
    %1      --- Timetag (in seconds)
    %2      --- Left Eye X (in pixels) 
    %3      --- Left Eye Y (in pixels)
    %4      --- Left Pupil Diameter (in pixels)
    %5      --- Right Eye X (in pixels)
    %6      --- Right Eye Y (in pixels)
    %7      --- Right Pupil Diameter (in pixels)
    %8      --- Digital Input Values (24 bits)
    %9      --- Left Blink Detection (0=no, 1=yes)
    %10     --- Right Blink Detection (0=no, 1=yes) 
    %11     --- Digital Output Values (24 bits)
    %12     --- Left Eye Fixation Flag (0=no, 1=yes) 
    %13     --- Right Eye Fixation Flag (0=no, 1=yes)  
    %14     --- Left Eye Saccade Flag (0=no, 1=yes) 
    %15     --- Right Eye Saccade Flag (0=no, 1=yes)  
    %16     --- Message code (integer) 
    %17     --- Left Eye Raw X (in pixels) 
    %18     --- Left Eye Raw Y (in pixels)  
    %19     --- Right Eye Raw X (in pixels)  
    %20     --- Right Eye Raw Y (in pixels) 
    
    %IMPORTANT: "RIGHT" and "LEFT" refer to the right and left eyes shown
    %in the console overlay. In tabletop and MEG setups, this view is
    %inverted. This means "RIGHT" in our labelling convention corresponds
    %to the participant's left eye. Similarly "LEFT" in our convention
    %refers to left on the screen, which corresponds to the participant's
    %right eye.
    %If you are using an MRI setup with an inverting mirror, "RIGHT" will
    %correspond to the participant's right eye.
       
    %save eye data from trial as a table in the trial structure
    data(index).EyeData = array2table(bufferData, 'VariableNames', {'TimeTag', 'LeftEyeX', 'LeftEyeY', 'LeftPupilDiameter', 'RightEyeX', 'RightEyeY', 'RightPupilDiameter',...
                                    'DigitalIn', 'LeftBlink', 'RightBlink', 'DigitalOut', 'LeftEyeFixationFlag', 'RightEyeFixationFlag', 'LeftEyeSaccadeFlag', 'RightEyeSaccadeFlag',...
                                    'MessageCode', 'LeftEyeRawX', 'LeftEyeRawY', 'RightEyeRawX', 'RightEyeRawY'});
    %get some other trial data
    data(index).ViewingTime = time2 - time1;
    
    %interim save
    save(fileID, 'data');    
end
%Close everything
finish_time = Datapixx('GetTime');
Screen('Closeall');
Datapixx('SetTPxSleep');
Datapixx('RegWrRd');
Datapixx('Close');
%% Step 4 - Plot some gaze paths
figure();
numCols = 3;
numRows = ceil(numel(data)/numCols); 
for k = 1:numel(data)
    
    subplot(numRows, numCols, k);
    x = data(k).EyeData.LeftEyeX;
    y = data(k).EyeData.LeftEyeY;
    plot(x,y, 'ob', 'linewidth', 1, 'markersize', 1); 
    hold on
    x = data(k).EyeData.RightEyeX;
    y = data(k).EyeData.RightEyeY;
    plot(x,y, 'or', 'linewidth', 1, 'markersize', 1); 
    
    xlim([-rect(3)/2, rect(3)/2]);
    ylim([-rect(4)/2, rect(4)/2]);
    
    im = imread(data(k).Image); 
    h = image(xlim, -ylim,im);
    set(h,'alphadata', .5);
    
    title(data(k).Question);
    legend({'Left eye', 'Right Eye'});
    xlabel('X position (pixels)');
    ylabel('Y position (pixels)');
    
end
%% Step 5 - Write data to csv for subsequent analysis
rawResults = table();
for k = 1:numel(data)
    newTrial = data(k).EyeData;
    newTrial.TrialNumber = repmat(data(k).Trial, [height(newTrial), 1]);
    newTrial.Question = repmat({data(k).Question}, [height(newTrial), 1]);
    newTrial.ViewingTime = repmat(data(k).ViewingTime, [height(newTrial), 1]);
    newTrial = newTrial(:, [21:end, 1:20]);
    rawResults = [rawResults; newTrial];
end
savefig(fileName);
csvFileID = [fileName '_Results.csv'];
writetable(rawResults, csvFileID);
end