SR Research Support Site
Simple

The experiment first initializes the EyeLink library and connects to the EyeLink tracker. It then creates a full-screen window, and sends a series of commands to the tracker to configure its display resolution, eye movement parsing thresholds, and data types. Using a dialog box built into the eyelink_w32_comp library, it asks for a file name for an EDF data file, which it commands the EyeLink tracker to open on its hard disk.

The program then runs a block of trials. Each block begins by calling up the tracker's Camera Setup screen, from which the experimenter can perform camera setup, calibration, and validation. Four trials are run, each of which displays a single word. After all blocks of trials are completed, the EDF file is closed and transferred via the link from the EyeLink hard disk to the Display PC. At the end of the experiment, the window is erased and the connection to the EyeLink tracker is closed.

Each trial begins by performing a drift correction, where the participant fixates a target to allow the eye tracker to correct for any drift errors. Recording is then started. Recording can be stopped by pressing the 'Esc' key on the Display PC keyboard, the EyeLink Abort menu ('Ctrl' 'Alt' 'A' on the EyeLink keyboard) or by pressing any button on the EyeLink button box.

Source Files for "Simple"

Before proceeding, you may want to print out copies of these files for reference:

simple.hDeclarations to link together the template experiment files.
main.c WinMain() function for windows win32 compilation and main() for other compilations, setup and shutdown link and graphics, open EDF file.
trials.cCalled to run a block of trials for the simple template. Performs system setup at the start of each block, then runs the trials. Handles standard return codes from trials to allow trial skip, repeat, and experiment abort. This file can be modified for your experiments, by replacing the trial instance code.
trial.cImplements a trial with simple graphics that can be drawn in one screen refresh, and therefore doesn't need display blanking. You should be able to adapt this file to your experiments by updating the drawing code, messages sent to the EDF file, and changing the handling of participant responses as required.

These files are also required to compile all templates. These must not be modified.

eyetypes.hDeclarations of basic data types.
eye_data.hDeclaration of complex EyeLink data types and link data structures.
eyelink.hDeclarations and constants for basic EyeLink functions, Ethernet link, and timing.
sdl_expt.h, core_expt.h

Declarations of EyeLink Developers Kit DLL functions and types. This file will also reference the other EyeLink header files.

eyelink_core.libImport library for eyelink_core.dll.
eyelink_core_graphics.libImport library for eyelink_core_graphics.dll.
SDL_util.lib, SDL_ttf.lib, SDLmain.lib, SDL.libSDL_util library that implements font loading and text printing.
eyelink_w32_comp.libImport library for implementing windows dialog boxes for SDL.

Analysis of "main.c"

This file has the initialization and cleanup code for the experiment, and is used by all the templates. You should use as much of the code and operation sequence from this file as possible in your experiments. Also, try to keep your source files separated by function in the same way as the source files in this template are: this will make the maintenance of the code easier.

WinMain()/main()

Execution of every Windows non-console program begins with WinMain(), others begin with main(). In this case it simply calls app_main(), which actually does all the work. The main() processes any given command line parameters. The WinMain does not take any command line parameters.

#if defined(WIN32) && !defined(_CONSOLE)
// WinMain - Windows calls this to execute application
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
char exepath[2048];
GetModuleFileName(hInstance, exepath, sizeof(exepath));
initialize_sdl_util(exepath, NULL,1);
add_font_search_path("../shared");
app_main(NULL, NULL);// call our real program
open_output_folder();
return 0;
}
#else
// non windows application or win32 console application.
int main(int argc, char ** argv)
{
char *trackerip = NULL;
int rv = parseArgs(argc,argv, &trackerip, &disp);
if(rv) return rv;
initialize_sdl_util(argv[0],NULL,1);
#ifndef __APPLE__ // in macOS the Fonts are in the resources folder.
add_font_search_path("../shared");
#endif
if(disp.width)
app_main(trackerip, &disp);// call our real program
else
app_main(trackerip, NULL);// call our real program - no display parameters set
open_output_folder();
return 0;
}
#endif

The function parseArgs() in main() is used to set the experiment configuration (such as the display resolution, color bits, and tracker IP address) in the command-line mode. This function can be extended for other parameter settings, such as the EDF file name etc.

Initialization

The app_main() function begins by asking for an EDF file name, initializing the EyeLink library and opening a connection to the eye tracker. It sets up the getkey() system as well. Finally, it checks the EyeLink tracker type using eyelink_get_tracker_version(), to determine what enhanced features are available. The get_tracker_sw_version() is used to retrieve the major version of the host software (e.g., 2 for EyeLink II, 3/4 for EyeLink 1000, 5 for EyeLink 1000 Plus, and 6 for EyeLink Portable Duo).

#ifdef WIN32
edit_dialog(NULL,"Create EDF File", "Enter Tracker EDF file name:", our_file_name,260);
#endif
if(trackerip)
set_eyelink_address(trackerip);
return -1; // abort if we can't open link
flush_getkey_queue();// initialize getkey() system
eyelink_ver = eyelink_get_tracker_version(verstr);
if (eyelink_ver == 3)
tracker_software_ver = get_tracker_sw_version(verstr);

Next, the video mode is set by calling init_expt_graphics().

if(init_expt_graphics(NULL, disp))
return exit_eyelink(); // register window with EXPTSPPT
window = SDL_GetVideoSurface();

Following this, information on the current display mode is retrieved. The display resolution and color depth is checked for suitability to the experiment (256-color modes are fine for text and simple graphics but work poorly with the samples that use pictures). Warnings or errors are reported using the alert_printf() function supplied by the eyelink_core library.

get_display_information(&dispinfo); // get window size, characteristics
// NOTE: Camera display does not support 16-color modes
// NOTE: Picture display examples don't work well with 256-color modes
// However, all other sample programs should work well.
//
if(dispinfo.palsize==16) // 16-color modes not functional
{
alert_printf("This program cannot use 16-color displays");
return exit_eyelink();
}
// 256-color modes: palettes not supported by this example
if(dispinfo.palsize)
alert_printf("This program is not optimized for 256-color displays");

Calibration and drift correction are customized by setting the target size, the background and target colors, and the sounds to be used as feedback. Target size is set as a fraction of display width, so that the templates will be relatively display-mode independent. A gray background and black target is initially set for the window: this will be changed before calibration to match trial stimuli brightness. The default sounds are used for most, but the sound after drift correction is turned off. On Windows, the simple example uses a small video clip ("cal_star_black_bg.avi") as the calibration target by calling set_cal_animation_target("cal_star_black_bg.avi", 0, 255).

#ifdef WIN32
set_cal_animation_target("cal_star_black_bg.avi", 0, 255); //set animation target
#else
i = SCRWIDTH/60; // select best size for calibration target
j = SCRWIDTH/300; // and focal spot in target
if(j < 2) j = 2;
set_target_size(i, j); // tell DLL the size of target features
set_calibration_colors(&target_foreground_color, &target_background_color); // tell EXPTSPPT the colors
set_cal_sounds("", "", "");
set_dcorr_sounds("", "off", "off");
#endif

For this template, we print a title screen. The display is cleared by clear_full_screen_window(), a font is selected, and several lines of text are printed using graphic_printf(). The text is made visible by calling SDL_Flip(window);

clear_full_screen_window(target_background_color); // clear screen
get_new_font(NULL, SCRHEIGHT/32, 0);
// select a font eg. "Times New Roman". passing NULL will select default font.
// Draw text
graphic_printf(window, target_foreground_color, CENTER, SCRWIDTH/2, 1*SCRHEIGHT/30,
"EyeLink Demonstration Experiment: Sample Code");
graphic_printf(window, target_foreground_color, CENTER,SCRWIDTH/2, 2*SCRHEIGHT/30,
"Included with the EyeLink Developers Kit");
graphic_printf(window, target_foreground_color, CENTER,SCRWIDTH/2, 3*SCRHEIGHT/30,
"All code is Copyright (c) 1997-2023 SR Research Ltd.");
graphic_printf(window, target_foreground_color, CENTER,SCRWIDTH/5, 4*SCRHEIGHT/30,
"Source code may be used as template for your experiments.");

Opening an EDF file

The eyelink_w32_comp library function edit_dialog() is called to ask for a file name, up to 8 characters in length and containing letters, numbers, and underscores (_) only. If no file name was entered, our_file_name is left blank and no file is created. A line of text is added to the start of the EDF file, to mark the application that created the file. This can later be viewed with a text editor. While in full screen mode of SDL calling edit_dialog() may be a problem since, the dialog will be covered by the full screen window. So, what you want to do is to call this function before calling init_expt_graphics().

if (our_file_name[0]) // If file name set, open it
{
// add extension
if (!strstr(our_file_name, ".")) strcat(our_file_name, ".EDF");
i = open_data_file(our_file_name); // open file
if (i != 0) // check for error
{
alert_printf("Cannot create an EDF file named '%s'.", our_file_name);
return exit_eyelink();
} // add title to preamble
eyecmd_printf("add_file_preamble_text 'RECORDED BY %s' ", program_name);
}

EyeLink Tracker Configuration

Before recording, the EyeLink tracker must be set up. The first step is to record the display resolution, so that the EyeLink tracker and viewers will work in display pixel coordinates. The calibration type is also set, with "HV13" setting the usual 13-point calibration. A calibration type of "H3" would calibrate and collect data for horizontal gaze position only.

We also write information on the display to the EDF file, to document the experiment and for use during analysis. The "DISPLAY_COORDS" message records the size of the display, which may be used to control data display tools. The refresh rate is recorded in the "FRAMERATE" message, which can be used to correct the onset time of stimuli for monitor refresh delay. This message should not be included if refresh synchronization is not available, and is optional if refresh-locked presentation of stimuli is not used.

// Now configure tracker for display resolution
// Set display resolution
eyecmd_printf("screen_pixel_coords = %ld %ld %ld %ld",
dispinfo.left, dispinfo.top, dispinfo.right, dispinfo.bottom);
// eyecmd_printf("sample_rate = 1000"); // Set the intended sampling rate
// Setup calibration type
eyecmd_printf("calibration_type = HV13");
// Add resolution to EDF file /
eyemsg_printf("DISPLAY_COORDS %ld %ld %ld %ld",
dispinfo.left, dispinfo.top, dispinfo.right, dispinfo.bottom);
if(dispinfo.refresh>40)
eyemsg_printf("FRAMERATE %1.2f Hz.", dispinfo.refresh);

The saccade detection thresholds and the EDF file data contents are set next. Setting these at the start of the experiment prevents changes to default settings made by other experiments from affecting your experiment. The saccadic detection thresholds determine if small saccades are detected and how sensitive to noise the tracker will be. If we are connected to an EyeLink II, 1000, 1000 Plus, and Portable Duo, we select a parser configuration (0 for standard/cognitive configuration or 1 for high sensitivity/psychophysical configuration) rather than changing individual saccade detector parameters.

The types of the sample and event data that are recorded to the EDF file and available through the link during data collection are controlled by the following four commands: file_event_filter, link_event_filter, file_sample_data, and link_sample_data. For EyeLink 1000, 1000 Plus, and Portable Duo, we also record the target data ("HTARGET") when in the remote mode.

// SET UP TRACKER CONFIGURATION
// set parser saccade thresholds (conservative settings)
if(eyelink_ver>=2)
{
eyecmd_printf("select_parser_configuration 0"); // 0 = standard sensitivity
if(eyelink_ver == 2) //turn off scenelink camera stuff
{
eyecmd_printf("scene_camera_gazemap = NO");
}
}
else
{
eyecmd_printf("saccade_velocity_threshold = 35");
eyecmd_printf("saccade_acceleration_threshold = 9500");
}
// Select which events are saved in the EDF file. Include everything just in case
eyecmd_printf("file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT");
// Select which events are available online for gaze - contingent experiments. Include everything just in case
eyecmd_printf("link_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,BUTTON,FIXUPDATE,INPUT");
//Select which sample data is saved in EDF file or available online.Include everything just in case
// Check tracker version and include 'HTARGET' to save head target sticker data for supported eye trackers
eyecmd_printf("file_sample_data = LEFT,RIGHT,GAZE,HREF,PUPIL,AREA,GAZERES,BUTTON,STATUS%s,INPUT", (tracker_software_ver >= 4) ? ",HTARGET" : "");
eyecmd_printf("link_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS%s,INPUT", (tracker_software_ver >= 4) ? ",HTARGET" : "");

With the supported button box, button (#5) is often used by the participant to start trials, by terminating the drift correction. This command to the EyeLink tracker causes the button to be programmed to act like the ENTER key or spacebar on the tracker.

// Program button #5 for use in drift correction
eyecmd_printf("button_function 5 'accept_target_fixation'");

Running the Experiment

Everything is now set up. We check to make sure the program has not been terminated, then call run_trials() to perform a block of trials. This function is implemented in trials.c, and each of the templates implements this function differently. For a multiple-block experiment, you would call this function once for every block, and call this function in a loop. The function run_trials() returns an result code: if this is ABORT_EXPT, the program should exit the block loop and terminate the experiment.

// make sure we're still alive
return end_expt(our_file_name);
// RUN THE EXPERIMENTAL TRIALS (code depends on type of experiment)
// Calling run_trials() performs a calibration followed by trials
// This is equivalent to one block of an experiment
// It will return ABORT_EXPT if the program should exit
i = run_trials();
return end_expt(our_file_name);

Transferring the EDF file

Once all trials are run, the EDF file is closed and transferred via the link to the Display PC's hard disk. The tracker is set to Offline mode to speed the transfer. The receive_data_file() function is called to copy the file. If the file name is not known (i.e. opened from the Output menu on the tracker) the first argument can be "" to receive the last recorded data file. If the second argument is "", a dialog box will be displayed to allow the file to be renamed, or the transfer cancelled. The second argument could also be used to supply a name for the file, in which case the dialog box will not be displayed. If the last argument is not 0, the second argument is considered to be a path to store the new file in.

int end_expt(char * our_file_name)
{
//END: close, transfer EDF file
set_offline_mode(); // set offline mode so we can transfer file
pump_delay(500); // delay so tracker is ready
// close data file
eyecmd_printf("close_data_file");
return exit_eyelink(); // don't get file if we aborted experiment
if(our_file_name[0]&& eyelink_is_connected()) // make sure we created a file
{
close_expt_graphics(); // tell EXPTSPPT to release window
receive_data_file(our_file_name, get_output_folder(), 1);
}
// transfer the file, ask for a local name
return exit_eyelink();
}

Cleaning Up

Finally, the program cleans up. It un-registers the window with eyelink_core_graphics, closes the connection to the eye tracker, and closes its window.

int exit_eyelink()
{
// CLEANUP
close_expt_graphics(); // tell EXPTSPPT to release window
close_eyelink_connection(); // disconnect from tracker
return 0;
}

Extending the Experiment Setup

The code in app_main() is very basic. In a real experiment, you would need to add some of these functions:

Analysis of "trials.c"

This module performs the actions needed to perform block of experiment trials. There are two functions: run_trials() is called to loop through a set of trials; and do_simple_trial(), which supplies stimuli and trial identifiers for each trial, indexed by the trial number.

Initial Setup and Calibration

The run_trials() function begins by setting the calibration background color (which should match the average brightness of the screen in the following trials), then calling up the Camera Setup screen, for the experimenter to perform camera setup, calibration, and validation. Scheduling this at the start of each block gives the experimenter a chance to fix any setup problems, and calibration can be skipped by simply pressing the 'Esc' key immediately. This also allows the participant an opportunity for a break, as the headband can be removed and the entire setup repeated when the participant is reseated.

int run_trials(void)
{
int i;
int trial;
SETCOLOR(target_background_color ,255,255,255); // This should match the display
set_calibration_colors(&target_foreground_color, &target_background_color);
// PERFORM CAMERA SETUP, CALIBRATION

Trial Loop and Result Code Processing

Each block loops through a number of trials, which calls a function to execute the trial itself. The trial should return one of a set of trial return codes, which match those returned by the eyelink_core trial support functions. The trial return code should be interpreted by the block-of-trials loop to determine which trial to execute next, and to report errors via messages in the EDF file. The trial loop function should return the code ABORT_EXPT if a fatal error occurred, otherwise 0 or your own return code.

The standard trial return codes are listed below, and the appropriate message to be placed in the EDF file is also given. The example uses a switch() statement to handle the return codes.

Return CodeMessageCaused by
TRIAL_OK"TRIAL OK"Trial recorded successfully
TRIAL_ERROR"TRIAL ERROR"Error: could not record trial
ABORT_EXPT"EXPERIMENT ABORTED"Experiment aborted from EyeLink Abort menu, link disconnect or ALT-F4 key
SKIP_TRIAL"TRIAL SKIPPED"Trial terminated from EyeLink Abort menu
REPEAT_TRIAL"TRIAL REPEATED"Trial terminated from EyeLink Abort menu: repeat requested

The REPEAT_TRIAL function cannot always be implemented, because of randomization requirements or the experimental design. In this case, it should be treated as SKIP_TRIAL.

This is the code that executes the trials and processes the result code. This code can be used in your experiments, simply by changing the function called to execute each trial:

// loop through trials
for(trial=1;trial<=NTRIALS;trial++)
{
if(eyelink_is_connected()==0 || break_pressed()) // drop out if link closed
{
return ABORT_EXPT;
}
// RUN THE TRIAL
i = do_simple_trial(trial);
end_realtime_mode(); // safety: make sure realtime mode stopped
switch(i) // REPORT ANY ERRORS
{
case ABORT_EXPT: // handle experiment abort or disconnect
eyemsg_printf("EXPERIMENT ABORTED");
return ABORT_EXPT;
case REPEAT_TRIAL: // trial restart requested
eyemsg_printf("TRIAL REPEATED");
trial--;
break;
case SKIP_TRIAL: // skip trial
eyemsg_printf("TRIAL ABORTED");
break;
case TRIAL_OK: // successful trial
eyemsg_printf("TRIAL OK");
break;
default: // other error code
eyemsg_printf("TRIAL ERROR");
break;
}
} // END OF TRIAL LOOP
return 0;
}

Trial Setup Function

The actual trial is performed in two steps. The first is performed in the trials.c module, and does trial-specific setup. The second, in trial.c, presents the stimulus and performs the actual recording. This division of the code is ideal for most experiments, where every trial is recorded in the same way but only stimuli and trial identification differ. If trials do differ in function, the setup function can pass an argument to the trial execution function specifying trial type, or call the appropriate version of this function.

The trial setup is performed by do_simple_trial(). It first sets the trial identification title by sending the record_status_message command to the tracker. This message should be less than 80 characters long, and should contain the trial and block number and trial conditions. This message is displayed at the bottom right of the EyeLink tracker display during recording. This will let the experimenter know how far the experiment has progressed, and will be essential in fixing problems that the experimenter notices during recording.

"TRIALID" Message

The "TRIALID" message is sent to the EDF file next. This message must be placed in the EDF file before the drift correction and before recording begins, and is critical for data analysis. EyeLink Data Viewer by default uses the "TRIALID" message to mark the start of a recording trial; the actual information following the "TRIALID" message is not used by the software. Instead Data Viewer uses one or multiple "!V TRIAL_VAR" messages to report the trial condition information. Each "!V TRIAL_VAR" message should contain the label of a trial condition variable followed by the value of that variable.

If you plan to write your own data analysis tool, then you may have the "TRIALID" message contain information that uniquely identifies the trial for analysis, including a number or code for each independent variable.

int do_simple_trial(int num)
{
// This supplies the title at the bottom of the eyetracker display
eyecmd_printf("record_status_message 'SIMPLE WORDS, TRIAL %d/%d' ", num, NTRIALS);
// Always send a TRIALID message before starting to record.
// It marks the start of the trial and should precede any other messages
eyemsg_printf("TRIALID %s", trial_word[num - 1]);
// !V TRIAL_VAR message is recorded for EyeLink Data Viewer analysis
// It specifies a trial variable and value for the given trial
// This must be specified within the scope of an individual trial (i.e., after
// "TRIALID" and before "TRIAL_RESULT")
eyemsg_printf("!V TRIAL_VAR trial %d", num);
eyemsg_printf("!V TRIAL_VAR word %s", trial_word[num - 1]);

Tracker Feedback Graphics

Next, tracker background graphics are drawn on the EyeLink display, to allow the experimenter to evaluate participant performance and tracking errors using the real-time gaze cursor. Without these graphics, it is impossible to monitor the accuracy of data being produced by the EyeLink tracker, and to judge when a re-calibration is required. Very simple boxes around important details on the participant display are sufficient. For example, marking lines or words in text is sufficient for reading studies.

This is a list of the most useful drawing commands, which are further documented in the chapter "Useful EyeLink Commands".

CommandDrawing Operation
clear_screenClears EyeLink display to any color
draw_lineDraw a line in any color between two points
draw_boxOutlines a box in any color
draw_filled_boxDraws a filled rectangle in any color
draw_textPrints text in any color at any location
draw_crossDraws a small "+" target to indicate an important visual location

The background graphics can be very simple: in this case, a small box at the display center to mark the drift-correction target. The tracker must first be placed in Offline mode before drawing. Graphics drawn in Offline mode are saved for display in the next recording session.

The command "clear_screen" erases the tracker display to color 0 (black), and "draw_box" draws a small box in color 7 (medium gray). Drawing coordinates are the same as used for drawing on the local display: the EyeLink tracker converts these gaze coordinates before drawing.

// Before recording, we place reference graphics on the EyeLink display
set_offline_mode(); // Must be offline to draw to EyeLink screen
eyecmd_printf("clear_screen 0"); // clear tracker display and draw box at center
eyecmd_printf("draw_box %d %d %d %d 7", SCRWIDTH/2-16, SCRHEIGHT/2-16,
SCRWIDTH/2+16, SCRHEIGHT/2+16);

Executing the Trial

Finally, we can call the actual trial recording loop, supplying any data it needs. In this case, this is the text to display and the maximum recording time in milliseconds.

// Actually run the trial: display a single word
return simple_recording_trial(trial_word[num-1], 20000L);
}

Control by Scripts

Another method that can be used to sequence trials is to read a text file, interpreting each line as instructions for a single trial. These instructions might include the title of the trial (displayed at the bottom of the EyeLink tracker display during recording), the "TRIALID" message for the trial, location and identity of stimuli, and independent variable values.

The script file format could be of two types. The simplest contain one line per trial, containing all data needed to specify the trial conditions. A more flexible format defines one parameter with each line in the file, with the first word on the line specifying what the line contains. These lines might also be used to command tracker setup, or the display of an instruction display. Such scripted experiments do not require block or trial loops. Instead, a loop reads lines from the script file, interprets each line, and carries out its instructions. This loop must still interpret the return code from trials, and place proper messages into the EDF file. It may be helpful to record the script lines as messages in the EDF file as well, to help document the experiment.

Scripts can be generated by a separate C, Python, or other program, which will do the randomization, convert independent variables into program data (i.e. BMP file names for graphical stimuli, or saccade target positions), and create the "TRIALID" message and title lines. These are then written to a large text file. These files are also more easily analyzed for randomization errors than is the case for an experiment with a built-in randomizer.

Analysis of "trial.c"

The second part of the trial is the actual recording loop, implemented by simple_recording_trial(). This is the most basic recording loop, and is the basis for all the other template recording loops. It performs a drift correction, displays the graphics, records the data, and exits when the appropriate conditions are met. These include a response button press, the maximum recording time expired, the 'Esc' key pressed, or the program being terminated.

Overview of Recording

The sequence of operations for implementing the trial is:

Each of these sections of the code is described below.

Drift Correction/Drift Check

At the start of each trial, a fixation point should be displayed, and the participant needs to look at the target and then (the experimenter or the participant) confirm the gaze position by pressing a key. The tracker will estimate the current tracking accuracy with the reported gaze position and the physical position of the target.

This feature is known as drift-correction or drift-check, and one can think of it as a 1-point validation of tracking accuracy. The concept of drift correction is inherited from earlier head-mounted versions of the EyeLink eye-tracker (EyeLink I and II). In these models, the headband could slip and cause drifts in the gaze data, especially when operating the eye tracker in the pupil-only tracking mode. This issue was tackled by a linear correction of the gaze data based on the gaze error reported by the drift-correction procedure. For recent EyeLink eye-trackers(EyeLink 1000, 1000 Plus, and Portable Duo), by default, the gaze error is no longer used to correct the gaze data, as the systems are resilient to small head or camera displacement when performing recording in the pupil-CR mode. Instead, the drift-correction routine only performs a "drift-check"; it checks the tracking accuracy and allows users to recalibrate if necessary.

The eyelink_core DLL function do_drift_correct() implements this operation. The display coordinates where the target is to be displayed must be supplied. Usually this is at the center of the display, but could be anywhere that gaze should be located at the trial start. For example, it could be located over the first word in a page of text.

//NOTE: TRIALID AND TITLE MUST HAVE BEEN SET BEFORE DRIFT CORRECTION!
//FAILURE TO INCLUDE THESE MAY CAUSE INCOMPATIBILITIES WITH ANALYSIS SOFTWARE!
//DO PRE-TRIAL DRIFT CORRECTION
//We repeat if ESC key pressed to do setup.
while(1)
{
// Check link often so we can exit if tracker stopped
// We let do_drift_correct() draw target in this example
// 3rd argument would be 0 if we already drew the fixation target
error = do_drift_correct((INT16)(SCRWIDTH/2), (INT16) (SCRHEIGHT/2), 1, 1);
// repeat if ESC was pressed to access Setup menu
if(error!=27) break;
}
clear_full_screen_window(target_background_color); // make sure display is blank

In the template, we told do_drift_correct() to draw the drift correction display for us, by setting the third argument to 1. It cleared the screen to the calibration background color, drew the target, and cleared the screen again when finished. Usually, you will let eyelink_core clear the display and draw the drift correction target, as in the template. It's a good precaution to clear the display after drift correction completes - this should always cleared to target_background_color, to prevent abrupt display changes that could affect participant readiness.

Sometimes it is better to draw the target ourselves, for example if the drift correction is part of the initial fixation in a saccadic task and we want the target to stay on the screen after the drift correction. To do this, we pre-draw the target, then call do_drift_correct() with the third parameter set to 0.

If the 'Esc' key was pressed during drift correction, the EyeLink Camera Setup screen is called up to allow calibration problems to be corrected, and do_drift_correct() will return 27 (ESC_KEY). In this case, the drift correction should be repeated. Any fixation target that was pre-drawn must also be redrawn, as this will have cleared the display. Access to the camera setup mode can be disabled by setting the fourth argument to 0, which will cause the 'Esc' key press to simply abort the drift correction and return 27 (ESC_KEY).

Starting Recording

After drift correction, recording is initiated. Recording should begin in about 100 milliseconds before the trial graphics are displayed to the participant, to ensure that no data is lost.

//ensure the eye tracker has enough time to switch modes (to start recording)
// Start data recording to EDF file, BEFORE DISPLAYING STIMULUS
// You should always start recording 50-100 msec before required
// otherwise you may lose a few msec of data
error = start_recording(1,1,0,0); // record samples and events to EDF file only
if(error != 0) return error; // return error code if failed

The eyelink_core function start_recording() starts the EyeLink tracker recording, and does not return until recording has actually begun. If link data has been requested, it will wait for data to become available. If an error occurs or data is not available within 2 seconds, it returns an error code. This code should be returned as the trial result if recording fails. Note, the set_offline_mode() is called before start_recording() to make sure the eye tracker has enough time to switch modes (to start recording).

Four arguments to start_recording() set what data will be recorded to the EDF file and sent via the link. If an argument is 0, recording of the corresponding data is disabled. At least one of the data selectors must be enabled. This is the prototype and arguments to the function:

INT16 start_recording(INT16 file_samples, INT16 file_events, INT16 link_samples, INT16 link_events);

ArgumentControls
file_samples Enables writing of samples to EDF file
file_events Enables writing of events to EDF file
link_samples Enables real-time samples through link
link_events Enables real-time events through link

The type of data recorded to the EDF file affects the file size and what processing can be done with the file later. If only events are recorded, the file will be fairly small (less than 300 kilobytes for a 30-minute experiment) and can be used for analysis of cognitive tasks such as reading. Adding samples to the file increases its size (about several megabytes for a 30-minute experiment) but allows the file to be viewed with Data Viewer software, and reprocessed to remove artifacts if required. The data stored in the EDF file also sets the data types available for post-trial playback.

We also introduce a 100 millisecond delay after recording begins (using begin_realtime_mode(), discussed below), to ensure that no data is missed before the important part of the trial starts. The EyeLink tracker requires 10 to 30 milliseconds after the recording command to begin writing data. This extra data also allows the detection of blinks or saccades just before the trial start, allowing bad trials to be discarded in saccadic RT analysis. A "DISPLAY ON" message later in the trial marks the actual zero-time (stimulus onset) in the trial's data record for Data Viewer.

// record for 100 msec before displaying stimulus
begin_realtime_mode(100); // Windows: no interruptions till display start marked

Starting Realtime Mode

Under Windows, it is possible to place your application in realtime priority mode - this forces Windows to stop most other activities, including background disk access, that might cause your experiment to have unpredictable delays. This will make the timing of stimulus presentation more predictable, and should be used whenever possible.

Realtime mode is entered by calling begin_realtime_mode(), which implements the correct procedure for whatever version of Windows is running the application. This function may take up to 100 milliseconds to return, and this delay may change in future versions of the eyelink_core DLL as new operating systems are introduced. The minimum delay of begin_realtime_mode() may be specified (100 milliseconds in this template, to allow recording of data before the display start) so that this delay can serve a useful purpose. We end realtime mode with end_realtime_mode(), which has no significant delay.

In the case of the simple template, the only critical temporal section is marking the onset of the display by a message in the EDF file. We begin realtime mode after recording starts, and will end it after the display-onset messages for the EDF file have been sent to the EyeLink tracker. In this experiment, we don't need refresh-locked accuracy in determining when to erase the stimulus word. If we needed accurate display timing (as we will in some other examples), we will not exit realtime mode until the end of the trial. The eyelink_core recording support functions check_recording() will end realtime mode if an error occurs, and calling check_record_exit() at the end of the trial function will also return to normal mode.

The major problem with realtime mode is that certain system functions simply cease to work - so try to exit realtime mode as soon as it is no longer needed. You will not be able to play sounds, and the keyboard may not work properly while in realtime mode, especially with earlier versions of Windows. Using getkey() will probably return key presses, but it does this at the expense of allowing Windows to do background activity. This appears as unpredictable delays that can be as long as 20 milliseconds (but are usually 5-10 milliseconds). Therefore you should try to avoid using the Display PC keyboard for control or participant responses (keyboards are not very time accurate in any case). Instead, use last_button_press() to detect tracker button responses, break_pressed() to detect program shutdown, eyelink_is_connected() to detect tracker disconnection, check_recording() to detect recording aborted by the eye tracker, and, if required, eyelink_read_keybutton() to monitor the EyeLink tracker keyboard.

Drawing the Participant Display

The simple template uses the simplest possible graphics for clarity, drawing a single word of text to the display. We let eyelink_core to draw the drift correction target, so it also clears the display for us. The word is drawn rapidly, so we don't need to draw to a bitmap and copy it to the display. If a lot of text were being drawn, it might be useful to first draw the text in the background color - this allows Windows to generate and cache the font bitmaps internally (this is not demonstrated here).

// DISPLAY OUR IMAGE TO PARTICIPANT
// If graphics are very simple, you may be able to draw them
// in one refresh period. Otherwise, draw to a bitmap first.
get_new_font(NULL, SCRWIDTH/25, 1); // select font for drawing
// Because of faster drawing speeds and good refresh locking,
// we now place the stimulus onset message just after display refresh
// and before drawing the stimulus. This is accurate and will allow
// drawing a new stimulus each display refresh.
// However, we do NOT send the message after the retrace--this may take too long
// instead, we add a number to the message that represents the delay
// from the event to the message in msec
graphic_printf(window, target_foreground_color, CENTER, // Draw the stimulus, centered
SCRWIDTH/2, height, "%s", text);
Flip(window);
drawing_time = current_msec(); // time of retrace
trial_start = drawing_time;
graphic_printf(window, target_foreground_color, CENTER, // Draw the stimulus, centered
SCRWIDTH/2, height, "%s", text);
drawing_time = current_msec()-drawing_time; // delay from retrace (time to draw)
eyemsg_printf("%d DISPLAY ON", drawing_time); // message for RT recording in analysis

The drawing procedure uses Flip(), which does not return until the next screen refresh really occurs. Immediately following this function, the time of the retrace is read. This allows us to determine the time of stimulus onset (actually the stimulus is painted on the monitor phosphors at a delay dependent on its vertical position on the display, but this can be corrected for during analysis). If additional drawing is made, the time is read again, and used to compute the delay from the retrace. This delay is included in the messages "DISPLAY ON", which are then placed in the EDF file to mark the time the stimulus appeared. (NOTE that the delay is placed first in the "DISPLAY ON" message - this allows new EyeLink Data Viewer to automatically adjust the message time, and this method should be used in all messages where delay must be corrected for). Synchronizing with the display refresh before drawing and recording the delay from the retrace to the message writing will allow accurate calculation of the time of stimulus onset, as the refresh time can be recomputed by subtracting the delay from the message timestamp. The offset between the time that the stimulus appeared on the monitor and the message timestamp depends only on the stimulus vertical position.

Recording Loop

With recording started and the stimulus visible, we wait for an event to occur that ends the trial. In the template, we look for the maximum trial duration to be exceeded, the 'Esc' key on the local keyboard to be pressed, a button press from the EyeLink button box, or the program being terminated.

Any tracker buttons pressed before the trial, and any pending events from the local keyboard are discarded:

// we would stay in realtime mode if timing is critical
// for example, if a dynamic (changing) stimulus was used
// or if display duration accuracy of 1 video refresh. was needed
// we don't care as much about time now, allow keyboard to work
// Now get ready for trial loop
eyelink_flush_keybuttons(0); // reset keys and buttons from tracker
// we don't use getkey() especially in a time-critical trial
// as Windows may interrupt us and cause an unpredictable delay
// so we would use buttons or tracker keys only
// Trial loop: till timeout or response

The main part of the trial is a loop that tests for error conditions and response events. Any such event will stop recording and exit the loop, or return an error code. The process of halting recording and blanking the display is implemented as a small local function, because it is done from several places in the loop:

// End recording: adds 100 msec of data to catch final events
static void end_trial(void)
{
clear_full_screen_window(target_background_color); // hide display
end_realtime_mode(); // NEW: ensure we release realtime lock
pump_delay(100); // CHANGED: allow Windows to clean up
// while we record additional 100 msec of data
}

The trial recording loop tests for recording errors, trial timeout, the local 'Esc' key, program termination, or tracker button presses. The first test in the loop detects recording errors or aborts, and handles EyeLink Abort menu selections. It also stops recording, and returns the correct result code for the trial:

// First, check if recording aborted
if((error=check_recording())!=0) return error;

Next, the trial duration is tested. This could be used to implement fixed- duration trials, or to limit the time a participant is allowed to respond. When the trial times out, a "TIMEOUT" message is placed in the EDF file and the trial is stopped. The local end_trial() function will properly clear the display, and stop recording after an additional 100 milliseconds.

// Check if trial time limit expired
if(current_time() > trial_start+time_limit)
{
eyemsg_printf("TIMEOUT"); // message to log the timeout
end_trial(); // local function to stop recording
button = 0; // trial result message is 0 if timeout
break; // exit trial loop
}

Next, the local keyboard is checked to see if we should abort the trial. This is most useful for testing and debugging an experiment. There is some time penalty for using getkey() in the recording loop on the Display PC, as this function allows Windows multitasking and background disk activity to occur. It is preferable to use escape_pressed() and break_pressed() to test for termination without processing system messages, as these do not allow interruption.

if(break_pressed()) // check for program termination or ALT-F4 or CTRL-C keys
{
end_trial(); // local function to stop recording
return ABORT_EXPT; // return this code to terminate experiment
}
if(escape_pressed()) // check for local ESC key to abort trial (useful in debugging)
{
end_trial(); // local function to stop recording
return SKIP_TRIAL; // return this code if trial terminated
}

Finally, we check for a participant response to end the trial. The tracker button box is the preferred way to collect a response, as it accurately records the time of the response. Using eyelink_last_button_press() is the simplest way to check for new button presses since the last call to this function. It returns 0 if no button has been pressed since recording started, or the button number. This function reports only the latest button press, and so could miss multiple button presses if not called often. This is not usually a problem, as all button presses will be recorded in the EDF file.

// BUTTON RESPONSE TEST
// Check for eye-tracker buttons pressed
// This is the preferred way to get response data or end trials
if(button!=0) // button number, or 0 if none pressed
{
eyemsg_printf("ENDBUTTON %d", button); // message to log the button press
end_trial(); // local function to stop recording
break; // exit trial loop
}

The message "ENDBUTTON" is placed in the EDF data file to record the button press. The timestamp of this message is not as accurate as the button press event that is also recorded in the EDF file, but serves to record the reason for ending the trial. The "ENDBUTTON" message can also be used if the local keyboard as well as buttons can be used to respond, with keys translated into a button number.

Cleaning Up and Reporting Trial Results

After exiting the recording loop, it's good programming practice to prevent anything that happened during the trial from affecting later code, in this case by making sure we are out of realtime mode and by discarding any accumulated key presses. Remember, it's better to have some extra code than to waste time debugging unexpected problems.

} // END OF RECORDING LOOP
end_realtime_mode(); // safety cleanup code
while(getkey()); // dump any accumulated key presses

Finally, the trial is completed by reporting the trial result and returning the result code. The standard message "TRIAL_RESULT" records the button pressed, or 0 if the trial timed out. This message is used during analysis to determine why the recording stopped. Note Data Viewer also uses the "TRIAL_RESULT" message to mark the end of a recording trial. Any post-recording participant responses (i.e. questionnaires, etc.) should be performed before returning from the trial, to place them before the "TRIAL_RESULT" message. Finally, the call to the check_record_exit() function makes sure that no Abort menu operations remain to be performed, and generates the trial return code.

// report response result: 0=timeout, else button number
eyemsg_printf("TRIAL_RESULT %d", button);
// Call this at the end of the trial, to handle special conditions
}

Extending "trial.c"

For many experiments, this code can be used with few modifications, except for changing the stimulus drawn. Another common enhancement would be to filter which buttons can be used for response.

A more sophisticated modification would be to animate the display. This might involve erasing and redrawing fixation targets for saccadic response experiments. For this experiment, the time each target is drawn and erased must be controlled and monitored carefully, which required leaving realtime mode on throughout the trial. Checking for recording errors or responses can be postponed until after the display sequence, unless it is very long (for example, a continuously moving smooth pursuit target).

Be sure to call eyemsg_printf() to place a message in the EDF file to record the time the display is changed. Don't send more than 10 messages every 20 milliseconds, or the EyeLink tracker may not be able to write them all to the EDF file.

This sample code illustrates the above concepts:

SDL_FlipEx(window);// synchronized flip. This will return after the flip occur
eyemsg_printf("TARGET MOVED %d %d", x, y); // record time, new position
eyemsg_printf("MARK 1"); // time marker #1

Copyright ©2002-2023, SR Research Ltd.