Source code for scitex_seizure_metrics.sensitivity_tiw._inputs
"""Input normalisation for the sensitivity-vs-TiW trade-off.
Two input modes are supported, mirroring the rest of the package:
- Mode A: per-window binary pre-ictal ``labels`` (+ optional ``times``).
- Mode B: explicit ``seizure_times`` + per-window ``times``.
Both collapse to a common ``(scores, times, seizures, total_T, cadence)``
tuple consumed by the curve and significance code.
"""
from __future__ import annotations
import numpy as np
[docs]
def seizures_from_labels(labels, times) -> np.ndarray:
"""Derive one onset time per contiguous pre-ictal (label==1) run.
The onset is placed at the END of the pre-ictal run (the first
timestamp after the run) because pre-ictal windows precede the
seizure. If a run reaches the end of the recording, the onset is
placed one cadence after the last labelled window.
Args:
labels: per-window binary pre-ictal labels.
times: matching per-window timestamps (seconds).
Returns:
np.ndarray of seizure onset times (seconds), sorted ascending.
"""
labels = np.asarray(labels).astype(int).ravel()
times = np.asarray(times, dtype=float).ravel()
if labels.shape != times.shape:
raise ValueError(
f"labels and times shape mismatch: {labels.shape} vs {times.shape}"
)
if labels.size == 0:
return np.array([])
cadence = float(np.median(np.diff(times))) if times.size > 1 else 1.0
onsets = []
prev = 0
for i, lab in enumerate(labels):
if lab == 0 and prev == 1:
# Pre-ictal run ended at i-1; seizure onset at this timestamp.
onsets.append(times[i])
prev = lab
if prev == 1:
# Run reached the end; place onset just past the last window.
onsets.append(times[-1] + cadence)
return np.sort(np.asarray(onsets, dtype=float))
def resolve_inputs(scores, *, labels, seizure_times, times):
"""Normalise the two input modes to a common tuple.
Args:
scores: 1-D per-window scores / probabilities.
labels: per-window binary labels (mode A) or None.
seizure_times: explicit onset timestamps (mode B) or None.
times: per-window timestamps or None (defaults to unit cadence).
Returns:
(scores, times, seizures, total_T, cadence).
Raises:
ValueError: empty scores, shape mismatch, or neither mode given.
"""
scores = np.asarray(scores, dtype=float).ravel()
if scores.size == 0:
raise ValueError("scores must be non-empty")
if times is None:
times = np.arange(scores.size, dtype=float)
else:
times = np.asarray(times, dtype=float).ravel()
if times.shape != scores.shape:
raise ValueError(
f"scores and times shape mismatch: {scores.shape} vs {times.shape}"
)
if seizure_times is not None:
seizures = np.sort(np.asarray(seizure_times, dtype=float).ravel())
elif labels is not None:
seizures = seizures_from_labels(labels, times)
else:
raise ValueError(
"provide either `labels` (per-window binary) or `seizure_times`"
)
cadence = float(np.median(np.diff(times))) if times.size > 1 else 1.0
total_T = float(times.max() - times.min()) + cadence
return scores, times, seizures, total_T, cadence