ivt#
- pymovements.events.detection.ivt(velocities: list[list[float]] | list[tuple[float, float]] | ndarray | Series, timesteps: list[int] | ndarray | Series | None = None, minimum_duration: int = 100, velocity_threshold: float = 20.0, include_nan: bool = False, name: str = 'fixation') Events[source]#
Identification of fixations based on a velocity-threshold (I-VT).
The algorithm classifies each point as a fixation if the velocity is below the given velocity threshold. Consecutive fixation points are merged into one fixation.
The implementation and its default parameter values are based on the description and pseudocode from Salvucci and Goldberg [Salvucci and Goldberg, 2000].
- Parameters:
velocities (list[list[float]] | list[tuple[float, float]] | numpy.ndarray | polars.Series) – shape (N, 2) Corresponding continuous 2D velocity 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)velocity_threshold (float) – Threshold for a point to be classified as a fixation. If the velocity is below the threshold, the point is classified as a fixation. (default: 20.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 velocities is a polars Series and dtype not List
ValueError – If velocities is None If velocities does not have shape (N, 2) If velocity threshold is None. If velocity threshold is not greater than 0.
Examples
Create a synthetic velocity signal representing low-velocity fixations.
>>> import numpy as np >>> from pymovements.synthetic import step_function >>> from pymovements.gaze import from_numpy >>> velocities = step_function( ... length=200, steps=[2, 5, 9, 111, 150], ... values=[(10., 20.), (20., 30.), (0., 0.), (20., 20.), (0., 0.)], ... start_value=(0., 0.), ... ) >>> velocities.shape (200, 2)
Apply event detection algorithm on numpy array:
>>> ivt(velocities) shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 110 ┆ 101 │ └──────────┴───────┴────────┴──────────┘
Run fixation detection with custom parameters:
>>> ivt(velocities, minimum_duration = 50, velocity_threshold=30) shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 199 ┆ 190 │ └──────────┴───────┴────────┴──────────┘
Polars series are also supported as input. Let’s create a nested position series from our numpy array:
>>> df = polars.from_numpy(velocities, schema=['x', 'y']) >>> velocity_series = df.select(polars.concat_list(('x', 'y')).alias('velocity'))['velocity'] >>> velocity_series shape: (200,) Series: 'velocity' [list[f64]] [ [0.0, 0.0] [0.0, 0.0] [10.0, 20.0] [10.0, 20.0] [10.0, 20.0] … [0.0, 0.0] [0.0, 0.0] [0.0, 0.0] [0.0, 0.0] [0.0, 0.0] ]
Apply event detection algorithm on polars series:
>>> ivt(velocity_series) shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 110 ┆ 101 │ └──────────┴───────┴────────┴──────────┘
We can also apply the detection on a
Gazeobject.>>> from pymovements import Experiment >>> gaze = from_numpy( ... velocity=velocities.T, ... time=np.arange(len(velocities)), ... ) >>> gaze shape: (200, 2) ┌──────┬──────────────┐ │ time ┆ velocity │ │ --- ┆ --- │ │ i64 ┆ list[f64] │ ╞══════╪══════════════╡ │ 0 ┆ [0.0, 0.0] │ │ 1 ┆ [0.0, 0.0] │ │ 2 ┆ [10.0, 20.0] │ │ 3 ┆ [10.0, 20.0] │ │ 4 ┆ [10.0, 20.0] │ │ … ┆ … │ │ 195 ┆ [0.0, 0.0] │ │ 196 ┆ [0.0, 0.0] │ │ 197 ┆ [0.0, 0.0] │ │ 198 ┆ [0.0, 0.0] │ │ 199 ┆ [0.0, 0.0] │ └──────┴──────────────┘
Run fixation detection by using the
detect()method.>>> gaze.detect('ivt') >>> gaze.events shape: (1, 4) ┌──────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════╪═══════╪════════╪══════════╡ │ fixation ┆ 9 ┆ 110 ┆ 101 │ └──────────┴───────┴────────┴──────────┘
Passing parameters to
detect():>>> gaze.detect('ivt', minimum_duration = 50, velocity_threshold=30, name='fixation_ivt') >>> gaze.events.filter_by_name('fixation_ivt') shape: (1, 4) ┌──────────────┬───────┬────────┬──────────┐ │ name ┆ onset ┆ offset ┆ duration │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════════════╪═══════╪════════╪══════════╡ │ fixation_ivt ┆ 9 ┆ 199 ┆ 190 │ └──────────────┴───────┴────────┴──────────┘