API documentation

Python-package to help with processing of pupillometric and eyetracking data.

This package provides classes for handling pupillometric and eyetracking data as well as tools for plotting and analyzing these data.

Unit Handling:

Times, distances and angles can carry units, either from the pypillometry.u namespace (3 * u.min, (-200, 800) * u.ms, 60 * u.cm, 20 * u.deg) or as strings ("3 min", "60 cm", "20 deg").

Overview

        classDiagram
 EyeData <|-- GazeData
 EyeData <|-- PupilData
 PupilData <|-- GenericEyeData
 GazeData <|-- GenericEyeData

 GazeData: +EyeDataDict data[left_x, left_y, right_x, right_y]
 GazeData: +GazePlotter plot
 GazeData: +set_experimental_setup() (and other gaze-specific functions)
 PupilData: +EyeDataDict data[left_pupil, right_pupil]
 PupilData: +PupilPlotter plot
 PupilData: +pupil_blinks_detect() (and other pupil-specific functions)
 EyeData: +EyeDataDict data[left_pupil, right_pupil, left_x, left_y, right_x, right_y]
 EyeData: +EyePlotter plot
 EyeData: +correct_pupil_foreshortening() (and other functions needing both gaze and pupil data)
    

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

Class for handling eye-tracking data.

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

Class representing pupillometric data.

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

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

GenericEyeData()

Generic class for eyedata.

EyeDataDict(*args, **kwargs)

A dictionary that contains 1-dimensional ndarrays of equal length and with the same datatype (float).

Primary classes

Several different classes are available for handling different types of eye data.

  • PupilData: If you have only pupillometric data, use this class.

  • GazeData: If you only have gaze data, use this class.

  • EyeData: If you have both gaze and pupillometric data, use this class.

These classes inherit from GenericEyeData, which provides some basic, shared functionality for handling eye data.

The data inside each of these classes is stored in the data attribute, which is a dictionary-like object of type EyeDataDict.

Each of these classes provides access to a matching plotter object (from module pypillometry.plot) that is stored in the plot attribute.

EyeData

class pypillometry.EyeData(time=None, left_x=None, left_y=None, left_pupil=None, right_x=None, right_y=None, right_pupil=None, event_onsets=None, event_labels=None, sampling_rate=None, experimental_setup=None, name=None, calibration=None, fill_time_discontinuities=True, keep_orig=False, info=None, inplace=None)[source]

Class for handling eye-tracking data. This class is a subclass of GazeData and PupilData and inherits all their methods and attributes. In addition to the methods in GazeData which implement functions for eye-tracking data and those in PupilData for pupillometric data, EyeData provides methods that need both gaze and pupil data (e.g., foreshortening correction).

Properties such as eyes and variables are inherited from GenericEyeData.

Parameters:
  • time (ndarray) – timing array (in ms) or None, in which case the time-array goes from [0,maxT] using sampling_rate

  • left_x (ndarray) – data from left eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • left_y (ndarray) – data from left eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • left_pupil (ndarray) – data from left eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • right_x (ndarray) – data from right eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • right_y (ndarray) – data from right eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • right_pupil (ndarray) – data from right eye (at least one of the eyes must be provided, both x and y) pupil is optional

  • event_onsets (ndarray) – time-onsets of any events in the data (in ms, matched in time vector)

  • event_labels (ndarray) – for each event in event_onsets, provide a label

  • sampling_rate (float) – sampling-rate of the signal in Hz; if None, it is estimated from the median difference in time (either time or sampling_rate must be provided)

  • experimental_setup (Optional[ExperimentalSetup]) – Geometric model of the experimental setup including screen geometry, eye position, camera position, and screen orientation.

  • name (str) – name of the dataset or None (in which case a random string is selected)

  • calibration (Optional[Dict[str, SpatialCalibration]]) – Dictionary of SpatialCalibration objects, one per eye (keys: ‘left’, ‘right’)

  • fill_time_discontinuities (bool) – sometimes, when the eyetracker loses signal, no entry in the EDF is made; when this option is True, such entries will be made and the signal set to 0 there (or do it later using fill_time_discontinuities())

  • keep_orig (bool) – keep a copy of the original dataset? If True, a copy of the object as initiated in the constructor is stored in member original

  • info (dict) – optional dictionary with additional information about the dataset (stored in member info)

  • inplace (Optional[bool]) – default for methods called with inplace=None: if True, they modify the object in place; if False, they return a modified copy; if None (default), use the package-level setting (see set_default_inplace(), initially True). The method-level inplace argument overrides this.

Examples

>>> import numpy as np
>>> from pypillometry.eyedata import EyeData
>>> d=EyeData(time=np.arange(0, 1000, 1),
...         left_x=np.random.randn(1000),
...         left_y=np.random.randn(1000))
>>> print(d)
correct_pupil_foreshortening(eyes=[], midpoint=None, store_as='pupil', inplace=None)[source]

Correct pupil data using a simple algorithm to correct for foreshortening effects.

Correct the pupil data for foreshortening effects caused by saccades/eye movements. This method is based on a simple algorithm that assumes the pupil appears perfectly circular when fixating midpoint: for each sample, the distance \(\Delta\) (in mm) of the gaze position from midpoint is computed and the pupil signal is multiplied by the correction factor \(\sqrt{1+\Delta^2/d^2}\), where \(d\) is the eye-to-screen distance from the experimental setup. For a more complete model that also estimates the camera position, see fit_foreshortening().

Relevant publication (not the description of the algorithm used here): https://link.springer.com/article/10.3758/s13428-015-0588-x

  • [ ] TODO: when using interpolated pupil data, x/y data may still be missing. Make sure that the pupil data is not touched where x/y is missing

Parameters:
  • eyes (list) – Which eyes to correct. If empty, correct all available eyes.

  • midpoint (tuple or None) – The center of the screen (x,y) in pixels where it is assumed that the pupil is completely circular. If None, the midpoint is taken to be the center of the screen as registered in the experimental_setup.

  • store_as (str) – name of the variable in which to store the corrected pupil data (as data[eye, store_as]); the default “pupil” replaces the original pupil data

  • inplace (bool or None) – Whether to modify the object in place or return a new object. true: modify in place false: return a new object None: use the object’s default setting (initialized in __init__)

Returns:

An EyeData object with the corrected pupil

Return type:

EyeData

Raises:

ValueError – if no experimental setup is set (see set_experimental_setup())

fit_foreshortening(eye, r=None, d=None, intervals=None, target_fs=50.0, lowpass_freq=4.0, lambda_smooth=1.0, knots_per_second=1.0, initial_theta=None, initial_phi=None, verbose=True)[source]

Fit foreshortening correction model to estimate camera geometry and true pupil size.

This method implements Stage 1 of the foreshortening correction algorithm, which jointly estimates the camera position (theta, phi) and the temporal dynamics of true pupil size A0(t) using B-splines. The data is automatically preprocessed (filtered and downsampled) before fitting.

Parameters:
  • eye (str) – Which eye to fit (‘left’ or ‘right’)

  • r (Union[float, str, None]) –

    Eye-to-camera distance. If not provided, uses the r from experimental_setup.

    • Plain number: assumed to be mm (with warning)

    • String: e.g., “600 mm”, “60 cm”

    • Quantity: e.g., 600 * u.mm

  • d (Union[float, str, None]) –

    Eye-to-screen distance. If not provided, uses the d from experimental_setup.

    • Plain number: assumed to be mm (with warning)

    • String: e.g., “700 mm”, “70 cm”

    • Quantity: e.g., 700 * u.mm

  • intervals (Optional[Intervals]) – Time intervals to use for fitting. If None, uses all available data. Useful for fitting only on calibration periods. Intervals in any time units or in sample indices (units=None) are accepted; a plain list of (start, end) tuples is taken as ms.

  • target_fs (float) – Target sampling rate in Hz after downsampling for efficiency. Pupil dynamics are typically well-preserved at 50 Hz.

  • lowpass_freq (float) – Lowpass filter cutoff frequency in Hz. Removes high-frequency noise before downsampling. Should be below target_fs/2 (Nyquist).

  • lambda_smooth (float) – Smoothness regularization weight. Higher values produce smoother A0(t) estimates. Tune via cross-validation if needed.

  • knots_per_second (float) – Number of B-spline knots per second. Controls temporal resolution of A0(t) model. Typical values: 0.5-2.0.

  • initial_theta (Union[float, str, None]) –

    Initial guess for camera polar angle. If None, uses pi/2 (horizontal) as starting point.

    • Plain number: assumed to be radians (with warning)

    • String: e.g., “20 degrees”, “0.349 radians”

    • Quantity: e.g., 20 * u.deg

  • initial_phi (Union[float, str, None]) –

    Initial guess for camera azimuthal angle. If None, uses 0 (camera on x-axis) as starting point.

    • Plain number: assumed to be radians (with warning)

    • String: e.g., “-90 degrees”, “-1.57 radians”

    • Quantity: e.g., -90 * u.deg

  • verbose (bool) – Print optimization progress and fit statistics.

Returns:

calibration – Fitted calibration object containing camera geometry (theta, phi), spline coefficients for A0(t), and fit quality metrics.

Return type:

ForeshorteningCalibration

Raises:

ValueError – If specified eye doesn’t exist or lacks gaze/pupil data, if the experimental setup is not set or lacks required information, if lowpass_freq is not below the Nyquist frequency or if fewer than 100 valid samples are available.

Notes

The model is:

A_measured(x,y,t) = A0(t) * cos(alpha(x,y; theta, phi))

where:

  • A0(t) = sum_k a_k * B_k(t) is modeled with B-splines

  • cos(alpha) is the foreshortening factor depending on viewing angle

  • theta, phi parameterize camera position in spherical coordinates

The optimization minimizes:

sum_i (A_i - A_pred_i)^2 + lambda_smooth * sum_k (a_{k+1} - a_k)^2

Examples

>>> import pypillometry as pp
>>> data = pp.EyeData.from_eyelink('recording.edf')
>>>
>>> # Set experimental setup
>>> data.set_experimental_setup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm"),
...     eye_to_screen_center="65 cm",
...     camera_offset=("0 cm", "-30 cm", "0 cm"),
... )
>>>
>>> # Fit on calibration period (first 2 minutes)
>>> cal_intervals = pp.Intervals([(0, 120000)], units="ms")
>>> calib = data.fit_foreshortening(
...     eye='left',
...     intervals=cal_intervals
... )
>>>
>>> # Or provide r and d explicitly
>>> calib = data.fit_foreshortening(
...     eye='left',
...     r="60 cm",
...     d="70 cm",
...     intervals=cal_intervals
... )
>>>
>>> # Inspect fit quality
>>> print(calib)
>>> print(f"R² = {calib.fit_metrics['r2']:.3f}")
>>>
>>> # Compute correction for all data
>>> correction_factor = calib.get_correction_factor(
...     data['left', 'x'],
...     data['left', 'y']
... )
>>> corrected_pupil = data['left', 'pupil'] * correction_factor

See also

ForeshorteningCalibration

Container for fitted parameters

correct_pupil_foreshortening

Simple foreshortening correction method

get_pupildata(eye=None)[source]

Return the pupil data as a PupilData object.

Parameters:

eye (str, optional) – Which eye to return data for. If None, the dataset must contain only one eye, which is then used.

Returns:

A PupilData object containing the pupil data of eye (as "<eye>_pupil"), the time vector and the events.

Return type:

PupilData

Raises:

ValueError – if eye is None and there is more than one eye, or if there is no data for eye

property plot

Plotting functions for this object (a EyePlotter).

summary()[source]

Return a summary of the dataset as a dictionary.

Returns:

A dictionary summarizing the dataset.

Return type:

dict

PupilData

class pypillometry.PupilData(time=None, left_pupil=None, right_pupil=None, event_onsets=None, event_labels=None, sampling_rate=None, name=None, fill_time_discontinuities=True, keep_orig=False, info=None, inplace=None)[source]

Class representing pupillometric data.

The class is a subclass of GenericEyeData and inherits all its methods.

If eye-tracking data is available in addition to pupillometry, use the EyeData class.

Properties such as eyes and variables are inherited from GenericEyeData.

Parameters:
  • time (ndarray) – timing array (in ms) or None, in which case the time-array goes from [0,maxT] using sampling_rate

  • left_pupil (ndarray) – data from left eye (at least one of the eyes must be provided)

  • right_pupil (ndarray) – data from right eye (at least one of the eyes must be provided)

  • event_onsets (ndarray) – time-onsets of any events in the data (in ms, matched in time vector)

  • event_labels (ndarray) – for each event in event_onsets, provide a label

  • sampling_rate (float) – sampling-rate of the signal in Hz; if None, it is estimated from the median difference in time (either time or sampling_rate must be provided)

  • name (str) – name of the dataset or None (in which case a random string is selected)

  • fill_time_discontinuities (bool) – sometimes, when the eyetracker loses signal, no entry in the EDF is made; when this option is True, such entries will be made and the signal set to 0 there (or do it later using fill_time_discontinuities())

  • keep_orig (bool) – keep a copy of the original dataset? If True, a copy of the object as initiated in the constructor is stored in member original

  • info (dict) – optional dictionary with additional information about the dataset (stored in member info)

  • inplace (Optional[bool]) – default for methods called with inplace=None: if True, they modify the object in place; if False, they return a modified copy; if None (default), use the package-level setting (see set_default_inplace(), initially True). The method-level inplace argument overrides this.

property plot

Plotting functions for this object (a PupilPlotter).

Detect blinks in the pupillary signal using several strategies. First, blinks are detected as consecutive sequence of blink_val (f.eks., 0 or NaN). Second, blinks are defined as everything between two crossings of the velocity profile (from negative to positive).

Detected blinks are always stored in the object (retrieve them with get_blinks()). When apply_mask=True, the blinks are also applied as masks to the data. When apply_mask=False (default), the detected blinks are returned as Intervals objects.

Finally, detected blinks have to be at least min_duration ms.

Parameters:
  • eyes (list) – list of eyes to process; if empty, all available eyes are processed

  • min_duration (float) – minimum duration (in ms, or with units) for a sequence of missing numbers to be treated as blink

  • blink_val (float) – “missing value” code for the “zero” strategy. Missing samples (NaN) are always detected; samples equal to blink_val (default 0) are detected in addition. Use np.nan to detect only NaN (e.g., for baseline-corrected data where 0 is a valid value). The “zero” strategy always uses the raw data, so regions masked for other reasons are not detected as blinks.

  • winsize (float) – window-size for Savitzky-Golay velocity estimation (in ms, or with units)

  • vel_onset (float or str) –

    velocity threshold to detect blink onset (negative value).

    • If float: threshold in pupil-units per millisecond (e.g., -5.0)

    • If str: percentile of velocity distribution (e.g., “5%” for bottom 5%)

  • vel_offset (float or str) –

    velocity threshold to detect blink offset (positive value).

    • If float: threshold in pupil-units per millisecond (e.g., 5.0)

    • If str: percentile of velocity distribution (e.g., “95%” for top 5%)

  • vel_onset_min_duration (float) – minimum duration (in ms, or with units) that velocity must exceed threshold to detect as onset (to avoid noise-induced false detections)

  • vel_offset_min_duration (float) – minimum duration (in ms, or with units) that velocity must exceed threshold to detect as offset (to avoid noise-induced false detections)

  • max_duration (float) – maximum duration (in ms, or with units) of a blink detected by the “velocity” strategy; an onset without an offset within this time is dropped. None means no limit. Does not apply to the “zero” strategy.

  • strategies (list) – list of strategies to use; so far, use a list containing any combination of “zero” and “velocity”

  • apply_mask (bool) – if True, apply detected blinks as masks to the data and return the object if False (default), return detected blinks as Intervals (or dict of Intervals)

  • ignore_existing_mask (bool) – if True, use raw data ignoring existing masks if False (default), only detect blinks in non-masked data (already masked regions are skipped)

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

Returns:

If apply_mask=True: returns the object (for chaining) If apply_mask=False and single eye: returns Intervals object If apply_mask=False and multiple eyes: returns dict of Intervals objects (keys like "left_pupil")

Return type:

PupilData or Intervals or dict

Interpolating blinks in the pupillary signal.

Implements the blink-interpolation method by Mahot (2013).

Mahot, 2013: https://figshare.com/articles/A_simple_way_to_reconstruct_pupil_size_during_eye_blinks/688001.

The blinks to interpolate are taken from the blinks stored in the object, so blinks have to be detected first (e.g., using pupil_blinks_detect()). The on- and offsets of each blink are refined using the velocity profile before interpolating. Blinks that are so close that the anchor points of one fall into the other are merged and interpolated together. Interpolated samples remain masked; the interpolated blinks can be retrieved with get_interpolated_blinks().

This procedure relies heavily on eye-balling (reconstructing visually convincing signal), so it can help to inspect the result visually (e.g., using plot_blinks()) to set good parameter values for winsize, vel_onset, vel_offset and margin (see paper linked above).

Parameters:
  • eyes (str | list) – str or list of eyes to process; if empty, all available eyes are processed

  • store_as (str) – how to store the interpolated data; either “pupil” (default) which replaces the original pupil data or a string that will be used as the new variable name in the data (e.g., “pupilinterp”)

  • method (str) – method to use; so far, only “mahot” is implemented

  • winsize (float) – size of the Savitzky-Golay window (in ms, or with units)

  • vel_onset (float or str) –

    velocity threshold to detect blink onset (negative value).

    • If float: threshold in pupil-units per millisecond (e.g., -5.0)

    • If str: percentile of velocity distribution (e.g., “5%” for bottom 5%)

  • vel_offset (float or str) –

    velocity threshold to detect blink offset (positive value).

    • If float: threshold in pupil-units per millisecond (e.g., 5.0)

    • If str: percentile of velocity distribution (e.g., “95%” for top 5%)

  • margin (Tuple[float, float]) – margin that is subtracted/added to onset and offset (in ms, or with units, e.g. (10, 30) * u.ms)

  • blinkwindow (float) – how much time before and after each blink to include (in ms, or with units)

  • interp_type (str) – type of interpolation accepted by scipy.interpolate.interp1d()

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

Returns:

the object with the interpolated pupil data

Return type:

PupilData

pupil_estimate_baseline(eyes=[], variable='pupil', method='envelope_iter_bspline_2', inplace=None, **kwargs)[source]

Apply one of the baseline-estimation methods.

Parameters:
  • eyes (list or str) – str or list of eyes to process; if empty, all available eyes are processed

  • variable (str) – default is to use the “pupil” but it could be used to process, e.g., interpolated pupil data stored in a different variables, e.g., “pupilinterp”

  • method (str) –

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

  • **kwargs (dict) – named arguments passed to the low-level function in pypillometry.signal.baseline.

Returns:

object with baseline estimated (stored in data[eye, "baseline"])

Return type:

PupilData

Raises:

ValueError – if method is unknown or the data contain NaN (interpolate blinks first, see pupil_blinks_interpolate())

pupil_estimate_response(eyes=[], npar='free', tmax='free', verbose=50, bounds={'npar': (1, 20), 'tmax': (100, 2000)}, inplace=None)[source]

Estimate pupil-response based on event-onsets, see pupil_response().

If a baseline has been estimated (see pupil_estimate_baseline()), it is subtracted from the pupil signal before estimating the response; otherwise, a baseline of zero is used.

Parameters:
  • eyes (list or str) – str or list of eyes to process; if empty, all available eyes are processed

  • npar (str | float) – npar-parameter for the canonical response-function or “free”; in case of “free”, the function optimizes for this parameter

  • tmax (str | float) – tmax-parameter for the canonical response-function or “free”; in case of “free”, the function optimizes for this parameter

  • verbose (int) – verbosity level, passed on to pupil_response()

  • bounds (dict) – in case that one or both parameters are estimated, give the lower and upper bounds for the parameters

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

Returns:

the object with the estimated response

Return type:

PupilData

Notes

The predicted response is stored in data[eye, "response"]; the estimated parameters (npar, tmax, the coefficients coef and the bounds) are stored in params["response"][eye].

pupil_lowpass_filter(cutoff, order=2, eyes=[], inplace=None)[source]

Lowpass-filter pupil signal using a Butterworth-filter.

Uses an iterative approach to handle NaN/masked values properly, preventing NaN propagation through the filter (see lowpass_filter_iterative()). The filtered signal replaces the pupil data; the mask is preserved.

Parameters:
  • cutoff (float) – lowpass-filter cutoff frequency in Hz

  • order (int) – filter order

  • eyes (list) – list of eyes to filter; if empty, all available eyes are filtered

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

Returns:

the object with the filtered pupil data

Return type:

PupilData

pupil_merge_eyes(method='local_offset', lowpass_cutoff=0.2, max_iter=10, keep_eyes=True, inplace=None)[source]

Merge pupil data from both eyes into a single “merged” variable.

This method combines left and right pupil signals, handling missing data (masked values) intelligently. Where both eyes are valid, the signals are averaged to reduce noise. Where only one eye is valid, that eye’s data is used (with offset correction if applicable).

Parameters:
  • method (str) –

    Method for merging the two eyes. Options:

    • ”local_offset” (default): Compute a slowly-varying offset between eyes using lowpass filtering. This handles drift over the session. The right eye is shifted to match the left eye’s level before merging.

    • ”offset”: Use a single global offset (mean difference) computed over the entire signal where both eyes are valid.

    • ”both_available”: Only use data where both eyes are valid (take mean). Timepoints where only one eye is valid remain masked.

    # TODO: Implement “signal_quality” method that weights eyes by their # local signal quality (SNR), giving more weight to the cleaner eye.

  • lowpass_cutoff (float) – Cutoff frequency (Hz) for the lowpass filter used in “local_offset” method. Lower values produce a more stable offset that only tracks very slow drift. Default is 0.2 Hz (~5 second time constant).

  • max_iter (int) – Maximum iterations for iterative lowpass filtering in “local_offset” method. Default is 10.

  • keep_eyes (bool) – If True (default), keep the original left/right pupil data. If False, remove the original eye data after merging.

  • inplace (bool | None) – If True, modify in-place. If False, return a copy. If None, use the object’s default setting.

Returns:

The object with merged pupil data stored as “merged_pupil”.

Return type:

PupilData

Raises:

ValueError – If only one eye is available (nothing to merge). If an unknown method is specified.

Notes

The merged signal is stored as “merged_pupil” and can be accessed via data[“merged”, “pupil”] or data[“merged_pupil”].

The mask for the merged signal reflects which timepoints have valid data: - For “local_offset” and “offset”: masked only where BOTH eyes are masked - For “both_available”: masked where EITHER eye is masked Masked samples still hold the merged stored values (e.g., interpolated blinks), computed as for valid samples (NaN only if both eyes are NaN).

The “local_offset” method is recommended for most use cases as it: 1. Uses all available data (doesn’t discard single-eye timepoints) 2. Handles session-long drift in the inter-ocular difference 3. Produces smooth transitions at boundaries between single/both-eye regions

Examples

>>> # Default: local offset correction
>>> data.pupil_merge_eyes()
>>> merged = data["merged", "pupil"]
>>> # Use global offset (simpler, assumes no drift)
>>> data.pupil_merge_eyes(method="offset")
>>> # Only use timepoints where both eyes are valid
>>> data.pupil_merge_eyes(method="both_available")
>>> # Remove original eye data after merging
>>> data.pupil_merge_eyes(keep_eyes=False)

See also

merge_eyes

Generic merge method in GenericEyeData (masks if all eyes invalid)

pypillometry.signal.pupil.pupil_eye_offset

Compute local offset between eyes

pupil_smooth_window(eyes=[], window='hanning', winsize=11, inplace=None)[source]

Apply smoothing of the signal using a moving window. See smooth_window(). The smoothed signal replaces the pupil data; the mask is preserved.

Parameters:
  • eyes (list) – list of eyes to smooth; if empty, all available eyes are smoothed

  • window (str) – (the type of window from ‘flat’, ‘hanning’, ‘hamming’, ‘bartlett’, ‘blackman’); flat window will produce a moving average smoothing.

  • winsize (float) – the length of the window in ms (or with units, e.g. "20 ms")

  • inplace (bool or None) – if True, make change in-place and return the object if False, make and return copy before making changes if None, use the object-level setting

Returns:

the object with the smoothed pupil data

Return type:

PupilData

summary()[source]

Return a summary of the dataset as a dictionary.

Returns:

dictionary containing description of dataset

Return type:

dict

GazeData

class pypillometry.GazeData(time=None, left_x=None, left_y=None, right_x=None, right_y=None, event_onsets=None, event_labels=None, sampling_rate=None, experimental_setup=None, name=None, calibration=None, fill_time_discontinuities=True, keep_orig=False, info=None, inplace=None)[source]

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

If you also want to store pupil-size, use the EyeData class. If you only want to store pupil-size, use the PupilData class.

Properties such as eyes and variables are inherited from GenericEyeData.

Parameters:
  • time (ndarray) – timing array (in ms) or None, in which case the time-array goes from [0,maxT] using sampling_rate

  • left_x (ndarray) – data from left eye (at least one of the eyes must be provided, both x and y)

  • left_y (ndarray) – data from left eye (at least one of the eyes must be provided, both x and y)

  • right_x (ndarray) – data from right eye (at least one of the eyes must be provided, both x and y)

  • right_y (ndarray) – data from right eye (at least one of the eyes must be provided, both x and y)

  • event_onsets (ndarray) – time-onsets of any events in the data (in ms, matched in time vector)

  • event_labels (ndarray) – for each event in event_onsets, provide a label

  • sampling_rate (float) – sampling-rate of the signal in Hz; if None, it is estimated from the median difference in time (either time or sampling_rate must be provided)

  • experimental_setup (Optional[ExperimentalSetup]) – Geometric model of the experimental setup including screen geometry, eye position, camera position, and screen orientation.

  • name (str) – name of the dataset or None (in which case a random string is selected)

  • calibration (Optional[Dict[str, SpatialCalibration]]) – Dictionary of SpatialCalibration objects, one per eye (keys: ‘left’, ‘right’)

  • fill_time_discontinuities (bool) – sometimes, when the eyetracker loses signal, no entry in the EDF is made; when this option is True, such entries will be made and the signal set to 0 there (or do it later using fill_time_discontinuities())

  • keep_orig (bool) – keep a copy of the original dataset? If True, a copy of the object as initiated in the constructor is stored in member original

  • info (dict) – optional dictionary with additional information about the dataset (stored in member info)

  • inplace (Optional[bool]) – default for methods called with inplace=None: if True, they modify the object in place; if False, they return a modified copy; if None (default), use the package-level setting (see set_default_inplace(), initially True). The method-level inplace argument overrides this.

mask_eye_divergences(threshold=0.99, thr_type='percentile', store_as=None, apply_mask=True, ignore_existing_mask=True, inplace=None)[source]

Calculate Euclidean distance between left and right eye coordinates and detect divergences.

This method computes the distance between corresponding (x,y) coordinates in the left and right eyes. Points where the distance exceeds a threshold are detected as divergences. When apply_mask=True, these points are masked. When apply_mask=False, the divergences are returned as Intervals objects.

Parameters:
  • threshold (float) – If thr_type is “percentile”: percentile value (0-1) for threshold calculation. If thr_type is “pixel”: maximum allowed distance in pixels.

  • thr_type (str) –

    Type of threshold to apply:

    • ”percentile”: threshold is calculated as the given percentile of the distance distribution

    • ”pixel”: threshold is the maximum allowed distance in pixels

  • store_as (str | None) – Variable name to store the distance timeseries. If None, distance is not stored.

  • apply_mask (bool) – If True, apply detected divergences as masks to the data and return self. If False, return detected divergences as an Intervals object.

  • ignore_existing_mask (bool) – If True (default), check all data points including those already masked (e.g., from blinks). If False, only check non-masked data points for divergences.

  • inplace (bool or None) – If True, make change in-place and return the object. If False, make and return copy before making changes. If None, use the object-level setting.

Returns:

If apply_mask=True: returns the object for chaining. If apply_mask=False: returns Intervals object containing detected divergence intervals.

Return type:

GazeData or Intervals

Raises:

ValueError – If both left and right eye data are not available. If thr_type is not “percentile” or “pixel”.

Examples

>>> # Mask divergences at 99th percentile
>>> gaze_data = gaze_data.mask_eye_divergences(threshold=0.99, thr_type="percentile")
>>> # Detect divergences without masking
>>> divergences = gaze_data.mask_eye_divergences(threshold=0.99, apply_mask=False)
>>> # Mask divergences beyond 100 pixels
>>> gaze_data = gaze_data.mask_eye_divergences(threshold=100, thr_type="pixel")
>>> # Only check non-masked data points
>>> gaze_data = gaze_data.mask_eye_divergences(ignore_existing_mask=False)
mask_offscreen_coords(eyes=[], apply_mask=True, ignore_existing_mask=False, inplace=None)[source]

Detect and mask gaze coordinates that fall outside the screen boundaries.

This method identifies points where the (x,y) coordinates fall outside the defined screen limits. When apply_mask=True, these points are masked. When apply_mask=False, the offscreen periods are returned as Intervals objects.

Parameters:
  • eyes (str | list) – Eye(s) to check for offscreen coordinates. If empty, checks all available eyes. Can be a single eye name (e.g., “left”) or a list (e.g., [“left”, “right”]).

  • apply_mask (bool) – If True, apply detected offscreen periods as masks to the data and return self. If False, return detected offscreen periods as dict of Intervals objects.

  • ignore_existing_mask (bool) – If False (default), only check non-masked data points for offscreen coordinates. If True, check all data points including those already masked (e.g., from blinks).

  • inplace (bool or None) – If True, make change in-place and return the object. If False, make and return copy before making changes. If None, use the object-level setting.

Returns:

If apply_mask=True: returns self for chaining. If apply_mask=False: returns dict of Intervals objects (one per eye) containing detected offscreen intervals.

Return type:

GazeData or dict of Intervals

Examples

>>> # Mask offscreen coordinates for all eyes
>>> gaze_data = gaze_data.mask_offscreen_coords()
>>> # Detect offscreen periods without masking
>>> offscreen = gaze_data.mask_offscreen_coords(apply_mask=False)
>>> # Mask offscreen coordinates for left eye only
>>> gaze_data = gaze_data.mask_offscreen_coords(eyes='left')
>>> # Check all data including already masked points (e.g., during blinks)
>>> gaze_data = gaze_data.mask_offscreen_coords(ignore_existing_mask=True)
property plot

Plotting functions for this object (a GazePlotter).

set_experimental_setup(**kwargs)[source]

Set or update the experimental setup geometry (in-place).

Creates a new ExperimentalSetup or updates the existing one with the provided parameters (parameters that are not given are taken from the existing setup). All parameters are passed through to the ExperimentalSetup constructor.

Parameters:

**kwargs (dict) –

Any parameters accepted by ExperimentalSetup:

  • screen_resolution: tuple (width, height) in pixels

  • physical_screen_size: tuple (width, height) with units

  • eye_to_screen_center: distance from the eye to the screen center

  • screen_offset: tuple (delta_x, delta_y) - offset of screen center from eye

  • screen_pitch: tilt angle (alpha_tilt)

  • screen_yaw: tilt angle (beta_tilt)

  • camera_offset: tuple (x, y, z) in eye-centric frame

  • camera_spherical: tuple (theta, phi, r)

  • ipd: inter-pupillary distance

Examples

>>> data.set_experimental_setup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm"),
...     eye_to_screen_center="65 cm",
... )
summary()[source]

Return a summary of the dataset as a dictionary.

Returns:

dictionary containing description of dataset

Return type:

dict

Plotting

class pypillometry.EyePlotter(obj)[source]

Class for plotting combined gaze and pupil data. It provides all methods of GazePlotter and PupilPlotter and additional plots that combine gaze and pupil data.

It is usually not created directly; instead, it is available as the plot attribute of an EyeData object, e.g. d.plot.plot_pupil_foreshortening_error_surface().

Parameters:

obj (GenericEyeData) – the data object to plot (stored as obj)

Plot detected blinks in separate subplots.

Parameters:
  • eyes (str | list) – Eyes to plot

  • variables (str | list) – Variables to plot

  • pdf_file (str | None) – Save to PDF file if provided

  • nrow (int) – Number of rows per figure

  • ncol (int) – Number of columns per figure

  • figsize (tuple) – Figure size (width, height)

  • pre_blink (float) – Time before blink to include (in ms)

  • post_blink (float) – Time after blink to include (in ms)

  • units (str) – Display units for time axis

  • show_index (bool) – Show blink index numbers

Returns:

List of matplotlib Figure objects

Return type:

list

plot_calibration(eyes=None, show_surface=True, interpolation='rbf', figsize=(10, 8))

Plot spatial calibration data for one or more eyes in subplots.

Creates a figure with subplots for each eye and calls the plot() method on each SpatialCalibration object to show calibration accuracy with target points (black X), measured points (dark blue +), and optional interpolated error surface.

Parameters:
  • eyes (Union[str, list, None]) – Which eye(s) to plot. If None or empty list, plots all available eyes. Can be a single eye name (e.g., ‘left’) or list of eye names (e.g., [‘left’, ‘right’]).

  • show_surface (bool) – If True, show interpolated error surface as background. If False, show only the calibration points.

  • interpolation (str) – Interpolation method for surface (only used if show_surface=True). Options: ‘rbf’, ‘linear’, ‘cubic’, ‘nearest’

  • figsize (tuple) – Figure size per subplot (width, height) in inches.

Returns:

  • fig (matplotlib.figure.Figure) – The figure object

  • axes (list or matplotlib.axes.Axes) – List of axis objects (or single axis if only one eye)

Raises:

ValueError – If no calibration data is available

Examples

>>> import pypillometry as pp
>>> data = pp.EyeData.from_eyelink('data.edf')
>>> # Plot all eyes with surface
>>> fig, axes = data.plot.plot_calibration()
>>>
>>> # Plot only left eye without surface
>>> fig, ax = data.plot.plot_calibration(eyes='left', show_surface=False)
>>>
>>> # Plot specific eyes
>>> fig, axes = data.plot.plot_calibration(eyes=['left', 'right'])
plot_experimental_setup(theta=None, phi=None, calibration=None, show_gaze_samples=True, n_gaze_samples=9, ax=None, viewing_angle=None, projection='2d')[source]

Plot visualization of eye-tracking experimental setup geometry.

Shows the spatial relationship between eye, camera, and screen using the experimental parameters stored in the EyeData object. Delegates to ExperimentalSetup.plot().

Parameters:
  • theta (Optional[float]) – Camera polar angle; a float is interpreted as radians, a string can carry units (e.g., “20 deg”). If None, uses calibration’s theta (if calibration is given), else setup’s theta.

  • phi (Optional[float]) – Camera azimuthal angle; a float is interpreted as radians, a string can carry units (e.g., “-90 deg”). If None, uses calibration’s phi (if calibration is given), else setup’s phi.

  • calibration (ForeshorteningCalibration, optional) – Fitted calibration object containing theta and phi.

  • show_gaze_samples (bool) – Show sample gaze vectors to screen positions

  • n_gaze_samples (int) – Number of sample gaze positions

  • ax (Optional[Axes]) – Existing axis to plot on (only for ‘3d’ projection)

  • viewing_angle (tuple) – Viewing angle for 3D projection (elev, azim) or (elev, azim, roll). Plain numbers are in degrees (as in matplotlib’s view_init); strings with units (e.g., ("30 deg", "-60 deg")) are parsed.

  • projection (str) – ‘2d’ for three orthogonal views, ‘3d’ for interactive 3D view

Return type:

tuple

Returns:

  • fig (matplotlib Figure)

  • ax (matplotlib axis or array of axes)

Raises:

ValueError – if the data object has no experimental_setup

Examples

>>> # Use camera geometry from setup
>>> data.plot.plot_experimental_setup()
>>>
>>> # Use fitted angles from calibration
>>> calib = data.fit_foreshortening(eye='left')
>>> data.plot.plot_experimental_setup(calibration=calib)
>>>
>>> # Override angles
>>> data.plot.plot_experimental_setup(theta=np.radians(20), phi=np.radians(-90))
plot_heatmap(plot_range=(-inf, inf), eyes=[], show_screen=True, show_masked=False, rois=None, roi_style={}, units='sec', cmap='jet', gridsize=30, min_samples=1, smooth=None, limits=None)

Plot EyeData as a heatmap. Typically used for a large amount of data to spot the most frequent locations. To plot a scanpath, use plot_scanpath() instead. Each eye is plotted in a separate subplot of the current figure (a new figure is created if none exists); the colorbar shows the number of samples per bin.

Parameters:
  • plot_range (tuple) – The time range to plot (in units, or with units, e.g. (3, 5) * u.min). Default is (-np.inf, +np.inf), i.e. all data.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, average, regression, …)

  • show_screen (bool) – Whether to plot the screen limits (if an experimental setup is available). Default is True.

  • show_masked (bool) – Whether to plot the masked data (because of blinks, artifacts, …). Default is False.

  • rois (List[ROI]) – List of ROIs (ROI) to plot. Default is None.

  • roi_style (dict) –

    Style for the ROIs, passed as keyword arguments to the ROIs’ plot() method. Default is {} (ROI defaults). Example:

    {
        "facecolor": "red",
        "edgecolor": "black",
        "linewidth": 2,
        "alpha": 0.5
    }
    

  • units (str) – The units of plot_range (“ms”, “sec”, “min”, “h”). Default is “sec”.

  • cmap (str) – The colormap to use. Default is “jet”.

  • gridsize (int | str) – The gridsize for the hexbin plot or histogram. If “auto”, the square root of the number of samples in plot_range is used. Default is 30.

  • min_samples (Optional[int]) – Minimum number of samples required in a bin to display it. Bins with fewer samples will not be shown. If None, automatically calculates a threshold at the 10th percentile of bin counts. Default is 1.

  • smooth (Optional[float]) – If provided, applies Gaussian smoothing to the binned histogram. The value is the sigma of the Gaussian kernel in bin units (e.g., smooth=1 applies minimal smoothing with a 1-bin sigma, smooth=2 applies more smoothing). When smooth is provided, uses a rectangular grid instead of hexbin.

  • limits (Optional[Tuple[float, float, float, float]]) – Axis limits as (xmin, xmax, ymin, ymax). If not provided, uses screen limits when available (and data falls within), otherwise uses data extent.

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

plot_intervals(intervals, eyes=[], variables=[], pdf_file=None, nrow=5, ncol=3, figsize=(10, 10), show_mask=False, show_index=True)

Plotting data around intervals.

Intervals can be extracted with get_intervals(). Each interval gets a separate subplot. The data is plotted for each eye and variable in different colors. The x-axis is plotted in the units of the Intervals object. If the intervals contain event onsets, these are marked by a dashed red line.

Parameters:
  • intervals (Intervals) – Intervals object containing the intervals to plot (from get_intervals())

  • eyes (str | list) – eyes to plot; if empty, all available eyes are plotted

  • variables (str | list) – variables to plot; if empty, all available variables are plotted

  • pdf_file (Optional[str]) – if the name of a file is given, the figures are saved into a multi-page PDF file (missing directories are created)

  • nrow (int) – number of rows for the subplots for the intervals

  • ncol (int) – number of columns for the subplots for the intervals

  • figsize (Tuple[int, int]) – size (width, height) of each figure in inches

  • show_mask (bool) – shade samples masked in any of the plotted eyes/variables

  • show_index (bool) – plot a number with the interval’s index (e.g., for identifying abnormal blinks)

Returns:

one figure per nrow x ncol intervals (also returned when pdf_file is given; the figures are then closed)

Return type:

list of matplotlib.figure.Figure

Raises:

TypeError – if intervals is not an Intervals object

plot_masked(eyes=[], variables=[], units='min', merge=False, show_labels=True, show_events=False, **kwargs)

Plot masked intervals for specified eyes and variables.

Visualizes masked data segments as horizontal lines on a timeline, similar to Intervals.plot(). Each interval is shown at a different y-level. When plotting multiple eyes/variables without merging, each is shown in a different color with slight y-offsets for clarity.

Parameters:
  • eyes (str | list) – Eye(s) to plot masks for. If empty, uses all available eyes.

  • variables (str | list) – Variable(s) to plot masks for. If empty, uses all available variables.

  • units (str) – Time units for x-axis: “ms”, “sec”, “min”, “h”, or None for indices. Default is “min”.

  • merge (bool) – If True, merge all masks into a single set of intervals for plotting. If False, plot each eye/variable separately with different colors. Default is False.

  • show_labels (bool) – Whether to show labels for each interval group. Default is True.

  • show_events (bool) – Whether to show vertical grey stripes at event positions. Default is False.

  • **kwargs (dict) – Additional keyword arguments passed to matplotlib plot()

Returns:

Modifies the current axes in place

Return type:

None

Examples

Plot masked intervals for left pupil:

>>> data.plot.plot_masked('left', 'pupil')

Plot for multiple eyes with different colors:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=False)

Merge all masks and plot as one:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=True)

Plot with event markers:

>>> data.plot.plot_masked('left', 'pupil', show_events=True)
plot_pupil_foreshortening_error_surface(eyes=[], plot_range=(-inf, inf), units='sec', show_screen=True, cmap='jet', gridsize=30, vmin=None, vmax=None, min_samples=None, smooth=None, limits=None)[source]

Plot a heatmap showing average pupil size across x/y gaze positions.

This visualizes how pupil size varies across different gaze positions, which can reveal foreshortening effects (pupils appear smaller when looking away from the camera) or other spatial artifacts. Each eye is plotted in a separate subplot.

Parameters:
  • eyes (Union[str, List[str]]) – Which eye(s) to plot: “left”, “right”, “average”, or a list of these. Default is [] which plots all available eyes.

  • plot_range (tuple) – The time range to include in the analysis (in units, or with units, e.g. (3, 5) * u.min). Default is (-np.inf, +np.inf), i.e. all data.

  • units (str) – Time units for plot_range. Default is “sec”.

  • show_screen (bool) – Whether to plot the screen limits. Default is True.

  • cmap (str) – Colormap to use. Default is “jet”.

  • gridsize (int) – Number of bins in each direction. Default is 30.

  • vmin (Optional[float]) – Minimum value for color scale. If None, uses data minimum.

  • vmax (Optional[float]) – Maximum value for color scale. If None, uses data maximum.

  • min_samples (Optional[int]) – Minimum number of samples required in a bin to display it. Bins with fewer samples will not be shown. If None (default), automatically calculates a threshold at the 10th percentile of bin counts, effectively displaying only the top 90% of bins by sample count.

  • smooth (Optional[float]) – If provided, applies Gaussian smoothing to the binned surface. The value is the sigma of the Gaussian kernel in bin units (e.g., smooth=1 applies minimal smoothing with a 1-bin sigma, smooth=2 applies more smoothing). When smooth is provided, uses a rectangular grid instead of hexbin.

  • limits (Optional[Tuple[float, float, float, float]]) – Axis limits as (xmin, xmax, ymin, ymax). If not provided, uses screen limits when available (and data falls within), otherwise uses data extent.

Returns:

Creates a matplotlib figure with the heatmap(s)

Return type:

None

Examples

Plot foreshortening effect for left eye:

>>> data.plot.plot_pupil_foreshortening_error_surface('left')

Plot for multiple eyes:

>>> data.plot.plot_pupil_foreshortening_error_surface(['left', 'right'])

Plot all available eyes with custom color range:

>>> data.plot.plot_pupil_foreshortening_error_surface(vmin=3, vmax=5)

Plot with a specific minimum sample threshold:

>>> data.plot.plot_pupil_foreshortening_error_surface(min_samples=50)

Plot with Gaussian smoothing:

>>> data.plot.plot_pupil_foreshortening_error_surface(smooth=1.5)

Plot with custom limits:

>>> data.plot.plot_pupil_foreshortening_error_surface(limits=(0, 1920, 0, 1080))
plot_scanpath(plot_range=(-inf, inf), eyes=[], show_screen=True, rois=None, roi_style={}, show_onsets=True, title='', units=None, figsize=(10, 10))

Plot EyeData as a scanpath. Typically used for single trials or another small amount of data. To plot a larger amount of data, use plot_heatmap() instead.

If several eyes are available, they are plotted in the same figure in different colors. You can choose between them using the eyes parameter. The samples are drawn as dots colored by time. A new figure is created for the plot.

Parameters:
  • plot_range (tuple) – The time range to plot (in units, or with units, e.g. (3, 5) * u.min). Default is (-np.inf, +np.inf), i.e. all data.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, average, regression, …)

  • show_screen (bool) – Whether to plot the screen limits (if an experimental setup is available). Default is True.

  • rois (List[ROI]) – List of ROIs (ROI) to plot. Default is None.

  • roi_style (dict) –

    Style for the ROIs, passed as keyword arguments to the ROIs’ plot() method. Default is {} (ROI defaults). Example:

    {
        "facecolor": "red",
        "edgecolor": "black",
        "linewidth": 2,
        "alpha": 0.5
    }
    

  • show_onsets (bool) – Whether to plot the event labels at the gaze position at event onset. Plotted only for the first eye. Default is True.

  • title (str) – The title of the plot. Default is “”.

  • units (str) – The units of plot_range (“ms”, “sec”, “min”, “h”). If None (default), the units of the time vector (ms) are used.

  • figsize (tuple) – The figure size (width, height) in inches. Default is (10,10).

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

plot_timeseries(plot_range=(-inf, inf), variables=[], eyes=[], units=None, show_onsets='line', show_masked=True, show_mask_highlight=True, label_prefix='', style=None)

Plot a part of the EyeData. Each data type is plotted in a separate subplot.

Uses the current figure/axes. If no figure exists, creates one automatically.

Parameters:
  • plot_range (tuple) – The time range to plot (in units). Default is (-np.inf, +np.inf), i.e. all data.

  • variables (list) – The data to plot. Default is [], which means all available data will be plotted. Available data are [“x”,”y”,”pupil”] but can be extended by the user.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, “average”, “regression”, …)

  • units (Optional[str]) – The units of plot_range and the time axis (“ms”, “sec”, “min”, “h”). If None (default), use the units in the time vector (ms).

  • show_onsets (str) – Whether to plot markers for the event onsets. One of “line” (vertical lines), “label” (text label), “both” (both lines and labels), or “none” (no markers). Default is “line”.

  • show_masked (bool) – Whether to plot the data in masked regions. If True (default), plots all data including masked regions. If False, masked data points are not plotted (gaps in the line).

  • show_mask_highlight (bool) – Whether to highlight masked regions with a light red background. Default is True.

  • label_prefix (str) – Prefix to add to labels in the legend. Useful for overlaying multiple datasets. Default is “” (no prefix).

  • style (dict) –

    Styling for plotted lines. Can be:

    • Single dict: Applied to all eyes with automatic differentiation (e.g., {‘color’: ‘red’})

    • Dict of dicts: Per-eye styling (e.g., {‘left’: {‘linestyle’: ‘-‘}, ‘right’: {‘linestyle’: ‘–‘}})

    Default is None (uses matplotlib defaults with automatic per-eye colors and linestyles).

Raises:

ValueError – if show_onsets is not one of the allowed values or if plot_range does not overlap with the data

Return type:

None

plot_timeseries_segments(pdf_file=None, interv=1, figsize=(15, 5), ylim=None, eyes=[], variables=[], units='min', **kwargs)

Plot the whole dataset chunked up into segments (usually to a PDF file).

Parameters:
  • pdf_file (str) – file name to store the PDF; if None, no PDF is written

  • interv (float) – duration of each of the segments to be plotted (in the units specified by units)

  • figsize (Tuple[int,int]) – dimensions of the figures

  • ylim (Tuple[float,float] or None) – y-axis limits for all subplots

  • eyes (list) – list of eyes to plot; if empty, all available eyes are plotted

  • variables (list) – list of variables to plot; if empty, all available variables are plotted

  • units (str) – units for the time axis; default is “min” (minutes). The interv parameter is also interpreted in these units.

  • **kwargs – additional arguments passed to plot_timeseries()

Returns:

one figure per segment; None if pdf_file is given

Return type:

list of matplotlib.figure.Figure or None

pupil_plot(eyes=[], plot_range=(-inf, inf), show_events=True, show_blinks=True, units='sec', label_prefix='', style=None)

Make a plot of the pupil data using matplotlib.

Has pupil-specific options which makes it different from plot_timeseries(). All eyes are plotted into the current axes.

Parameters:
  • eyes (str | list) – eye(s) to plot; if empty, all eyes with pupil data are plotted

  • plot_range (Tuple[float, float]) – plot from start to end (in units of units, or with units, e.g. (3, 5) * u.min)

  • show_events (bool) – plot events as vertical lines with labels

  • show_blinks (bool) – highlight masked regions of the pupil data (e.g., detected blinks) with a red background

  • units (str) – one of “sec”=seconds (default), “ms”=millisec, “min”=minutes, “h”=hours

  • label_prefix (str) – Prefix to add to labels in the legend. Useful for overlaying multiple datasets. Default is “” (no prefix).

  • style (dict) –

    Styling for plotted lines. Can be:

    • Single dict: Applied to all eyes with automatic differentiation (e.g., {‘color’: ‘red’})

    • Dict of dicts: Per-eye styling (e.g., {‘left’: {‘linestyle’: ‘-‘}, ‘right’: {‘linestyle’: ‘–‘}})

    Default is None (uses matplotlib defaults with automatic per-eye colors and linestyles).

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

pupil_plot_segments(pdf_file=None, interv=1, figsize=(15, 5), ylim=None, units='min', **kwargs)

Plot the whole dataset chunked up into segments (usually to a PDF file).

Parameters:
  • pdf_file (Optional[str]) – file name to store the PDF; if None, no PDF is written

  • interv (float) – duration of each of the segments to be plotted (in units specified by units, or with units, e.g. "2 min")

  • figsize (Tuple[int,int]) – dimensions of the figures

  • ylim (Tuple[float,float] or None) – y-axis limits for the plots; if None, matplotlib’s automatic limits are used

  • units (str) – Time units for segment boundaries passed to pupil_plot(). Default “min”.

  • **kwargs – arguments passed to pupil_plot()

Returns:

one figure per segment; None if pdf_file is given

Return type:

list of matplotlib.figure.Figure or None

class pypillometry.PupilPlotter(obj)[source]

Class for plotting pupil data. The class is initialized with a GenericEyeData object and provides methods to plot the data in various ways.

It is usually not created directly; instead, it is available as the plot attribute of a PupilData object, e.g. d.plot.pupil_plot().

Parameters:

obj (GenericEyeData) – the data object to plot (stored as obj)

Plot detected blinks in separate subplots.

Parameters:
  • eyes (str | list) – Eyes to plot

  • variables (str | list) – Variables to plot

  • pdf_file (str | None) – Save to PDF file if provided

  • nrow (int) – Number of rows per figure

  • ncol (int) – Number of columns per figure

  • figsize (tuple) – Figure size (width, height)

  • pre_blink (float) – Time before blink to include (in ms)

  • post_blink (float) – Time after blink to include (in ms)

  • units (str) – Display units for time axis

  • show_index (bool) – Show blink index numbers

Returns:

List of matplotlib Figure objects

Return type:

list

plot_intervals(intervals, eyes=[], variables=[], pdf_file=None, nrow=5, ncol=3, figsize=(10, 10), show_mask=False, show_index=True)

Plotting data around intervals.

Intervals can be extracted with get_intervals(). Each interval gets a separate subplot. The data is plotted for each eye and variable in different colors. The x-axis is plotted in the units of the Intervals object. If the intervals contain event onsets, these are marked by a dashed red line.

Parameters:
  • intervals (Intervals) – Intervals object containing the intervals to plot (from get_intervals())

  • eyes (str | list) – eyes to plot; if empty, all available eyes are plotted

  • variables (str | list) – variables to plot; if empty, all available variables are plotted

  • pdf_file (Optional[str]) – if the name of a file is given, the figures are saved into a multi-page PDF file (missing directories are created)

  • nrow (int) – number of rows for the subplots for the intervals

  • ncol (int) – number of columns for the subplots for the intervals

  • figsize (Tuple[int, int]) – size (width, height) of each figure in inches

  • show_mask (bool) – shade samples masked in any of the plotted eyes/variables

  • show_index (bool) – plot a number with the interval’s index (e.g., for identifying abnormal blinks)

Returns:

one figure per nrow x ncol intervals (also returned when pdf_file is given; the figures are then closed)

Return type:

list of matplotlib.figure.Figure

Raises:

TypeError – if intervals is not an Intervals object

plot_masked(eyes=[], variables=[], units='min', merge=False, show_labels=True, show_events=False, **kwargs)

Plot masked intervals for specified eyes and variables.

Visualizes masked data segments as horizontal lines on a timeline, similar to Intervals.plot(). Each interval is shown at a different y-level. When plotting multiple eyes/variables without merging, each is shown in a different color with slight y-offsets for clarity.

Parameters:
  • eyes (str | list) – Eye(s) to plot masks for. If empty, uses all available eyes.

  • variables (str | list) – Variable(s) to plot masks for. If empty, uses all available variables.

  • units (str) – Time units for x-axis: “ms”, “sec”, “min”, “h”, or None for indices. Default is “min”.

  • merge (bool) – If True, merge all masks into a single set of intervals for plotting. If False, plot each eye/variable separately with different colors. Default is False.

  • show_labels (bool) – Whether to show labels for each interval group. Default is True.

  • show_events (bool) – Whether to show vertical grey stripes at event positions. Default is False.

  • **kwargs (dict) – Additional keyword arguments passed to matplotlib plot()

Returns:

Modifies the current axes in place

Return type:

None

Examples

Plot masked intervals for left pupil:

>>> data.plot.plot_masked('left', 'pupil')

Plot for multiple eyes with different colors:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=False)

Merge all masks and plot as one:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=True)

Plot with event markers:

>>> data.plot.plot_masked('left', 'pupil', show_events=True)
plot_timeseries(plot_range=(-inf, inf), variables=[], eyes=[], units=None, show_onsets='line', show_masked=True, show_mask_highlight=True, label_prefix='', style=None)

Plot a part of the EyeData. Each data type is plotted in a separate subplot.

Uses the current figure/axes. If no figure exists, creates one automatically.

Parameters:
  • plot_range (tuple) – The time range to plot (in units). Default is (-np.inf, +np.inf), i.e. all data.

  • variables (list) – The data to plot. Default is [], which means all available data will be plotted. Available data are [“x”,”y”,”pupil”] but can be extended by the user.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, “average”, “regression”, …)

  • units (Optional[str]) – The units of plot_range and the time axis (“ms”, “sec”, “min”, “h”). If None (default), use the units in the time vector (ms).

  • show_onsets (str) – Whether to plot markers for the event onsets. One of “line” (vertical lines), “label” (text label), “both” (both lines and labels), or “none” (no markers). Default is “line”.

  • show_masked (bool) – Whether to plot the data in masked regions. If True (default), plots all data including masked regions. If False, masked data points are not plotted (gaps in the line).

  • show_mask_highlight (bool) – Whether to highlight masked regions with a light red background. Default is True.

  • label_prefix (str) – Prefix to add to labels in the legend. Useful for overlaying multiple datasets. Default is “” (no prefix).

  • style (dict) –

    Styling for plotted lines. Can be:

    • Single dict: Applied to all eyes with automatic differentiation (e.g., {‘color’: ‘red’})

    • Dict of dicts: Per-eye styling (e.g., {‘left’: {‘linestyle’: ‘-‘}, ‘right’: {‘linestyle’: ‘–‘}})

    Default is None (uses matplotlib defaults with automatic per-eye colors and linestyles).

Raises:

ValueError – if show_onsets is not one of the allowed values or if plot_range does not overlap with the data

Return type:

None

plot_timeseries_segments(pdf_file=None, interv=1, figsize=(15, 5), ylim=None, eyes=[], variables=[], units='min', **kwargs)

Plot the whole dataset chunked up into segments (usually to a PDF file).

Parameters:
  • pdf_file (str) – file name to store the PDF; if None, no PDF is written

  • interv (float) – duration of each of the segments to be plotted (in the units specified by units)

  • figsize (Tuple[int,int]) – dimensions of the figures

  • ylim (Tuple[float,float] or None) – y-axis limits for all subplots

  • eyes (list) – list of eyes to plot; if empty, all available eyes are plotted

  • variables (list) – list of variables to plot; if empty, all available variables are plotted

  • units (str) – units for the time axis; default is “min” (minutes). The interv parameter is also interpreted in these units.

  • **kwargs – additional arguments passed to plot_timeseries()

Returns:

one figure per segment; None if pdf_file is given

Return type:

list of matplotlib.figure.Figure or None

pupil_plot(eyes=[], plot_range=(-inf, inf), show_events=True, show_blinks=True, units='sec', label_prefix='', style=None)[source]

Make a plot of the pupil data using matplotlib.

Has pupil-specific options which makes it different from plot_timeseries(). All eyes are plotted into the current axes.

Parameters:
  • eyes (str | list) – eye(s) to plot; if empty, all eyes with pupil data are plotted

  • plot_range (Tuple[float, float]) – plot from start to end (in units of units, or with units, e.g. (3, 5) * u.min)

  • show_events (bool) – plot events as vertical lines with labels

  • show_blinks (bool) – highlight masked regions of the pupil data (e.g., detected blinks) with a red background

  • units (str) – one of “sec”=seconds (default), “ms”=millisec, “min”=minutes, “h”=hours

  • label_prefix (str) – Prefix to add to labels in the legend. Useful for overlaying multiple datasets. Default is “” (no prefix).

  • style (dict) –

    Styling for plotted lines. Can be:

    • Single dict: Applied to all eyes with automatic differentiation (e.g., {‘color’: ‘red’})

    • Dict of dicts: Per-eye styling (e.g., {‘left’: {‘linestyle’: ‘-‘}, ‘right’: {‘linestyle’: ‘–‘}})

    Default is None (uses matplotlib defaults with automatic per-eye colors and linestyles).

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

pupil_plot_segments(pdf_file=None, interv=1, figsize=(15, 5), ylim=None, units='min', **kwargs)[source]

Plot the whole dataset chunked up into segments (usually to a PDF file).

Parameters:
  • pdf_file (Optional[str]) – file name to store the PDF; if None, no PDF is written

  • interv (float) – duration of each of the segments to be plotted (in units specified by units, or with units, e.g. "2 min")

  • figsize (Tuple[int,int]) – dimensions of the figures

  • ylim (Tuple[float,float] or None) – y-axis limits for the plots; if None, matplotlib’s automatic limits are used

  • units (str) – Time units for segment boundaries passed to pupil_plot(). Default “min”.

  • **kwargs – arguments passed to pupil_plot()

Returns:

one figure per segment; None if pdf_file is given

Return type:

list of matplotlib.figure.Figure or None

class pypillometry.GazePlotter(obj)[source]

Class for plotting gaze data. The class is initialized with a GenericEyeData object and provides methods to plot the data in various ways.

It is usually not created directly; instead, it is available as the plot attribute of a GazeData object, e.g. d.plot.plot_heatmap().

Parameters:

obj (GenericEyeData) – the data object to plot (stored as obj)

plot_calibration(eyes=None, show_surface=True, interpolation='rbf', figsize=(10, 8))[source]

Plot spatial calibration data for one or more eyes in subplots.

Creates a figure with subplots for each eye and calls the plot() method on each SpatialCalibration object to show calibration accuracy with target points (black X), measured points (dark blue +), and optional interpolated error surface.

Parameters:
  • eyes (Union[str, list, None]) – Which eye(s) to plot. If None or empty list, plots all available eyes. Can be a single eye name (e.g., ‘left’) or list of eye names (e.g., [‘left’, ‘right’]).

  • show_surface (bool) – If True, show interpolated error surface as background. If False, show only the calibration points.

  • interpolation (str) – Interpolation method for surface (only used if show_surface=True). Options: ‘rbf’, ‘linear’, ‘cubic’, ‘nearest’

  • figsize (tuple) – Figure size per subplot (width, height) in inches.

Returns:

  • fig (matplotlib.figure.Figure) – The figure object

  • axes (list or matplotlib.axes.Axes) – List of axis objects (or single axis if only one eye)

Raises:

ValueError – If no calibration data is available

Examples

>>> import pypillometry as pp
>>> data = pp.EyeData.from_eyelink('data.edf')
>>> # Plot all eyes with surface
>>> fig, axes = data.plot.plot_calibration()
>>>
>>> # Plot only left eye without surface
>>> fig, ax = data.plot.plot_calibration(eyes='left', show_surface=False)
>>>
>>> # Plot specific eyes
>>> fig, axes = data.plot.plot_calibration(eyes=['left', 'right'])
plot_heatmap(plot_range=(-inf, inf), eyes=[], show_screen=True, show_masked=False, rois=None, roi_style={}, units='sec', cmap='jet', gridsize=30, min_samples=1, smooth=None, limits=None)[source]

Plot EyeData as a heatmap. Typically used for a large amount of data to spot the most frequent locations. To plot a scanpath, use plot_scanpath() instead. Each eye is plotted in a separate subplot of the current figure (a new figure is created if none exists); the colorbar shows the number of samples per bin.

Parameters:
  • plot_range (tuple) – The time range to plot (in units, or with units, e.g. (3, 5) * u.min). Default is (-np.inf, +np.inf), i.e. all data.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, average, regression, …)

  • show_screen (bool) – Whether to plot the screen limits (if an experimental setup is available). Default is True.

  • show_masked (bool) – Whether to plot the masked data (because of blinks, artifacts, …). Default is False.

  • rois (List[ROI]) – List of ROIs (ROI) to plot. Default is None.

  • roi_style (dict) –

    Style for the ROIs, passed as keyword arguments to the ROIs’ plot() method. Default is {} (ROI defaults). Example:

    {
        "facecolor": "red",
        "edgecolor": "black",
        "linewidth": 2,
        "alpha": 0.5
    }
    

  • units (str) – The units of plot_range (“ms”, “sec”, “min”, “h”). Default is “sec”.

  • cmap (str) – The colormap to use. Default is “jet”.

  • gridsize (int | str) – The gridsize for the hexbin plot or histogram. If “auto”, the square root of the number of samples in plot_range is used. Default is 30.

  • min_samples (Optional[int]) – Minimum number of samples required in a bin to display it. Bins with fewer samples will not be shown. If None, automatically calculates a threshold at the 10th percentile of bin counts. Default is 1.

  • smooth (Optional[float]) – If provided, applies Gaussian smoothing to the binned histogram. The value is the sigma of the Gaussian kernel in bin units (e.g., smooth=1 applies minimal smoothing with a 1-bin sigma, smooth=2 applies more smoothing). When smooth is provided, uses a rectangular grid instead of hexbin.

  • limits (Optional[Tuple[float, float, float, float]]) – Axis limits as (xmin, xmax, ymin, ymax). If not provided, uses screen limits when available (and data falls within), otherwise uses data extent.

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

plot_intervals(intervals, eyes=[], variables=[], pdf_file=None, nrow=5, ncol=3, figsize=(10, 10), show_mask=False, show_index=True)

Plotting data around intervals.

Intervals can be extracted with get_intervals(). Each interval gets a separate subplot. The data is plotted for each eye and variable in different colors. The x-axis is plotted in the units of the Intervals object. If the intervals contain event onsets, these are marked by a dashed red line.

Parameters:
  • intervals (Intervals) – Intervals object containing the intervals to plot (from get_intervals())

  • eyes (str | list) – eyes to plot; if empty, all available eyes are plotted

  • variables (str | list) – variables to plot; if empty, all available variables are plotted

  • pdf_file (Optional[str]) – if the name of a file is given, the figures are saved into a multi-page PDF file (missing directories are created)

  • nrow (int) – number of rows for the subplots for the intervals

  • ncol (int) – number of columns for the subplots for the intervals

  • figsize (Tuple[int, int]) – size (width, height) of each figure in inches

  • show_mask (bool) – shade samples masked in any of the plotted eyes/variables

  • show_index (bool) – plot a number with the interval’s index (e.g., for identifying abnormal blinks)

Returns:

one figure per nrow x ncol intervals (also returned when pdf_file is given; the figures are then closed)

Return type:

list of matplotlib.figure.Figure

Raises:

TypeError – if intervals is not an Intervals object

plot_masked(eyes=[], variables=[], units='min', merge=False, show_labels=True, show_events=False, **kwargs)

Plot masked intervals for specified eyes and variables.

Visualizes masked data segments as horizontal lines on a timeline, similar to Intervals.plot(). Each interval is shown at a different y-level. When plotting multiple eyes/variables without merging, each is shown in a different color with slight y-offsets for clarity.

Parameters:
  • eyes (str | list) – Eye(s) to plot masks for. If empty, uses all available eyes.

  • variables (str | list) – Variable(s) to plot masks for. If empty, uses all available variables.

  • units (str) – Time units for x-axis: “ms”, “sec”, “min”, “h”, or None for indices. Default is “min”.

  • merge (bool) – If True, merge all masks into a single set of intervals for plotting. If False, plot each eye/variable separately with different colors. Default is False.

  • show_labels (bool) – Whether to show labels for each interval group. Default is True.

  • show_events (bool) – Whether to show vertical grey stripes at event positions. Default is False.

  • **kwargs (dict) – Additional keyword arguments passed to matplotlib plot()

Returns:

Modifies the current axes in place

Return type:

None

Examples

Plot masked intervals for left pupil:

>>> data.plot.plot_masked('left', 'pupil')

Plot for multiple eyes with different colors:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=False)

Merge all masks and plot as one:

>>> data.plot.plot_masked(['left', 'right'], 'pupil', merge=True)

Plot with event markers:

>>> data.plot.plot_masked('left', 'pupil', show_events=True)
plot_scanpath(plot_range=(-inf, inf), eyes=[], show_screen=True, rois=None, roi_style={}, show_onsets=True, title='', units=None, figsize=(10, 10))[source]

Plot EyeData as a scanpath. Typically used for single trials or another small amount of data. To plot a larger amount of data, use plot_heatmap() instead.

If several eyes are available, they are plotted in the same figure in different colors. You can choose between them using the eyes parameter. The samples are drawn as dots colored by time. A new figure is created for the plot.

Parameters:
  • plot_range (tuple) – The time range to plot (in units, or with units, e.g. (3, 5) * u.min). Default is (-np.inf, +np.inf), i.e. all data.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, average, regression, …)

  • show_screen (bool) – Whether to plot the screen limits (if an experimental setup is available). Default is True.

  • rois (List[ROI]) – List of ROIs (ROI) to plot. Default is None.

  • roi_style (dict) –

    Style for the ROIs, passed as keyword arguments to the ROIs’ plot() method. Default is {} (ROI defaults). Example:

    {
        "facecolor": "red",
        "edgecolor": "black",
        "linewidth": 2,
        "alpha": 0.5
    }
    

  • show_onsets (bool) – Whether to plot the event labels at the gaze position at event onset. Plotted only for the first eye. Default is True.

  • title (str) – The title of the plot. Default is “”.

  • units (str) – The units of plot_range (“ms”, “sec”, “min”, “h”). If None (default), the units of the time vector (ms) are used.

  • figsize (tuple) – The figure size (width, height) in inches. Default is (10,10).

Raises:

ValueError – if plot_range does not overlap with the data

Return type:

None

plot_timeseries(plot_range=(-inf, inf), variables=[], eyes=[], units=None, show_onsets='line', show_masked=True, show_mask_highlight=True, label_prefix='', style=None)

Plot a part of the EyeData. Each data type is plotted in a separate subplot.

Uses the current figure/axes. If no figure exists, creates one automatically.

Parameters:
  • plot_range (tuple) – The time range to plot (in units). Default is (-np.inf, +np.inf), i.e. all data.

  • variables (list) – The data to plot. Default is [], which means all available data will be plotted. Available data are [“x”,”y”,”pupil”] but can be extended by the user.

  • eyes (list) – The eyes to plot. Default is [], which means all available data (“left”, “right”, “average”, “regression”, …)

  • units (Optional[str]) – The units of plot_range and the time axis (“ms”, “sec”, “min”, “h”). If None (default), use the units in the time vector (ms).

  • show_onsets (str) – Whether to plot markers for the event onsets. One of “line” (vertical lines), “label” (text label), “both” (both lines and labels), or “none” (no markers). Default is “line”.

  • show_masked (bool) – Whether to plot the data in masked regions. If True (default), plots all data including masked regions. If False, masked data points are not plotted (gaps in the line).

  • show_mask_highlight (bool) – Whether to highlight masked regions with a light red background. Default is True.

  • label_prefix (str) – Prefix to add to labels in the legend. Useful for overlaying multiple datasets. Default is “” (no prefix).

  • style (dict) –

    Styling for plotted lines. Can be:

    • Single dict: Applied to all eyes with automatic differentiation (e.g., {‘color’: ‘red’})

    • Dict of dicts: Per-eye styling (e.g., {‘left’: {‘linestyle’: ‘-‘}, ‘right’: {‘linestyle’: ‘–‘}})

    Default is None (uses matplotlib defaults with automatic per-eye colors and linestyles).

Raises:

ValueError – if show_onsets is not one of the allowed values or if plot_range does not overlap with the data

Return type:

None

plot_timeseries_segments(pdf_file=None, interv=1, figsize=(15, 5), ylim=None, eyes=[], variables=[], units='min', **kwargs)

Plot the whole dataset chunked up into segments (usually to a PDF file).

Parameters:
  • pdf_file (str) – file name to store the PDF; if None, no PDF is written

  • interv (float) – duration of each of the segments to be plotted (in the units specified by units)

  • figsize (Tuple[int,int]) – dimensions of the figures

  • ylim (Tuple[float,float] or None) – y-axis limits for all subplots

  • eyes (list) – list of eyes to plot; if empty, all available eyes are plotted

  • variables (list) – list of variables to plot; if empty, all available variables are plotted

  • units (str) – units for the time axis; default is “min” (minutes). The interv parameter is also interpreted in these units.

  • **kwargs – additional arguments passed to plot_timeseries()

Returns:

one figure per segment; None if pdf_file is given

Return type:

list of matplotlib.figure.Figure or None

Intervals and events

Intervals hold time intervals (e.g., blinks or windows around events, see get_intervals()); Events hold event onsets and labels (see get_events()).

class pypillometry.Intervals(intervals, units=<object object>, label=None, event_labels=None, event_indices=None, data_time_range=None, event_onsets=None, sampling_rate=None, time_offset=None, interval_window=None)[source]

Container for intervals with metadata.

This class wraps a list of (start, end) intervals and provides additional metadata such as units, labels, and associated event information.

Usually, Intervals objects are not created directly but returned by methods of the eye-data classes, e.g., get_intervals() (event-locked intervals, input for get_segments()), get_blinks() or mask_as_intervals(). Detection methods such as pupil_blinks_detect() store their results as Intervals. Several Intervals can be combined with + or merge_intervals() and subtracted with -.

Parameters:
  • intervals (list of tuples, np.ndarray or Quantity) – Intervals as list of (start, end) tuples or 2D array; can carry units, e.g. [(0, 100), (200, 300)] * u.ms or [("1 min", "2 min")]

  • units (str, Unit or None) – Units of the intervals (“ms”, “sec”, “min”, “h”, or None for indices). Can be omitted if intervals carry units; otherwise, intervals with units are converted to units.

  • label (str, optional) – Descriptive label for the interval collection

  • event_labels (list, optional) – Event labels corresponding to each interval

  • event_indices (np.ndarray, optional) – Indices (into the dataset’s events) of the event corresponding to each interval

  • data_time_range (tuple, optional) – Time range (min, max) of the original dataset (in same units as intervals)

  • event_onsets (list or np.ndarray, optional) – Original event onset times (in same units as intervals)

  • sampling_rate (float, optional) – Sampling rate in Hz. Required to convert index-based intervals to time units.

  • time_offset (float, optional) – Time offset in milliseconds. When intervals are stored in indices (units=None), this offset is added when converting to time units. This is needed when the dataset’s time vector doesn’t start at 0. Default is 0.

  • interval_window (tuple, optional) – The relative time window around events, in same units as intervals (e.g., (-200, 800) for 200ms before to 800ms after). When set, indicates that intervals are event-locked and zero corresponds to event onset. Used by SegmentedEyeData to create the appropriate time axis.

Examples

>>> intervals = Intervals([(0, 100), (200, 300)], units="ms", label="stimulus")
>>> intervals = Intervals([(0, 100), (200, 300)] * u.ms, label="stimulus")
>>> len(intervals)
2
>>> for start, end in intervals:
...     print(f"{start}-{end}")
as_pandas()[source]

Represent intervals as a pandas.DataFrame.

Returns:

DataFrame with columns start, end, and duration. When metadata is available, columns event_label, event_index, and event_onset are included. If the intervals have units, the column units is added.

Return type:

pandas.DataFrame

Raises:

ImportError – If pandas is not installed.

filter(selector=None, min_duration=None, max_duration=None, time_range=None)[source]

Filter intervals and return a new Intervals object.

This method supports multiple filtering criteria that can be combined: - Custom function: applied to each interval - Duration constraints: minimum and/or maximum duration - Time range: intervals must overlap with the specified range

All specified criteria are combined with AND logic.

Parameters:
  • selector (callable) – Function called as selector(start, end) for each interval that returns bool: True to keep the interval, False to discard it.

  • min_duration (float) – Minimum duration of intervals to keep (in current units, unless it carries units, e.g. "50 ms")

  • max_duration (float) – Maximum duration of intervals to keep (as min_duration)

  • time_range (tuple) – (start, end) time range (in current units, unless it carries units, e.g. (1, 2) * u.min). Only keeps intervals that overlap with this range. An interval overlaps if its end > range_start AND its start < range_end.

Returns:

New Intervals object containing only the filtered intervals

Return type:

Intervals

Examples

>>> intervals = Intervals([(0, 100), (200, 400), (500, 550)], units="ms")
>>> # Filter by duration
>>> short = intervals.filter(max_duration=100)
>>> len(short)
2
>>> # Filter by minimum duration
>>> long = intervals.filter(min_duration=150)
>>> len(long)
1
>>> # Filter by time range (keeps intervals overlapping with range)
>>> middle = intervals.filter(time_range=(150, 450))
>>> # Filter by custom function
>>> starts_at_multiple_of_100 = intervals.filter(
...     selector=lambda start, end: start % 100 == 0
... )
>>> # Combine multiple criteria
>>> filtered = intervals.filter(
...     min_duration=50,
...     max_duration=200,
...     time_range=(0, 500)
... )
classmethod from_mask(mask, units=None, label=None, sampling_rate=None, time_offset=None)[source]

Create Intervals from a binary mask array.

Parameters:
  • mask (ndarray) – Binary array where non-zero values indicate interval membership.

  • units (str or None, optional) – Units for the intervals. Default is None (indices). Time units (“ms”, “sec”, …) require sampling_rate; the sample indices are converted with to_units().

  • label (str) – Label for the intervals.

  • sampling_rate (float) – Sampling rate in Hz.

  • time_offset (float) – Time offset in milliseconds for index-to-time conversion (time of the first sample).

Returns:

New Intervals object created from the mask.

Return type:

Intervals

Examples

>>> mask = np.array([0, 0, 1, 1, 1, 0, 0, 1, 1, 0])
>>> intervals = Intervals.from_mask(mask)
>>> intervals.intervals
[(2, 5), (7, 9)]
merge(merge_sep='_', distance=0)[source]

Merge overlapping or close intervals.

When intervals are merged, metadata is handled as follows: - event_labels: Labels of merged intervals are joined with merge_sep - event_indices: First index of merged group is kept - event_onsets: First onset of merged group is kept

Parameters:
  • merge_sep (str, optional) – Separator to use when joining event labels of merged intervals. Default is ‘_’.

  • distance (float) –

    Maximum gap (in milliseconds, or with units such as 50 * u.ms) between intervals to still merge them. If two intervals are separated by less than or equal to this distance, they will be merged. Default is 0 (only merge overlapping intervals).

    Example: If distance=2 and intervals are (10, 15) and (17, 22), they will be merged into (10, 22) because the gap (17-15=2) is <= distance.

Returns:

New Intervals object with merged intervals and updated metadata.

Return type:

Intervals

Raises:

ValueError – If distance > 0 and conversion requires sampling_rate but it is not set.

pad(left=0, right=0)[source]

Add padding to the left (start) and right (end) of each interval.

The padding is in the current units of the Intervals object, unless it carries units (e.g., left=50 * u.ms). Padding is applied independently to each interval without merging. Use .merge() after padding if you want to merge overlapping intervals.

Intervals are clipped to stay within valid bounds: - If data_time_range is set, intervals are clipped to that range - Otherwise, intervals are clipped to start at 0 (or time_offset for time units)

Parameters:
  • left (float) – Amount to subtract from the start of each interval (expand left). Default is 0.

  • right (float) – Amount to add to the end of each interval (expand right). Default is 0.

Returns:

New Intervals object with padded intervals, clipped to valid bounds.

Return type:

Intervals

Examples

>>> intervals = Intervals([(100, 200), (300, 400)], units="ms")
>>> padded = intervals.pad(left=10, right=20)
>>> padded.intervals
[(90, 220), (290, 420)]
>>> # Padding that would go out of bounds is clipped
>>> intervals = Intervals([(10, 50)], units="ms", data_time_range=(0, 100))
>>> intervals.pad(left=20).intervals  # Would go to -10, clipped to 0
[(0, 50)]
>>> # Pad and then merge overlapping intervals
>>> intervals.pad(left=50, right=50).merge()

Notes

  • Negative padding values will shrink intervals (left adds to start, right subtracts from end)

plot(show_labels=True, **kwargs)[source]

Plot intervals as horizontal lines on a timeline.

Uses the current matplotlib axes to create a visualization where each interval is shown as a horizontal line at a different y-level.

Parameters:
  • show_labels (bool) – Whether to display event labels for each interval (default True)

  • **kwargs (dict) – Additional keyword arguments passed to matplotlib plot()

Returns:

Modifies the current axes in place

Return type:

None

Examples

>>> intervals = data.get_intervals("F", interval=(-200, 200), units="ms")
>>> plt.figure()
>>> intervals.plot()
>>> plt.show()
plot_highlight(ax=None, color='lightblue', alpha=0.3, **kwargs)[source]

Draw highlighted backgrounds for each interval on existing plot(s).

Uses matplotlib’s axvspan to draw semi-transparent vertical spans for each interval. This is useful for highlighting regions of interest on top of timeseries plots.

Parameters:
  • ax (Axes, list of Axes, Figure, or None) – Where to draw highlights: - None: Apply to ALL axes in current figure (useful after plot_timeseries) - Single Axes: Apply only to that axes - List of Axes: Apply to each axes in the list - Figure: Apply to all axes in that figure

  • color (str) – Color for the highlighted regions (default ‘lightblue’)

  • alpha (float) – Transparency level, 0-1 (default 0.3)

  • **kwargs (dict) – Additional keyword arguments passed to ax.axvspan() (e.g., label, linestyle, edgecolor, zorder); zorder defaults to 0 (behind the data)

Returns:

List of matplotlib patches created

Return type:

list

Examples

>>> # Highlight on a single pupil plot
>>> data.plot.pupil_plot(units="sec")
>>> intervals = data.get_intervals("stim", units="sec")
>>> intervals.plot_highlight(color='green', alpha=0.2)
>>> # Highlight on all subplots from plot_timeseries
>>> data.plot.plot_timeseries(units="ms")
>>> intervals = data.get_intervals("stim", units="ms")
>>> intervals.plot_highlight()  # Applies to all subplots
>>> # Highlight only specific subplot
>>> fig = plt.gcf()
>>> intervals.plot_highlight(ax=fig.axes[0])  # Only first subplot

Notes

Make sure the intervals are in the same units as the plot’s x-axis. Use intervals.to_units(“sec”) to convert if needed.

stats()[source]

Get statistics about interval durations.

Returns:

Dictionary with summary statistics (n, mean, sd, min, max, total_duration, units); for empty Intervals, mean/sd/min/max are NaN and total_duration is 0

Return type:

dict

to_mask(length=None)[source]

Convert intervals to a binary mask array.

If intervals have time units, they are automatically converted to indices using the sampling_rate.

Parameters:

length (int) – Length of the mask array. If None, uses data_time_range[1] (converted to indices if necessary).

Returns:

Binary array with 1s where intervals exist, 0s elsewhere.

Return type:

ndarray

Raises:

ValueError – If length is not provided and data_time_range is not set. If intervals have time units but sampling_rate is not set.

Examples

>>> intervals = Intervals([(0, 10), (20, 30)], units=None, data_time_range=(0, 50))
>>> mask = intervals.to_mask()
>>> mask.shape
(50,)
>>> # Time-based intervals are auto-converted
>>> intervals = Intervals([(0, 100), (200, 300)], units="ms",
...                       data_time_range=(0, 500), sampling_rate=1000)
>>> mask = intervals.to_mask()  # Converts to indices automatically
to_units(target_units)[source]

Convert intervals to different time units or indices.

Parameters:

target_units (str | None) – Target units: “ms”, “sec”, “min”, “h”, or “indices”/”index” for sample indices. Also accepts None for indices.

Returns:

New Intervals object with converted units

Return type:

Intervals

Raises:

ValueError – If conversion requires sampling_rate but it is not set

Examples

>>> intervals = data.get_intervals("stim", units="ms")
>>> intervals_sec = intervals.to_units("sec")
>>> intervals_seconds = intervals.to_units("seconds")  # alias works too

Convert from indices to time units (requires sampling_rate):

>>> intervals_idx = Intervals([(0, 100), (200, 300)], units=None, sampling_rate=1000)
>>> intervals_ms = intervals_idx.to_units("ms")

Convert from time units to indices (requires sampling_rate):

>>> intervals_ms = Intervals([(0, 1000), (2000, 3000)], units="ms", sampling_rate=1000)
>>> intervals_idx = intervals_ms.to_units("indices")
pypillometry.merge_intervals(*args, label='merged')[source]

Combine multiple Intervals objects into a single Intervals object.

Takes multiple Intervals objects and concatenates them into one, preserving all intervals and their metadata. Does NOT merge overlapping intervals - call .merge() on the result if you want to merge overlapping intervals. All input Intervals must have the same units.

Parameters:
  • *args (Intervals objects, list, or dict) – Variable number of Intervals objects, or a single list/dict of Intervals. If dict, keys are ignored and all Intervals are combined.

  • label (str) – Label for the resulting Intervals object. Default is “merged”.

Returns:

A single Intervals object containing all intervals from all input Intervals objects with their metadata preserved.

Return type:

Intervals

Raises:
  • ValueError – If no Intervals objects are provided or if units don’t match.

  • TypeError – If any argument is not an Intervals object (or a list/dict of them).

Examples

Combine multiple Intervals objects:

>>> intervals1 = Intervals([(0, 100), (200, 300)], units="ms", event_labels=["a", "b"])
>>> intervals2 = Intervals([(50, 150), (400, 500)], units="ms", event_labels=["c", "d"])
>>> combined = merge_intervals(intervals1, intervals2)
>>> len(combined)
4
>>> combined.event_labels
['a', 'b', 'c', 'd']

Combine from a list:

>>> intervals_list = [intervals1, intervals2]
>>> combined = merge_intervals(intervals_list)

Combine from a dict:

>>> intervals_dict = {"left_pupil": intervals1, "right_pupil": intervals2}
>>> combined = merge_intervals(intervals_dict)

Then merge overlapping intervals if needed:

>>> combined.merge()
class pypillometry.Events(onsets, labels, units=<object object>, data_time_range=None)[source]

Container for events with metadata.

This class wraps arrays of event onsets and labels and provides additional functionality such as unit conversion, filtering, and display capabilities.

The events of a dataset are obtained with get_events(). An (e.g., filtered) Events object can be passed as event_select to get_intervals() or stored in a dataset with set_events().

Parameters:
  • onsets (Union[ndarray, List]) – Event onset times (stored as float array); can carry units, e.g. [100, 500] * u.ms

  • labels (Union[ndarray, List]) – Event labels (stored as array of strings), same length as onsets

  • units (Optional[str]) – Units of the onsets (“ms”, “sec”, “min”, “h”, or None for indices). Default is “ms”, or the units of onsets if they carry units (otherwise, onsets with units are converted to units).

  • data_time_range (Optional[Tuple[float, float]]) – Time range (min, max) of the original dataset (in same units as onsets)

Raises:

ValueError – If onsets and labels differ in length.

Examples

>>> events = Events(onsets=[100, 500, 1000], labels=["A", "B", "C"], units="ms")
>>> len(events)
3
>>> for onset, label in events:
...     print(f"{label} at {onset}")
filter(selector)[source]

Filter events and return a new Events object.

This method supports multiple types of selectors for flexible filtering: - String: matches labels containing the substring - Callable: applies function to each label, keeps events where it returns True - Tuple: filters by time range (min_time, max_time)

Parameters:

selector (str, callable, or tuple) –

Filtering criterion:

  • str: substring to search for in labels

  • callable: function that takes a label and returns bool

  • tuple: (min_time, max_time) to filter by onset time range (inclusive, in the units of the Events object)

Returns:

New Events object containing only the filtered events

Return type:

Events

Examples

>>> events = Events([100, 500, 1000], ["stim", "resp", "stim"], units="ms")
>>> stim_events = events.filter("stim")
>>> len(stim_events)
2
>>> # Filter by custom function
>>> events = Events([100, 200], ["A1", "B2"], units="ms")
>>> numeric = events.filter(lambda label: label[-1].isdigit())
>>> # Filter by time range
>>> events = Events([100, 500, 1000], ["A", "B", "C"], units="ms")
>>> middle = events.filter((200, 800))
>>> len(middle)
1
plot(show_labels='auto', marker='.', units='min', **kwargs)[source]

Plot events as points on a time axis.

Parameters:
  • show_labels (str) – Labeling strategy. “auto” (default) shows a subset spaced apart, “all” annotates every event, “none” disables labels.

  • marker (str) – Matplotlib marker style for the events (default ‘.’).

  • units (Optional[str]) – Units to display in the plot. Defaults to “min”. Use None to keep existing units.

  • **kwargs – Additional keyword arguments passed to matplotlib.axes.Axes.plot().

Returns:

Plot is drawn on the current matplotlib axes.

Return type:

None

to_array()[source]

Convert events to numpy arrays.

Returns:

Tuple of (onsets, labels) as numpy arrays

Return type:

Tuple[ndarray, ndarray]

Examples

>>> events = Events([100, 200], ["A", "B"], units="ms")
>>> onsets, labels = events.to_array()
to_units(target_units)[source]

Convert event onsets to different time units.

Parameters:

target_units (Optional[str]) – Target units: “ms”, “sec”, “min”, “h”, or None for indices

Returns:

New Events object with converted units

Return type:

Events

Raises:

ValueError – If current units are None (indices) or conversion is not possible

Examples

>>> events = Events([1000, 2000], ["A", "B"], units="ms")
>>> events_sec = events.to_units("sec")
>>> events_sec.onsets
array([1., 2.])

Segmented data

Data cut into segments around events (see get_segments()), for single subjects and on the group level.

class pypillometry.SegmentedEyeData(name, variable, tx, data, intervals=None, sampling_rate=None)[source]

Class representing segmented eye data (e.g., event-related pupil dilation).

Holds equal-length segments of a single eye data variable (e.g., “left_pupil”, “right_x”) extracted around events. Tracks masked/missing data.

Usually created with get_segments() from the Intervals returned by get_intervals(), e.g. d.get_segments(d.get_intervals("F", interval=(-200, 800), units="ms"), "left_pupil"). Segments of several subjects can be combined with group_segments().

Parameters:
  • name (str) – Name of the dataset (e.g., “cue-locked”, “conflict-trials”)

  • variable (str) – Variable name in “eye_variable” format (e.g., “left_pupil”, “right_x”)

  • tx (ndarray) – Time axis relative to segment onset (in ms)

  • data (MaskedArray) – Masked array with shape (n_timepoints, n_segments)

  • intervals (Intervals, optional) – Source Intervals object with event metadata

  • sampling_rate (float) – Original sampling rate in Hz

baseline_correct(window=0)[source]

Apply baseline correction to the segments.

The window is specified in milliseconds relative to the zero-point of the intervals (typically the event onset). For example, window=(-200, 0) uses the 200ms before the event as baseline.

Parameters:

window (Union[Tuple[float, float], float, None]) –

Baseline window in ms relative to event onset (time 0):

  • If None, no baseline correction is applied

  • If tuple (start, end), the mean value in this time window is subtracted

  • If float, the value at the time point closest to this value is used

Default is 0 (baseline at event onset).

Returns:

New instance with baseline-corrected data (the object itself if window is None)

Return type:

SegmentedEyeData

Examples

>>> # Use pre-event period as baseline
>>> segments_bc = segments.baseline_correct(window=(-200, 0))
>>> # Use single time point as baseline
>>> segments_bc = segments.baseline_correct(window=-100)
property eye: str

Return the eye part of the variable (e.g., ‘left’ from ‘left_pupil’).

classmethod from_eyedata(eyedata, intervals, variable, name=None)[source]

Extract segments from a GenericEyeData object using intervals.

Parameters:
  • eyedata (GenericEyeData) – Source eye data object (PupilData, GazeData, etc.)

  • intervals (Intervals) – Intervals object defining the segments to extract

  • variable (str) – Variable to extract in “eye_variable” format (e.g., “left_pupil”)

  • name (str) – Name for the dataset. If None, uses intervals.label or “segments”

Returns:

New instance with extracted segments

Return type:

SegmentedEyeData

classmethod from_file(fname, **kwargs)[source]

Read a SegmentedEyeData object from a pickle file.

Parameters:
  • fname (str) – Filename (or HTTP(S)/SSH URL) to read from

  • **kwargs – passed to read_pickle() (e.g., cache_file, cache_dir, cache_layout, autodelete, session)

Returns:

Loaded instance

Return type:

SegmentedEyeData

classmethod from_masked_array(data, tx, variable, intervals=None, name='segments', sampling_rate=None, mask=None)[source]

Create SegmentedEyeData from a masked array.

Parameters:
  • data (Union[ndarray, MaskedArray]) – Data array with shape (n_timepoints, n_segments)

  • tx (ndarray) – Time axis relative to segment onset (in ms)

  • variable (str) – Variable name in “eye_variable” format

  • intervals (Intervals, optional) – Source Intervals object with event metadata

  • name (str) – Name for the dataset (default “segments”)

  • sampling_rate (float) – Original sampling rate in Hz

  • mask (ndarray) – Mask array (1=masked/missing, 0=valid). Only used if data is not already a MaskedArray.

Returns:

New instance with the provided data

Return type:

SegmentedEyeData

property n_segments: int

Number of segments.

property n_timepoints: int

Number of time points per segment.

plot(meanfct=<function mean>, varfct=<function sem>, show_missing=None, show_zero_line=True, show_legend=None, title=None, label=None, ribbon_alpha=0.3, ax=None, **kwargs)[source]

Plot mean and error ribbons.

Can be used standalone for a quick look, or with ax parameter to overlay multiple segments on the same plot or across subplots.

Parameters:
  • meanfct (callable) – Function to compute mean across segments (default: np.mean)

  • varfct (callable or None) – Function to compute error bands (e.g., np.std, scipy.stats.sem) If None, no error bands are plotted. Both functions are called as fct(data, axis=1), so they must accept an axis argument, and they must ignore masked values (e.g., np.ma.median, not np.median; a warning is logged otherwise).

  • show_missing (bool) – Plot percentage of missing data per time point on secondary y-axis. If None, defaults to True in standalone mode, False in overlay mode.

  • show_zero_line (bool) – Show vertical line at time 0 (default: True)

  • show_legend (bool) – Show legend. If None, only shows legend in standalone mode (ax=None)

  • title (str) – Plot title. If None and ax is None, uses the dataset name. Ignored when ax is provided (for overlay use).

  • label (str) – Label for legend. If None, uses self.name

  • ribbon_alpha (float) – Alpha (transparency) for error ribbon (default: 0.3)

  • ax (matplotlib.axes.Axes, optional) – Axes to plot on. If None, uses current axes (standalone mode)

  • **kwargs (dict) – Additional arguments passed to ax.plot() (e.g., color, linestyle, linewidth)

Returns:

ax – The axes object for further customization

Return type:

matplotlib.axes.Axes

Examples

>>> # Quick standalone plot
>>> segments.plot()
>>> # Overlay multiple segments
>>> fig, ax = plt.subplots()
>>> seg1.plot(ax=ax, color='blue', label='Condition A')
>>> seg2.plot(ax=ax, color='red', label='Condition B')
>>> ax.legend()
>>> # Customize appearance
>>> segments.plot(varfct=np.std, color='green', linewidth=2, linestyle='--')
summary()[source]

Return a summary of the SegmentedEyeData object.

Returns:

Dictionary with name, variable, n_segments, n_timepoints, window, percent_masked, and sampling_rate

Return type:

dict

property var: str

Return the variable part (e.g., ‘pupil’ from ‘left_pupil’).

property window: Tuple[float, float]

Time window in ms as (start, end).

write_file(fname, compress=True)[source]

Save to file using pickle (see write_pickle()).

Parameters:
  • fname (str) – Filename to save to

  • compress (bool) – compress the file with zstd (default True)

class pypillometry.GroupLevelSegmentedData(name, variable, tx, data, mask_percent, n_subjects, intervals=None, sampling_rate=None)[source]

Group-level segmented eye data combining multiple subjects.

This class inherits from SegmentedEyeData and adds group-level functionality. It combines multiple SegmentedEyeData objects (one per subject) into a single group-level object, either by stacking or aggregating.

Usually created with group_segments() (or from_segments()) from a list or dict of SegmentedEyeData objects, one per subject.

The data keep their mask: stacked segments keep their own mask, and in aggregate mode a subject’s mean is masked where all of their segments are masked. In addition, mask_percent stores the average percentage of masked trials per subject at each timepoint.

Parameters:
  • name (str) – Name of the dataset

  • variable (str) – Variable name in “eye_variable” format

  • tx (ndarray) – Time axis relative to segment onset (in ms)

  • data (MaskedArray) – Masked array with shape (n_timepoints, n_subjects) or (n_timepoints, n_segments)

  • mask_percent (ndarray) – Percentage of masked data per subject at each timepoint (shape: n_timepoints)

  • n_subjects (int) – Number of subjects combined

  • intervals (Intervals, optional) – Source Intervals object

  • sampling_rate (float) – Original sampling rate in Hz

classmethod from_segments(segments, meanfct=<function mean>, name=None)[source]

Create GroupLevelSegmentedData by combining multiple SegmentedEyeData objects.

Parameters:
  • segments (list) – List of SegmentedEyeData objects (one per subject)

  • meanfct (callable or None) – Function to aggregate within each subject, called as meanfct(data, axis=1); it must accept an axis argument and ignore masked values (e.g., np.mean or np.ma.median, but not np.median; a warning is logged otherwise). If None, all segments from all subjects are stacked (concatenated).

  • name (str) – Name for the group-level dataset. If None, uses "group_" + first segment’s name

Returns:

New instance with combined data

Return type:

GroupLevelSegmentedData

Examples

>>> # Aggregate within subjects, then combine
>>> group = GroupLevelSegmentedData.from_segments([subj1, subj2, subj3])
>>> # Stack all trials from all subjects
>>> group = GroupLevelSegmentedData.from_segments([subj1, subj2, subj3], meanfct=None)
plot(meanfct=<function mean>, varfct=<function sem>, show_missing=None, show_zero_line=True, show_legend=None, title=None, label=None, ribbon_alpha=0.3, ax=None, **kwargs)[source]

Plot grand mean and error ribbons.

Overrides SegmentedEyeData.plot() to use mask_percent for the missing-data display. Mean and error are computed across the columns of data (subjects when aggregated, all segments when stacked).

Parameters:
  • meanfct (callable) – Function to compute mean across columns (default: np.mean)

  • varfct (callable or None) – Function to compute error bands (e.g., np.std, scipy.stats.sem; default: scipy.stats.sem). If None, no error bands are plotted. Both functions are called as fct(data, axis=1), so they must accept an axis argument, and they must ignore masked values (e.g., np.ma.median, not np.median; a warning is logged otherwise).

  • show_missing (bool) – Plot mask_percent (average percentage of missing data per subject) on secondary y-axis. If None, defaults to True in standalone mode, False in overlay mode.

  • show_zero_line (bool) – Show vertical line at time 0 (default: True)

  • show_legend (bool) – Show legend. If None, only shows legend in standalone mode (ax=None)

  • title (str) – Plot title. If None and ax is None, uses the dataset name. Ignored when ax is provided (for overlay use).

  • label (str) – Label for legend. If None, uses self.name

  • ribbon_alpha (float) – Alpha (transparency) for error ribbon (default: 0.3)

  • ax (matplotlib.axes.Axes, optional) – Axes to plot on. If None, uses current axes (standalone mode)

  • **kwargs (dict) – Additional arguments passed to ax.plot() (e.g., color, linestyle, linewidth)

Returns:

ax – The axes object for further customization

Return type:

matplotlib.axes.Axes

summary()[source]

Return a summary of the GroupLevelSegmentedData object.

Returns:

Dictionary with group-level statistics

Return type:

dict

pypillometry.group_segments(segments, meanfct=<function mean>, name=None)[source]

Combine multiple SegmentedEyeData objects into a GroupLevelSegmentedData.

Convenience function that wraps GroupLevelSegmentedData.from_segments().

Parameters:
  • segments (list or dict of SegmentedEyeData) – SegmentedEyeData objects to combine. If dict, values are used.

  • meanfct (callable or None) – Function to aggregate within each subject, called as meanfct(data, axis=1); it must accept an axis argument and ignore masked values (e.g., np.mean or np.ma.median, but not np.median; a warning is logged otherwise). If None, all segments from all subjects are stacked (concatenated).

  • name (str) – Name for the group-level dataset. If None, uses "group_" + first segment’s name

Returns:

Combined group-level data

Return type:

GroupLevelSegmentedData

Examples

>>> import pypillometry as pp
>>> # From a list
>>> group = pp.group_segments([subj1_seg, subj2_seg, subj3_seg])
>>> # From a dict
>>> segments = {"S01": subj1_seg, "S02": subj2_seg}
>>> group = pp.group_segments(segments)
>>> # Stack all trials instead of averaging
>>> group = pp.group_segments(segments, meanfct=None)

Regions of interest

class pypillometry.ROI(name=None, setup=None)[source]

Base class for Region of Interest (ROI).

This class provides common functionality for different types of ROIs. Subclasses should implement the contains() method to define their specific containment logic (coords in roi uses it for single points) and the plot() method.

Coordinates are specified in pixels with origin at the upper-left corner of the screen (y increases downward). Use factory methods from_mm() or from_degrees() on subclasses for alternative coordinate systems.

Parameters:
  • name (str) – Name of the ROI

  • setup (Optional[ExperimentalSetup]) – Experimental setup for coordinate conversions. If provided, enables coordinate system conversion methods.

contains(coords)[source]

Test if coordinates are within the ROI.

Supports both single coordinate and array of coordinates.

Parameters:

coords (Union[Tuple[float, float], ndarray]) – a single (x, y) point or an array of shape (n, 2) with one point per row (in pixels)

Returns:

for a single point, whether it is inside the ROI; for an array, a boolean array of length n

Return type:

Union[bool, ndarray]

plot(ax=None, **kwargs)[source]

Plot the ROI on the given axes.

Parameters:
  • ax (matplotlib.axes.Axes, optional) – The axes to plot on. If None, the current axes will be used.

  • **kwargs (dict) – Additional keyword arguments passed to the plotting function.

Returns:

The artist object created for the ROI.

Return type:

matplotlib.artist.Artist

class pypillometry.CircularROI(center, radius, name=None, setup=None)[source]

Circular Region of Interest defined by center point and radius.

Coordinates are in pixels (origin at upper-left, y increases downward) by default. Use from_mm() or from_degrees() factory methods for alternative coordinate systems.

Parameters:
  • center (Tuple[float, float]) – (x, y) coordinates of the center point in pixels

  • radius (float) – radius of the circle in pixels (must not be negative)

  • name (str) – Name of the ROI

  • setup (Optional[ExperimentalSetup]) – Experimental setup for coordinate conversions

Raises:

ValueError – If radius is negative

Examples

>>> import numpy as np
>>> from pypillometry import CircularROI
>>> roi = CircularROI(center=(640, 512), radius=100, name="center")
>>> bool((650, 500) in roi)
True
>>> roi.contains(np.array([[640, 512], [900, 900]]))
array([ True, False])
contains(coords)[source]

Test if coordinates are within the circle (boundary included).

Parameters:

coords (Union[Tuple[float, float], ndarray]) – a single (x, y) point or an array of shape (n, 2) with one point per row (in pixels)

Returns:

for a single point, whether it is inside the ROI; for an array, a boolean array of length n

Return type:

Union[bool, ndarray]

classmethod from_degrees(center, radius, setup, name=None)[source]

Create circular ROI from visual angle coordinates.

Parameters:
  • center (Tuple[Union[float, str], Union[float, str]]) – (x, y) center in visual angle from screen center. Positive x = right, positive y = up. Each coordinate can be: - float: assumed to be radians - str: with units, e.g., “5 deg”, “0.087 rad” - with a unit, e.g., 5 * u.deg

  • radius (Union[float, str]) – Radius in visual angle. Can be: - float: assumed to be radians - str: with units, e.g., “2 deg”, “0.035 rad” - with a unit, e.g., 5 * u.deg

  • setup (ExperimentalSetup) – Experimental setup with screen geometry and eye distance (screen_resolution, physical_screen_size, and eye_to_screen_center must be set)

  • name (str) – Name of the ROI

Returns:

ROI with coordinates converted to pixels

Return type:

CircularROI

Examples

>>> from pypillometry import ExperimentalSetup, CircularROI
>>> setup = ExperimentalSetup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm"),
...     eye_to_screen_center="60 cm"
... )
>>> roi = CircularROI.from_degrees(center=("5 deg", "2.5 deg"), radius="2 deg", setup=setup)
>>> roi = CircularROI.from_degrees(center=(0.087, 0.044), radius=0.035, setup=setup)  # radians
classmethod from_mm(center_mm, radius_mm, setup, name=None)[source]

Create circular ROI from mm coordinates (origin at screen center).

Parameters:
  • center_mm (Tuple[float, float]) – (x, y) center in mm relative to screen center. Positive x = right, positive y = up.

  • radius_mm (float) – Radius in mm

  • setup (ExperimentalSetup) – Experimental setup with screen geometry (screen_resolution and physical_screen_size must be set)

  • name (str) – Name of the ROI

Returns:

ROI with coordinates converted to pixels

Return type:

CircularROI

Examples

>>> from pypillometry import ExperimentalSetup, CircularROI
>>> setup = ExperimentalSetup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm")
... )
>>> roi = CircularROI.from_mm(center_mm=(50, 25), radius_mm=30, setup=setup)
plot(ax=None, **kwargs)[source]

Plot the circular ROI.

Parameters:
  • ax (matplotlib.axes.Axes, optional) – The axes to plot on. If None, the current axes will be used.

  • **kwargs (dict) – Additional keyword arguments passed to patches.Circle. Common options include: - facecolor: color of the circle fill - edgecolor: color of the circle edge - alpha: transparency - fill: whether to fill the circle - label: label for the legend

Returns:

The circle patch object.

Return type:

matplotlib.patches.Circle

class pypillometry.RectangularROI(corner1, corner2, name=None, setup=None)[source]

Rectangular Region of Interest defined by two corners.

Coordinates are in pixels (origin at upper-left, y increases downward) by default. Use from_mm() or from_degrees() factory methods for alternative coordinate systems.

Parameters:
  • corner1 (Tuple[float, float]) – (x, y) coordinates of the first corner in pixels

  • corner2 (Tuple[float, float]) – (x, y) coordinates of the opposite corner in pixels (the order of the corners does not matter)

  • name (str) – Name of the ROI

  • setup (Optional[ExperimentalSetup]) – Experimental setup for coordinate conversions

contains(coords)[source]

Test if coordinates are within the rectangle (boundary included).

Parameters:

coords (Union[Tuple[float, float], ndarray]) – a single (x, y) point or an array of shape (n, 2) with one point per row (in pixels)

Returns:

for a single point, whether it is inside the ROI; for an array, a boolean array of length n

Return type:

Union[bool, ndarray]

classmethod from_degrees(corner1, corner2, setup, name=None)[source]

Create rectangular ROI from visual angle coordinates.

Parameters:
  • corner1 (Tuple[Union[float, str], Union[float, str]]) – (x, y) first corner in visual angle from screen center. Positive x = right, positive y = up. Each coordinate can be: - float: assumed to be radians - str: with units, e.g., “-5 deg”, “-0.087 rad” - with a unit, e.g., 5 * u.deg

  • corner2 (Tuple[Union[float, str], Union[float, str]]) – (x, y) second corner in visual angle from screen center. Positive x = right, positive y = up. Each coordinate can be: - float: assumed to be radians - str: with units, e.g., “5 deg”, “0.087 rad” - with a unit, e.g., 5 * u.deg

  • setup (ExperimentalSetup) – Experimental setup with screen geometry and eye distance (screen_resolution, physical_screen_size, and eye_to_screen_center must be set)

  • name (str) – Name of the ROI

Returns:

ROI with coordinates converted to pixels

Return type:

RectangularROI

Examples

>>> from pypillometry import ExperimentalSetup, RectangularROI
>>> setup = ExperimentalSetup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm"),
...     eye_to_screen_center="60 cm"
... )
>>> # Rectangle from (-5, -2.5) deg to (5, 2.5) deg
>>> roi = RectangularROI.from_degrees(
...     corner1=("-5 deg", "-2.5 deg"), corner2=("5 deg", "2.5 deg"), setup=setup
... )
classmethod from_mm(corner1_mm, corner2_mm, setup, name=None)[source]

Create rectangular ROI from mm coordinates (origin at screen center).

Parameters:
  • corner1_mm (Tuple[float, float]) – (x, y) first corner in mm relative to screen center. Positive x = right, positive y = up.

  • corner2_mm (Tuple[float, float]) – (x, y) second corner in mm relative to screen center. Positive x = right, positive y = up.

  • setup (ExperimentalSetup) – Experimental setup with screen geometry (screen_resolution and physical_screen_size must be set)

  • name (str) – Name of the ROI

Returns:

ROI with coordinates converted to pixels

Return type:

RectangularROI

Examples

>>> from pypillometry import ExperimentalSetup, RectangularROI
>>> setup = ExperimentalSetup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm")
... )
>>> # Rectangle from (-50, -25) mm to (50, 25) mm (centered, 100x50 mm)
>>> roi = RectangularROI.from_mm(
...     corner1_mm=(-50, -25), corner2_mm=(50, 25), setup=setup
... )
plot(ax=None, **kwargs)[source]

Plot the rectangular ROI.

Parameters:
  • ax (matplotlib.axes.Axes, optional) – The axes to plot on. If None, the current axes will be used.

  • **kwargs (dict) – Additional keyword arguments passed to patches.Rectangle. Common options include: - facecolor: color of the rectangle fill - edgecolor: color of the rectangle edge - alpha: transparency - fill: whether to fill the rectangle - label: label for the legend

Returns:

The rectangle patch object.

Return type:

matplotlib.patches.Rectangle

Supporting classes

These classes are used internally by the main classes will only rarely be used directly by the user.

GenericEyeData

class pypillometry.GenericEyeData[source]

Generic class for eyedata. Defines the basic structure of an eyedata object and implements some basic functions.

add_to_history(event)[source]

Add event to history.

Usually called by the keephistory decorator.

Parameters:

event (dict) – description of the operation with keys “funcstring” (printable call) and “funcname” (method name)

Return statistics on blink durations.

Uses the blinks stored for the “pupil” variable of each eye (see stats()).

Parameters:
  • eyes (list) – eye(s) to consider; if empty, all available eyes

  • units (str) – Units for statistics: “ms”, “sec”, “min”, or “h”. Default “ms”.

Returns:

Dictionary mapping eye name to stats dict (number, mean, sd, min, max and total duration of blinks), or None if no blinks

Return type:

dict

Return blinks (intervals) for all eyes.

Returns:

dictionary with blinks for each eye

Return type:

dict

Merge blinks that are close together.

Parameters:
  • eyes (list) – eye(s) to process; if empty, all available eyes

  • variables (list) – variable(s) to process; if empty, all available variables

  • distance (float) – Merge blinks closer than this distance, e.g. 100 * u.ms or "0.1 sec"; plain numbers are in units given by units. Default 100.

  • units (str) – Units for distance if it is a plain number. Can be “ms”, “sec”, “min”, or “h”. Default “ms”.

  • apply_mask (bool) – If True, store the merged blinks, apply them as masks to the data and return the object. If False, return merged blinks as dict of Intervals objects without storing them or applying masks (same default as pupil_blinks_detect()).

  • inplace (bool | None) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor). Only relevant when apply_mask=True.

Returns:

If apply_mask=True: returns modified object for chaining. If apply_mask=False: returns dict of Intervals objects (one per eye/variable combination).

Return type:

GenericEyeData or dict of Intervals

copy(new_name=None)[source]

Make and return a deep-copy of the eyedata object.

Parameters:

new_name (Optional[str]) – New name for the copy. If None, “_copy” is appended to the original name.

Returns:

deep copy of the object (same class as the original)

Return type:

GenericEyeData

downsample(fsd, dsfac=False, inplace=None)[source]

Simple downsampling scheme using mean within the downsampling window.

All data fields and the time-vector are downsampled simultaneously. See pypillometry.signal.baseline.downsample(). Masked samples are left out of the window means; a new sample is masked only if all samples in its window were masked. Stored blinks (in sample indices) are rescaled to the new sampling rate.

Parameters:
  • fsd (float) – new sampling-rate (in Hz) or decimate-factor

  • dsfac (bool) – if False (default), fsd is the new sampling rate (the decimate factor is int(fs/fsd)); if True, fsd is the decimate factor

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the downsampled object (self or a copy, depending on inplace)

Return type:

GenericEyeData

drop_data(eyes=[], variables=[])[source]

Drop specified eyes and/or variables from the data and return a new object.

This method never works in place - it always returns a new object. All data of the given eyes and all data of the given variables are dropped (together with their stored blinks). The returned object’s class is determined by what data remains:

  • If only pupil variables remain: returns PupilData

  • If only x/y variables remain: returns GazeData

  • If both pupil and x/y remain: returns EyeData

Parameters:
  • eyes (list) – Eyes to drop (e.g., [‘left’] or [‘right’] or [‘left’, ‘right’]); if empty, no eye is dropped

  • variables (list) – Variables to drop (e.g., [‘pupil’] or [‘x’, ‘y’]); if empty, no variable is dropped

Returns:

A new object with the specified data removed, cast to the appropriate class.

Return type:

GenericEyeData subclass

Raises:

ValueError – If dropping would result in no data remaining.

Examples

>>> # Drop left eye data
>>> right_only = data.drop_data(eyes=['left'])
>>>
>>> # Drop pupil data, keep only gaze
>>> gaze_only = data.drop_data(variables=['pupil'])
>>>
>>> # Drop right eye and pupil data
>>> left_gaze = data.drop_data(eyes=['right'], variables=['pupil'])
drop_original(inplace=None)[source]

Drop original dataset from record (e.g., to save space).

Sets the original attribute (stored when the object was created with keep_orig=True) to None.

Parameters:

inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the object without the original data (self or a copy, depending on inplace)

Return type:

GenericEyeData

property events

Events (onsets in ms) as an Events object.

Shortcut for get_events() with default arguments.

property eyes: List[str]

Return a list of available eyes in the dataset.

fill_time_discontinuities(yval=nan, print_info=True)[source]

Find gaps in the time-vector and fill them in (in-place).

A gap is a step between consecutive time points that is larger than twice the median step. New time points are inserted into each gap at the median step size and all data variables are set to yval at these new time points; they are masked as missing. Existing masks are kept and stored blinks (in sample indices) are moved to the new sample positions. This is called by the constructor when fill_time_discontinuities=True.

Parameters:
  • yval (float, optional) – value used for the data in the filled-in gaps; default NaN, so the filled-in samples are masked as missing data

  • print_info (bool, optional) – if True (default), log the number of gaps that are filled in

Returns:

the object itself (always modified in-place)

Return type:

GenericEyeData

Reads an eyedata object from an Eyelink file.

All “messages” are stored as event_labels (can be filtered later) and it tries to be smart to detect different information from the EDF file. Spatial calibration/validation data is automatically parsed and stored in the calibration attribute as SpatialCalibration objects. Eyelink codes missing pupil samples (e.g., during blinks) as 0; these are converted to NaN (and therefore masked).

However, the EDF file may contain other information of interest, e.g., eyelink-detected saccades, etc. If you need more control, use the “eyelinkio” package directly and create the object manually.

Parameters:
  • fname (str) – filename of the EDF file, HTTP(S) URL or SSH URL (ssh://server/folder/file.edf, see pypillometry.io.fetch_url())

  • return_edf_obj (bool) – if True, return a tuple with the eyedata object and the object returned by the “eyelinkio” package; if False (default), return only the eyedata object

  • remove_eyelink_triggers (bool) – if True (default), remove all Eyelink triggers from the event_labels (related to eye-tracker settings, calibration, validation etc)

  • **kwargs – additional arguments passed to pypillometry.io.read_eyelink() (e.g., cache_file, cache_dir, cache_layout, autodelete, session)

Returns:

object of the class this method is called on (e.g., EyeData), or tuple with that object and the object returned by the “eyelinkio” package

Return type:

GenericEyeData or tuple

Examples

>>> import pypillometry as pp
>>> d = pp.EyeData.from_eyelink('data.edf')
>>> # Access calibration data
>>> print(d.calibration['left'])
<SpatialCalibration: left eye, HV9, n=9, error=0.45±0.23° (max=0.79°)>
classmethod from_file(fname, **kwargs)[source]

Reads a GenericEyeData object from a pickle-file. Use as pypillometry.PupilData.from_file("yourfile.pd").

The file is read with pypillometry.io.read_pickle(), so fname can also be an HTTP(S) or SSH URL.

Parameters:
  • fname (str) – filename (or URL)

  • **kwargs – passed to pypillometry.io.read_pickle() (e.g., cache_file, cache_dir, cache_layout, autodelete, session)

Returns:

the object stored in the file (its class is the one that was stored, independent of the class this method is called on)

Return type:

GenericEyeData

Get blinks as Intervals object or dict of Intervals.

If no blinks are stored for an eye/variable, an empty Intervals object is returned for it.

Parameters:
  • eyes (str | list) – Eye(s) to get blinks for; if empty, all available eyes. If multiple, returns dict.

  • variables (str | list) – Variable(s) to get blinks for; if empty, all available variables. If multiple, returns dict.

  • units (str | None) – Units for intervals: “ms”, “sec”, “min”, “h”, or None for indices

Returns:

If single eye and variable: returns Intervals object. If multiple eyes/variables: returns dict with keys like “left_pupil”.

Return type:

Intervals or dict

Examples

>>> blinks = data.get_blinks('left', 'pupil')
>>> blinks_ms = data.get_blinks('left', 'pupil', units="ms")
>>> blinks_dict = data.get_blinks(['left', 'right'], 'pupil')
>>> blinks_idx = blinks_ms.to_units("indices")
get_duration(units='min')[source]

Return duration of the dataset in units specified.

Parameters:

units (str, optional) – unit one of “min”, “sec”, “h”, by default “min”

Returns:

duration of dataset in specified units

Return type:

float

get_events(units='ms')[source]

Get events as an Events object.

Returns the event onsets and labels stored in the dataset as an Events object, which provides convenient filtering and display capabilities.

Parameters:

units (str) – Units for the event onsets: “ms”, “sec”, “min”, or “h”. Default is “ms” (the internal storage format).

Returns:

Events object containing the event onsets and labels

Return type:

Events

Examples

>>> events = data.get_events()
>>> len(events)
42
>>> # Get events in seconds
>>> events_sec = data.get_events(units="sec")
>>> # Filter and work with events
>>> stim_events = data.get_events().filter("stim")

See also

set_events

Replace events from an Events object

get_intervals

Extract intervals around events

Get interpolated blinks as Intervals object or dict of Intervals.

Interpolated blinks are the blink regions that were successfully interpolated by pupil_blinks_interpolate(). These may differ from detected blinks because:

  • Blink boundaries are refined using velocity thresholds

  • Some blinks may be skipped if they’re too short to interpolate

If no interpolated blinks are stored for an eye/variable, an empty Intervals object is returned for it.

Parameters:
  • eyes (str | list) – Eye(s) to get interpolated blinks for; if empty, all available eyes. If multiple, returns dict.

  • variables (str | list) – Variable(s) to get interpolated blinks for; if empty, all available variables. If multiple, returns dict.

  • units (str | None) – Units for intervals: “ms”, “sec”, “min”, “h”, or None for indices

Returns:

If single eye and variable: returns Intervals object. If multiple eyes/variables: returns dict with keys like “left_pupil”.

Return type:

Intervals or dict

See also

get_blinks

Get detected (not interpolated) blinks

pupil_blinks_interpolate

Interpolate blinks

get_intervals(event_select, interval=(-200, 200), units=<object object>, label=None, **kwargs)[source]

Return an Intervals object containing intervals relative to event-onsets. For example, extract the interval before and after a stimulus has been presented. It is possible to select based on the event label, e.g. only select events matching “stimulus” or “response”. It is also possible to select the intervals situated between two different events, e.g., trial start and response by specifying event_select as a tuple with two entries.

Parameters:
  • event_select (str, tuple, function, or Events) –

    variable describing which events to select and align to

    • if str: use all events whose label contains the string

    • if function: apply function to all labels, use those where the function returns True

    • if tuple: use the intervals between the events selected by the first and the second entry (each a str or function); interval[0] is added to the first and interval[1] to the second event (so interval=(0, 0) gives exactly the span between them). Both selectors must result in identical number of events; events are paired by order (n-th first with n-th second event) and a ValueError is raised if a second event comes before its first event.

    • if Events: use the events from the Events object directly

  • interval (tuple) – time-window relative to event-onset (0 is event-onset); i.e., negative numbers are before the event, positive numbers after, e.g. (-200, 800) * u.ms or ("-0.2 sec", "0.8 sec"). Plain numbers are in units given by the units parameter. Default (-200, 200).

  • units (str | None) – units of the returned intervals (one of “ms”, “sec”, “min”, “h”, or None for sampling units, i.e., indices into the time-array); plain numbers in interval are in these units, an interval with units is converted to them. If not given, the units of interval are used (sampling units if interval has no units).

  • label (str | None) – optional label for the Intervals object. If None, a label is automatically generated from the event_select parameter

  • **kwargs – passed onto the event_select function

Returns:

Intervals object containing interval on- and offsets for each match, with associated metadata (event labels, indices, units)

Return type:

Intervals

get_segments(intervals, variable, name=None)[source]

Extract segments from data using intervals.

Creates a SegmentedEyeData object containing equal-length segments of a single variable extracted at the specified intervals.

Parameters:
  • intervals (Intervals) – Intervals object defining the segments to extract (from get_intervals())

  • variable (str) – Variable to extract in “eye_variable” format (e.g., “left_pupil”, “right_x”)

  • name (str) – Name for the segmented dataset. If None, uses intervals.label or “segments”

Returns:

Object containing the extracted segments with mask information

Return type:

SegmentedEyeData

Examples

>>> # Extract pupil segments around stimulus events
>>> intervals = data.get_intervals("stimulus", interval=(-200, 800), units="ms")
>>> segments = data.get_segments(intervals, "left_pupil")
>>> segments.plot()
>>> # Apply baseline correction
>>> segments_bc = segments.baseline_correct(window=(-200, 0))

See also

get_intervals

Create Intervals object from events

SegmentedEyeData

Class holding segmented data

get_size()[source]

Return the size of the object in bytes.

Returns:

Total size of the object, including data and all attributes.

Return type:

ByteSize

interpolate_intervals(intervals, eyes=[], variables=[], method='linear', margin=(-20, 20), store_as_suffix=None, inplace=None)[source]

Interpolate data within specified intervals.

This function replaces data within the specified intervals with interpolated values based on boundary points outside each interval. The interpolated samples are also marked as masked. Intervals without valid (non-NaN) boundary points on both sides are skipped with a warning.

Parameters:
  • intervals (Intervals or dict) –

    Either:

    • A single Intervals object: applied to all specified eyes/variables

    • A dict of Intervals: keys like “left_pupil”, “right_x”, etc. When dict is provided, eyes and variables are ignored.

  • eyes (str or list of str, optional) – eye(s) to interpolate; if empty, all available eyes. Ignored if intervals is a dict.

  • variables (str or list of str, optional) – variable(s) to interpolate; if empty, all available variables. Ignored if intervals is a dict.

  • method (str, default "linear") – Interpolation method. Options: “linear”, “nearest”, “zero”, “slinear”, “quadratic”, “cubic”, “previous”, “next”. Passed to scipy.interpolate.interp1d.

  • margin (tuple, default (-20, 20)) – Time window (in ms) before and after the interval to collect boundary points for interpolation, relative to the start and end of each interval: points are taken from start + margin[0] to the start and from the end to end + margin[1], so margin[0] must be <= 0 and margin[1] >= 0. Masked or NaN samples are not used. For higher-order methods like “cubic” or “quadratic”, more boundary points from this margin are used to fit the interpolation curve.

  • store_as_suffix (str or None, default None) – If None, replace the original data. If a string, store interpolated data in a new variable with this suffix (e.g., “_interp” creates “left_pupil_interp”).

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

The object with interpolated data (self or a copy, depending on inplace).

Return type:

GenericEyeData

Examples

Interpolate blinks for left eye pupil data:

>>> blinks = data.get_blinks(eyes='left', variables='pupil')
>>> data.interpolate_intervals(blinks, eyes=['left'], variables=['pupil'])

Interpolate using a dict of intervals:

>>> blinks = data.get_blinks()  # returns dict
>>> data.interpolate_intervals(blinks)

Store interpolated data in a new variable:

>>> data.interpolate_intervals(blinks, store_as_suffix="_interp")
>>> # Access via data['left', 'pupil_interp']

Use cubic interpolation with wider margin:

>>> data.interpolate_intervals(blinks, method="cubic", margin=(-50, 50))

See also

mask_intervals

Apply intervals as masks without interpolation

get_blinks

Retrieve stored blink intervals

mask_as_intervals(eyes=[], variables=[], units=None, label=None)[source]

Convert masks to Intervals objects.

Each contiguous run of masked samples becomes one interval.

Parameters:
  • eyes (str | list) – Eye(s) to get masks for; if empty, all available eyes. If multiple, returns dict.

  • variables (str | list) – Variable(s) to get masks for; if empty, all available variables. If multiple, returns dict (combinations not present in the data are left out).

  • units (str | None) – Units for intervals: “ms”, “sec”, “min”, “h”, or None for indices. Default is None (indices).

  • label (str | None) – Descriptive label for the intervals. Default is None.

Returns:

If single eye and variable: returns Intervals object. If multiple eyes/variables: returns dict with keys like “left_pupil”.

Return type:

Intervals or dict

Examples

>>> mask_intervals = data.mask_as_intervals('left', 'pupil')
>>> mask_ms = data.mask_as_intervals('left', 'pupil', units='ms')
>>> mask_dict = data.mask_as_intervals(['left', 'right'], 'pupil')
mask_intervals(intervals, eyes=[], variables=[], inplace=None)[source]

Apply Intervals as masks to selected data.

Takes detected intervals (e.g., blinks, artifacts) and applies them as masks to specified eyes and variables. Existing masks are kept (the new mask is added to them).

Parameters:
  • intervals (Intervals or dict) –

    Either:

    • A single Intervals object: applied to all specified eyes/variables

    • A dict of Intervals: keys like “left_pupil”, “right_x”, etc. When dict is provided, eyes and variables are ignored.

  • eyes (str or list of str, optional) – eye(s) to apply mask to (e.g., ["left", "right"]); if empty, all available eyes. Ignored if intervals is a dict.

  • variables (str or list of str, optional) – variable(s) to apply mask to (e.g., ["pupil", "x", "y"]); if empty, all available variables. Ignored if intervals is a dict.

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

The object with masks applied (self or a copy, depending on inplace).

Return type:

GenericEyeData

Examples

Apply blinks from a single Intervals object to left eye pupil data:

>>> blinks = data.pupil_blinks_detect(apply_mask=False)
>>> data.mask_intervals(blinks['left_pupil'], eyes=['left'], variables=['pupil'])

Apply a dict of intervals (one per eye/variable):

>>> blinks = data.pupil_blinks_detect(apply_mask=False)
>>> data.mask_intervals(blinks)  # eyes and variables ignored

Chain with other operations:

>>> data.mask_intervals(blinks).pupil_lowpass_filter(cutoff=5)

See also

pupil_blinks_detect

Detect blinks with optional masking

get_blinks

Retrieve stored blink intervals

merge_eyes(eyes=[], variables=[], method='mean', keep_eyes=True, inplace=None)[source]

Merge data from both eyes into a single variable.

The merged data is stored under the eye name “mean” (e.g., obj["mean", "pupil"]). Each sample is the mean over the eyes in which it is not masked; it is masked in the merged data only if it is masked in all merged eyes (it then holds the mean of the stored values, e.g., of interpolated blinks).

Parameters:
  • eyes (str or list of str, optional) – eye(s) to merge; if empty, all available eyes

  • variables (str or list of str, optional) – variable(s) to merge; if empty, all available variables

  • method (str, optional) – which method to use for merging; currently only “mean” is implemented, by default “mean”

  • keep_eyes (bool, optional) – keep original eye data (True, default) or drop it together with its stored blinks (False)

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the object with merged data (self or a copy, depending on inplace)

Return type:

GenericEyeData

merge_masks(inplace=None)[source]

Merge masks of all variables into a single joint mask.

This function creates a joint mask by taking the logical OR of all individual masks using get_mask(), then applies this joint mask to all variables.

Parameters:

inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the object with merged masks (self or a copy, depending on inplace)

Return type:

GenericEyeData

Return number of detected blinks. Should be run after pupil_blinks_detect().

By default, all eyes and variables are considered.

Parameters:
  • eyes (str or list of str, optional) – eye(s) to consider; if empty, all available eyes

  • variables (str or list of str, optional) – variable(s) to consider; if empty, all available variables

Returns:

number of detected blinks per eye/variable, with keys like “left_pupil”; combinations without blinks are left out

Return type:

Dict[str, int]

nevents()[source]

Return number of events in data.

Return type:

int

params: dict

Constructor

print_history()[source]

Pretty-print the history of the current dataset (which manipulations where done on it).

reset_time(t0=None, inplace=None)[source]

Resets time so that time t0 becomes time zero.

t0 is subtracted from the time-vector and from the event onsets.

Parameters:
  • t0 (float | None) – time (in ms) that becomes the new time zero; if None, use the first time point (so that the time-vector starts at zero)

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the object with shifted time (self or a copy, depending on inplace)

Return type:

GenericEyeData

scale(variables=[], mean=None, sd=None, eyes=[], inplace=None)[source]

Scale the signal by subtracting mean and dividing by sd. If these variables are not provided, use the signal’s mean and std. Specify whether to scale x, y, pupil or other variables.

Parameters:
  • variables (str or list of str, optional) – variables to scale; can be “pupil”, “x”,”y”, “baseline”, “response” or any other variable that is available in the dataset; either a string or a list of strings; available variables can be checked with obj.variables; empty list means all variables

  • mean (Union[float, dict, None]) – mean to subtract from signal; if None, use the signal’s mean (of the non-masked samples); if float, use the same value for all eyes and variables; if dict, provide mean for each eye and variable as {eye: {variable: value}}

  • sd (Union[float, dict, None]) – sd to scale with; if None, use the signal’s std (of the non-masked samples); if float, use the same value for all eyes and variables; if dict, provide sd for each eye and variable as {eye: {variable: value}}

  • eyes (str or list of str, optional) – eye(s) to consider; if empty, all available eyes

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the scaled object (self or a copy, depending on inplace)

Return type:

GenericEyeData

Notes

Scaling-parameters are being saved in the params[“scale”] attribute and are used by unscale().

Set blinks for specified eyes and variables (in-place).

Parameters:
  • intervals (Intervals, dict or None) –

    Either:

    • a single Intervals object: stored for all specified eyes/variables;

    • a dict of Intervals (or None) with keys like “left_pupil”, “right_x”, etc.; when a dict is provided, eyes and variables are ignored;

    • None: remove the stored blinks for the specified eyes/variables (the mask is not changed).

  • eyes (str or list of str, optional) – eye(s) to set blinks for (e.g., ["left", "right"]); if empty, all available eyes. Ignored if intervals is a dict.

  • variables (str or list of str, optional) – variable(s) to set blinks for (e.g., ["pupil", "x", "y"]); if empty, all available variables. Ignored if intervals is a dict.

  • apply_mask (bool, optional) – If True, also apply the intervals as mask to the data (existing masks are kept). If False, only store intervals. Default is True.

set_event_onsets(event_onsets, event_labels)[source]

Set onsets of events in the data (in-place).

Parameters:
  • event_onsets (ndarray) – array of onsets (in ms); None removes all events

  • event_labels (ndarray) – array of labels (strings), same length as event_onsets; if None, all labels are set to 0

Raises:

ValueError – if any onset is outside the range of the time-vector or if event_labels and event_onsets differ in length

See also

set_events

set events from an Events object

set_events(events, inplace=None)[source]

Replace event onsets and labels from an Events object.

This method updates the internal event_onsets and event_labels arrays from an Events object. The Events object’s onsets are automatically converted to milliseconds (the internal storage format) if needed.

Parameters:
  • events (Events) – Events object containing the new events to set

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the object with updated events (self or a copy, depending on inplace)

Return type:

GenericEyeData

Examples

>>> # Filter events and set them back
>>> events = data.get_events()
>>> stim_events = events.filter("stim")
>>> data.set_events(stim_events)
>>> # Modify events and update
>>> events = data.get_events()
>>> # ... filter or modify events ...
>>> data.set_events(events)

See also

get_events

Get events as an Events object

set_event_onsets

Lower-level method to set events from arrays

stat_per_event(intervals=None, interval=None, event_select=None, eyes=[], variables=[], statfct=<function mean>, units='ms', **kwargs)[source]

Return result of applying a statistical function to data in a given interval relative to event-onsets. For example, extract mean pupil-size in interval before trial onset.

Parameters:
  • intervals (Intervals, optional) – Intervals object to use, in any time units or in sample indices (units=None, the default of get_intervals()). If provided, interval and event_select are ignored.

  • interval (Tuple[float, float]) – time-window relative to event-onset (0 is event-onset), e.g. (-200, 0) * u.ms; plain numbers are in units given by units. Required if intervals is not provided.

  • event_select (str or function, optional) – variable describing which events to select and align to see get_intervals() for details. Required if intervals is not provided.

  • eyes (str | list) – eye(s) to consider; if empty, all available eyes

  • variables (str | list) – variable(s) to consider; if empty, all available variables

  • statfct (Callable) – function mapping a masked array to a single number; it must ignore masked samples (e.g., np.mean (default) or np.ma.median, but not np.median; a warning is logged otherwise). Intervals where all samples are masked give NaN.

  • units (str) – units for plain numbers in interval (if used); default “ms”

  • **kwargs – passed onto the event_select function

Returns:

one array per eye/variable (keys like “left_pupil”), with one value per interval/event

Return type:

dict

sub_slice(start=-inf, end=inf, units=None, inplace=None)[source]

Return a shortened version of the current object.

The result contains all data (and events) between start and end (inclusive), e.g. d.sub_slice(3*u.min, 5*u.min) or d.sub_slice("3 min", "5 min"). start and end refer to the values of the time-vector (not to the time since the start of the recording). Masks are kept; stored blinks (in sample indices) are shifted and cut to the new range.

Parameters:
  • start (float) – start for new dataset; default: beginning of the data

  • end (float) – end of new dataset; default: end of the data

  • units (str) – time units of start and end if they are plain numbers (one of “ms”, “sec”, “min”, “h”). If units is None, plain numbers are ms (the units of the time vector).

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the shortened object (self or a copy, depending on inplace)

Return type:

GenericEyeData

abstractmethod summary()[source]

Return a summary of the GenericEyedata-object.

Return type:

dict

unscale(variables=[], mean=None, sd=None, eyes=[], inplace=None)[source]

Scale back to original values using either values provided as arguments or the values stored in params[“scale”] by scale().

The signal is multiplied by sd and mean is added.

Parameters:
  • variables (str or list of str, optional) – variables to unscale; can be “pupil”, “x”,”y”, “baseline”, “response” or any other variable that is available in the dataset; either a string or a list of strings; available variables can be checked with obj.variables; empty list means all variables

  • mean (None, float or dict, optional) – mean to add to the signal; a number (same for all eyes and variables) or {eye: {variable: value}}; if None, use the mean stored by scale()

  • sd (None, float or dict, optional) – sd to multiply the signal with; a number (same for all eyes and variables) or {eye: {variable: value}}; if None, use the sd stored by scale()

  • eyes (str or list of str, optional) – eye(s) to consider; if empty, all available eyes

  • inplace (bool or None, optional) – if True, make change in-place and return the object; if False, make and return copy before making changes; if None, use the object-level setting (specified in the constructor)

Returns:

the unscaled object (self or a copy, depending on inplace)

Return type:

GenericEyeData

property variables: List[str]

Return a list of available variables in the dataset.

write_file(fname, compress=True)[source]

Save to file (using write_pickle()).

Parameters:
  • fname (str) – filename

  • compress (bool) – compress the file with zstd (default True)

EyeDataDict

class pypillometry.EyeDataDict(*args, **kwargs)[source]

A dictionary that contains 1-dimensional ndarrays of equal length and with the same datatype (float). Drops empty entries (None or length-0 ndarrays).

Keys stored in this dictionary have the following shape: (eye)_(variable) where eye can be any string identifier (e.g., “left”, “right”, “mean”, “median”, “regress”, or any other custom identifier) and variable is any string identifier (e.g., “x”, “y”, “pupil”, “baseline”, “response”, or any other custom identifier).

Values are stored as given (converted to float); NaN values are masked. No other values are treated as missing (e.g., a pupil size of 0 is kept); converting missing-value codes to NaN is up to the code that reads the data.

The dictionary can be indexed by a string “eye_variable” or by a tuple (“eye”, “variable”) like data[“left”,”x”] or data[“left_x”].

copy()[source]

Create a deep copy of the dictionary (data and masks).

Return type:

EyeDataDict

count_masked(per_key=False)[source]

Count the number of missing values based on masks.

Parameters:

per_key (bool) – If True, return a dictionary with counts per key. If False, return maximum count across all keys. Default is False.

Returns:

If per_key is False, returns maximum number of missing values across all keys. If per_key is True, returns a dictionary mapping keys to their missing value counts.

Return type:

Union[int, Dict[str, int]]

Examples

>>> d = EyeDataDict(left_x=[1,2,3], left_y=[4,5,6])
>>> d.set_mask("left_x", [0,1,0])  # second value is missing
>>> d.set_mask("left_y", [1,0,1])  # first and last values are missing
>>> d.count_masked()  # max missing values
2
>>> d.count_masked(per_key=True)  # missing values per key
{'left_x': 1, 'left_y': 2}
property eyes: List[str]

List of all available eyes.

get_available_eyes(variable=None)[source]

Return a list of available eyes.

Parameters:

variable (str, optional) – If specified, return only eyes for this variable.

Returns:

names of the available eyes (e.g., ["left", "right"]), in the order in which they were added

Return type:

list of str

get_available_variables()[source]

Return a list of available variables.

Returns:

names of the available variables (e.g., ["x", "y", "pupil"]), in the order in which they were added

Return type:

list of str

get_eye(eye)[source]

Return a subset EyeDataDict with all variables for a given eye.

Parameters:

eye (str) – The eye to get data for (‘left’, ‘right’, ‘mean’, etc.)

Returns:

A new EyeDataDict containing only data for the specified eye

Return type:

EyeDataDict

Examples

>>> d = EyeDataDict(left_x=[1,2], left_y=[3,4], right_x=[5,6])
>>> left_data = d.get_eye('left')
>>> print(left_data.data.keys())
dict_keys(['left_x', 'left_y'])
get_mask(key=None)[source]

Get mask for a specific key or joint mask across all keys.

Parameters:

key (str) – Key to get mask for. If None, returns logical OR of all masks.

Returns:

Mask array for specified key, or joint mask if no key given.

Return type:

NDArray

get_size()[source]

Return the size of the dictionary in bytes.

Returns:

Total size in bytes.

Return type:

ByteSize

get_variable(variable)[source]

Return a subset EyeDataDict with all eyes for a given variable.

Parameters:

variable (str) – The variable to get data for (‘x’, ‘y’, ‘pupil’, etc.)

Returns:

A new EyeDataDict containing only data for the specified variable

Return type:

EyeDataDict

set_mask(key, mask)[source]

Set mask for a specific key.

Parameters:
  • key (str) – Key of the data (e.g., “left_pupil”)

  • mask (NDArray) – Mask array (1 for masked, 0 for valid values) with the same shape as the data; converted to int

Raises:
  • KeyError – if there is no data for key

  • ValueError – if mask does not have the same shape as the data

Return type:

None

set_with_mask(key, value, mask=None, preserve_mask=False)[source]

Set data array with explicit mask control.

This method provides more control over mask handling than __setitem__, which always resets the mask to zeros (except for NaN values).

Parameters:
  • key (Union[str, Tuple[str, str]]) – Dictionary key for the data (e.g., “left_pupil” or (“left”, “pupil”))

  • value (NDArray) – Data array to store

  • mask (Optional[NDArray]) – Mask array to use. If None and preserve_mask=True, keeps existing mask. If None and preserve_mask=False, creates new zero mask (with NaNs masked).

  • preserve_mask (bool) – If True and mask is None, preserve the existing mask for this key. Ignored if mask is explicitly provided.

Return type:

None

Examples

>>> # Set data and preserve existing mask
>>> data_dict.set_with_mask("left_pupil", new_values, preserve_mask=True)
>>> # Set data with explicit mask
>>> data_dict.set_with_mask("left_pupil", new_values, mask=new_mask)
>>> # Set data with fresh zero mask (same as __setitem__)
>>> data_dict.set_with_mask("left_pupil", new_values)
property variables: List[str]

List of all available variables.

Stateless signal-processing functions

These functions are used by the main classes to process the data. They are stateless, meaning that they do not store any state between calls.

baseline.py

Functions for estimating tonic pupillary fluctuations (baseline).

pypillometry.signal.baseline.baseline_envelope(tx, sy, event_onsets, fs=1000, lp=2, prominence_thr=80, interp_method='cubic')[source]

Extract baseline based on the lower envelope of the (filtered) signal.

Steps:

  • filter away noise

  • detect high-prominence troughs in the signal

  • calculate lower envelope based on these peaks

Parameters:
  • tx (np.ndarray) – time-vector (any unit; the baseline is evaluated at tx)

  • sy (np.ndarray) – raw pupil signal (must not contain NaN)

  • event_onsets (np.ndarray) – onsets of events (stimuli/responses); currently not used

  • fs (float) – sampling rate in Hz

  • lp (float) – low-pass filter cutoff in Hz for removing random noise

  • prominence_thr (float) – percentile (in [0, 100]) of the prominence distribution (of the peaks) to use for determining prominent peaks (see scipy.signal.peak_prominences())

  • interp_method ({"cubic", "linear", "spline"}) –

    • “linear”: linear interpolation between the high-prominence peaks

    • ”cubic”: cubic interpolation through all high-prominence peaks

    • ”spline”: a smoothing spline that is guaranteed to go through all high-prominence peaks and smoothes through all the other (lower-prominence) peaks

Returns:

base – baseline estimate (same length as tx)

Return type:

np.ndarray

Raises:

ValueError – if interp_method is not one of the supported methods

pypillometry.signal.baseline.baseline_envelope_iter_bspline(tx, sy, event_onsets, fs, fsd=10, lp=2, lam_sig=1, lam_min=1, lam_max=100, verbose=0, seed=None)[source]

Extract baseline based on (re-)estimating the lower envelope using B-splines. The signal is downsampled (to fsd Hz) for speed.

The B-spline fits are done with a Stan model (baseline_model_asym_laplac.stan, variational inference via cmdstanpy); in between, the event-related responses are estimated with pupil_response() (fixed npar=10, tmax=917) and removed before the second round of peak detection.

Parameters:
  • tx (np.ndarray) – time-vector in milliseconds

  • sy (np.ndarray) – raw pupil signal (must not contain NaN)

  • event_onsets (np.ndarray) – onsets of events (stimuli/responses) in milliseconds

  • fs (float) – sampling rate in Hz

  • fsd (float) – downsampled sampling rate in Hz (if too slow, decrease)

  • lp (float) – lowpass-filter cutoff in Hz (applied before downsampling)

  • lam_sig (float) – parameter steering how much the baseline is shaped by the non-peaks of the signal

  • lam_min (float) – weight given to the lowest-prominence peaks; peak prominences are mapped linearly to [lam_min, lam_max]

  • lam_max (float) – weight given to the highest-prominence peaks

  • verbose (int) – not used (progress is reported via the logger)

  • seed (int or None) – random seed for the variational inference (for reproducible results)

Returns:

  • txd (np.ndarray) – downsampled time-vector (ms)

  • syd (np.ndarray) – downsampled and lowpass-filtered pupil signal

  • base2 (np.ndarray) – final baseline estimate (after the second round), sampled at txd

  • base1 (np.ndarray) – baseline estimate after the first round, sampled at txd

pypillometry.signal.baseline.baseline_pupil_model(tx, sy, event_onsets, fs=1000, lp1=2, lp2=0.2)[source]

Extract baseline based on filtering after removing stim-locked activity.

Steps:

  • filter away noise

  • regress out event-locked activity from the filtered signal using NNLS

  • remove modeled signal from filtered data

  • run another lowpass-filter to get rid of spurious signals

Parameters:
  • tx (np.ndarray) – time-vector

  • sy (np.ndarray) – raw pupil signal (must not contain NaN)

  • event_onsets (np.ndarray) – onsets of events (stimuli/responses), in the same units as tx (each onset is assigned to the closest sample in tx)

  • fs (float) – sampling rate in Hz

  • lp1 (float) – low-pass filter cutoff in Hz for removing random noise

  • lp2 (float) – low-pass filter cutoff in Hz for removing spurious peaks from the baseline-signal

Returns:

base – baseline estimate (same length as sy)

Return type:

np.ndarray

pypillometry.signal.baseline.bspline(txd, knots, spline_degree=3)[source]

Re-implementation from https://mc-stan.org/users/documentation/case-studies/splines_in_stan.html. Similar behaviour as R’s bs() function from the splines-library.

Parameters:
  • txd (np.ndarray) – time-vector (any unit, same as knots)

  • knots (np.ndarray) – location of the knots (including the boundary knots)

  • spline_degree (int) – degree of the spline (default 3, cubic)

Returns:

B – matrix of basis functions, shape (len(txd), len(knots)+spline_degree-1)

Return type:

np.ndarray

pypillometry.signal.baseline.butter_lowpass(cutoff, fs, order=5)[source]

Get lowpass-filter coefficients for Butterworth-filter.

Parameters:
  • cutoff (float) – lowpass-filter cutoff in Hz

  • fs (float) – sampling rate in Hz

  • order (int) – filter order

Returns:

b, a – numerator and denominator filter coefficients (see scipy.signal.butter())

Return type:

np.ndarray

pypillometry.signal.baseline.butter_lowpass_filter(data, cutoff, fs, order=5)[source]

Lowpass-filter signal using a Butterworth-filter.

The filter is applied forward and backward (scipy.signal.filtfilt()), so the result has zero phase shift. data must not contain NaN values.

Parameters:
  • data (np.ndarray) – data to lowpass-filter

  • cutoff (float) – lowpass-filter cutoff in Hz

  • fs (float) – sampling rate in Hz

  • order (int) – filter order

Returns:

y – filtered data

Return type:

np.ndarray

pypillometry.signal.baseline.downsample(y, R)[source]

Simple downsampling scheme using mean within the downsampling window.

The signal is split into consecutive windows of R samples and each window is replaced by its mean (ignoring NaN). If the length of y is not a multiple of R, the last window is shorter.

Parameters:
  • y (np.ndarray) – signal to downsample

  • R (int) – decimate-factor (number of samples per window)

Returns:

y2 – downsampled data of length ceil(len(y)/R)

Return type:

np.ndarray

preproc.py

preprocessing functions (blinks, smoothing, missing data…)

Method for finding the on- and offset for each blink (excluding transient). See https://figshare.com/articles/A_simple_way_to_reconstruct_pupil_size_during_eye_blinks/688001.

Uses asymmetric Savitzky-Golay velocity estimation: - Backward-looking velocity for onset detection (so future zeros/NaN don’t affect the estimate) - Forward-looking velocity for offset detection (so past zeros/NaN don’t affect the estimate)

The asymmetric approach naturally handles blink regions (zeros/NaN) because: - At blink onset: backward velocity only sees valid data before the blink → detects the drop - At blink offset: forward velocity only sees valid data after the blink → detects the rise

Parameters:
  • sy (np.ndarray) – pupil data (can contain NaN or zeros for missing values)

  • blinks (np.ndarray or Intervals) – blink onset/offset sample indices (nblinks x 2), e.g. from detect_blinks_zero() or an Intervals object in index units; anything that iterates over (start, end) pairs works

  • smooth_winsize (int) – size of the Savitzky-Golay window in samples (odd)

  • vel_onset (float) – velocity-threshold to detect the onset of the blink; in units per sample (negative value)

  • vel_offset (float) – velocity-threshold to detect the offset of the blink; in units per sample (positive value)

  • margin (tuple of int) – (before, after) margin that is subtracted/added to onset and offset (in samples)

  • blinkwindow (int) – how much time before and after each blink to search for the on-/offset (in samples)

  • min_onset_len (int) – minimum number of consecutive samples in an onset run to be considered valid

  • min_offset_len (int) – minimum number of consecutive samples in an offset run to be considered valid

Returns:

array (n x 2) of refined onset/offset sample indices; blinks for which no valid velocity-threshold crossing was found are dropped, so n can be smaller than the number of input blinks

Return type:

np.ndarray

Detect blinks as everything between a fast downward and a fast upward-trending PD-changes.

Uses asymmetric Savitzky-Golay velocity estimation to handle NaN values properly: - Backward-looking velocity for onset detection (so future NaN doesn’t affect the estimate) - Forward-looking velocity for offset detection (so past NaN doesn’t affect the estimate)

This works similarly to blink_onsets_mahot().

Parameters:
  • sy (np.ndarray) – pupil data (can contain NaN or zeros for missing values)

  • smooth_winsize (int) – size of the Savitzky-Golay window in samples (odd)

  • vel_onset (float) – velocity-threshold to detect the onset of the blink; in units per sample (negative value)

  • vel_offset (float) – velocity-threshold to detect the offset of the blink; in units per sample (positive value)

  • min_onset_len (int) – number of consecutive samples that must cross the threshold to detect an onset (runs must have at least min_onset_len samples)

  • min_offset_len (int) – number of consecutive samples that must cross the threshold to detect an offset (runs must have at least min_offset_len samples)

  • min_blink_samples (int) – minimum duration of a blink in samples; offsets that would create shorter intervals are skipped during matching (to avoid spurious short offsets stealing real onsets)

  • max_blink_samples (int or None) – maximum duration of a blink in samples; an onset without an offset within this many samples is not a blink and is dropped (otherwise it would be paired with an arbitrarily distant offset); None means no limit

Returns:

array (nblinks x 2) of blink onset/offset sample indices (both inclusive); the offset is the last sample of the matching offset run, or the last sample of sy if no offset was found after the last onset (and max_blink_samples allows it); empty array if no blinks were found

Return type:

np.ndarray

Detect blinks as consecutive sequence of blink_val (e.g., 0 or NaN) of at least min_duration successive values in the signal sy. Detected blinks are put a matrix blinks (nblinks x 2) where start and end are stored as indexes.

blink_val=np.nan matches NaN values; any other value is compared with sy == blink_val.

Parameters:
  • sy (np.ndarray) – signal

  • min_duration (int) – minimum number of samples for a sequence of missing numbers to be treated as blink

  • blink_val (float) – “missing value” code, e.g. 0 or np.nan (default 0)

Returns:

array (nblinks x 2) containing the indices of the start/end of the blinks (both inclusive); empty array if no blinks were found

Return type:

np.ndarray

pypillometry.signal.preproc.smooth_window(x, window_len=11, window='hanning', direction='center')[source]

Smooth the data using a window with requested size.

This method is based on the convolution of a scaled window with the signal. The direction parameter controls whether the smoothing uses past, future, or both samples relative to each point.

Note: If using backward or forward smoothing, the smoothed signal will be shifted by half the window size. This is intentional for onset and offset detection, where the smoothed signal is used to detect the onset and offset of the blink.

Adapted from SciPy Cookbook: https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html.

Parameters:
  • x (np.ndarray) – The input signal

  • window_len (int) – The dimension of the smoothing window; should be an odd integer

  • window (str) – The type of window from ‘flat’, ‘hanning’, ‘hamming’, ‘bartlett’, ‘blackman’. ‘flat’ window will produce a moving average smoothing.

  • direction (str) –

    • ‘center’: symmetric smoothing using both past and future samples (default)

    • ’backward’: only uses current and past samples (useful for onset detection before missing data, where future NaN shouldn’t affect the estimate)

    • ’forward’: only uses current and future samples (useful for offset detection after missing data, where past NaN shouldn’t affect the estimate)

Returns:

The smoothed signal (same length as input)

Return type:

np.ndarray

Examples

>>> # Standard centered smoothing
>>> smoothed = smooth_window(signal, window_len=11, window='hanning')
>>>
>>> # Backward-looking for onset detection
>>> smoothed_back = smooth_window(signal, window_len=11, direction='backward')
>>>
>>> # Forward-looking for offset detection
>>> smoothed_fwd = smooth_window(signal, window_len=11, direction='forward')
pypillometry.signal.preproc.velocity_savgol(x, window_len=11, polyorder=2, direction='center')[source]

Compute velocity (first derivative) using Savitzky-Golay filter.

This computes velocity by fitting a polynomial to a window of samples and evaluating its derivative, which is more robust than simple differencing. The direction parameter controls whether the velocity estimate uses past, future, or both samples relative to each point.

Parameters:
  • x (np.ndarray) – The input signal

  • window_len (int) – The dimension of the smoothing window; should be an odd integer. Must be greater than polyorder.

  • polyorder (int) – Order of the polynomial to fit. Default is 2 (quadratic).

  • direction (str) – ‘center’ - symmetric, uses both past and future samples (default) ‘backward’ - only uses current and past samples (useful for onset detection) ‘forward’ - only uses current and future samples (useful for offset detection)

Returns:

The velocity (first derivative) in units per sample (same length as input)

Return type:

np.ndarray

Examples

>>> # Standard centered velocity
>>> vel = velocity_savgol(signal, window_len=11)
>>>
>>> # Backward-looking for onset detection
>>> vel_back = velocity_savgol(signal, window_len=11, direction='backward')
>>>
>>> # Forward-looking for offset detection
>>> vel_fwd = velocity_savgol(signal, window_len=11, direction='forward')

pupil.py

functions related to pupillary responses.

pypillometry.signal.pupil.lowpass_filter_iterative(signal, cutoff, fs, order=2, max_iter=5)[source]

Lowpass filter that handles NaN values via iterative interpolation.

NaN values are initially filled with the signal mean, then iteratively replaced with filtered values until convergence. This avoids sharp artifacts at gap boundaries that would occur with simple interpolation.

Parameters:
  • signal (np.ndarray) – Input signal (can contain NaN values)

  • cutoff (float) – Lowpass filter cutoff frequency in Hz

  • fs (float) – Sampling rate in Hz

  • order (int, optional) – Butterworth filter order (default: 2)

  • max_iter (int, optional) – Maximum number of iterations (default: 5)

Returns:

filtered – Lowpass-filtered signal with NaN gaps smoothly filled

Return type:

np.ndarray

Notes

The iterative approach works by: 1. Fill NaN positions with the signal mean 2. Apply lowpass filter to the filled signal 3. Replace NaN positions with the filtered values 4. Repeat steps 2-3 until max_iter reached

This converges to a smooth solution that respects the observed data while filling gaps with values consistent with the surrounding signal.

Examples

>>> signal = np.array([1, 2, np.nan, np.nan, 5, 6])
>>> filtered = lowpass_filter_iterative(signal, cutoff=2.0, fs=100)
pypillometry.signal.pupil.plot_prf(npar=10.1, tmax=930, max_duration='estimate', fs=500, **kwargs)[source]

Plot profile of the pupil-response function (PRF) with parameters npar and tmax.

The PRF is plotted into the current matplotlib axes.

Parameters:
  • npar (float) – PRF parameter \(n\), see pupil_kernel()

  • tmax (float) – PRF parameter \(t_{max}\) in ms, see pupil_kernel()

  • max_duration (float or "estimate") – length of the plotted time window in ms; “estimate” uses pupil_get_max_duration()

  • fs (float) – sampling rate in Hz used to evaluate the PRF

  • **kwargs – passed on to matplotlib.pyplot.plot()

pypillometry.signal.pupil.pupil_build_design_matrix(tx, event_onsets, fs, npar, tmax, max_duration='estimate')[source]

Construct design matrix (nevents x n). Each row has a single pupil-kernel with parameters npar, tmax starting at each event_onset.

Parameters:
  • tx (np.ndarray) – time-vector in ms, sampled at fs

  • event_onsets (np.ndarray) – timing of the events in ms (each onset is assigned to the closest sample in tx)

  • fs (float) – sampling rate (Hz)

  • npar (float) – n in the equation of pupil_kernel()

  • tmax (float) – \(t_{max}\) in pupil_kernel(), in ms

  • max_duration (float or "estimate") – either maximum duration in milliseconds or string “estimate” which causes the function to determine an appropriate duration based on the kernel parameters npar and tmax

Returns:

x1 – design matrix (nevents x n), where n is the length of tx

Return type:

np.ndarray

pypillometry.signal.pupil.pupil_eye_offset(left, right, fs, left_mask=None, right_mask=None, lowpass_cutoff=0.2, max_iter=10)[source]

Compute a slowly-varying offset between left and right pupil signals.

This function calculates the local inter-ocular difference (left - right) to account for drift over an experimental session. The offset is computed where both eyes have valid data, and lowpass filtered using an iterative method that properly handles missing values (NaN gaps).

Parameters:
  • left (np.ndarray or np.ma.MaskedArray) – Left eye pupil signal. Can be: - A regular numpy array (requires left_mask) - A numpy masked array (mask is extracted automatically)

  • right (np.ndarray or np.ma.MaskedArray) – Right eye pupil signal. Same format options as left.

  • fs (float) – Sampling rate in Hz.

  • left_mask (np.ndarray, optional) – Boolean mask for left eye where True = invalid/masked. Required if left is a regular numpy array. Ignored if left is a masked array.

  • right_mask (np.ndarray, optional) – Boolean mask for right eye where True = invalid/masked. Required if right is a regular numpy array. Ignored if right is a masked array.

  • lowpass_cutoff (float, optional) – Cutoff frequency for lowpass filtering the offset (default: 0.2 Hz). Lower values produce a more stable offset that only tracks very slow drift. Higher values allow faster changes but may introduce noise.

  • max_iter (int, optional) – Maximum iterations for iterative lowpass filtering (default: 10). The iterative filter fills NaN gaps by repeatedly filtering and replacing NaN values until convergence.

Returns:

offset – The local offset signal (left - right), smoothly lowpass filtered. Can be added to right eye data to align it with left eye level: right_corrected = right + offset.

Return type:

np.ndarray

Notes

The offset is computed as follows: 1. Calculate raw difference (left - right) where both eyes are valid 2. Apply iterative lowpass filter that handles NaN gaps properly

The iterative filtering approach (see lowpass_filter_iterative) ensures that only actual measurements influence the result, not interpolated values. NaN gaps are filled with values consistent with the surrounding signal through an iterative process that converges to a smooth solution.

This handles session-long drift by allowing the offset to vary slowly over time, rather than assuming a constant inter-ocular difference.

For typical pupillometry experiments, a lowpass cutoff of 0.1-0.5 Hz is recommended. This corresponds to changes over 2-10 seconds, which captures drift while ignoring trial-by-trial noise.

Examples

>>> # With masked arrays
>>> offset = pupil_eye_offset(left_ma, right_ma, fs=500)
>>> right_corrected = right_ma + offset
>>>
>>> # With explicit masks
>>> offset = pupil_eye_offset(left, right, fs=500,
...                           left_mask=left_mask, right_mask=right_mask)
>>>
>>> # Merge eyes using the offset
>>> both_valid = ~left_mask & ~right_mask
>>> merged = np.where(both_valid,
...                   (left + right + offset) / 2,
...                   np.where(~left_mask, left, right + offset))

See also

lowpass_filter_iterative

Lowpass filter that handles NaN values

pypillometry.signal.pupil.pupil_get_max_duration(npar, tmax, thr=1e-08, stepsize=1.0)[source]

Get the time when the PRF with parameters \(n\) and \(t_{max}\) is decayed to thr. This gives an indication of how long the duration parameter in pupil_kernel() should be chosen.

Parameters:
  • npar (float) – PRF parameter \(n\), see pupil_kernel()

  • tmax (float) – PRF parameter \(t_{max}\) in ms, see pupil_kernel()

  • thr (float) – desired value to which the PRF is decayed (the lower thr, the longer the duration)

  • stepsize (float) – precision of the maximum duration in ms

Returns:

tdur – first value of t (in ms, searched from tmax onwards in steps of stepsize) so that PRF(t) <= thr

Return type:

float

pypillometry.signal.pupil.pupil_kernel(duration=4000, fs=1000, npar=10.1, tmax=930.0)[source]

According to Hoeks and Levelt (1993, https://link.springer.com/content/pdf/10.3758%2FBF03204445.pdf), the PRF can be described by the so-called Erlang gamma function

\[h_{HL}(t) = t^n e^{-nt/t_{max}}\]
which we normalize to
\[h(t)=\frac{1}{h_{max}} h_{HL}(t)\]
where
\[h_{max} = \max_t{\left(h_{HL}(t)\right)} = e^{-n}t_{max}^{n}\]
which yields a maximum value of 1 for this function. The function \(h(t)\) is implemented in pp.pupil_kernel().

Parameters:
  • duration (float) – in ms; maximum of the time window for which to calculate the PRF [0,duration]

  • fs (float) – sampling rate in Hz for resolving the PRF

  • npar (float) – n in the equation above

  • tmax (float) – \(t_{max}\) in the equation above, in ms

Returns:

h – sampled version of h(t) at sampling rate fs, starting at t=0, with int(duration/1000*fs) samples

Return type:

np.ndarray

pypillometry.signal.pupil.pupil_kernel_t(t, npar, tmax)[source]

According to Hoeks and Levelt (1993, https://link.springer.com/content/pdf/10.3758%2FBF03204445.pdf), the PRF can be described by the so-called Erlang gamma function

\[h_{HL}(t) = t^n e^{-nt/t_{max}}\]
which we normalize to
\[h(t)=\frac{1}{h_{max}} h_{HL}(t)\]
where
\[h_{max} = \max_t{\left(h_{HL}(t)\right)} = e^{-n}t_{max}^{n}\]
which yields a maximum value of 1 for this function. The function \(h(t)\) is implemented in pupil_kernel().

This version of the function evaluates the PRF at inputs t.

Parameters:
  • t (float or np.ndarray) – time(s) at which to evaluate the PRF, in ms

  • npar (float) – n in the equation above

  • tmax (float) – \(t_{max}\) in the equation above (time of the peak), in ms

Returns:

h – PRF evaluated at t

Return type:

np.ndarray

pypillometry.signal.pupil.pupil_response(tx, sy, event_onsets, fs, npar='free', tmax='free', verbose=10, bounds={'npar': (1, 20), 'tmax': (100, 2000)}, display_progress=True)[source]

Estimate pupil-response based on event-onsets.

The amplitudes of one PRF per event (see pupil_kernel()) are estimated with non-negative least-squares (NNLS). If npar and/or tmax are “free”, they are optimized (minimizing the sum of squared residuals) within bounds.

Parameters:
  • tx (np.ndarray) – time-vector in milliseconds

  • sy (np.ndarray) – (baseline-corrected) pupil signal

  • event_onsets (np.ndarray) – onsets of events (stimuli/responses) in milliseconds

  • fs (float) – sampling rate in Hz

  • npar (float or "free") – npar-parameter for the canonical response-function or “free”; in case of “free”, the function optimizes for this parameter

  • tmax (float or "free") – tmax-parameter (in ms) for the canonical response-function or “free”; in case of “free”, the function optimizes for this parameter

  • verbose (int) – not used (progress is reported via the logger)

  • bounds (dict) – in case that one or both parameters are estimated, give the lower and upper bounds for the parameters (keys “npar” and “tmax”, tmax in ms)

  • display_progress (bool) – show the progress of the optimization (if both parameters are free, each iteration is logged; otherwise passed as disp option to scipy.optimize.minimize_scalar())

Returns:

  • pred (np.ndarray) – predicted signal (same length as sy)

  • coef (np.ndarray) – non-negative amplitude of each event’s PRF

  • npar (float) – npar used (estimated if “free”)

  • tmax (float) – tmax used in ms (estimated if “free”)

  • x1 (np.ndarray) – design matrix (nevents x len(tx)), see pupil_build_design_matrix()

pypillometry.signal.pupil.pupil_signal_quality(signal, fs, mask=None, lowpass_cutoff=4.0, window_size_ms=500, metric='snr')[source]

Compute local signal quality metric for pupil signal.

Estimates signal quality by separating the signal into a slow “true signal” component (via lowpass filtering) and a fast “noise” component (the residual). Quality is then computed in sliding windows.

Parameters:
  • signal (np.ndarray or np.ma.MaskedArray) –

    Pupil signal. Can be either:

    • A regular numpy array, in which case mask must be provided

    • A numpy masked array, in which case its mask is used

  • fs (float) – Sampling rate in Hz

  • mask (np.ndarray, optional) – Boolean mask where True indicates invalid/masked samples. Required if signal is a regular numpy array. Ignored if signal is a masked array.

  • lowpass_cutoff (float, optional) – Cutoff frequency for separating signal from noise (default: 4.0 Hz). Pupil responses are typically < 4 Hz, so higher frequencies are noise.

  • window_size_ms (float, optional) – Window size for local quality estimation in milliseconds (default: 500 ms)

  • metric (str, optional) –

    Quality metric to return:

    • ”snr”: signal-to-noise ratio (signal_power / noise_power)

    • ”snr_db”: SNR in decibels, 10 * log10(snr) (0 dB = equal, +10 dB = 10x better, -10 dB = 10x worse)

    • ”noise_power”: raw noise power (signal units squared)

    • ”noise_cv”: coefficient of variation (noise_std / signal), interpretable as fractional noise (0.05 = 5% noise)

Returns:

quality – Local quality metric as a masked array (same length as input). Masked at the same locations as the input.

Return type:

np.ma.MaskedArray

Raises:

ValueError – If metric is not one of the supported options. If signal is a regular array and mask is not provided. If there are NaN values in the unmasked (valid) portion of the signal.

Notes

The signal-noise separation assumes pupil dynamics are slow (< lowpass_cutoff Hz). The “signal” estimate is the lowpass-filtered version, and “noise” is the high-frequency residual (original - lowpass).

Local power is computed as the mean of squared values in a sliding window. SNR = signal_power / noise_power indicates how much true signal there is relative to measurement noise.

Examples

>>> # With explicit mask
>>> mask = pupil == 0  # e.g., blinks marked as 0
>>> snr = pupil_signal_quality(pupil, fs=500, mask=mask, metric="snr")
>>>
>>> # With masked array
>>> pupil_ma = np.ma.array(pupil, mask=(pupil == 0))
>>> snr = pupil_signal_quality(pupil_ma, fs=500, metric="snr")
>>>
>>> # Get noise as coefficient of variation (percentage)
>>> noise_cv = pupil_signal_quality(pupil, fs=500, mask=mask, metric="noise_cv")
>>> print(f"Average noise level: {np.mean(noise_cv[~mask])*100:.1f}%")
pypillometry.signal.pupil.pupilresponse_nnls(tx, sy, event_onsets, fs, npar=10.1, tmax=930)[source]

Estimate single-event pupil responses based on canonical PRF (pupil_kernel()) using non-negative least-squares (NNLS).

Parameters:
  • tx (np.ndarray) – time-vector in milliseconds

  • sy (np.ndarray) – (baseline-corrected) pupil signal

  • event_onsets (np.ndarray) – onsets of events (stimuli/responses) in milliseconds

  • fs (float) – sampling rate in Hz

  • npar (float) – parameter \(n\) for pupil_kernel()

  • tmax (float) – parameter \(t_{max}\) (in ms) for pupil_kernel()

Returns:

  • coef (np.ndarray) – purely-positive regression coefficients (one per event)

  • pred (np.ndarray) – predicted signal

  • resid (np.ndarray) – residuals (sy-pred)

Synthetic data generation functions for testing and validation.

This module provides stateless functions for generating individual components of eye-tracking data. Functions use abstract names (true_pupil, correction_factor) to maintain generality. High-level orchestrator functions combine these generators and use algorithm-specific names (e.g., A0, cos_alpha for foreshortening).

pypillometry.signal.fake.add_measurement_noise(signal, noise_level=0.05, seed=None)[source]

Add Gaussian measurement noise to a signal.

Parameters:
  • signal (np.ndarray) – Input signal

  • noise_level (float) – Standard deviation of Gaussian noise to add

  • seed (int, optional) – Random seed for reproducibility

Returns:

  • noisy_signal (np.ndarray) – Signal with added noise

  • params (dict) – Parameters used for generation

Examples

>>> t, pupil, _ = fake_pupil_baseline(duration=10, fs=1000, seed=42)
>>> noisy_pupil, params = add_measurement_noise(pupil, noise_level=0.1, seed=42)
pypillometry.signal.fake.fake_gaze_fixations(duration, fs, fixation_duration_mean=300, fixation_duration_std=100, screen_bounds=((0, 1920), (0, 1080)), n_fixations=None, seed=None)[source]

Generate step-function gaze positions (fixations with random locations and durations).

Creates gaze time series by: 1. Determining fixation times (random durations) 2. Selecting random fixation locations within screen bounds 3. Creating step functions for x and y coordinates

Parameters:
  • duration (float) – Duration in seconds

  • fs (float) – Sampling rate in Hz

  • fixation_duration_mean (float) – Mean fixation duration (ms)

  • fixation_duration_std (float) – Standard deviation of fixation duration (ms)

  • screen_bounds (tuple of tuples) – ((xmin, xmax), (ymin, ymax)) screen boundaries in pixels

  • n_fixations (int, optional) – Number of fixations (if None, determined by duration and mean fixation time); the fixation durations are scaled so that the fixations fill duration

  • seed (int, optional) – Random seed for reproducibility

Returns:

  • t (np.ndarray) – Time vector (ms)

  • x (np.ndarray) – X gaze positions (pixels)

  • y (np.ndarray) – Y gaze positions (pixels)

  • fixation_info (dict) – Fixation times, positions, durations

  • params (dict) – Parameters used for generation

Examples

>>> t, x, y, info, params = fake_gaze_fixations(duration=10, fs=1000, seed=42)
>>> print(f"Number of fixations: {len(info['fixation_times'])}")
pypillometry.signal.fake.fake_pupil_baseline(duration, fs, mean=750, amplitude=100, freq=0.5, filter_order=4, seed=None)[source]

Generate smooth baseline pupil size via filtered noise.

Creates pupil size time series by: 1. Generating white noise 2. Low-pass filtering at specified frequency with edge handling 3. Scaling to desired amplitude and mean

Parameters:
  • duration (float) – Duration in seconds

  • fs (float) – Sampling rate in Hz

  • mean (float, default 750) – Mean pupil size in arbitrary units (typical EyeLink range: 500-1000 AU). Corresponds to area-proportional measurements, not diameter.

  • amplitude (float, default 100) – Amplitude of slow fluctuations (standard deviation of baseline variations)

  • freq (float, default 0.5) – Cutoff frequency for low-pass filter (Hz), controls fluctuation speed. Typical values: 0.1-0.5 Hz for spontaneous fluctuations, 0.5-1 Hz for cognitive load changes, 1-3 Hz for fast responses.

  • filter_order (int, default 4) – Order of Butterworth filter. Higher order = sharper cutoff. Roll-off is (filter_order * 6) dB/octave.

  • seed (int, optional) – Random seed for reproducibility

Returns:

  • t (np.ndarray) – Time vector (ms)

  • true_pupil (np.ndarray) – True pupil size time series

  • params (dict) – Parameters used for generation

Examples

>>> t, pupil, params = fake_pupil_baseline(duration=10, fs=1000, seed=42)
>>> print(f"Mean: {pupil.mean():.2f}, Std: {pupil.std():.2f}")
pypillometry.signal.fake.generate_foreshortening_data(experimental_setup=None, duration=120, fs=1000, eye='left', A0_mean=750, A0_amplitude=100, A0_freq=0.5, fixation_duration_mean=500, fixation_duration_std=100, measurement_noise=5.0, seed=None, **kwargs)[source]

Generate complete synthetic dataset for testing foreshortening correction algorithm.

Orchestrates: 1. Generate true pupil size A0(t) with slow fluctuations (~0.5 Hz by default) 2. Generate fixation-based gaze positions (x, y) 3. Apply foreshortening: measured_pupil = A0 * cos(alpha(x,y)) 4. Package into FakeEyeData with all metadata and ground truth

Uses algorithm-specific names (A0, cos_alpha) since this function is specifically designed for testing the foreshortening correction algorithm.

Parameters:
  • experimental_setup (ExperimentalSetup) – Experimental setup containing screen geometry (resolution, physical size), eye-to-screen distance (d), and camera position (theta, phi, r). If None, uses defaults: 1920x1080 screen (52x29 cm), d=60cm, camera at theta=20°, phi=-90°, r=70cm.

  • duration (float) – Duration in seconds

  • fs (float) – Sampling rate in Hz

  • eye (str) – Which eye (‘left’ or ‘right’)

  • A0_mean (float) – Mean true pupil size in arbitrary units (typical EyeLink range: 500-1000 AU). Corresponds to area-proportional measurements, not diameter.

  • A0_amplitude (float) – Amplitude of pupil fluctuations (standard deviation of slow variations)

  • A0_freq (float) – Frequency of pupil fluctuations in Hz (controls speed of baseline changes)

  • fixation_duration_mean (float) – Mean fixation duration in ms

  • fixation_duration_std (float) – Standard deviation of fixation duration in ms

  • measurement_noise (float) – Standard deviation of Gaussian measurement noise added to pupil signal (in AU)

  • seed (int) – Random seed for reproducibility

  • **kwargs – additional parameters passed to FakeEyeData

Returns:

data – Synthetic eye-tracking data with:

  • sim_fct: reference to this function

  • sim_fct_name: ‘generate_foreshortening_data’

  • sim_params: all generation parameters

  • sim_data: EyeDataDict with ‘{eye}_A0’ and ‘{eye}_cosalpha’ ground truth

Return type:

FakeEyeData

Examples

>>> from pypillometry.signal.fake import generate_foreshortening_data
>>>
>>> # Use defaults
>>> data = generate_foreshortening_data(duration=60, seed=42)
>>>
>>> # Or provide custom setup
>>> from pypillometry.eyedata import ExperimentalSetup
>>> setup = ExperimentalSetup(
...     screen_resolution=(1920, 1080),
...     physical_screen_size=("52 cm", "29 cm"),
...     eye_to_screen_center="60 cm",
...     camera_spherical=("20 deg", "-90 deg", "70 cm"),
... )
>>> data = generate_foreshortening_data(setup, duration=60, seed=42)
>>>
>>> # Access ground truth
>>> A0_true = data.sim_data['left_A0']
>>> cos_alpha_true = data.sim_data['left_cosalpha']

Units

Times, distances and angles can be given with units, either multiplied by a unit from pypillometry.u or as strings:

from pypillometry import u

d.sub_slice(3*u.min, 5*u.min)                   # or ("3 min", "5 min")
d.get_intervals("stim", (-200, 800) * u.ms)
d.pupil_blinks_detect(min_duration="20 ms")
d.plot.pupil_plot(plot_range=(3, 5) * u.min)
ExperimentalSetup(eye_to_screen_center=60 * u.cm, screen_pitch="-5 deg")

Internally, all values are converted to ms, mm and radians. Plain numbers are interpreted as documented for each function (usually ms for times). Available units: u.ms, u.sec, u.min, u.h, u.samples, u.mm, u.cm, u.m, u.inch, u.deg, u.rad (plus aliases such as u.seconds or u.degree).

class pypillometry.Quantity(value, unit)[source]

A number or array together with a Unit.

Usually created by multiplying with a unit, e.g. 3 * u.min or (-200, 800) * u.ms. Tuples and lists are stored as numpy arrays. Iterating over or indexing an array quantity gives quantities.

Parameters:
  • value (float or array-like) – magnitude

  • unit (Unit or str) – unit of value

to(unit, fs=None)[source]

Return the quantity converted to unit.

Parameters:
  • unit (Unit or str) – target unit; must have the same dimension

  • fs (float, optional) – sampling rate in Hz; needed to convert from or to samples

pypillometry.parse_time(value, to='ms', default='ms', fs=None)[source]

Convert a time (or tuple/array of times) to unit to.

Parameters:
  • value (float, array-like, str, Quantity, tuple or None) – time value(s), e.g. 500, "2 sec", 3 * u.min, (-200, 800) * u.ms or ("1 min", "2 min")

  • to (str or Unit, optional) – target unit; default “ms”

  • default (str, Unit or None, optional) – unit of plain numbers; default “ms”. If None, plain numbers raise a ValueError.

  • fs (float, optional) – sampling rate in Hz; needed to convert from or to “samples”

Returns:

the value in unit to (an ndarray for tuples, lists and arrays)

Return type:

float, ndarray or None

Examples

>>> parse_time("2 sec")
2000.0
>>> parse_time(3 * u.min, to="sec")
180.0
>>> parse_time((-200, 800), to="sec")
array([-0.2,  0.8])
>>> parse_time(10 * u.samples, fs=500)
20.0
pypillometry.parse_distance(value)[source]

Convert a distance to mm.

Parameters:

value (float, str, Quantity or None) – e.g. "60 cm", 60 * u.cm, "0.6 m"; plain numbers are mm (with a one-time warning)

Returns:

distance in mm

Return type:

float, ndarray or None

Examples

>>> parse_distance("60 cm")
600.0
>>> parse_distance(60 * u.cm)
600.0
pypillometry.parse_angle(value)[source]

Convert an angle to radians.

Parameters:

value (float, str, Quantity or None) – e.g. "20 deg", 20 * u.deg, "0.349 rad"; plain numbers are radians (with a one-time warning)

Returns:

angle in radians

Return type:

float, ndarray or None

Examples

>>> round(parse_angle("180 deg"), 4)
3.1416

Logging

By default, the log-level is set to INFO which results in a moderate amount of logging information. The logging can be turned off by running logging_disable() and turned back on by running logging_enable(). You can also set the log-level to DEBUG or WARN by running logging_set_level().

pypillometry.logging_set_level(level='INFO')[source]

Set the logging level for all pypillometry submodules.

In the case of DEBUG, the logging for cmdstanpy is also enabled.

Parameters:

level (str) – The logging level. Can be one of “DEBUG”, “INFO”, “WARNING”, “ERROR”, “CRITICAL”.

pypillometry.logging_disable()[source]

Disable logging for all pypillometry submodules.

pypillometry.logging_enable()[source]

Enable logging for all pypillometry submodules.

Modules

io.py

Read/Write data from/to disk.

Reading EDF files is slow: read_eyelink() (and from_eyelink()) takes about a minute for a 90 MB EDF file (64 min of binocular data at 1000 Hz), mostly in the eyelinkio package. It is therefore recommended to convert EDF files once and to work with the converted files, e.g., with the command-line program edf2pypil (edf2pypil recording.edf writes recording.pkl) or with write_pickle(). Reading the pickle file with read_pickle() is more than 100 times faster. write_pickle() compresses the file with zstd by default: a 400 MB pickle (90 MB EDF file) shrinks to about 31 MB, takes 0.5 s to write and 0.35 s to read (uncompressed: 0.2 s from a warm cache, more from a slow disk or network). Pickle files should only be read from trusted sources.

pypillometry.io.fetch_url(url, cache_file=None, cache_dir=None, force_download=False, session=None, cache_layout='flat')[source]

Download an HTTP(S) or SSH URL to a local file, with caching.

HTTP(S) URLs are downloaded with download() (OSF client URLs such as https://osf.io/<id> are converted to download URLs).

SSH URLs have the form ssh://[user@]host[:port]/path and are downloaded with the system’s scp, which uses ~/.ssh/config (host aliases, users, keys). Only key-based login is supported: password prompts are disabled (BatchMode=yes). The path is absolute on the server; use ssh://host/~/path for a path relative to the home directory. An SSH URL ending in / downloads the whole folder.

Parameters:
  • url (str) – HTTP(S) or SSH URL

  • cache_file (Optional[str]) – local filename to store the download in

  • cache_dir (Optional[str]) – directory to store the download in (used if cache_file is None); see cache_layout. If both are None, a new temporary directory is used. The location is logged.

  • force_download (bool) – if False (default), an existing local copy is reused; if True, the file is downloaded again

  • session (Optional[Session]) – requests session for HTTP(S) downloads (e.g., from osf_authenticate() for private OSF projects)

  • cache_layout (str) – where in cache_dir the download is stored: "flat" (default) uses the file name only (cache_dir/<file>), "mirror" the full remote path (cache_dir/<host>/<path>). With "flat", files with the same name from different URLs share one local copy, so the first one would be reused; use "mirror" (or force_download) in that case.

Returns:

local path of the downloaded file (or folder)

Return type:

str

pypillometry.io.local_copy(source, cache_file=None, cache_dir=None, autodelete=None, force_download=False, session=None, cache_layout='flat')[source]

Context manager that gives a local filename for source.

HTTP(S) and SSH URLs are downloaded with fetch_url(); local paths are passed through unchanged (and never deleted).

Parameters:
  • source (str) – local path, HTTP(S) URL or SSH URL

  • cache_file (Optional[str]) – see fetch_url()

  • cache_dir (Optional[str]) – see fetch_url()

  • force_download (bool) – see fetch_url()

  • session (Optional[Session]) – see fetch_url()

  • cache_layout (str) – see fetch_url()

  • autodelete (Optional[bool]) – if True, the downloaded copy (also in cache_file/cache_dir) is deleted when the context is left, i.e. after the file was read; a temporary directory is removed completely. If None (default), only a temporary directory (no cache_file/cache_dir) is removed.

Yields:

str – local filename

pypillometry.io.osf_authenticate(access_token, validate=True, session=None, timeout=10.0)[source]

Authenticate against the OSF API using a personal access token.

Parameters:
  • access_token (str) – Personal access token generated on OSF.

  • validate (bool) – If True (default), verify the token by calling /v2/users/me/.

  • session (Optional[Session]) – Existing session to configure. If None, a new session is created.

  • timeout (float) – Timeout (in seconds) for the validation request when validate is True.

Returns:

Session configured with the OSF bearer token.

Return type:

Session

Raises:

ValueError – If no access token is provided or validation fails.

Read an Eyelink file/URL and return the object returned by the “eyelinkio” package.

Requires the optional package eyelinkio. Its console output is only shown when the log level is “DEBUG”. URLs are downloaded with fetch_url().

Parameters:
  • source (str) – filename of the Eyelink EDF file, HTTP(S) URL (OSF client URLs such as https://osf.io/<id> are converted to download URLs) or SSH URL (ssh://server/folder/file.edf)

  • cache_file (Optional[str]) – local filename to store a downloaded file in

  • cache_dir (Optional[str]) – directory to store a downloaded file in (as cache_dir/<file>, see cache_layout), used if cache_file is None. If both are None, a temporary directory is used.

  • force_download (bool) – if True, download again even if a local copy exists in cache_file/cache_dir

  • session (Optional[Session]) – requests session for HTTP(S) downloads (e.g., from osf_authenticate() for private projects)

  • autodelete (Optional[bool]) – if True, the downloaded copy is deleted after it was read (also in cache_file/cache_dir). If None (default), only a temporary download (no cache_file/cache_dir) is deleted. Local files are never deleted.

  • cache_layout (str) – "flat" (default) or "mirror" (cache_dir/<host>/<path>), see fetch_url()

Returns:

object returned by eyelinkio.read_edf()

Return type:

eyelinkio.EDF

See also

pypillometry.GenericEyeData.from_eyelink

create an eyedata object from an EDF file

pypillometry.io.read_pickle(fname, cache_file=None, force_download=False, session=None, cache_dir=None, autodelete=None, cache_layout='flat')[source]

Read a Python object from a file using pickle.

Plain, zstd- and gzip-compressed pickle files are supported (the format is detected from the file content).

URLs are downloaded with fetch_url(), in the same way as in read_eyelink().

Parameters:
  • fname (str) – filename, HTTP(S) URL or SSH URL (ssh://server/folder/file.pd)

  • cache_file (Optional[str]) – local filename to store a downloaded file in

  • cache_dir (Optional[str]) – directory to store a downloaded file in (as cache_dir/<file>, see cache_layout), used if cache_file is None. If both are None, a temporary directory is used.

  • force_download (bool) – if True, download again even if a local copy exists in cache_file/cache_dir

  • session (Optional[Session]) – requests session for HTTP(S) downloads (e.g., from osf_authenticate() for private projects)

  • autodelete (Optional[bool]) – if True, the downloaded copy is deleted after it was read (also in cache_file/cache_dir). If None (default), only a temporary download (no cache_file/cache_dir) is deleted. Local files are never deleted.

  • cache_layout (str) – "flat" (default) or "mirror" (cache_dir/<host>/<path>), see fetch_url()

Returns:

loaded object

Return type:

object

pypillometry.io.write_pickle(obj, fname, compress=True)[source]

Store any Python object in a file using pickle.

By default, the file is compressed with zstd (about 13 times smaller for eyedata objects). read_pickle() detects the compression automatically.

Parameters:
  • obj (object) – object to save

  • fname (str) – filename to save to

  • compress (bool) – if True (default), compress the file with zstd; if False, write a plain pickle file

study.py

Load a complete study (all subjects) from a local directory, an SSH URL or an OSF project, based on a user-provided configuration file (pypillometry_conf.py) that defines a dict raw_data and a function read_subject(info).

pypillometry.study.load_study_local(path, config_file='pypillometry_conf.py', subjects=None, cache_dir=None, autodelete=None, force_download=False)[source]

Read a study from a local directory (or an SSH URL) using the configuration file.

The configuration file must define a dict raw_data (subject ID -> dict of data files, relative to path) and a function read_subject(info) that returns the data for one subject; it is called with the working directory changed to path.

If path is an SSH URL (ssh://server/folder/, see fetch_url()), the configuration file and the data files of the selected subjects are downloaded to cache_dir before they are read.

Parameters:
  • path (str) – Local path or SSH URL where the study data is stored

  • config_file (str) – Name of the configuration file. Default is “pypillometry_conf.py”. It is first looked up in the current working directory and, if not found there, in path.

  • subjects (list[str]) – List of subject IDs to load. If None, all subjects will be loaded. If a subject ID is provided that doesn’t exist in the data, it will be skipped.

  • cache_dir (Optional[str]) – only for SSH URLs: directory to download the files to; if None, a temporary directory is used

  • autodelete (Optional[bool]) – only for SSH URLs: if True, the data files of each subject are deleted after they were read (and a temporary directory is removed at the end). If None (default), this is done only when a temporary directory is used (no cache_dir).

  • force_download (bool) – only for SSH URLs: download files again even if they are already in cache_dir

Returns:

  • study_data (dict) – Dictionary containing the loaded study data (subject ID -> result of read_subject)

  • config (module) – Module containing the configuration (pypillometry_conf.py imported as a module)

pypillometry.study.load_study_osf(osf_id, path, subjects=None, force_download=False, config_file='pypillometry_conf.py', session=None)[source]

Read a study from OSF using the configuration file. Example: https://osf.io/p2u74/

The configuration file (downloaded from the project) must define a dict raw_data (subject ID -> dict of data files; entries that are not files in the project are passed on unchanged) and a function read_subject(info) that returns the data for one subject. The listing of the project’s files is cached in path/info_osf.pkl and data files are only downloaded if they are not yet in path.

Parameters:
  • osf_id (str) – The OSF project ID

  • path (str) – Local path where files should be downloaded/stored

  • subjects (list[str]) – List of subject IDs to load. If None, all subjects will be loaded. If a subject ID is provided that doesn’t exist in the data, it will be skipped.

  • force_download (bool) – If True, force re-download (including the file listing) even if files exist locally. Default False.

  • config_file (str) – Name of the configuration file. Default is “pypillometry_conf.py”

  • session (Optional[Session]) – Authenticated session for accessing private projects. If None, uses unauthenticated requests.

Returns:

  • study_data (dict) – Dictionary containing the loaded study data (subject ID -> result of read_subject)

  • config (module) – Module containing the configuration (pypillometry_conf.py imported as a module)

example_data.py

Example datasets for pypillometry.

This module provides example datasets for pypillometry. The datasets are used in the documentation and for testing purposes. Most of them are stored on OSF: https://osf.io/p2u74/

pypillometry.example_data.get_available_datasets()[source]

Return available example datasets.

Returns a dictionary that displays as a nicely formatted pandas DataFrame in Jupyter notebooks but can be used as a normal dict otherwise.

Returns:

A dictionary (subclass of dict) containing all dataset metadata. When displayed in Jupyter, it will render as a pandas DataFrame with columns for Dataset name, Type, and Description.

Return type:

DatasetDict

Examples

>>> datasets = get_available_datasets()
>>> # In Jupyter, this displays as a nice DataFrame
>>> datasets
>>> # But you can still use it like a normal dict
>>> datasets['rlmw_002']
pypillometry.example_data.get_example_data(key, cache_dir=None)[source]

Load example data for a given example (see example_datasets).

Parameters:
  • key (str) – Key of the example dataset. Available keys are in the dictionary example_datasets.

  • cache_dir (str, optional) – directory to store downloaded files in (reused in later calls); default $XDG_CACHE_HOME/pypillometry (~/.cache/pypillometry). Ignored for datasets included in the package.

Returns:

The example dataset as an EyeData, GazeData or PupilData object.

Return type:

EyeData, GazeData or PupilData

Indices and tables