cogpy.events.cluster

Spatio-temporal clustering of detector outputs.

When event detectors fire per channel (e.g. local-maxima detection on a spatially-correlated cube, or band-power above-threshold runs on each channel of a TF spectrogram), a single biological event manifests as multiple co-localized rows in the events DataFrame — once per participating electrode. This inflates trigger / target counts in downstream PETH-style coupling analyses without scaling the underlying biology.

This module provides a generic helper that groups co-occurring per-channel events into spatial clusters and emits one summary row per cluster.

Algorithm (fixed-window on the time axis):

  1. Sort events by time_col.

  2. Walk the sorted list. Open a new cluster at event 0. Subsequent events join the open cluster while t - cluster_t_first <= tolerance_s. The first event whose time exceeds that bound starts a new cluster.

  3. Within each cluster, count distinct spatial coordinates and apply min_participation_fraction: cluster passes only if it covers n_participating / n_valid_channels >= min_participation_fraction.

  4. For passing clusters, compute summary statistics (median / earliest / latest time, list of participating channels, mean amplitude).

The fixed-window design (rather than single-link) is critical: when events are dense in time (e.g. 21 channels each firing within ~14 ms of one another for a single slow-wave cycle), single-link would chain through multiple successive cycles and produce one mega-cluster. Fixed-window caps each cluster’s duration at tolerance_s regardless of how many events fall in that window, isolating consecutive cycles cleanly.

Pick tolerance_s to span the expected spatial-propagation time of a single biological event: ~200 ms for slow oscillations on a small cortical grid (Massimini 2004 propagation: 1–7 m/s, 30 mm grid → 4–30 ms transit, plus detection-time jitter). For spectral events: 100 ms for spindles, 30 ms for ripples.

Use this rather than a full DBSCAN-style spatial cluster when (a) the detector already enforces spatial coherence at detection time, and (b) you want a deterministic, parameter-light pooling operation.

Functions

cluster_spatiotemporal(events_df, *[, ...])

Cluster per-channel events into spatial events by temporal proximity.