TRACKPixx Demo 3 – Creating a circular gaze-contingent mask with the TRACKPixx3

Note

This demo requires TRACKPixx Revision 18 or later. You can check for recent firmware updates at vpixx.com/whatsnew

This demo continuously samples eye position from the TRACKPixx3 eye tracker and uses it to update the location of a circular grey mask obscuring part of the displayed image. The mask is tied to the left eye position; participants can use the arrow keys to offset mask location, and “Q” and “A” keys to adjust the size of the mask.

At the beginning of the demo there is an optional calibration step. This step calls TPxTrackpixx3CalibrationTesting, which implements a standard MATLAB TRACKPixx3 calibration script. The tracker must be calibrated for every new participant. We also recommend calibrating if a participant moves away from the chinrest.

While other TRACKPixx demos use the eye-tracking schedule and store data on the I/O controller for later access, this demo does not record eye position. Instead, it repeatedly samples the tracker with GetEyePosition, which calls the most recent eye-tracking data available. Because this sampling is performed within MATLAB, rather than the I/O controller, eye positions cannot be recorded at the maximum rate of 2000 samples/second. Because we draw our frames at a much slower rate, this is fine for our gaze contingent display. No eye position data is saved.

Psychtoolbox’s “Screen” coordinates define the origin (x, y = 0, 0) as the top left corner of the display. In contrast, the TRACKPixx treats the center of the display as the origin. The ConvertCoordSysToCustom converts the eye position in tracker coordinates into screen coordinates so that the mask is drawn at the correct location on the display. For more details, please see the documentation for ConvertCoordSysToCustom.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
function TPxScotomaDemo(initRequired)
%
% This is a simple demonstration of a gaze-contingent display. A
% static image is shown and a binocular scotoma follows the participant's
% gaze. Participants can move the position of the scotoma with respect to
% gaze via the arrows on the keyboard. Size can be adjusted using 'q' and 'a' 
%
% 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


% Oct 16, 2019  lef     Written

%% Step 1 - Initialize (if needed)

if nargin==0
    initRequired=1;
end

%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 - Show our image and record eye position

AssertOpenGL;                                             
KbName('UnifyKeyNames');

%set our max viewing time
viewingTime = 600;

%set our scotoma refresh rate - 60 Hz for now
refreshRate = 1/60;

%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');

%load our image 
im = imread('Renoir_PontNeuf.jpg');
imTexture = Screen('MakeTexture', windowPtr, im); 

%set some initial scotoma characteristics
diameter = 100;
offsetX = 25;
offsetY = 10;

startTime = Datapixx('GetTime');
time2 = startTime;

while (time2 - startTime) < viewingTime
    
    Datapixx('RegWrRd');
    time1 = Datapixx('GetTime');
    time2 = time1 ;
    
    %Draw image 
    Screen('DrawTexture', windowPtr, imTexture, [], rect);   
    
    %Draw scotoma. For ease we'll say the scotoma follows the participant's
    %left eye. In the TRACKPix output, "RIGHT" and "LEFT" refer to the
    %right and left eyes shown in the console overlay. In tabletop and MEG
    %setups, this view is typically inverted. So in a tabletop or MEG
    %setup, 'ScreenRight' is the viewer's left eye.

    %If you are using an MRI setup with an inverting mirror, "RIGHT" will
    %correspond to the participant's right eye.
    [xScreenRight, yScreenRight, ~, ~, ~, ~, ~, ~, ~] = Datapixx('GetEyePosition');
    pos = Datapixx('ConvertCoordSysToCustom', [xScreenRight, yScreenRight]);
    Screen('FillOval', windowPtr, [128, 128, 128], [(pos(1)-offsetX)-diameter/2, (pos(2)-offsetY)-diameter/2, (pos(1)-offsetX)+diameter/2, (pos(2)-offsetY)+diameter/2]);
    
    %Some instructions at the top of the screen
    text_to_draw = 'Use the arrow keys to change the offset of the blind spot. Use Q and A keys to increase/decrease size. Press Escape to exit demo';
    Screen('DrawText', windowPtr, text_to_draw, 50, 10, [255, 255, 255], [0,0,0]);
    Screen('Flip', windowPtr);
    
    %Check for keys down
    [keyIsDown, ~, keyCode, ~] = KbCheck;
    if keyIsDown
        if keyCode(KbName('UpArrow'))
            offsetY = offsetY - 5;
        elseif keyCode(KbName('DownArrow'))
            offsetY = offsetY + 5;
        elseif keyCode(KbName('LeftArrow'))
            offsetX = offsetX - 5;
        elseif keyCode(KbName('RightArrow'))
            offsetX = offsetX + 5;
        elseif keyCode(KbName('a'))
            diameter = diameter + 10;
        elseif keyCode(KbName('q'))
            diameter = diameter - 10; 
            if diameter < 0
                diameter = 10;
            end
        elseif keyCode(KbName('escape'))
            break
        end
    end
            
    while (time2 - time1) < refreshRate
        Datapixx('RegWrRd');
        time2 = Datapixx('GetTime');
    end
end

%Close everything
Screen('Closeall');
Datapixx('SetTPxSleep');
Datapixx('RegWrRd');
Datapixx('Close');

end