Working with eye tracker data in MNE-Python#

In this tutorial we will load some eye tracker data and plot the average pupil response to light flashes (i.e. the pupillary light reflex).

# Authors: Dominik Welke <dominik.welke@web.de>
#          Scott Huberty <scott.huberty@mail.mcgill.ca>
#
# License: BSD-3-Clause

Data loading#

First we will load an eye tracker recording from SR research’s proprietary '.asc' file format.

By default, Eyelink files will output events for occular events (blinks, saccades, fixations), and experiment messages. MNE will store these events as mne.Annotations. If we are only interested in certain event types from the Eyelink file, we can select for these using the 'create_annotations' argument of mne.io.read_raw_eyelink. Here, we will only create annotations for blinks, and experiment messages.

The info structure tells us we loaded a monocular recording with 2 'eyegaze', channels (X/Y), 1 'pupil' channel, and 1 'stim' channel.

from mne import Epochs, find_events
from mne.io import read_raw_eyelink
from mne.datasets.eyelink import data_path

eyelink_fname = data_path() / "mono_multi-block_multi-DINS.asc"

raw = read_raw_eyelink(eyelink_fname, create_annotations=["blinks", "messages"])
raw.crop(tmin=0, tmax=146)
Loading /home/circleci/mne_data/eyelink-example-data/mono_multi-block_multi-DINS.asc
Pixel coordinate data detected.
Pass `scalings=dict(eyegaze=1e3)` when using plot method to make traces more legible.
Pupil-size area reported.
There are 4 recording blocks in this file. Times between blocks will be annotated with bad_rec_gap.
Measurement date November 25, 2022 17:10:47 GMT
Experimenter Unknown
Participant Unknown
Digitized points Not available
Good channels 2 Eye-tracking (Gaze position), 1 Eye-tracking (Pupil size), 1 Stimulus
Bad channels None
EOG channels Not available
ECG channels Not available
Sampling frequency 1000.00 Hz
Highpass 0.00 Hz
Lowpass 500.00 Hz
Filenames mono_multi-block_multi-DINS.asc
Duration 00:02:26 (HH:MM:SS)


Get stimulus events from DIN channel#

Eyelink eye trackers have a DIN port that can be used to feed in stimulus or response timings. mne.io.read_raw_eyelink() loads this data as a 'stim' channel. Alternatively, the onset of stimulus events could be sent to the eyetracker as messages - these can be read in as mne.Annotations.

In the example data, the DIN channel contains the onset of light flashes on the screen. We now extract these events to visualize the pupil response.

events = find_events(raw, "DIN", shortest_event=1, min_duration=0.02, uint_cast=True)
event_dict = {"flash": 3}
16 events found
Event IDs: [3]

Plot raw data#

As the following plot shows, we now have a raw object with the eye tracker data, eyeblink annotations and stimulus events (from the DIN channel).

The plot also shows us that there is some noise in the data (not always categorized as blinks). Also, notice that we have passed a custom dict into the scalings argument of raw.plot. This is necessary to make the eyegaze channel traces legible when plotting, since the file contains pixel position data (as opposed to eye angles, which are reported in radians).

raw.plot(
    events=events,
    event_id={"Flash": 3},
    event_color="g",
    start=25,
    duration=45,
    scalings=dict(eyegaze=1e3),
)
Raw plot

Plot average pupil response#

We now visualize the pupillary light reflex. Therefore, we select only the pupil channel and plot the evoked response to the light flashes.

As we see, there is a prominent decrease in pupil size following the stimulation. The noise starting about 2.5 s after stimulus onset stems from eyeblinks and artifacts in some of the 16 trials.

epochs = Epochs(raw, events, tmin=-0.3, tmax=5, event_id=event_dict, preload=True)
epochs.pick_types(eyetrack="pupil")
epochs.average().plot()
Eye-tracking (Pupil size) (1 channel)
Not setting metadata
16 matching events found
Setting baseline interval to [-0.3, 0.0] s
Applying baseline correction (mode: mean)
0 projection items activated
Using data from preloaded Raw for 16 events and 5301 original time points ...
0 bad epochs dropped
NOTE: pick_types() is a legacy function. New code should use inst.pick(...).
NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).

It is important to note that pupil size data are reported by Eyelink (and stored internally by MNE) as arbitrary units (AU). While it often can be preferable to convert pupil size data to millimeters, this requires information that is not always present in the file. MNE does not currently provide methods to convert pupil size data. See Importing Data from Eyetracking devices for more information on pupil size data.

Total running time of the script: ( 0 minutes 6.911 seconds)

Estimated memory usage: 128 MB

Gallery generated by Sphinx-Gallery