Skip to content

feat: add compute_inactivity_bouts() for detecting inactivity periods#884

Open
mujju-212 wants to merge 3 commits into
neuroinformatics-unit:mainfrom
mujju-212:feat/760-compute-inactivity-bouts
Open

feat: add compute_inactivity_bouts() for detecting inactivity periods#884
mujju-212 wants to merge 3 commits into
neuroinformatics-unit:mainfrom
mujju-212:feat/760-compute-inactivity-bouts

Conversation

@mujju-212

Copy link
Copy Markdown

Description

Closes #760.

Adds compute_inactivity_bouts() to movement.kinematics, implementing the inactivity detection feature requested in #760 (reframed by @niksirbi from "freezing detection" to the more general "inactivity" concept).


API

from movement.kinematics import compute_inactivity_bouts

bout_ids = compute_inactivity_bouts(
    data=ds.position,
    speed_threshold=0.5,       # px/s — frames below this are "inactive"
    min_bout_duration=0.25,    # seconds — bouts shorter than this are dropped
)

Returns an xr.DataArray named "inactivity_bout_id" with the same time (and any extra individual/keypoint) dimensions as the input but without the space dimension. Each frame is labelled with an integer: 0 = active, 1, 2, 3, … = consecutive inactive bouts ordered by onset time.


Implementation details

  • Built on compute_speed() — uses the existing kinematics pipeline (velocity → norm) rather than reimplementing differentiation.
  • speed_threshold: frames where speed < speed_threshold are candidates for inactivity.
  • min_bout_duration: contiguous inactive stretches shorter than this (in the units of the time coordinate) are discarded. Defaults to 0.0 (keep all bouts including single-frame ones).
  • NaN contract: frames with any NaN in the space dimension are always treated as active, never as inactive. This prevents the central-differences artefact where numpy.gradient can compute speed ≈ 0 at a NaN frame by using its non-NaN neighbours.
  • Per-track labelling: xr.apply_ufunc with vectorize=True labels each individual/keypoint track independently, so bout IDs restart from 1 for each track.

Changes

File Change
movement/kinematics/kinematics.py New function compute_inactivity_bouts() + import numpy as np
movement/kinematics/__init__.py Export compute_inactivity_bouts
tests/test_unit/test_kinematics/test_inactivity_bouts.py 20 unit tests across 5 classes

Tests (20 new)

Class Count What it covers
TestComputeInactivityBoutsBasic 7 stationary→1 bout; moving→0; still-move-still→2; ordered IDs; space dim removed; int dtype; threshold=0
TestMinBoutDuration 5 suppress short bouts; keep long; single-frame at 0.0; drop at 0.5; non-default time unit
TestNaNHandling 2 NaN frame → active; all-NaN → no bouts
TestMultipleIndividuals 2 independent per-track labelling; mixed still/moving
TestInvalidInputs 4 negative threshold; negative min_dur; missing time dim; missing space dim

Type of change

  • New feature (non-breaking addition)

Checklist

  • I have read the contributing guidelines
  • Follows existing patterns (compute_speed, validate_dims_coords, logger.error)
  • Full docstring with Parameters, Returns, Raises, Notes, Examples, See Also
  • 20 unit tests; all pass locally

- Add strict=False to zip() call in _label_bouts (B905)
- Wrap long lines in test_inactivity_bouts.py to stay within 79 chars (E501)
- Shorten two docstrings that exceeded the line limit
@mujju-212 mujju-212 marked this pull request as ready for review March 20, 2026 16:44
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Freezing Detection

1 participant