Skip to main content
Ctrl+K
pymovements  documentation - Home pymovements  documentation - Home
  • User Guide
  • Tutorials
  • Datasets
  • API Reference
  • Contributing
    • About Us
    • Bibliography
  • GitHub
  • User Guide
  • Tutorials
  • Datasets
  • API Reference
  • Contributing
  • About Us
  • Bibliography
  • GitHub

Section Navigation

  • pymovements in 10 minutes
  • Downloading Public Datasets
  • Working with a Local Dataset
  • Parsing SR Research EyeLink Data
  • Plotting Gaze Data
  • Preprocessing Raw Gaze Data
  • Saving and Loading Preprocessed Data
  • Handling Gaze Events
  • Creating Synthetic Data
  • Detecting Blinks from the Pupil Signal
  • Cleaning Gaze Data During Blinks
  • How to use pymovements in R
  • Tutorials
  • Parsing SR Research EyeLink Data

Parsing SR Research EyeLink Data#

What you will learn in this tutorial:#

  • how to parse raw eye tracking files created with SR Research EyeLink

  • how to extract experiment information using patterns

  • how to create a custom dataset definition to load a complete dataset of multiple files

Preparations#

We import pymovements as the alias pm for convenience.

import pymovements as pm

Let’s start by downloading a toy dataset ToyDatasetEyeLink that contains *.asc files:

dataset = pm.Dataset("ToyDatasetEyeLink", path='data/ToyDatasetEyeLink')
dataset.download()
INFO:pymovements.dataset.dataset:
        You are downloading the pymovements Toy Dataset EyeLink. Please be aware that pymovements does not
        host or distribute any dataset resources and only provides a convenient interface to
        download the public dataset resources that were published by their respective authors.

        Please cite the referenced publication if you intend to use the dataset in your research.
        
Downloading https://github.com/pymovements/pymovements-toy-dataset-eyelink/archive/refs/heads/main.zip to data/ToyDatasetEyeLink/downloads/pymovements-toy-dataset-eyelink.zip
Checking integrity of pymovements-toy-dataset-eyelink.zip
Extracting pymovements-toy-dataset-eyelink.zip to data/ToyDatasetEyeLink/raw
Extracting archive:   0%|          | 0/4 [00:00<?, ?file/s]
Extracting archive: 100%|██████████| 4/4 [00:00<00:00, 122.82file/s]

Dataset
  • DatasetDefinition
    DatasetDefinition
    • name:
      'ToyDatasetEyeLink'
    • long_name:
      'pymovements Toy Dataset EyeLink'
    • 'Example toy dataset with EyeLink data. This datas...'
      'Example toy dataset with EyeLink data.\n\nThis dataset includes monocular eye tracking data from a single participant in a single\nsession. Eye movements are recorded at a sampling frequency of 1000 Hz using an EyeLink Portable\nDuo video-based eye tracker and are provided as pixel coordinates.\n\nThe participant is instructed to read a single text and some JuDo trials.\n'
    • Experiment
      Experiment
      • EyeTracker
        EyeTracker
        • left:
          True
        • model:
          'EyeLink Portable Duo'
        • mount:
          None
        • right:
          False
        • sampling_rate:
          1000.0
        • vendor:
          'EyeLink'
        • version:
          None
      • Screen
        Screen
        • distance_cm:
          68
        • height_cm:
          30.2
        • height_px:
          1024
        • origin:
          'upper left'
        • tuple (2 items)
          • 1280
          • 1024
        • tuple (2 items)
          • 38
          • 30.2
        • width_cm:
          38
        • width_px:
          1280
        • x_max_dva:
          15.599386487782953
        • x_min_dva:
          -15.599386487782953
        • y_max_dva:
          12.508044410882546
        • y_min_dva:
          -12.508044410882546
    • list (1 items)
      • ResourceDefinition
        • content:
          'gaze'
        • filename:
          'pymovements-toy-dataset-eyelink.zip'
        • filename_pattern:
          'subject_{subject_id:d}_session_{session_id:d}.asc'
        • dict (2 items)
          • subject_id:
            <class 'int'>
          • session_id:
            <class 'int'>
        • load_function:
          None
        • dict (4 items)
          • list (2 items)
            • 'task'
            • 'trial_id'
          • list (9 items)
              • pattern:
                'SYNCTIME_READING_SCREEN'
              • column:
                'task'
              • (1 more)
              • pattern:
                'SYNCTIME_JUDO'
              • column:
                'task'
              • (1 more)
            • (7 more)
          • (2 more)
        • md5:
          '966c0b6aefe61f32942366ed719454d3'
        • mirrors:
          None
        • WebSource
          WebSource(url='https://github.com/pymovements/pymovements-toy-dataset-eyelink/archive/refs/heads/main.zip', filename='pymovements-toy-dataset-eyelink.zip', md5='966c0b6aefe61f32942366ed719454d3', mirrors=None)
        • 'https://github.com/pymovements/pymovements-toy-dat...'
          'https://github.com/pymovements/pymovements-toy-dataset-eyelink/archive/refs/heads/main.zip'
  • events:
    tuple (0 items)
  • DataFrame (0 columns, 0 rows)
    shape: (0, 0)
  • gaze:
    list (0 items)
  • Participants
    Participants
    • DataFrame (1 columns, 0 rows)
      shape: (0, 1)
      participant_id
      str
    • dict (1 items)
      • dict (1 items)
        • Format:
          'string'
  • path:
    PosixPath('data/ToyDatasetEyeLink')
  • DatasetPaths
    DatasetPaths
    • dataset:
      PosixPath('data/ToyDatasetEyeLink')
    • downloads:
      PosixPath('data/ToyDatasetEyeLink/downloads')
    • events:
      PosixPath('data/ToyDatasetEyeLink/events')
    • precomputed_events:
      PosixPath('data/ToyDatasetEyeLink/precomputed_events')
    • precomputed_reading_measures:
      PosixPath('data/ToyDatasetEyeLink/precomputed_reading_measures')
    • preprocessed:
      PosixPath('data/ToyDatasetEyeLink/preprocessed')
    • raw:
      PosixPath('data/ToyDatasetEyeLink/raw')
    • root:
      PosixPath('data/ToyDatasetEyeLink')
    • stimuli:
      PosixPath('data/ToyDatasetEyeLink/stimuli')
  • precomputed_events:
    list (0 items)
  • precomputed_reading_measures:
    list (0 items)
  • stimuli:
    list (0 items)

This dataset includes *.asc files that store raw eye-tracking data along with synchronization messages. Below, we’ll inspect the files included in the dataset:

asc_files = list(dataset.path.glob('**/*.asc'))
asc_files
[PosixPath('data/ToyDatasetEyeLink/raw/pymovements-toy-dataset-eyelink-main/raw/subject_1_session_1.asc'),
 PosixPath('data/ToyDatasetEyeLink/raw/pymovements-toy-dataset-eyelink-main/raw/subject_2_session_1.asc')]

Let’s display the first 20 lines of one of the files to get a sense of its structure:

!head -n 20 data/ToyDatasetEyeLink/raw/pymovements-toy-dataset-eyelink-main/raw/subject_1_session_1.asc
** CONVERTED FROM D:\SamplePymovements\results\sub_1\sub_1.edf using edfapi 4.2.1 Win32  EyeLink Dataviewer Sub ComponentApr 12 2021 on Fri Mar 10 18:07:57 2023

** DATE: Wed Mar  8 09:25:20 2023

** TYPE: EDF_FILE BINARY EVENT SAMPLE TAGGED

** VERSION: EYELINK II 1

** SOURCE: EYELINK CL

** EYELINK II CL v6.12 Feb  1 2018 (EyeLink Portable Duo)

** CAMERA: EyeLink USBCAM Version 1.01

** SERIAL NUMBER: CLU-DAB50

** CAMERA_CONFIG: DAB50200.SCD

** RECORDED BY SleepAlc

** SREB2.2.299 WIN32 LID:20A87A96 Mod:2023.03.08 11:03 MEZ

**



MSG	2091650 !CMD 1 select_parser_configuration 0

MSG	2091659 !CMD 0 fixation_update_interval = 50

MSG	2091659 !CMD 0 fixation_update_accumulate = 50

MSG	2091681 !CMD 1 auto_calibration_messages = YES

MSG	2095865 DISPLAY_COORDS 0 0 1279 1023

MSG	2095865 RETRACE_INTERVAL  16.646125144

MSG	2095865 ENVIRONMENT   OpenGL on Windows (6, 2, 9200, 2, '')

We can see that this file is a converted version of an *.edf file created by EyeLink.

Let’s try loading one of these files directly using pm.gaze.from_asc:

Loading eye-tracking data from a file#

Loading eye-tracking data is straightforward. You can load an .asc file with a single call to pm.gaze.from_asc:

gaze = pm.gaze.from_asc(file=asc_files[0])
gaze
Gaze
  • DataFrame (3 columns, 128342 rows)
    shape: (128_342, 3)
    timepupilpixel
    i64f64list[f64]
    2154556778.0[138.1, 132.8]
    2154557778.0[138.2, 132.7]
    2154558778.0[138.2, 132.3]
    2154559778.0[138.1, 131.9]
    2154560777.0[137.9, 131.6]
    ………
    2339287619.0[637.7, 531.7]
    2339288619.0[637.9, 531.8]
    2339289618.0[637.8, 531.6]
    2339290618.0[637.6, 531.4]
    2339291618.0[637.3, 531.2]
  • Events
    Events
    • DataFrame (4 columns, 0 rows)
      shape: (0, 4)
      nameonsetoffsetduration
      stri64i64i64
    • trial_columns:
      None
  • metadata:
    dict (0 items)
  • messages:
    None
  • trial_columns:
    None
  • Experiment
    Experiment
    • EyeTracker
      EyeTracker
      • left:
        True
      • model:
        'EyeLink Portable Duo'
      • mount:
        'Desktop'
      • right:
        False
      • sampling_rate:
        1000.0
      • vendor:
        'EyeLink'
      • version:
        '6.12'
    • Screen
      Screen
      • distance_cm:
        None
      • height_cm:
        None
      • height_px:
        1024
      • origin:
        None
      • tuple (2 items)
        • 1280
        • 1024
      • size:
        None
      • width_cm:
        None
      • width_px:
        1280

This function automatically loads the raw eye-tracking data and attempts to infer the experimental settings used.

Let’s inspect a few rows from the resulting GazeDataFrame:

gaze.samples
shape: (128_342, 3)
timepupilpixel
i64f64list[f64]
2154556778.0[138.1, 132.8]
2154557778.0[138.2, 132.7]
2154558778.0[138.2, 132.3]
2154559778.0[138.1, 131.9]
2154560777.0[137.9, 131.6]
………
2339287619.0[637.7, 531.7]
2339288619.0[637.9, 531.8]
2339289618.0[637.8, 531.6]
2339290618.0[637.6, 531.4]
2339291618.0[637.3, 531.2]

We can see that timestamps (column time), pupil diameter (column pupil), and raw pixel coordinates (column pixel) are extracted automatically.

Let’s now take a look at the experimental metadata that was retrieved:

gaze.experiment
Experiment
  • EyeTracker
    EyeTracker
    • left:
      True
    • model:
      'EyeLink Portable Duo'
    • mount:
      'Desktop'
    • right:
      False
    • sampling_rate:
      1000.0
    • vendor:
      'EyeLink'
    • version:
      '6.12'
  • Screen
    Screen
    • distance_cm:
      None
    • height_cm:
      None
    • height_px:
      1024
    • origin:
      None
    • tuple (2 items)
      • 1280
      • 1024
    • size:
      None
    • width_cm:
      None
    • width_px:
      1280

All relevant experimental metadata have been successfully extracted, such as the eye tracker model and the screen resolution used during recording.

Loading eye-tracking data along with SR Research recording messages#

To extract all MSG-prefixed SR Research messages, simply pass True to the pm.gaze.from_asc. The messages are stored in gaze.messages:

gaze = pm.gaze.from_asc(file=asc_files[0], messages=True)
gaze.messages
shape: (2_359, 2)
timecontent
f64str
2.09165e6"!CMD 1 select_parser_configura…
2.091659e6"!CMD 0 fixation_update_interva…
2.091659e6"!CMD 0 fixation_update_accumul…
2.091681e6"!CMD 1 auto_calibration_messag…
2.095865e6"DISPLAY_COORDS 0 0 1279 1023"
……
2.339982e6"!V TRIAL_VAR forid "
2.339983e6"!V TRIAL_VAR sessiontype "
2.339984e6"!V TRIAL_VAR combinationid -32…
2.339985e6"TRIAL_RESULT 0"
2.342322e6"JUDO.STOP"

We can also control which messages are parsed by specifying them in the messages argument. For example, to extract only trial-related messages containing the keyword TRIAL, we can do the following:

gaze = pm.gaze.from_asc(file=asc_files[0], messages=['TRIAL'])
gaze.messages
shape: (2_027, 2)
timecontent
f64str
2.15454e6"TRIALID 0"
2.222716e6"!V TRIAL_VAR Session_Name_ sub…
2.222717e6"!V TRIAL_VAR Trial_Index_ 1"
2.222718e6"!V TRIAL_VAR RT_KAROLINSKA -1"
2.222719e6"!V TRIAL_VAR RESPONSE_KAROLINS…
……
2.339981e6"!V TRIAL_VAR q00corrans "
2.339982e6"!V TRIAL_VAR forid "
2.339983e6"!V TRIAL_VAR sessiontype "
2.339984e6"!V TRIAL_VAR combinationid -32…
2.339985e6"TRIAL_RESULT 0"

Defining custom patterns for data extraction#

Now let’s define our own patterns to extract additional information from the *.asc files and add them to the GazeDataFrame. We can do this using the parameter patterns using pm.gaze.from_asc.

patterns accepts either a list of custom patterns to match additional columns or a key identifying predefined and eye-tracker-specific patterns.

Let’s define a set of custom patterns to extract more information from parsed messages and show the resulting GazeDataFrame:

patterns = [
    {
        'pattern': 'SYNCTIME_READING_SCREEN',
        'column': 'task',
        'value': 'reading',
    },
    {
        'pattern': 'SYNCTIME_JUDO',
        'column': 'task',
        'value': 'judo',
    },
    r'TRIALID (?P<trial_id>\d+)',
]

gaze = pm.gaze.from_asc(file=asc_files[0], patterns=patterns)
gaze.samples
shape: (128_342, 5)
timepupiltasktrial_idpixel
i64f64strstrlist[f64]
2154556778.0null"0"[138.1, 132.8]
2154557778.0null"0"[138.2, 132.7]
2154558778.0null"0"[138.2, 132.3]
2154559778.0null"0"[138.1, 131.9]
2154560777.0null"0"[137.9, 131.6]
……………
2339287619.0"judo""12"[637.7, 531.7]
2339288619.0"judo""12"[637.9, 531.8]
2339289618.0"judo""12"[637.8, 531.6]
2339290618.0"judo""12"[637.6, 531.4]
2339291618.0"judo""12"[637.3, 531.2]

The examples above illustrate that patterns can be defined in different forms. Some patterns simply match a message and assign a fixed column value (see the first pattern above), while others use regular expressions to capture dynamic information—for instance, the trial_id in the last pattern.

Given the patterns defined above, we can see that the columns for task and trial_id has been added.

The trial_id was extracted from messages such as MSG 2762689 TRIALID 0, while the task value was obtained from messages like MSG 2814942 SYNCTIME_JUDO.

Writing a DatasetDefinition to parse the complete dataset#

Let’s create a custom DatasetDefinition to load all asc files, including the patterns we defined earlier.

First we create a ResourceDefinition that specifies how we want to load our asc files. We can use the patterns that we identified and specify them as one of the load keyword arguments (load_kwargs).

In addition, we also define the filename pattern, which represents subject and session information encoded in the filename. The datatypes of the additional metadata parsed from the filename can be specified via filename_pattern_schema_overrides.

resource_definition = pm.ResourceDefinition(
    content='gaze',
    filename_pattern=r'subject_{subject_id:d}_session_{session_id:d}.asc',
    filename_pattern_schema_overrides={
        'subject_id': int,
        'session_id': int,
    },
    load_kwargs={
        'patterns': patterns,
        'schema': {'trial_id': int},
    },
)

Next, we need to define the experiment:

experiment = pm.Experiment(
    screen_width_px=1280,
    screen_height_px=1024,
    screen_width_cm=38,
    screen_height_cm=30.2,
    distance_cm=68,
    origin='lower left',
    sampling_rate=1000,
)

We now use these to write our DatasetDefinition. We choose ToyDatasetEyeLink as the name.

dataset_definition = pm.DatasetDefinition(
    name='ToyDatasetEyeLink',
    experiment=experiment,
    resources=[resource_definition],
)

Let’s initialize a new Dataset and load the data using the dataset definition we just set up:

dataset = pm.Dataset(
    definition=dataset_definition,
    path='data/ToyDatasetEyeLink',
)
dataset.load()
Dataset
  • DatasetDefinition
    DatasetDefinition
    • name:
      'ToyDatasetEyeLink'
    • long_name:
      None
    • description:
      None
    • Experiment
      Experiment
      • EyeTracker
        EyeTracker
        • left:
          None
        • model:
          None
        • mount:
          None
        • right:
          None
        • sampling_rate:
          1000
        • vendor:
          None
        • version:
          None
      • Screen
        Screen
        • distance_cm:
          68
        • height_cm:
          30.2
        • height_px:
          1024
        • origin:
          'lower left'
        • tuple (2 items)
          • 1280
          • 1024
        • tuple (2 items)
          • 38
          • 30.2
        • width_cm:
          38
        • width_px:
          1280
    • list (1 items)
      • ResourceDefinition
        • content:
          'gaze'
        • filename:
          None
        • filename_pattern:
          'subject_{subject_id:d}_session_{session_id:d}.asc'
        • dict (2 items)
          • subject_id:
            <class 'int'>
          • session_id:
            <class 'int'>
        • load_function:
          None
        • dict (2 items)
          • list (3 items)
              • pattern:
                'SYNCTIME_READING_SCREEN'
              • column:
                'task'
              • (1 more)
              • pattern:
                'SYNCTIME_JUDO'
              • column:
                'task'
              • (1 more)
            • (1 more)
          • dict (1 items)
            • trial_id:
              <class 'int'>
        • md5:
          None
        • mirrors:
          None
        • source:
          None
        • url:
          None
  • tuple (2 items)
    • Events
      • DataFrame (4 columns, 0 rows)
        shape: (0, 4)
        nameonsetoffsetduration
        stri64i64i64
      • trial_columns:
        None
    • Events
      • DataFrame (4 columns, 0 rows)
        shape: (0, 4)
        nameonsetoffsetduration
        stri64i64i64
      • trial_columns:
        None
  • dict (1 items)
    • DataFrame (3 columns, 2 rows)
      shape: (2, 3)
      subject_idsession_idfilepath
      i64i64str
      11"pymovements-toy-dataset-eyelin…
      21"pymovements-toy-dataset-eyelin…
  • list (2 items)
    • Gaze
      • DataFrame (5 columns, 128342 rows)
        shape: (128_342, 5)
        timepupiltasktrial_idpixel
        i64f64stri64list[f64]
        2154556778.0null0[138.1, 132.8]
        2154557778.0null0[138.2, 132.7]
        2154558778.0null0[138.2, 132.3]
        2154559778.0null0[138.1, 131.9]
        2154560777.0null0[137.9, 131.6]
        ……………
        2339287619.0"judo"12[637.7, 531.7]
        2339288619.0"judo"12[637.9, 531.8]
        2339289618.0"judo"12[637.8, 531.6]
        2339290618.0"judo"12[637.6, 531.4]
        2339291618.0"judo"12[637.3, 531.2]
      • Events
        Events
        • DataFrame (4 columns, 0 rows)
          shape: (0, 4)
          nameonsetoffsetduration
          stri64i64i64
        • trial_columns:
          None
      • dict (2 items)
        • subject_id:
          1
        • session_id:
          1
      • messages:
        None
      • trial_columns:
        None
      • Experiment
        Experiment
        • EyeTracker
          EyeTracker
          • left:
            True
          • model:
            'EyeLink Portable Duo'
          • mount:
            'Desktop'
          • right:
            False
          • sampling_rate:
            1000.0
          • vendor:
            'EyeLink'
          • version:
            '6.12'
        • Screen
          Screen
          • distance_cm:
            68
          • height_cm:
            30.2
          • height_px:
            1024
          • origin:
            'lower left'
          • tuple (2 items)
            • 1280
            • 1024
          • tuple (2 items)
            • 38
            • 30.2
          • width_cm:
            38
          • width_px:
            1280
    • Gaze
      • DataFrame (5 columns, 109216 rows)
        shape: (109_216, 5)
        timepupiltasktrial_idpixel
        i64f64stri64list[f64]
        2762704783.0null0[139.1, 142.8]
        2762705783.0null0[139.3, 142.8]
        2762706783.0null0[139.5, 142.4]
        2762707783.0null0[139.6, 141.9]
        2762708783.0null0[139.5, 141.3]
        ……………
        2903401705.0"judo"12[762.7, 605.5]
        2903402706.0"judo"12[762.6, 605.2]
        2903403706.0"judo"12[762.5, 605.0]
        2903404706.0"judo"12[762.7, 604.9]
        2903405705.0"judo"12[763.0, 604.9]
      • Events
        Events
        • DataFrame (4 columns, 0 rows)
          shape: (0, 4)
          nameonsetoffsetduration
          stri64i64i64
        • trial_columns:
          None
      • dict (2 items)
        • subject_id:
          2
        • session_id:
          1
      • messages:
        None
      • trial_columns:
        None
      • Experiment
        Experiment
        • EyeTracker
          EyeTracker
          • left:
            True
          • model:
            'EyeLink Portable Duo'
          • mount:
            'Desktop'
          • right:
            False
          • sampling_rate:
            1000.0
          • vendor:
            'EyeLink'
          • version:
            '6.12'
        • Screen
          Screen
          • distance_cm:
            68
          • height_cm:
            30.2
          • height_px:
            1024
          • origin:
            'lower left'
          • tuple (2 items)
            • 1280
            • 1024
          • tuple (2 items)
            • 38
            • 30.2
          • width_cm:
            38
          • width_px:
            1280
  • Participants
    Participants
    • DataFrame (1 columns, 0 rows)
      shape: (0, 1)
      participant_id
      str
    • dict (1 items)
      • dict (1 items)
        • Format:
          'string'
  • path:
    PosixPath('data/ToyDatasetEyeLink')
  • DatasetPaths
    DatasetPaths
    • dataset:
      PosixPath('data/ToyDatasetEyeLink')
    • downloads:
      PosixPath('data/ToyDatasetEyeLink/downloads')
    • events:
      PosixPath('data/ToyDatasetEyeLink/events')
    • precomputed_events:
      PosixPath('data/ToyDatasetEyeLink/precomputed_events')
    • precomputed_reading_measures:
      PosixPath('data/ToyDatasetEyeLink/precomputed_reading_measures')
    • preprocessed:
      PosixPath('data/ToyDatasetEyeLink/preprocessed')
    • raw:
      PosixPath('data/ToyDatasetEyeLink/raw')
    • root:
      PosixPath('data/ToyDatasetEyeLink')
    • stimuli:
      PosixPath('data/ToyDatasetEyeLink/stimuli')
  • precomputed_events:
    list (0 items)
  • precomputed_reading_measures:
    list (0 items)
  • stimuli:
    list (0 items)

Let’s inspect the first Gaze in this dataset:

dataset.gaze[0].samples
shape: (128_342, 5)
timepupiltasktrial_idpixel
i64f64stri64list[f64]
2154556778.0null0[138.1, 132.8]
2154557778.0null0[138.2, 132.7]
2154558778.0null0[138.2, 132.3]
2154559778.0null0[138.1, 131.9]
2154560777.0null0[137.9, 131.6]
……………
2339287619.0"judo"12[637.7, 531.7]
2339288619.0"judo"12[637.9, 531.8]
2339289618.0"judo"12[637.8, 531.6]
2339290618.0"judo"12[637.6, 531.4]
2339291618.0"judo"12[637.3, 531.2]

What you have learned in this tutorial:#

  • how to handle *.asc files

  • how to create a custom dataset loading all files and parsing custom messages

  • how to load the dataset into your working memory

previous

Working with a Local Dataset

next

Plotting Gaze Data

On this page
  • What you will learn in this tutorial:
  • Preparations
    • Loading eye-tracking data from a file
    • Loading eye-tracking data along with SR Research recording messages
    • Defining custom patterns for data extraction
    • Writing a DatasetDefinition to parse the complete dataset
  • What you have learned in this tutorial:
Show Source

© Copyright 2022-2025 The pymovements Project Authors.

Created using Sphinx 8.2.3.

Built with the PyData Sphinx Theme 0.18.0.