Bad Channel Detection

This tutorial walks through cogpy’s canonical bad-channel detection pipeline: extract per-channel features, normalize spatially, and label outliers.

Overview

The pipeline has four stages:

Raw signal → Feature extraction → Spatial normalization → Outlier labeling
              (temporal measures)   (neighborhood stats)    (DBSCAN)

Each stage is a separate module under cogpy.preprocess.badchannel.

Step 1: Extract channel features

from cogpy.preprocess.badchannel.channel_features import extract_channel_features_xr

# sig: xarray.DataArray with dims (time, AP, ML) and fs coordinate
features = extract_channel_features_xr(
    sig,
    window_size=2048,
    window_step=1024,
    feature_names=["variance", "amplitude", "kurtosis", "hurst_exponent"],
)
# Returns xarray.Dataset with dims (time_win, AP, ML) and one variable per feature

Each feature is computed per channel per time window. The underlying functions live in cogpy.measures.temporal:

from cogpy.measures.temporal import (
    relative_variance,
    amplitude,
    kurtosis,
    hurst_exponent,
)

Step 2: Spatial normalization

Raw feature values vary across the grid due to electrode impedance and anatomy. Spatial normalization expresses each channel’s feature value relative to its neighborhood:

from cogpy.preprocess.badchannel.pipeline import (
    compute_features_sliding,
    DEFAULT_FEATURE_SPECS,
)

# DEFAULT_FEATURE_SPECS defines 7 features with normalization strategies:
# - "ratio": channel / neighborhood_median (e.g., variance, amplitude)
# - "difference": channel - neighborhood_median (e.g., hurst_exponent)
# - "robust_z": (channel - median) / MAD (e.g., kurtosis)
# - "identity": no normalization (e.g., anticorrelation)

normalized = compute_features_sliding(
    sig,
    window_size=2048,
    window_step=1024,
    feature_specs=DEFAULT_FEATURE_SPECS,
)

Step 3: Label outliers

DBSCAN clusters channels in feature space. Outlier channels (noise label -1) are flagged as bad:

from cogpy.preprocess.badchannel.badlabel import (
    DbscanParams,
    dbscan_outliers,
)

params = DbscanParams(eps=1.5, min_samples=5)
labels = dbscan_outliers(normalized, params=params)
# labels: array of 0 (good) / 1 (bad) per channel

Full pipeline

For a complete example composing all stages:

from cogpy.preprocess.badchannel.pipeline import compute_features_sliding
from cogpy.preprocess.badchannel.badlabel import dbscan_outliers, DbscanParams
from cogpy.preprocess.badchannel.feature_normalization import (
    normalize_windowed_features,
    summarize_windowed_features,
)

# Extract and normalize
features = compute_features_sliding(sig, window_size=2048, window_step=1024)

# Summarize across time windows (median per channel)
summary = summarize_windowed_features(features)

# Label outliers
bad_mask = dbscan_outliers(summary, params=DbscanParams())

Interpolating bad channels

Once bad channels are identified, interpolate them:

from cogpy.preprocess.interpolate import interpolate_bads

sig_clean = interpolate_bads(sig, bad_mask=bad_mask)

Next steps