microsaccades#

pymovements.events.detection.microsaccades(velocities: list[list[float]] | list[tuple[float, float]] | ndarray | Series, timesteps: list[int] | ndarray | Series | None = None, minimum_duration: int = 6, threshold: ndarray | tuple[float, float] | str = 'engbert2015', threshold_factor: float = 6, minimum_threshold: float = 1e-10, include_nan: bool = False, name: str = 'saccade') Events[source]#

Detect micro-saccades from velocity gaze sequence.

This algorithm has a noise-adaptive velocity threshold parameter, which can also be set explicitly.

The implementation and its default parameter values are based on the description from Engbert & Kliegl [Engbert and Kliegl, 2003] and is adopted from the Microsaccade Toolbox 0.9 originally implemented in R [Engbert et al., 2015].

Parameters:
  • velocities (list[list[float]] | list[tuple[float, float]] | numpy.ndarray | polars.Series) – shape (N, 2) x and y velocities of N samples in chronological order

  • 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 saccade 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: 6)

  • threshold (numpy.ndarray | tuple[float, float] | str) – If tuple of floats then use this as explicit elliptic threshold. If str, then use a data-driven velocity threshold method. See compute_threshold() for a reference of valid methods. (default: ‘engbert2015’)

  • threshold_factor (float) – factor for relative velocity threshold computation. (default: 6)

  • minimum_threshold (float) – minimal threshold value. Raises ValueError if calculated threshold is too low. (default: 1e-10)

  • 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: ‘saccade’)

Returns:

A dataframe with detected saccades as rows.

Return type:

Events

Raises:
  • TypeError – If velocities is a polars Series and dtype not List

  • ValueError – If threshold value is below min_threshold value. If passed threshold is either not two-dimensional or not a supported method.

Examples

Create a synthetic velocity signal representing micro-saccades.

>>> 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=[(0.5, 0.5), (11.0, 12.0), (0.2, 0.2), (10.0, 20.0), (0.1, 0.1)],
...     start_value=(0., 0.),
...     noise=0.001,
... )
>>> velocities.shape
(200, 2)

Apply event detection algorithm on numpy array:

>>> microsaccades(velocities)
shape: (1, 4)
┌─────────┬───────┬────────┬──────────┐
│ name    ┆ onset ┆ offset ┆ duration │
│ ---     ┆ ---   ┆ ---    ┆ ---      │
│ str     ┆ i64   ┆ i64    ┆ i64      │
╞═════════╪═══════╪════════╪══════════╡
│ saccade ┆ 2     ┆ 199    ┆ 197      │
└─────────┴───────┴────────┴──────────┘

Run saccade detection with custom parameters:

>>> microsaccades(velocities, minimum_duration=10, threshold=0.1)
shape: (1, 4)
┌─────────┬───────┬────────┬──────────┐
│ name    ┆ onset ┆ offset ┆ duration │
│ ---     ┆ ---   ┆ ---    ┆ ---      │
│ str     ┆ i64   ┆ i64    ┆ i64      │
╞═════════╪═══════╪════════╪══════════╡
│ saccade ┆ 111   ┆ 149    ┆ 38       │
└─────────┴───────┴────────┴──────────┘

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.000628, 0.000055]
    [-0.000818, -0.000694]
    [0.50097, 0.498064]
    [0.500475, 0.500865]
    [0.498559, 0.499092]

    [0.100042, 0.100217]
    [0.099627, 0.099812]
    [0.101374, 0.098537]
    [0.099669, 0.100079]
    [0.098491, 0.101448]
]

Apply event detection algorithm on polars series:

>>> microsaccades(velocity_series)
shape: (1, 4)
┌─────────┬───────┬────────┬──────────┐
│ name    ┆ onset ┆ offset ┆ duration │
│ ---     ┆ ---   ┆ ---    ┆ ---      │
│ str     ┆ i64   ┆ i64    ┆ i64      │
╞═════════╪═══════╪════════╪══════════╡
│ saccade ┆ 2     ┆ 199    ┆ 197      │
└─────────┴───────┴────────┴──────────┘

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.000628, 0.000055]  │
│ 1    ┆ [-0.000818, -0.000694] │
│ 2    ┆ [0.50097, 0.498064]    │
│ 3    ┆ [0.500475, 0.500865]   │
│ 4    ┆ [0.498559, 0.499092]   │
│ …    ┆ …                      │
│ 195  ┆ [0.100042, 0.100217]   │
│ 196  ┆ [0.099627, 0.099812]   │
│ 197  ┆ [0.101374, 0.098537]   │
│ 198  ┆ [0.099669, 0.100079]   │
│ 199  ┆ [0.098491, 0.101448]   │
└──────┴────────────────────────┘

Run saccade detection by using the detect() method.

>>> gaze.detect('microsaccades')
>>> gaze.events
shape: (1, 4)
┌─────────┬───────┬────────┬──────────┐
│ name    ┆ onset ┆ offset ┆ duration │
│ ---     ┆ ---   ┆ ---    ┆ ---      │
│ str     ┆ i64   ┆ i64    ┆ i64      │
╞═════════╪═══════╪════════╪══════════╡
│ saccade ┆ 2     ┆ 199    ┆ 197      │
└─────────┴───────┴────────┴──────────┘

Passing parameters to detect():

>>> gaze.detect('microsaccades', minimum_duration=10, threshold=0.1, name='microsaccade')
>>> gaze.events.filter_by_name('microsaccade')
shape: (1, 4)
┌──────────────┬───────┬────────┬──────────┐
│ name         ┆ onset ┆ offset ┆ duration │
│ ---          ┆ ---   ┆ ---    ┆ ---      │
│ str          ┆ i64   ┆ i64    ┆ i64      │
╞══════════════╪═══════╪════════╪══════════╡
│ microsaccade ┆ 111   ┆ 149    ┆ 38       │
└──────────────┴───────┴────────┴──────────┘