Preprocessing Raw Gaze Data#
What you will learn in this tutorial:#
how to transform pixel coordinates into degrees of visual angle
how to transform positional data into velocity data
Preparations#
We import pymovements as the alias pm for convenience.
import pymovements as pm
Let’s start by downloading our ToyDataset and loading in its data:
dataset = pm.Dataset('ToyDataset', path='data/ToyDataset')
dataset.download()
dataset.load()
INFO:pymovements.dataset.dataset:
You are downloading the pymovements Toy Dataset. 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.
Verifying existing file: data/ToyDataset/downloads/pymovements-toy-dataset.zip
Using existing verified file: data/ToyDataset/downloads/pymovements-toy-dataset.zip
Extracting pymovements-toy-dataset.zip to data/ToyDataset/raw
Extracting archive: 0%| | 0/23 [00:00<?, ?file/s]
Extracting archive: 100%|██████████| 23/23 [00:00<00:00, 488.63file/s]
-
DatasetDefinitionDatasetDefinition
-
name:
'ToyDataset'
-
long_name:
'pymovements Toy Dataset'
-
'Example toy dataset. This dataset includes monocu...''Example toy dataset.\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 4 texts with 5 screens each.\n'
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
-
list (1 items)
-
ResourceDefinition
-
content:
'gaze'
-
filename:
'pymovements-toy-dataset.zip'
-
filename_pattern:
'trial_{text_id:d}_{page_id:d}.csv'
-
dict (2 items)
-
text_id:
<class 'int'>
-
page_id:
<class 'int'>
-
text_id:
-
load_function:
None
-
dict (4 items)
-
time_column:
'timestamp'
-
time_unit:
'ms'
- (2 more)
-
time_column:
-
md5:
'256901852c1c07581d375eef705855d6'
-
mirrors:
None
-
WebSourceWebSource(url='https://github.com/pymovements/pymovements-toy-dataset/archive/refs/heads/main.zip', filename='pymovements-toy-dataset.zip', md5='256901852c1c07581d375eef705855d6', mirrors=None)
-
'https://github.com/pymovements/pymovements-toy-dat...''https://github.com/pymovements/pymovements-toy-dataset/archive/refs/heads/main.zip'
-
content:
-
ResourceDefinition
-
name:
-
tuple (20 items)
-
Events
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
Events
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
- (18 more)
-
Events
-
dict (1 items)
-
DataFrame (3 columns, 20 rows)shape: (20, 3)
text_id page_id filepath i64 i64 str 0 1 "pymovements-toy-dataset-main/d… 0 2 "pymovements-toy-dataset-main/d… 0 3 "pymovements-toy-dataset-main/d… 0 4 "pymovements-toy-dataset-main/d… 0 5 "pymovements-toy-dataset-main/d… … … … 3 1 "pymovements-toy-dataset-main/d… 3 2 "pymovements-toy-dataset-main/d… 3 3 "pymovements-toy-dataset-main/d… 3 4 "pymovements-toy-dataset-main/d… 3 5 "pymovements-toy-dataset-main/d…
-
-
list (20 items)
-
Gaze
-
DataFrame (4 columns, 17223 rows)shape: (17_223, 4)
time stimuli_x stimuli_y pixel duration[μs] f64 f64 list[f64] 33m 8s 145ms -1.0 -1.0 [206.8, 152.4] 33m 8s 146ms -1.0 -1.0 [206.9, 152.1] 33m 8s 147ms -1.0 -1.0 [207.0, 151.8] 33m 8s 148ms -1.0 -1.0 [207.1, 151.7] 33m 8s 149ms -1.0 -1.0 [207.0, 151.5] … … … … 33m 25s 363ms -1.0 -1.0 [361.0, 415.4] 33m 25s 364ms -1.0 -1.0 [358.0, 414.5] 33m 25s 365ms -1.0 -1.0 [355.8, 413.8] 33m 25s 366ms -1.0 -1.0 [353.1, 413.2] 33m 25s 367ms -1.0 -1.0 [351.2, 412.9] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
1
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
-
-
Gaze
-
DataFrame (4 columns, 29799 rows)shape: (29_799, 4)
time stimuli_x stimuli_y pixel duration[μs] f64 f64 list[f64] 33m 28s 305ms -1.0 -1.0 [141.4, 153.6] 33m 28s 306ms -1.0 -1.0 [141.1, 153.2] 33m 28s 307ms -1.0 -1.0 [140.7, 152.8] 33m 28s 308ms -1.0 -1.0 [140.6, 152.7] 33m 28s 309ms -1.0 -1.0 [140.5, 152.6] … … … … 33m 58s 99ms -1.0 -1.0 [273.8, 773.8] 33m 58s 100ms -1.0 -1.0 [273.8, 774.1] 33m 58s 101ms -1.0 -1.0 [273.9, 774.5] 33m 58s 102ms -1.0 -1.0 [274.0, 774.4] 33m 58s 103ms -1.0 -1.0 [274.0, 773.9] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
2
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
-
- (18 more)
-
Gaze
-
ParticipantsParticipants
-
DataFrame (1 columns, 0 rows)shape: (0, 1)
participant_id str -
dict (1 items)
-
dict (1 items)
-
Format:
'string'
-
Format:
-
-
-
path:
PosixPath('data/ToyDataset')
-
DatasetPathsDatasetPaths
-
dataset:
PosixPath('data/ToyDataset')
-
downloads:
PosixPath('data/ToyDataset/downloads')
-
events:
PosixPath('data/ToyDataset/events')
-
precomputed_events:
PosixPath('data/ToyDataset/precomputed_events')
-
precomputed_reading_measures:
PosixPath('data/ToyDataset/precomputed_reading_measures')
-
preprocessed:
PosixPath('data/ToyDataset/preprocessed')
-
raw:
PosixPath('data/ToyDataset/raw')
-
root:
PosixPath('data/ToyDataset')
-
stimuli:
PosixPath('data/ToyDataset/stimuli')
-
dataset:
-
precomputed_events:
list (0 items)
-
precomputed_reading_measures:
list (0 items)
-
stimuli:
list (0 items)
We can verify that all files have been loaded in by checking the fileinfo attribute:
dataset.fileinfo
{'gaze': shape: (20, 3)
┌─────────┬─────────┬─────────────────────────────────┐
│ text_id ┆ page_id ┆ filepath │
│ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ str │
╞═════════╪═════════╪═════════════════════════════════╡
│ 0 ┆ 1 ┆ pymovements-toy-dataset-main/d… │
│ 0 ┆ 2 ┆ pymovements-toy-dataset-main/d… │
│ 0 ┆ 3 ┆ pymovements-toy-dataset-main/d… │
│ 0 ┆ 4 ┆ pymovements-toy-dataset-main/d… │
│ 0 ┆ 5 ┆ pymovements-toy-dataset-main/d… │
│ … ┆ … ┆ … │
│ 3 ┆ 1 ┆ pymovements-toy-dataset-main/d… │
│ 3 ┆ 2 ┆ pymovements-toy-dataset-main/d… │
│ 3 ┆ 3 ┆ pymovements-toy-dataset-main/d… │
│ 3 ┆ 4 ┆ pymovements-toy-dataset-main/d… │
│ 3 ┆ 5 ┆ pymovements-toy-dataset-main/d… │
└─────────┴─────────┴─────────────────────────────────┘}
Now let’s inspect our gaze dataframe:
dataset.gaze[0]
-
DataFrame (4 columns, 17223 rows)shape: (17_223, 4)
time stimuli_x stimuli_y pixel duration[μs] f64 f64 list[f64] 33m 8s 145ms -1.0 -1.0 [206.8, 152.4] 33m 8s 146ms -1.0 -1.0 [206.9, 152.1] 33m 8s 147ms -1.0 -1.0 [207.0, 151.8] 33m 8s 148ms -1.0 -1.0 [207.1, 151.7] 33m 8s 149ms -1.0 -1.0 [207.0, 151.5] … … … … 33m 25s 363ms -1.0 -1.0 [361.0, 415.4] 33m 25s 364ms -1.0 -1.0 [358.0, 414.5] 33m 25s 365ms -1.0 -1.0 [355.8, 413.8] 33m 25s 366ms -1.0 -1.0 [353.1, 413.2] 33m 25s 367ms -1.0 -1.0 [351.2, 412.9] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
1
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
Apart from some trial identifier columns we see the columns time and pixel.
Preprocessing#
We now want to transform these pixel position coordinates into coordinates in degrees of visual angle. This is simply done by:
dataset.pix2deg()
dataset.gaze[0]
-
DataFrame (5 columns, 17223 rows)shape: (17_223, 5)
time stimuli_x stimuli_y pixel position duration[μs] f64 f64 list[f64] list[f64] 33m 8s 145ms -1.0 -1.0 [206.8, 152.4] [-10.697598, -8.852399] 33m 8s 146ms -1.0 -1.0 [206.9, 152.1] [-10.695183, -8.859678] 33m 8s 147ms -1.0 -1.0 [207.0, 151.8] [-10.692768, -8.866956] 33m 8s 148ms -1.0 -1.0 [207.1, 151.7] [-10.690352, -8.869381] 33m 8s 149ms -1.0 -1.0 [207.0, 151.5] [-10.692768, -8.874233] … … … … … 33m 25s 363ms -1.0 -1.0 [361.0, 415.4] [-6.932438, -2.386672] 33m 25s 364ms -1.0 -1.0 [358.0, 414.5] [-7.006376, -2.408998] 33m 25s 365ms -1.0 -1.0 [355.8, 413.8] [-7.060582, -2.426362] 33m 25s 366ms -1.0 -1.0 [353.1, 413.2] [-7.12709, -2.441245] 33m 25s 367ms -1.0 -1.0 [351.2, 412.9] [-7.173881, -2.448686] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
1
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
The processed result has been added as a new column named position to our gaze dataframe.
Additionally, we would like to have velocity data available too. We have four different methods available:
preceding: this will just take the single preceding sample into account for velocity calculation. Most noisy variant.neighbors: this will take the neighboring samples into account for velocity calculation. A bit less noisy.smooth: this will increase the neighboring samples to two on each side. You can get a smooth conversion this way.savitzky_golay: this is using the Savitzky-Golay differentiation filter for conversion. You can specify additional parameters likewindow_lengthanddegree. Depending on your parameters, this will lead to the best results.
Let’s use the fivepoint method first:
dataset.pos2vel(method='fivepoint')
dataset.gaze[0]
-
DataFrame (6 columns, 17223 rows)shape: (17_223, 6)
time stimuli_x stimuli_y pixel position velocity duration[μs] f64 f64 list[f64] list[f64] list[f64] 33m 8s 145ms -1.0 -1.0 [206.8, 152.4] [-10.697598, -8.852399] [null, null] 33m 8s 146ms -1.0 -1.0 [206.9, 152.1] [-10.695183, -8.859678] [null, null] 33m 8s 147ms -1.0 -1.0 [207.0, 151.8] [-10.692768, -8.866956] [1.610194, -5.256267] 33m 8s 148ms -1.0 -1.0 [207.1, 151.7] [-10.690352, -8.869381] [0.402548, -4.447465] 33m 8s 149ms -1.0 -1.0 [207.0, 151.5] [-10.692768, -8.874233] [0.402561, -3.234462] … … … … … … 33m 25s 363ms -1.0 -1.0 [361.0, 415.4] [-6.932438, -2.386672] [-63.266374, -21.085616] 33m 25s 364ms -1.0 -1.0 [358.0, 414.5] [-7.006376, -2.408998] [-63.249652, -19.431326] 33m 25s 365ms -1.0 -1.0 [355.8, 413.8] [-7.060582, -2.426362] [-60.359624, -15.710061] 33m 25s 366ms -1.0 -1.0 [353.1, 413.2] [-7.12709, -2.441245] [null, null] 33m 25s 367ms -1.0 -1.0 [351.2, 412.9] [-7.173881, -2.448686] [null, null] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
1
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
The processed result has been added as a new column named velocity to our gaze dataframe.
We can also use the Savitzky-Golay differentiation filter with some additional parameters like this:
dataset.pos2vel(method='savitzky_golay', degree=2, window_length=7)
dataset.gaze[0]
-
DataFrame (6 columns, 17223 rows)shape: (17_223, 6)
time stimuli_x stimuli_y pixel position velocity duration[μs] f64 f64 list[f64] list[f64] list[f64] 33m 8s 145ms -1.0 -1.0 [206.8, 152.4] [-10.697598, -8.852399] [1.207641, -3.119165] 33m 8s 146ms -1.0 -1.0 [206.9, 152.1] [-10.695183, -8.859678] [1.20764, -4.072198] 33m 8s 147ms -1.0 -1.0 [207.0, 151.8] [-10.692768, -8.866956] [1.035119, -4.765267] 33m 8s 148ms -1.0 -1.0 [207.1, 151.7] [-10.690352, -8.869381] [1.207654, -4.245382] 33m 8s 149ms -1.0 -1.0 [207.0, 151.5] [-10.692768, -8.874233] [1.552735, -2.339263] … … … … … … 33m 25s 363ms -1.0 -1.0 [361.0, 415.4] [-6.932438, -2.386672] [-62.062479, -20.465552] 33m 25s 364ms -1.0 -1.0 [358.0, 414.5] [-7.006376, -2.408998] [-61.343786, -18.073031] 33m 25s 365ms -1.0 -1.0 [355.8, 413.8] [-7.060582, -2.426362] [-53.501231, -14.617634] 33m 25s 366ms -1.0 -1.0 [353.1, 413.2] [-7.12709, -2.441245] [-41.879965, -10.276475] 33m 25s 367ms -1.0 -1.0 [351.2, 412.9] [-7.173881, -2.448686] [-27.710881, -6.112645] -
EventsEvents
-
DataFrame (4 columns, 0 rows)shape: (0, 4)
name onset offset duration str duration[μs] duration[μs] duration[μs] -
trial_columns:
None
-
-
dict (2 items)
-
text_id:
0
-
page_id:
1
-
text_id:
-
messages:
None
-
trial_columns:
None
-
ExperimentExperiment
-
EyeTrackerEyeTracker
-
left:
None
-
model:
None
-
mount:
None
-
right:
None
-
sampling_rate:
1000
-
vendor:
None
-
version:
None
-
left:
-
ScreenScreen
-
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
-
distance_cm:
-
This has overwritten our velocity columns. As we see, the values in the velocity columns are slightly different.
What you have learned in this tutorial:#
transforming pixel coordinates into degrees of visual angle by using
Dataset.pix2deg()transforming positional data into velocity data by using
Dataset.pos2vel()passing additional keyword arguments when using the Savitzky-Golay differentiation filter