concord-metrics-univariate
Time-domain, single-channel features: Line Length and Hjorth Parameters
Package Structure
concord-metrics-univariate/
src/concord_metrics_univariate/
__init__.py exports LineLength, HjorthParameters
line_length.py LineLength metric
hjorth.py HjorthParameters metric
LineLength
Line length is one of the simplest and most effective seizure detection features. It measures how "wiggly" the signal is — how much total variation there is per unit time. High line length indicates fast, high-amplitude activity characteristic of seizures.
Formula: LL = Σ |x[n] − x[n-1]| — the sum of absolute first differences.
| Parameter | Default | Description |
|---|---|---|
| window_s | None | Window length in seconds. If None, computes over the entire recording. |
| step_s | None | Step between windows. If None, defaults to window_s (non-overlapping). Ignored if window_s is None. |
name: "line_length"
units: "V" (volts, since it's a sum of differences of voltage)
Output shape
| Mode | data.shape | time_axis |
|---|---|---|
| window_s = None (global) | (n_channels,) | None |
| window_s set | (n_channels, n_windows) | (n_windows,) — center time of each window |
How it works
# Windowed mode (simplified):
windows = segment(data, fs, window_s, step_s) # (n_ch, n_win, win_samples)
diff = np.diff(windows, axis=-1) # (n_ch, n_win, win_samples-1)
ll = np.sum(np.abs(diff), axis=-1) # (n_ch, n_win)
HjorthParameters
Hjorth parameters are three complementary descriptors proposed by Bo Hjorth (1970) that characterize a signal's temporal complexity. They are computed from the signal and its successive derivatives.
| Parameter | Formula | Physical interpretation |
|---|---|---|
| Activity | var(x) |
Signal power (variance). High in high-amplitude bursts. |
| Mobility | std(x') / std(x) |
Mean frequency of the signal. Proportional to the dominant frequency. |
| Complexity | mobility(x') / mobility(x) |
How closely the signal resembles a pure sine wave. 1.0 = pure sine, higher = more complex/noisy. |
Here, x' is the first derivative (approximated as successive differences), and x'' the second derivative.
| Parameter | Default | Description |
|---|---|---|
| window_s | None | Window length in seconds. If None, computes globally. |
| step_s | None | Step between windows. |
name: "hjorth_parameters"
Degenerate channels: If a channel is constant (std=0), all three parameters are set to NaN rather than raising an error or producing Inf.
Output shape
| Mode | data.shape | Axis 2 contents |
|---|---|---|
| window_s = None | (n_channels, 3) | [activity, mobility, complexity] |
| window_s set | (n_channels, n_windows, 3) | [activity, mobility, complexity] per window |
The frontend accesses individual parameters by index: data[:, :, 0] = activity, data[:, :, 1] = mobility, data[:, :, 2] = complexity.
How it works
def _hjorth_on_array(x):
# x: (..., n_samples) — last axis is time
var_x = np.var(x, axis=-1)
dx = np.diff(x, axis=-1)
var_dx = np.var(dx, axis=-1)
ddx = np.diff(dx, axis=-1)
var_ddx = np.var(ddx, axis=-1)
activity = var_x
mobility = np.sqrt(var_dx / var_x)
complexity = np.sqrt(var_ddx / var_dx) / mobility
return np.stack([activity, mobility, complexity], axis=-1)
Example Usage
from concord_io import read_bids_ieeg
from concord_metrics_univariate import LineLength, HjorthParameters
rec = read_bids_ieeg("sub-HUP117_run-01_ieeg.edf")
# Line length: 1-second windows, no overlap
ll = LineLength(window_s=1.0).compute(rec)
print(ll.data.shape) # (72, 300) — 72 channels, 300 one-second windows
print(ll.time_axis[:3]) # [0.5, 1.5, 2.5] — center times
# Hjorth: 2-second windows, 1-second step (50% overlap)
hjorth = HjorthParameters(window_s=2.0, step_s=1.0).compute(rec)
print(hjorth.data.shape) # (72, 299, 3)
activity = hjorth.data[:, :, 0] # (72, 299)
mobility = hjorth.data[:, :, 1]
complexity = hjorth.data[:, :, 2]