Changepoints#
Changepoint detection finds candidate behavioural boundaries in kinematic, audio, and spectral time series. Ethograph exposes detectors (binary masks aligned to the signal’s time axis), merging and snapping utilities, and feature generators that turn changepoints into model-ready inputs.
See Changepoints for the conceptual overview and the changepoint features section below for the representations used by downstream segmentation models.
Detection#
Detectors consume a 1-D signal and return a binary (0/1) mask of the same length. NaN boundaries in the input are always added as changepoints so that valid/invalid transitions are not lost.
- ethograph.features.changepoints.find_peaks_binary(x, **kwargs)[source]#
scipy.signal.find_peaks + NaN boundaries -> binary mask.
- ethograph.features.changepoints.find_troughs_binary(x, **kwargs)[source]#
Find troughs (local minima) + NaN boundaries -> binary mask.
- ethograph.features.changepoints.find_nearest_turning_points_binary(x, threshold=1, max_value=None, prominence=0.5, distance=2, **kwargs)[source]#
Convert a 1D signal into a binary mask marking boundaries of peak regions.
Identifies peaks in the signal, then finds the nearest “turning points” (where the gradient is near zero) on either side of each peak. These turning points define the boundaries of peak regions. The result is a binary mask where 1 indicates a turning-point boundary.
- The algorithm works in four steps:
Compute the gradient of x and find indices where |gradient| < threshold, treating these as candidate turning points (near-stationary regions).
Find peaks in x using scipy.signal.find_peaks with any additional kwargs.
For each peak, select the closest turning point to its left and right.
Add boundaries at NaN transitions in the original signal.
- Parameters:
x – Input 1D signal.
threshold – Maximum absolute gradient value to qualify as a turning point. Lower values select only very flat regions. Default is 1.
max_value – If set, discard turning points where x exceeds this value. Useful for ignoring turning points on high plateaus.
**kwargs – Passed to scipy.signal.find_peaks (e.g. height, distance, prominence).
- Returns:
Binary array of same length as x, with 1 at turning-point boundaries and NaN-transition boundaries, 0 elsewhere.
- ethograph.features.changepoints.add_NaN_boundaries(arr, changepoints)[source]#
Merge NaN-transition boundaries with other changepoints -> binary mask.
To attach detectors to a xarray.Dataset or a pynapple object:
- ethograph.io.dataset.add_changepoints_to_ds(ds, target_feature, changepoint_name, changepoint_func, **func_kwargs)[source]#
Detect changepoints in a feature and store them in the dataset.
Applies changepoint_func independently along every non-time dimension (e.g. per keypoint, per individual) using
xarray.apply_ufunc()withvectorize=True. The result is anint8binary array (1 = changepoint, 0 = not) stored asds["{target_feature}_{changepoint_name}"].- Parameters:
ds (xarray.Dataset) – Trial dataset containing target_feature.
target_feature (str) – Name of the variable to run detection on (e.g.
"speed").changepoint_name (str) – Suffix for the output variable name. The stored variable will be called
"{target_feature}_{changepoint_name}"(e.g."speed_troughs").changepoint_func (callable) – A function
f(x, **kwargs) -> array[int8]that takes a 1-D numpy array and returns a same-length binary indicator.**func_kwargs – Forwarded to changepoint_func.
- Returns:
The input dataset with the changepoint variable added in place.
- Return type:
Examples
>>> import ethograph as eto >>> from ethograph.features.changepoints import find_troughs_binary >>> dt = eto.open("experiment.nc") >>> ds = dt.itrial(0) >>> ds = eto.add_changepoints_to_ds( ... ds, ... target_feature="speed", ... changepoint_name="troughs", ... changepoint_func=find_troughs_binary, ... prominence=0.3, ... ) >>> ds["speed_troughs"] <xarray.DataArray 'speed_troughs' (time: 9000, keypoints: 7)>
Merging and time extraction#
Changepoints stored as multiple attrs["type"] == "changepoints"
DataArrays can be merged into a single boolean mask, or converted directly
to absolute times (seconds) for use by the GUI and correction pipeline.
- ethograph.features.changepoints.merge_changepoints(ds)[source]#
Merge all changepoint variables in a dataset into a single boolean mask.
Combines every variable with
attrs["type"] == "changepoints"using logical OR across all non-time dimensions. All changepoint variables must share the sametarget_featureattribute.- Parameters:
ds (xr.Dataset) – Dataset containing one or more changepoint variables.
- Returns:
ds (xr.Dataset) – Copy of the input with a new
"changepoints"DataArray (float 0/1) replacing the individual changepoint variables.target_feature (str) – The shared
target_featureattribute from the input variables.
- Raises:
ValueError – If changepoint variables reference different target features.
- ethograph.features.changepoints.extract_cp_times(ds, time, **cp_kwargs)[source]#
Extract merged changepoint times from dataset.
Replaces the inline pattern: merge_changepoints -> binary -> np.where -> times. Returns empty array if no CP variables exist.
- Return type:
- ethograph.features.changepoints.snap_to_nearest_changepoint_time(t_clicked, ds, feature_sel, time, **ds_kwargs)[source]#
Snap a clicked time (seconds) to the nearest changepoint time.
Works entirely in the time domain — no index conversion needed. Combines kinematic, audio, and oscillation changepoints.
- Return type:
Label correction#
Snap interval-based labels to nearby changepoints. The full pipeline
(correct_changepoints) runs purge → stitch → snap → purge; see
Changepoint correction for parameter guidance.
- ethograph.features.changepoints.correct_changepoints(df, cp_times, min_duration_s, stitch_gap_s, max_expansion_s, max_shrink_s, label_thresholds_s=None, do_purge=True, do_stitch=True, do_snap=True, do_purge_after=True)[source]#
Full interval-native correction pipeline.
- Return type:
- Steps:
purge_short_intervals — pre-cleanup (do_purge)
stitch_intervals — merge same-label across small gaps (do_stitch)
snap_boundaries — snap to changepoint times (do_snap)
purge_short_intervals — post-cleanup (do_purge_after)
- ethograph.features.changepoints.correct_changepoints_automatic(df, min_duration_s=0.001, stitch_gap_s=0.0)[source]#
Lightweight cleanup used while manually creating labels.
- Return type:
- ethograph.features.changepoints.correct_changepoints_dense(labels, ds, all_params)[source]#
Correct dense label arrays using changepoints (legacy ML pipeline).
Operates on integer label arrays, not interval DataFrames. Use
correct_changepoints()for the modern interval-native pipeline.- Parameters:
labels (array-like) – Dense integer label array of shape (T,).
ds (xr.Dataset) – Trial dataset containing changepoint variables.
all_params (dict) –
Keys:
cp_kwargs: Selection kwargs forwarded tods.sel().min_label_length_s: Minimum label duration in seconds.stitch_gap_len_s: Maximum gap to stitch in seconds.label_thresholds_s: Per-label minimum durations (dict).changepoint_params: Dict withmax_expansion_sandmax_shrink_s.fps: Frame rate used to convert seconds to sample counts.
- Returns:
Corrected integer label array of the same shape as
labels.- Return type:
np.ndarray
Changepoint features#
Once changepoints have been curated, they are converted into learnable features for action- and audio-segmentation models (transformers, MS-TCN, DLC2Action, ASFormer, …). Three complementary representations let the model pick up changepoint structure at different scales:
Feature |
Example (changepoints at t=4 and t=8) |
|---|---|
Binary changepoints — exact positions |
|
Smooth changepoints — proximity to nearest changepoint |
|
Segment IDs — unique ID per inter-changepoint region |
|
Smooth changepoints use a Laplacian kernel centred at each changepoint index \(i\):
Laplacian peaks are narrow (so they pinpoint the changepoint) but have long
tails (so they remain visible from far away). Passing several
sigmas — e.g. [0.5, 3, 5] — yields a multi-scale view.
Weighted variants emphasise changepoints where a target signal x
(typically speed) is low, via \(\exp(-x / (\bar{x} + \epsilon))\). This
helps models distinguish speed minima before/after movements from minima
occurring within a movement.
- ethograph.features.changepoints.more_changepoint_features(changepoint_binary, targ_feat_vals, sigmas, distribution='laplacian')[source]#
Create changepoint-based features from a binary changepoint array.
Generates three complementary representations that help a model learn changepoint locations:
Binary changepoints — exact changepoint positions (0/1 mask). Example:
0 0 0 0 1 0 0 0 1 0 0 0 0 0Smooth changepoints — proximity to the nearest changepoint, rendered as Laplacian (or Gaussian) peaks centred at each changepoint index. Example:
0 0 0 .3 1 .3 0 .3 1 .3 0 0 0 0Segment IDs — unique identifier for each contiguous region between changepoints (normalised to [0, 1]). Example:
0 0 0 0 1 1 1 1 2 2 2 2 2 2
Smooth changepoints use a Laplacian kernel by default:
\[\text{smooth}(t) = \sum_i \exp\!\left(-\frac{|t - i|}{\sigma}\right)\]where \(i\) are the changepoint indices and \(\sigma\) controls the peak width. Laplacians have a narrow peak that points directly at the changepoint while their long tails remain visible from far away. Passing multiple
sigmas(e.g.[0.5, 3, 5]) yields features at several scales.Additionally, weighted versions emphasise changepoints where the target feature (e.g. speed) is low, by multiplying the smooth features with \(\exp(-x / (\bar{x} + \epsilon))\). This helps the model distinguish speed minima before/after movements (low speed) from minima occurring within different movements.
- Parameters:
changepoint_binary (
ndarray) – Binary (0/1) array marking changepoint locations.targ_feat_vals (
ndarray) – Target feature values (e.g. speed) used to weight the smooth changepoint features.sigmas (
List[float]) – Kernel widths for the smooth changepoint representation.distribution (
Literal['gaussian','laplacian']) –"laplacian"(default) or"gaussian"kernel.
- Return type:
- Returns:
2D array of shape
(T, 1 + 2 * len(sigmas) + 1)stacking the binary mask, one smooth feature per sigma, their weighted (speed- emphasised, z-normalised) counterparts, and the normalised segment IDs.
Storage format#
Kinematic changepoints are stored as binary (int8) DataArrays sharing
their feature’s time axis, tagged with attrs["type"] = "changepoints"
and attrs["target_feature"]. Audio changepoints, which would be
prohibitively large at audio sample rates, are stored instead as
audio_cp_onsets / audio_cp_offsets float pairs. See
Kinematic changepoints and
Audio changepoints for examples.