Detection Framework
This page explains the design of cogpy’s event detection system: why it uses an abstract detector interface, how pipelines compose transforms and detectors, and what trade-offs shaped the architecture.
Design goals
ECoG analysis involves detecting many kinds of transient events — ripples, spindles, gamma bursts, epileptiform discharges. Each has different signal characteristics, but the workflow is always the same:
Transform the signal (filter, envelope, spectrogram)
Detect events in the transformed signal
Collect results into a uniform structure
The detection framework codifies this pattern so that detectors are interchangeable and pipelines are reproducible.
The EventDetector interface
All detectors inherit from EventDetector (in cogpy.detect.base):
class EventDetector(ABC):
@abstractmethod
def detect(self, data: xr.DataArray, **kwargs) -> EventCatalog: ...
@abstractmethod
def get_event_dims(self) -> list[str]: ...
detect() accepts any xr.DataArray and returns an EventCatalog.
The detector declares which dimensions it operates over via
get_event_dims() — for example, ["time"] for point events or
["time", "freq", "AP", "ML"] for spatiotemporal bursts.
Additional hooks:
Method |
Purpose |
|---|---|
|
Input validation (default: |
|
Whether the detector requires preprocessing |
|
Serialization for reproducibility |
Why an ABC? Detectors have fundamentally different algorithms (threshold crossing vs. spectrogram peak finding vs. template matching). An ABC enforces a common interface without constraining implementation.
Built-in detectors
Detector |
Strategy |
Event type |
Key parameters |
|---|---|---|---|
|
h-maxima on spectrogram |
Point events |
|
|
Amplitude threshold crossing |
Interval events |
|
|
Bandpass → envelope → dual threshold |
Interval events |
|
|
Same as ripple, different defaults |
Interval events |
|
Point events have a single time t. Interval events have t0, t,
t1 (onset, peak, offset) plus duration.
Transforms
Transforms are composable preprocessing steps that sit between raw signal and detector:
class Transform(ABC):
@abstractmethod
def compute(self, data: xr.DataArray) -> xr.DataArray: ...
Built-in transforms:
BandpassTransform,HighpassTransform,LowpassTransform— frequency filteringHilbertTransform— analytic signal envelopeZScoreTransform— per-channel z-score normalizationSpectrogramTransform— multitaper spectrogram
Transforms are deliberately simple — each wraps a single cogpy compute function.
This keeps them testable independently and avoids coupling detection logic to
specific filtering implementations.
DetectionPipeline
DetectionPipeline chains transforms and a detector into a reproducible unit:
pipeline = DetectionPipeline(
transforms=[BandpassTransform(100, 250), HilbertTransform(), ZScoreTransform()],
detector=ThresholdDetector(threshold=3.0, min_duration=0.02),
name="ripple_pipeline",
)
catalog = pipeline.run(raw_signal)
The pipeline:
Applies each transform in sequence
Passes the result to the detector
Returns an
EventCatalogwith pipeline metadata attached
Serialization: pipeline.to_dict() captures the full configuration
(transform params, detector params) so that detection can be reproduced
from a config file.
Pre-built pipelines
cogpy ships four ready-to-use pipelines:
Pipeline |
Target event |
Frequency band |
Threshold |
|---|---|---|---|
|
Broadband bursts |
Full spectrogram |
h-maxima (90th percentile) |
|
Sharp-wave ripples |
100–250 Hz |
3σ, ≥20 ms |
|
Fast ripples |
250–500 Hz |
3σ, ≥10 ms |
|
Gamma bursts |
30–80 Hz |
2.5σ, ≥50 ms |
These are importable directly:
from cogpy.detect import RIPPLE_PIPELINE
catalog = RIPPLE_PIPELINE.run(signal)
EventCatalog
All detectors return EventCatalog, a thin pandas DataFrame wrapper
(see Data Model for column definitions).
Key design choices:
Minimal required schema: Only
event_idandtare mandatory. Everything else (intervals, spatial coords, labels) is optional. This keeps the catalog flexible across event types.Validation on construction: The catalog checks required columns, interval constraints (
t1 > t0), and sorts by time.Factory methods:
from_hmaxima(),from_blob_candidates(),from_burst_dict(),from_spwr_mat()convert legacy formats.Query interface:
filter_by_time(),filter_by_channel(),filter_by_spatial()for subsetting.
Why separate transforms from detectors?
An alternative design would have each detector handle its own preprocessing
internally (and BurstDetector does support this “implicit” mode). The
explicit transform pipeline is preferred because:
Reuse. The same bandpass + envelope chain serves ripple, spindle, and gamma detection — only parameters differ.
Inspection. Intermediate signals (filtered, enveloped) can be examined for debugging without re-running detection.
Composition. Users can insert custom transforms (e.g., spatial whitening) without modifying detector code.
The trade-off is slightly more verbose setup for simple cases. The pre-built pipelines mitigate this.