Overview

pypillometry is a Python package for analyzing pupillometric data, providing a comprehensive toolkit for preprocessing, analyzing, and visualizing pupil size measurements and eye-tracking data. The package is built around the pypillometry.EyeData, pypillometry.PupilData, and pypillometry.GazeData classes, which serves as the main interface for handling pupillometric/eyetracking datasets.

EyeData([time, left_x, left_y, left_pupil, ...])

Class for handling eye-tracking data.

GazeData([time, left_x, left_y, right_x, ...])

Class representing Eye-Tracking data (x,y) from one or two eyes.

PupilData([time, left_pupil, right_pupil, ...])

Class representing pupillometric data.

GenericEyeData()

Generic class for eyedata.

Reading/writing eye-data

So far, reading in data is not part of the pypillometry-package. This is because the format of the eyetracker-files will vary depending on the setup of the eyetracker (there are many ways to represent event-triggers) and the actual model of the eyetracker. Python provides excellent functionality to parse text-based datafiles and we therefore give guidance how to use these tools rather than trying to implement that functionality in our package.

There are many ways in which pupillometric data can be read into Python. For example, Eyelink’s ASC-format generated by the EDF2ASC conversion tool outputs space-separated data that can be easily loaded using the I/O functionality of the pandas package .

Once the data has been manually parsed into numpy-arrays, it can be converted into a PupilData, GazeData, or EyeData object.

pypillometry provides functions to load data from local data directories (load_study_local()) and from OSF-projects (load_study_osf()) based on a user-provided configuration file (/examples/pypillometry_conf.py).

The package comes with a few example datasets for convenience (see the dictionary example_data for an overview). These can be loaded using the get_example_data() function.

Please refer to the following notebooks for more information:

Pipeline-based processing

pypillometry implements a pipeline-like approach where each operation executed on a PupilData,-GazeData or EyeData-object returns a copy of the (modified) object. This enables the “chaining” of commands as follows:

d=pp.get_example_data("rlmw_002_short").pupil_blinks_detect().blinks_merge().pupil_lowpass_filter(3).downsample(50)

This command loads an example data-file, applies a 3Hz low-pass filter to it, downsamples the signal to 50 Hz, detects blinks in the signal and merges short, successive blinks together. The final result of this processing-pipeline is stored in object d. This object stores also the complete history of the operations applied to the dataset and allows to transfer it to a new dataset.

See the following page more on this:

Data modalities/variables and monocular vs. binocular data

pypillometry supports monocular and binocular data and allows to process several data modalities. The package ensures that all timeseries always have the same length/dimension such that all data modalities are aligned. The data are stored internally as an EyeDataDict-object which is a dictionary of numpy.ndarray-objects that are constrained to have the same shape. In addition, the keys of the dictionary are of the form “<eye>_<variable>”, e.g., left_pupil or right_x.

Most functions that operate on EyeData-objects allow to specify the eye and variable to operate on with optional arguments eye= and variable= (per default, all eyes and all variables are processed). Functions that can potentially operate on all modalities are implemented as methods of GenericEyeData while functions that are specific to pupillometric or eye-tracking data are implemented as methods of PupilData or GazeData. However, all objects be they PupilData, GazeData or EyeData inherit from GenericEyeData and therefore support the same set of operations.

Finally, new variables (or even “eyes”) can be created and added to the object. For example, when combining the data from both eyes, one can create a new variable that contains the average of the left and right pupil size which might be called “average_pupil”. In that case, the eye="average" and variable="pupil" arguments should be used to access this new variable.

Please see the following example for more details:

Plotting data

Each pypillometry object has a .plot attribute that gives access to the plotting functionality. Depending on the type of the object, the plotting functionality will be different. For example, GazeData objects (and also EyeData objects which inherit from that class) support the plotting of heatmaps or scanpaths.

Here is an overview of the plotting functionality:

PupilPlotter(obj)

Class for plotting pupil data.

GazePlotter(obj)

Class for plotting eye data.

EyePlotter(obj)

Analyzing pupillometric data

Analyzing eye-tracking data

work in progress

Pre-processing data

Assuming you have generated a PupilData object, a range of pre-processing functions are available. The main pre-processing issues with pupillometric data are:

  • artifacts and missing data due to blinks (these can usually be corrected/interpolated)

  • missing data/artifacts from other sources (e.g., looking away, eyetracker losing pupil for other reasons)

  • smoothing/downsampling to get rid of high-freq low-amp noise

Smoothing/low-pass filtering

In most cases, pupillometric data should be low-pass filtered (e.g., using a cutoff of 4 Hz Jackson & Sirois, 2009) or smoothed in other ways (e.g., with a running-window).

Tge following is a list of functions for smoothing:

PupilData.downsample(fsd[, dsfac, inplace])

Simple downsampling scheme using mean within the downsampling window.

Changing/Slicing data

Often, pupillometric data needs to be trimmed, e.g., to remove pre-experiment recordings or to remove unusable parts of the data (PupilData.sub_slice()). The timing should usually be realigned to the start of the experiment (PupilData.reset_time()). Furthermore, a scaling (e.g., Z-transform) of the pupil-data can be useful for comparing multiple subjects (PupilData.scale()).

The following is a list of available functions for these purposes:

PupilData.sub_slice([start, end, units, inplace])

Return a new EyeData object that is a shortened version of the current one (contains all data between start and end in units given by units (one of "ms", "sec", "min", "h").

PupilData.copy([new_name])

Make and return a deep-copy of the pupil data.

PupilData.scale([variables, mean, sd, eyes, ...])

Scale the signal by subtracting mean and dividing by sd.

PupilData.unscale([variables, mean, sd, ...])

Scale back to original values using either values provided as arguments or the values stored in scale_params.

PupilData.reset_time([t0, inplace])

Resets time so that the time-array starts at time zero (t0).

Plotting/Summarizing Data

Plotting

It is crucial to validate preprocessing steps by visually inspecting the results using plots. Therefore, pypillometry implements several plotting facilities that encourage active exploration of the dataset.

Please see the tutorial Plotting of pupillometric data for more details.

PupilData.plot

PupilData.get_erpd(erpd_name, event_select)

Extract event-related pupil dilation (ERPD).

Inspecting/Summarizing

The package also provides several functions for summarizing datasets. Simply print()`ing a :class:`PupilData object gives a readable summary of the main properties of the dataset and also prints the complete history of the results. By calling PupilData.summary(), summary data can be arranged and summarized in tabular form.

See the notebook Summarizing pupillometric data for more details.

PupilData.summary()

Return a summary of the dataset as a dictionary.

PupilData.stat_per_event(interval[, ...])

Return result of applying a statistical function to data in a given interval relative to event-onsets.

PupilData.get_erpd(erpd_name, event_select)

Extract event-related pupil dilation (ERPD).

Modeling the pupillometric signal

For some applications, it is interesting to model the full pupillometric signal as consisting of a (tonic) baseline and a (phasic) response component. The package implements novel algorithms developed in our lab and documentation will become available here.

More details are availabel in this notebook: Modeling the pupillometric signal.

Artificial Data

For validation and testing purposes, it can be useful to generate artificial datasets. The package implements a FakePupilData as inheriting from regular PupilData and therefore shares all its functionality. In addition to that, FakePupilData stores “ground-truth” data and parameters that was used while creating the artificial data.

The function create_fake_pupildata() allows to quickly create datasets generating according to a provided experimental structure (see the functions documentation for an overview over the many available options).