idt#
- pymovements.events.detection.idt(positions: list[list[float]] | list[tuple[float, float]] | ndarray | Series, timesteps: list[int] | ndarray | Series | None = None, minimum_duration: int = 100, dispersion_threshold: float = 1.0, include_nan: bool = False, name: str = 'fixation') Events[source]#
Fixation identification based on a dispersion threshold (I-DT).
The algorithm identifies fixations by grouping consecutive points within a maximum separation (dispersion) threshold and a minimum duration threshold. The algorithm uses a moving window to check the dispersion of the points in the window. If the dispersion is below the threshold, the window represents a fixation, and the window is expanded until the dispersion is above threshold.
The implementation and its default parameter values are based on the description and pseudocode from Salvucci and Goldberg [Salvucci and Goldberg, 2000].
- Parameters:
positions (list[list[float]] | list[tuple[float, float]] | numpy.ndarray | polars.Series) – shape (N, 2) Continuous 2D position time series
timesteps (list[int] | numpy.ndarray | polars.Series | None) – shape (N, ) Corresponding continuous 1D timestep time series. If None, sample based timesteps are assumed. (default: None)
minimum_duration (int) – Minimum fixation duration. The duration is specified in the units used in
timesteps. Iftimestepsis None, thenminimum_durationis specified in numbers of samples. (default: 100)dispersion_threshold (float) – Threshold for dispersion for a group of consecutive samples to be identified as fixation. (default: 1.0)
include_nan (bool) – Indicator, whether we want to split events on missing/corrupt value (numpy.nan). (default: False)
name (str) – Name for detected events in Events. (default: ‘fixation’)
- Returns:
A dataframe with detected fixations as rows.
- Return type:
- Raises:
TypeError – If pixels is a polars Series and dtype not List If minimum_duration is not of type
intor timestepsValueError – If positions is not shaped (N, 2) If dispersion_threshold is not greater than 0 If duration_threshold is not greater than 0
Examples
Create a synthetic step signal representing gaze segments.
>>> import numpy as np >>> from pymovements.synthetic import step_function >>> from pymovements.gaze import from_numpy >>> positions = step_function( ... length=200, steps=[2, 5, 9, 111, 150], ... values=[(1., 2.), (2., 3.), (3., 4.), (1., 1.), (2., 2.)], ... start_value=(0., 0.), ... ) >>> positions.shape (200, 2)
Apply event detection algorithm on numpy array:
>>> idt(positions) shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 111 ┆ 102 │ └──────────┴───────┴────────┴──────────┘
Run fixation detection with custom parameters:
>>> idt(positions, minimum_duration = 50, dispersion_threshold = 0.5) shape: (2, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 111 ┆ 102 │ │ fixation ┆ 150 ┆ 199 ┆ 49 │ └──────────┴───────┴────────┴──────────┘
Polars series are also supported as input. Let’s create a nested position series from our numpy array:
>>> df = polars.from_numpy(positions, schema=['x', 'y']) >>> position_series = df.select(polars.concat_list(('x', 'y')).alias('position'))['position'] >>> position_series shape: (200,) Series: 'position' [list[f64]] [ [0.0, 0.0] [0.0, 0.0] [1.0, 2.0] [1.0, 2.0] [1.0, 2.0] … [2.0, 2.0] [2.0, 2.0] [2.0, 2.0] [2.0, 2.0] [2.0, 2.0] ]
Apply event detection algorithm on polars series:
>>> idt(position_series) shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 111 ┆ 102 │ └──────────┴───────┴────────┴──────────┘
We can also apply the detection on a
Gazeobject.>>> from pymovements import Experiment >>> gaze = from_numpy( ... position=positions.T, ... time=np.arange(len(positions)), ... ) >>> gaze shape: (200, 2) ┌──────┬────────────┐ │ time ┆ position │ │ --- ┆ --- │ │ i64 ┆ list[f64] │ ╞══════╪════════════╡ │ 0 ┆ [0.0, 0.0] │ │ 1 ┆ [0.0, 0.0] │ │ 2 ┆ [1.0, 2.0] │ │ 3 ┆ [1.0, 2.0] │ │ 4 ┆ [1.0, 2.0] │ │ … ┆ … │ │ 195 ┆ [2.0, 2.0] │ │ 196 ┆ [2.0, 2.0] │ │ 197 ┆ [2.0, 2.0] │ │ 198 ┆ [2.0, 2.0] │ │ 199 ┆ [2.0, 2.0] │ └──────┴────────────┘
Run fixation detection by using the
detect()method.>>> gaze.detect('idt') >>> gaze.events shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 111 ┆ 102 │ └──────────┴───────┴────────┴──────────┘
Passing parameters to
detect():>>> gaze.detect('idt', minimum_duration = 50, dispersion_threshold = 0.5, name='fixation_idt') >>> gaze.events.filter_by_name('fixation_idt') shape: (2, 4) ┌──────────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════════╪═══════╪════════╪══════════╡ │ fixation_idt ┆ 9 ┆ 111 ┆ 102 │ │ fixation_idt ┆ 150 ┆ 199 ┆ 49 │ └──────────────┴───────┴────────┴──────────┘