MATLAB Programming/Psychtoolbox/eyelink toolbox

% Short MATLAB example program that uses the Eyelink and Psychophysics
% Toolboxes to create a real-time gaze-dependent display.

% edf 09.19.06: adapted from http://psychtoolbox.org/eyelinktoolbox/EyelinkToolbox.pdf
% and http://www.kyb.tuebingen.mpg.de/bu/people/kleinerm/ptbosx/ptbdocu-1.0.5MK4R1.html
% (also available in local install at Psychtoolbox\ProgrammingTips.html)

% see also
% Psychtoolbox\PsychHardware\EyelinkToolbox\EyelinkDemos\Short demos\EyelinkExample.m
% which calls some functions that do not work on the windows openGL ptb

% ex: [v vs]=Eyelink('GetTrackerVersion') unimplemented for win
% also: el=EyelinkInitDefaults() sets el.keysCached to 0 on windows.
%       this causes EyelinkDoTrackerSetup() and EyelinkDoDriftCorrect() to 
%       fail, because they call EyelinkGetKey(), which depends on cached key id's.
%       updating the eyelink toolbox to use the new KbName('UnifyKeyNames')
%       should fix this.

% necessary for windows:
% copy EyelinkToolbox144\EyelinkToolbox\EyelinkBasic\eyelink.dll
% (from http://www.psychtoolbox.org/eyelinktoolbox/downloads/EyelinkToolbox144.zip)
% to Psychtoolbox\PsychHardware\EyelinkToolbox\EyelinkBasic\Eyelink.dll
% (note capitalizing Eyelink.dll is important)
% the rest of the eyelink toolbox is included in ptb 1.0.6 openGL beta

% see ptb forum thread http://tech.groups.yahoo.com/group/psychtoolbox/message/4993

% note eyelink functions are documented in
% C:\Program Files\SR Research\EyeLink\Docs\*.pdf

function eyelinkWinDemo
format long g
KbName('UnifyKeyNames') %enables cross-platform key id's

doDisplay=1; %use ptb
createFile=1; %record eyetracking data on the remote (eyetracking) computer and suck over the file when done
mouseInsteadOfGaze=0; %control gaze cursor using mouse instead of gaze (for testing, in case calibration isn't worked out yet)
textOut=1; %write reports from tracker to stdout

edfFile='demo.edf'; %name of remote data file to create
screenNum = 1; % use main screen

% STEP 1
% Initialization of the connection with the Eyelink Gazetracker.
% exit program if this fails.
if (Eyelink('initialize') ~= 0)
    error('could not init connection to Eyelink')
    return;
end;

try
    % STEP 2
    % Open a graphics window on the main screen
    % using the PsychToolbox's SCREEN function.

    priority = MaxPriority('KbCheck');
    oldPriority = Priority();

    if doDisplay
        AssertOpenGL
        window = Screen('OpenWindow', screenNum, 0, [], 32, 2);
        HideCursor;
        Priority(priority);
        ifi = Screen('GetFlipInterval', window, 200);
        Priority(oldPriority);

        white=WhiteIndex(window);
        black=BlackIndex(window);

        [scrWidth, scrHeight]=Screen('WindowSize', window);

        xRange = [0 scrWidth]; %range of gaze estimates over display, which probably come in terms of the ptb stim display
        yRange = [0 scrHeight];

        dotHeight = 7;
        dotWidth = 7;
    end

    % STEP 3
    % Provide Eyelink with details about the graphics environment
    % and perform some initializations. The information is returned
    % in a structure that also contains useful defaults
    % and control codes (e.g. tracker state bit and Eyelink key values).
    if doDisplay
        el=EyelinkInitDefaults(window);
    else
        el=EyelinkInitDefaults();
    end
    % make sure that we get gaze data from the Eyelink
    status=Eyelink('command','link_sample_data = LEFT,RIGHT,GAZE,AREA,GAZERES,HREF,PUPIL,STATUS,INPUT');
    if status~=0
        error('link_sample_data error, status: ',status)
    end
    
    % open file to record data to (just an example, not required)
    if createFile
        status=Eyelink('openfile',edfFile);
        if status~=0
            error('openfile error, status: ',status)
        end
    end

    % STEP 4
    if doDisplay && strcmp(el.computer,'MAC')==1 % OSX
        % Calibrate the eye tracker using the standard calibration routines
        EyelinkDoTrackerSetup(el); %fails on win, see header comments
    
        % do a final check of calibration using driftcorrection
        EyelinkDoDriftCorrect(el); %fails on win, see header comments        
    else
        warning('cannot do calibration/drift correction unless on OSX with an open ptb window')
    end
    
    % STEP 5
    % start recording eye position
    status=Eyelink('startrecording');
    if status~=0
        error('startrecording error, status: ',status)
    end
    % record a few samples before we actually start displaying
    WaitSecs(0.1);
    % mark zero-plot time in data file
    status=Eyelink('message','SYNCTIME');
    if status~=0
        error('message error, status: ',status)
    end

    stopkey=KbName('space');
    eye_used = -1; %just an initializer to remind us to ask tracker which eye is tracked

    % STEP 6
    % show gaze-dependent display

    Priority(priority);
    if doDisplay
        vbl = Screen('Flip', window); %Initially synchronize with retrace, take base time in vbl
    end

    while 1 % loop till error or space bar is pressed

        % Check recording status, stop display if error
        err=Eyelink('checkrecording');
        if(err~=0)
            error('checkrecording problem, status: ',err)
            break;
        end

        % check for presence of a new sample update
        status = Eyelink('newfloatsampleavailable');
        if  status> 0
            % get the sample in the form of an event structure
            evt = Eyelink('newestfloatsample');

            if textOut
                evt
            end        
            
            if eye_used ~= -1 % do we know which eye to use yet?
                % if we do, get current gaze position from sample
                x = evt.gx(eye_used+1); % +1 as we're accessing MATLAB array
                y = evt.gy(eye_used+1);

                % do we have valid data and is the pupil visible?
                if (x~=el.MISSING_DATA & y~=el.MISSING_DATA & evt.pa(eye_used+1)>0) || mouseInsteadOfGaze 

                    if mouseInsteadOfGaze
                        if doDisplay
                            [x,y,buttons] = GetMouse(window);
                        else
                            [x,y,buttons] = GetMouse(screenNum);
                        end
                    else
                        x=scrWidth*((x-min(xRange))/range(xRange));
                        y=scrHeight*((y-min(yRange))/range(yRange));
                    end

                    % if data is valid, draw a circle on the screen at current gaze position
                    % using PsychToolbox's SCREEN function
                    if doDisplay
                        gazeRect=[ x-dotWidth/2 y-dotHeight/2 x+dotWidth/2 y+dotHeight/2];
                        penSize=6;
                        Screen('FrameOval', window, white, gazeRect,penSize,penSize);
                    end
                else
                    % if data is invalid (e.g. during a blink), clear display
                    if doDisplay
                        Screen('FillRect', window,black);
                    end

                    disp('blink! (x or y is missing or pupil size<=0)')
                end

                if doDisplay
                    Screen('DrawingFinished', window);
                    vbl = Screen('Flip', window, vbl + 0.5*ifi);
                end

            else % if we don't, first find eye that's being tracked
                eye_used = Eyelink('eyeavailable'); % get eye that's tracked

                switch eye_used
                    case el.BINOCULAR
                        disp('tracker indicates binocular, we''ll use right')
                        eye_used = el.RIGHT_EYE;
                    case el.LEFT_EYE
                        disp('tracker indicates left eye')
                    case el.RIGHT_EYE
                        disp('tracker indicates right eye')
                    case -1
                        error('eyeavailable returned -1')
                    otherwise
                        error('uninterpretable result from eyeavailable: ',eye_used)
                end
            end
        else
            disp(sprintf('no sample available, status: %d',status))
        end % if sample available

        % check for keyboard press
        [keyIsDown,secs,keyCode] = KbCheck;
        % if spacebar was pressed stop display
        if keyCode(stopkey)
            break;
        end
    end % main loop

    % wait a while to record a few more samples
    WaitSecs(0.1);

    % STEP 7
    % finish up: stop recording eye-movements,
    % close graphics window, close data file and shut down tracker
    cleanup(createFile, oldPriority, edfFile);
catch
    cleanup(createFile, oldPriority, edfFile);
    ers=lasterror
    ers.stack.file
    ers.stack.name
    ers.stack.line
    rethrow(lasterror)
end

function cleanup(createFile, oldPriority, edfFile)
Eyelink('stoprecording');
Screen('CloseAll');
ShowCursor;
Priority(oldPriority);
if createFile
    status=Eyelink('closefile');
    if status ~=0
        disp(sprintf('closefile error, status: %d',status))
    end
    status=Eyelink('ReceiveFile',edfFile,pwd,1);
    if status~=0
        fprintf('problem: ReceiveFile status: %d\n', status);
    end
    if 2==exist(edfFile, 'file')
        fprintf('Data file ''%s'' can be found in ''%s''\n', edfFile, pwd );
    else
        disp('unknown where data file went')
    end
end
Eyelink('shutdown');