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. If timesteps is None, then minimum_duration is 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:

Events

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 Gaze object.

>>> 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      │
└──────────────┴───────┴────────┴──────────┘