Reading Tobii Data from Tobii Pro Lab#
import pupeyes as pe
import pandas as pd
At present, PupEyes does not support exported data from Tobii Pro Lab because these files already seem processed and ready for data analysis with basic Pandas syntax. For example, below is data kindly shared by Jason Geller for an experiment in which participants watched a video with eye movements recorded.
# file name
path = './data/sample_data_video.tsv'
# read data
df = pd.read_csv(path,
sep='\t',
low_memory=False # suppress dtypes warning
)
df.head()
| Recording timestamp [ms] | Computer timestamp [ms] | Sensor | Project name | Export date | Participant name | Recording date | Recording start time | Recording duration [ms] | Event | ... | Pupil diameter filtered [mm] | Eye openness left [mm] | Eye openness right [mm] | Eye openness filtered [mm] | Validity left | Validity right | Presented Stimulus name | Eye movement type | Eye movement event duration [ms] | Eye movement type index | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 3185423 | NaN | Demo | 7/31/25 | Participant1 | 3/5/25 | 04:06.2 | 102618 | RecordingStart | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Fixation | 4144.0 | 1.0 |
| 1 | 2 | 3185425 | Eye Tracker | Demo | 7/31/25 | Participant1 | 3/5/25 | 04:06.2 | 102618 | NaN | ... | 3.230 | 9.69 | 9.94 | 9.815 | Valid | Valid | NaN | Fixation | 4144.0 | 1.0 |
| 2 | 19 | 3185442 | Eye Tracker | Demo | 7/31/25 | Participant1 | 3/5/25 | 04:06.2 | 102618 | NaN | ... | 3.231 | 10.12 | 10.19 | 10.155 | Valid | Valid | NaN | Fixation | 4144.0 | 1.0 |
| 3 | 36 | 3185458 | Eye Tracker | Demo | 7/31/25 | Participant1 | 3/5/25 | 04:06.2 | 102618 | NaN | ... | 3.249 | 10.29 | 10.37 | 10.330 | Valid | Valid | NaN | Fixation | 4144.0 | 1.0 |
| 4 | 52 | 3185475 | Eye Tracker | Demo | 7/31/25 | Participant1 | 3/5/25 | 04:06.2 | 102618 | NaN | ... | 3.195 | 9.85 | 10.26 | 10.055 | Valid | Valid | NaN | Fixation | 4144.0 | 1.0 |
5 rows × 29 columns
Note
Check here for the meaning of these columns: https://go.tobii.com/Tobii-Pro-Lab-data-export-info
# some basic data cleaning seems sufficient
# select columns of interest
use_cols = ['Recording timestamp [ms]', 'Participant name', 'Gaze point X [DACS px]', 'Gaze point Y [DACS px]', 'Presented Stimulus name', 'Pupil diameter filtered [mm]']
samples = df[use_cols]
# select rows where stimulus is presented
samples = samples[samples['Presented Stimulus name']=='Inattentional Blindness - The Monkey Business Illusion 720'].reset_index(drop=True)
samples.head()
| Recording timestamp [ms] | Participant name | Gaze point X [DACS px] | Gaze point Y [DACS px] | Presented Stimulus name | Pupil diameter filtered [mm] | |
|---|---|---|---|---|---|---|
| 0 | 2669 | Participant1 | 1067.0 | 584.0 | Inattentional Blindness - The Monkey Business ... | 3.238 |
| 1 | 2686 | Participant1 | 1067.0 | 584.0 | Inattentional Blindness - The Monkey Business ... | 3.281 |
| 2 | 2702 | Participant1 | 1067.0 | 584.0 | Inattentional Blindness - The Monkey Business ... | 3.207 |
| 3 | 2719 | Participant1 | 1067.0 | 584.0 | Inattentional Blindness - The Monkey Business ... | 3.243 |
| 4 | 2736 | Participant1 | 1067.0 | 584.0 | Inattentional Blindness - The Monkey Business ... | 3.294 |
# do preprocessing
p = pe.PupilProcessor(
data=samples, # pass your data
trial_identifier=['Participant name','Presented Stimulus name'], # column(s) that disambiguate one trial from another in your data
x_col = 'Gaze point X [DACS px]',
y_col = 'Gaze point Y [DACS px]',
pupil_col = 'Pupil diameter filtered [mm]',
time_col = 'Recording timestamp [ms]',
samp_freq = 120, # Hz
device='tobii_prolab',
eyetracker_missing_value=pd.NA
).deblink().artifact_rejection().smooth(window=5).check_missing().interpolate(missing_threshold=.4)
Device: tobii_prolab
Eye-tracker missing value is <NA>. Replacing with 0.
Sampling frequency check skipped for tobii_prolab data.
PupilProcessor initialized with 121493 samples
Pupil column: Pupil diameter filtered [mm], Time column: Recording timestamp [ms], X column: Gaze point X [DACS px], Y column: Gaze point Y [DACS px]
Trial identifier: ['Participant name', 'Presented Stimulus name'], Number of trials: 3
Running deblink using sampling frequency 120Hz
✓ Deblinking completed!
→ New column: 'Pupil diameter filtered [mm]_db' (blinks removed)
→ Previous column 'Pupil diameter filtered [mm]' preserved.
→ 0 trial(s) failed.
✓ Artifact rejection completed!
→ New column: 'Pupil diameter filtered [mm]_db_ar' (artifacts removed)
→ Previous column 'Pupil diameter filtered [mm]_db' preserved.
→ 0 trial(s) failed.
✓ Smoothing completed!
→ New column: 'Pupil diameter filtered [mm]_db_ar_sm' (smoothed)
→ Previous column 'Pupil diameter filtered [mm]_db_ar' preserved.
→ 0 trial(s) failed.
✓ Missing values checked!
→ 0 trial(s) failed.
✓ Interpolation completed!
→ New column: 'Pupil diameter filtered [mm]_db_ar_sm_it' (interpolated)
→ Previous column 'Pupil diameter filtered [mm]_db_ar_sm' preserved.
→ 1 trial(s) failed.
Participant name Presented Stimulus name
0 Participant1 Inattentional Blindness - The Monkey Business ...
p.summary()
| Participant name | Presented Stimulus name | n_samples | run_deblink | pct_deblink | run_speed | pct_speed | run_size | pct_size | run_smooth | run_check_missing | missing | run_interpolate | pct_interpolate | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Participant1 | Inattentional Blindness - The Monkey Business ... | 5980 | True | 0.673411 | True | 0.0 | True | 0 | True | True | 0.683445 | False | 0.683445 |
| 1 | P1 | Inattentional Blindness - The Monkey Business ... | 61000 | True | 0.050377 | True | 0.046689 | True | 0 | True | True | 0.144115 | True | 0.144115 |
| 2 | P3 | Inattentional Blindness - The Monkey Business ... | 54513 | True | 0.022178 | True | 0.018014 | True | 0 | True | True | 0.056005 | True | 0.056005 |
viewer = pe.PupilViewer(p)
#viewer.run()
Warning
I do not use Tobii nor Pro Lab, so please take my words for caution. Although very unlikely, but if your computer explodes after running these code, I’m not responsible for that! If you have suggestions for improving the code, feel free to submit an issue.