GROND: automated characterization of periodic and rhythmic EEG patterns.
The 40 matches
- [1] § Methods › RDA characterization pipeline (RDA-Profiler) › NB-Hilbert: iterative NB-Hilbert refinement ↔ code/evaluation/verify_v12_holdout.py, lines 1 to 47 · score 0.96 · canonical cohort LRDA, freq_cap, top_k, p2 bw, p1 hi, NB Hilbert · Report a mismatch
- [2] § Results › Frequency estimation ↔ code/evaluation/lrda_laterality_v13_eval.py, lines 1 to 66 · score 0.96 · asymmetric volume conduction, narrowband envelope amplitude, amplitude normalized rhythmicity, spectral peak prominence, slow drift, rhythm frequency · Report a mismatch
- [3] § Methods › PD characterization pipeline (PD-Profiler) › Spatial localization ↔ paper_materials/generate_discharge_topo_viewer.py, lines 136 to 247 · score 0.95 · strongly suppresses phantom, GFP aligned peak, GFP profile, low GFP, Discharge locked topographic, cross correlates · Report a mismatch
- [4] § Methods › Frequency ranges and filter bands ↔ code/pd_profiler.py, lines 1 to 42 · score 0.95 · expectation maximization, template refinement, channel PD probabilities, characterization pipeline, dynamic programming, PD Profiler · Report a mismatch
- [5] § Methods › Dataset and annotations › Annotation framework ↔ paper_materials/build_fig_irr_bars.py, lines 1 to 51 · score 0.92 · interactive narrowband overlay, Open circles, error bars, rater independent expert, frequency Spearman, paired segment · Report a mismatch
- [6] § Methods › PD characterization pipeline (PD-Profiler) › channel CNN (ChannelPD-Net) ↔ code/pd_channel_detector/channel_cnn.py, lines 92 to 165 · score 0.91 · temporal attention pooling, PD detection, attention weighted, log frequency, PD probability, softmax · Report a mismatch
- [7] § Methods › Frequency ranges and filter bands ↔ paper_materials/draw_panel_b_rda.py, lines 58 to 187 · score 0.86 · iterative narrowband refinement, Pipeline architecture, refined lateralization, envelope amplitude, NB Hilbert, dominant channels · Report a mismatch
- [8] § Results › Model architecture comparison ↔ paper_materials/tables/generate_table7.py, lines 40 to 140 · score 0.85 · end neural models, E2E CNN, DP encoded, neural evidence generation, winning strategy, structured inference · Report a mismatch
- [9] § Methods › Dataset and annotations › Annotation framework ↔ code/evaluation/lrda_laterality_v13_eval.py, lines 1 to 66 · score 0.84 · amplitude normalized rhythmicity, spectral peak prominence, peak amplitude, laterality rule, hemisphere channels, independent expert · Report a mismatch
- [10] § Methods › PD characterization pipeline (PD-Profiler) › channel CNN (ChannelPD-Net) ↔ code/unified_model/dataset.py, lines 215 to 316 · score 0.84 · 20–40 dB, Gaussian noise, log frequency, amplitude scaling, circular, SNR · Report a mismatch
- [11] § Methods › Dataset and annotations › Evaluation framework ↔ paper_materials/build_fig_irr_bars.py, lines 1 to 51 · score 0.81 · interactive narrowband overlay, IRR comparisons, rater independent expert, EA, LB, tools · Report a mismatch
- [12] § Methods › RDA characterization pipeline (RDA-Profiler) › NB-Hilbert: iterative NB-Hilbert refinement ↔ code/generators/labeling/generate_rda_freq_labeler.py, lines 148 to 202 · score 0.80 · 0.3–5 Hz, Coarse lateralization, 0.5–3.5 Hz, dominant hemisphere, variance, W05 · Report a mismatch
- [13] § Methods › RDA characterization pipeline (RDA-Profiler) › NB-Hilbert: iterative NB-Hilbert refinement ↔ code/generators/labeling/generate_rda_freq_spatial_labeler.py, lines 148 to 202 · score 0.80 · 0.3–5 Hz, Coarse lateralization, 0.5–3.5 Hz, dominant hemisphere, variance, W05 · Report a mismatch
- [14] § Methods › PD characterization pipeline (PD-Profiler) › channel CNN (ChannelPD-Net) ↔ code/cet_model/lrda_crnn_dataset.py, lines 105 to 167 · score 0.80 · additive Gaussian noise, 20–40 dB, amplitude scaling, SNR, augmentation, shifts · Report a mismatch
- [15] § Methods › Dataset and annotations › Annotation framework ↔ code/evaluation/analyze_independent_expert_v1.py, lines 1 to 65 · score 0.78 · confidence interval, independent_expert_tasks, paper_materials, expert algorithm, v1, bootstrap · Report a mismatch
- [16] § Methods › Frequency ranges and filter bands ↔ paper_materials/draw_panel_b.py, lines 45 to 122 · score 0.77 · channel PD probabilities, UNet, PD Profiler, bipolar channels, alignment, template · Report a mismatch
- [17] § Results › Spatial localization › PD spatial localization ↔ paper_materials/generate_discharge_topo_viewer.py, lines 136 to 247 · score 0.77 · Laplacian GFP alignment, Laplacian transform, Discharge locked topographic, phantom, focal, squared · Report a mismatch
- [18] § Methods › Dataset and annotations › Evaluation framework ↔ code/evaluation/analyze_phlbsz_cohort.py, lines 1 to 37 · score 0.76 · narrowband overlay tools, validation cohort, tools developed, EA, LB, PH · Report a mismatch
- [19] § Methods › RDA characterization pipeline (RDA-Profiler) › RDA-PLV: spatial extent ↔ code/rda_spatial_extent.py, lines 63 to 142 · score 0.76 · PLV amplitude, phase locking, dominant hemisphere, narrowband filtered, envelope, NB · Report a mismatch
- [20] § Methods › Dataset and annotations › Contest-of-methods development strategy ↔ code/evaluation/lrda_freq_hyperparam_sweep.py, lines 1 to 40 · score 0.74 · channel selection, NB Hilbert, RDA frequency, independent expert, variants, contest · Report a mismatch
- [21] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/cet_model/train_cet_v3.py, lines 270 to 395 · score 0.74 · sharpness penalty, fold patient stratified, Training targets, augmentation, loss, Gaussian · Report a mismatch
- [22] § Methods › Dataset and annotations › Evaluation framework ↔ code/data_management/build_grond_h5_bank.py, lines 1 to 83 · score 0.74 · cohort flags, paper_materials, algorithm predictions, grond, bank, EEG segments · Report a mismatch
- [23] § Methods › PD characterization pipeline (PD-Profiler) › channel CNN (ChannelPD-Net) ↔ code/unified_model/model.py, lines 19 to 158 · score 0.74 · temporal attention pooling, softmax, kernel, sigmoid, logit, stride · Report a mismatch
- [24] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/discharge_detector.py, lines 1 to 65 · score 0.72 · approximately periodic prior, active interval, Candidate peaks, skip, DP, trace · Report a mismatch
- [25] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/discharge_detector.py, lines 764 to 845 · score 0.72 · post hoc confidence, template refinement, refined, median, candidates, DP · Report a mismatch
- [26] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/cet_model/cet.py, lines 95 to 235 · score 0.72 · skip connections, discharge evidence, upsamples, activated, resolution, encoder · Report a mismatch
- [27] § Methods › PD characterization pipeline (PD-Profiler) ↔ code/pd_profiler.py, lines 1 to 42 · score 0.72 · expectation maximization, PD Profiler, dynamic programming, periodic discharge, EM, TKEO · Report a mismatch
- [28] § Methods › BIPD detection ↔ code/bipd_detector.py, lines 896 to 964 · score 0.71 · Synthetic BIPD, phase shifting, single LPD, GBT, jitter, candidates · Report a mismatch
- [29] § Methods › Frequency ranges and filter bands ↔ code/generators/labeling/generate_rda_freq_spatial_labeler.py, lines 148 to 202 · score 0.71 · 0.5–3.5 Hz, refined lateralization, Hilbert frequency, dominant hemisphere, coarse, variance · Report a mismatch
- [30] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/discharge_detector.py, lines 160 to 245 · score 0.70 · skip connections, discharge evidence, transposed, resolution, encoder, sigmoid · Report a mismatch
- [31] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/hemi_detector/train_hemi_cet.py, lines 356 to 502 · score 0.69 · sharpness penalty, fold patient stratified, floor, augmentation, loss, Gaussian · Report a mismatch
- [32] § Methods › Statistical analysis ↔ code/evaluation/eval_3way_classification.py, lines 212 to 253 · score 0.68 · random forest, fold patient stratified, fold prediction, RF, classifier, training · Report a mismatch
- [33] § Results › Frequency estimation ↔ code/evaluation/lrda_laterality_features.py, lines 88 to 172 · score 0.68 · spectral peak prominence, envelope amplitude, LRDA laterality, artifact, drift, rhythm · Report a mismatch
- [34] § Methods › Dataset and annotations › EEG data ↔ paper_materials/tables/generate_table1.py, lines 141 to 232 · score 0.68 · expert review, expert votes, reference montage, single rater, channel monopolar, EEG segments · Report a mismatch
- [35] § Methods › Dataset and annotations › Contest-of-methods development strategy ↔ paper_materials/draw_panel_b_rda.py, lines 58 to 187 · score 0.67 · iterative narrowband refinement, NB Hilbert, coherence, rounds, W05, refined · Report a mismatch
- [36] § Methods › Frequency ranges and filter bands ↔ code/evaluation/lrda_freq_hyperparam_sweep.py, lines 56 to 97 · score 0.66 · 0.3–5 Hz, pre filter, half width, Hilbert, clipped, 0.25 Hz · Report a mismatch
- [37] § Results › Spatial localization › PD spatial localization ↔ paper_materials/tables/generate_table4.py, lines 33 to 111 · score 0.66 · model expert, inter rater agreement, EE pairs, Jaccard, S2, LB · Report a mismatch
- [38] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/evaluation/verify_c1_repro.py, lines 87 to 204 · score 0.66 · detected active interval, skip penalties, deviations, Candidate, DP, HemiCET · Report a mismatch
- [39] § Results › Spatial localization › PD spatial localization ↔ paper_materials/generate_fig0_examples.py, lines 238 to 365 · score 0.64 · Bottom row, rhythmic delta activity, lateralized rhythmic delta, generalized rhythmic delta, lateralized periodic discharges, Inter rater · Report a mismatch
- [40] § Methods › PD characterization pipeline (PD-Profiler) › Discharge timing: HemiCET-UNet and dynamic programming ↔ code/discharge_detector.py, lines 1 to 65 · score 0.63 · Product boosted, discharge detection, floor, aggregated, CET, HPP · Report a mismatch
Paper
Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 879 lines · 35 KB · CC-BY-NC-4.0 · 4 matches
- """
- Automated Periodic Discharge Detection from Raw EEG
- Self-contained implementation of the best-performing method for detecting
- periodic discharges (LPD/GPD) in 10-second EEG segments. No gold standard
- labels are used as input — the system operates from raw EEG only.
- Method: product-boosted max(HPP, CET-UNet) + CNN+ACF frequency
- + optimized DP + post-hoc filtering
- Performance: F1≈0.74, evaluated on 593 patients with expert-reviewed labels
- Architecture overview:
- EEG (18ch × 2000 samples @ 200 Hz)
- │
- ├─ CNN+Attention (5-fold ensemble)
- │ → per-channel PD probability + log-frequency
- │ → PD-weighted CNN frequency estimate
- │
- ├─ ACF on pointiness traces (per channel)
- │ → ACF frequency estimate
- │ → Ensemble: 0.8×CNN + 0.2×ACF
- │
- ├─ Handcrafted evidence (per channel):
- │ pointiness trace (prominence²/width at peaks)
- │ + TKEO on 20 Hz lowpassed signal
- │ → weighted combination → Gaussian smooth → E_hpp(t)
- │
- └─ CET-UNet (5-fold ensemble, per channel):
- → frame-level discharge evidence E_cet(t)
- │
- ├─ Aggregate channels (median, laterality-aware)
- └─ Combine: threshold CET (80th pct), floor (0.3),
- product-boost: max(HPP,CET) + 3×HPP×CET
- │
- └─ Active interval detection
- → candidate peak extraction (min height 5% of max)
- → forward DP with approximately-periodic prior
- (α=1.275, β=0.3, λ=0.05, max_skip=3)
- → EM template refinement
- → post-hoc filter (drop peaks < 0.3× median)
- → per-channel timing (±50ms)
- │
- Output: discharge times, IPI-derived frequency
- Dependencies: numpy, scipy, torch
- Model weights: data/cet_cache/cet_unet_fold{0-4}.pt
- data/pd_channel_cache/cnn_attn_fold{0-4}.pt
- Usage:
- from discharge_detector import DischargeDetector
- detector = DischargeDetector()
- result = detector.detect(eeg_18ch, subtype='lpd', laterality='left')
- # result['global_times'] = [0.42, 1.18, 1.95, 2.71, ...]
- # result['frequency'] = 1.31 (Hz, from IPI)
- # result['freq_estimate_input'] = 1.28 (Hz, CNN estimate)
- Authors: Westover Lab, Massachusetts General Hospital
- """
- import numpy as np
- import torch
- import torch.nn as nn
- from pathlib import Path
- from scipy.signal import find_peaks, butter, filtfilt
- from scipy.ndimage import gaussian_filter1d
- # ── Path setup ────────────────────────────────────────────────────────
- CODE_DIR = Path(__file__).resolve().parent
- PROJECT_DIR = CODE_DIR.parent
- # ── Constants ─────────────────────────────────────────────────────────
- FS = 200 # sampling rate (Hz)
- # Channel indices in 18-channel bipolar montage (double banana)
- # 0-3: Fp1-F7, F7-T3, T3-T5, T5-O1 (left temporal chain)
- # 4-7: Fp2-F8, F8-T4, T4-T6, T6-O2 (right temporal chain)
- # 8-11: Fp1-F3, F3-C3, C3-P3, P3-O1 (left parasagittal chain)
- # 12-15: Fp2-F4, F4-C4, C4-P4, P4-O2 (right parasagittal chain)
- # 16-17: Fz-Cz, Cz-Pz (midline)
- LEFT_INDICES = np.array([0, 1, 2, 3, 8, 9, 10, 11])
- RIGHT_INDICES = np.array([4, 5, 6, 7, 12, 13, 14, 15])
- # Handcrafted evidence parameters
- LOWPASS_HZ = 20.0
- POINTINESS_WEIGHT = 0.6
- TKEO_WEIGHT = 0.4
- SMOOTH_SIGMA_SAMPLES = 3 # ~15 ms at 200 Hz
- # Active interval detection
- ROLLING_WINDOW_S = 1.0
- ACTIVE_THRESHOLD_FRAC = 0.5
- MIN_ACTIVE_SECONDS = 3.0
- ACTIVE_EXPAND_S = 0.5
- # DP parameters (optimized via parameter sweep)
- DP_ALPHA = 1.275 # timing deviation penalty (loosened for noisy freq)
- DP_BETA = 0.3 # skip penalty
- DP_LAMBDA = 0.05 # new-sequence cost
- PEAK_HEIGHT_FRAC = 0.05
- MAX_SKIP = 3
- # EM refinement
- TEMPLATE_HALF_MS = 150 # ±150 ms for template extraction
- CHANNEL_REFINE_MS = 50 # ±50 ms for per-channel refinement
- # Evaluation
- TOLERANCE_S = 0.1 # ±100ms for discharge matching
- # ═══════════════════════════════════════════════════════════════════════
- # Neural network architectures
- # ═══════════════════════════════════════════════════════════════════════
- class ChannelPDNetAttention(nn.Module):
- """1D CNN with temporal attention for PD detection + frequency estimation.
- Input: (batch, 1, 2000) — one z-scored EEG channel
- Output: pd_prob (batch, 1) — PD probability [0, 1]
- freq_pred (batch, 1) — log frequency (Hz)
- attn_wts (batch, 1, T) — attention weights over time
- Architecture:
- 4 conv blocks (1→16→32→64→64), each: Conv1d(stride=2) → BN → GELU → Dropout
- Attention: Conv1d(64→1) → softmax → weighted pooling → (64,)
- PD head: Linear(64→1) → sigmoid
- Freq head: Linear(64→1)
- """
- def __init__(self):
- super().__init__()
- self.block1 = nn.Sequential(
- nn.Conv1d(1, 16, kernel_size=51, stride=2, padding=25),
- nn.BatchNorm1d(16), nn.GELU(), nn.Dropout(0.1))
- self.block2 = nn.Sequential(
- nn.Conv1d(16, 32, kernel_size=25, stride=2, padding=12),
- nn.BatchNorm1d(32), nn.GELU(), nn.Dropout(0.1))
- self.block3 = nn.Sequential(
- nn.Conv1d(32, 64, kernel_size=13, stride=2, padding=6),
- nn.BatchNorm1d(64), nn.GELU(), nn.Dropout(0.1))
- self.block4 = nn.Sequential(
- nn.Conv1d(64, 64, kernel_size=7, stride=2, padding=3),
- nn.BatchNorm1d(64), nn.GELU(), nn.Dropout(0.2))
- self.attn_conv = nn.Conv1d(64, 1, kernel_size=1)
- self.pd_head = nn.Linear(64, 1)
- self.freq_head = nn.Linear(64, 1)
- def forward(self, x):
- x = self.block1(x)
- x = self.block2(x)
- x = self.block3(x)
- x = self.block4(x)
- attn_logits = self.attn_conv(x)
- attn_wts = torch.softmax(attn_logits, dim=-1)
- pooled = (x * attn_wts).sum(dim=-1)
- pd_prob = torch.sigmoid(self.pd_head(pooled))
- freq_pred = self.freq_head(pooled)
- return pd_prob, freq_pred, attn_wts
- class CETUNet(nn.Module):
- """U-Net for frame-level discharge evidence at full temporal resolution.
- Input: (batch, 1, 2000) — one z-scored EEG channel
- Output: (batch, 1, 2000) — discharge evidence in [0, 1]
- Architecture:
- Encoder: 4 conv blocks with stride=2 downsampling
- e1: 1→16 (2000→1000) k=51
- e2: 16→32 (1000→500) k=25
- e3: 32→64 (500→250) k=13
- e4: 64→64 (250→125) k=7
- Decoder: 4 ConvTranspose blocks with skip connections from encoder
- d4: 64→64 (125→250), cat(d4, e3) → Conv(128→64)
- d3: 64→32 (250→500), cat(d3, e2) → Conv(64→32)
- d2: 32→16 (500→1000), cat(d2, e1) → Conv(32→16)
- d1: 16→8 (1000→2000), Conv(8→1) → Sigmoid
- """
- def __init__(self):
- super().__init__()
- self.enc1 = nn.Sequential(
- nn.Conv1d(1, 16, kernel_size=51, stride=2, padding=25),
- nn.BatchNorm1d(16), nn.GELU())
- self.enc2 = nn.Sequential(
- nn.Conv1d(16, 32, kernel_size=25, stride=2, padding=12),
- nn.BatchNorm1d(32), nn.GELU())
- self.enc3 = nn.Sequential(
- nn.Conv1d(32, 64, kernel_size=13, stride=2, padding=6),
- nn.BatchNorm1d(64), nn.GELU())
- self.enc4 = nn.Sequential(
- nn.Conv1d(64, 64, kernel_size=7, stride=2, padding=3),
- nn.BatchNorm1d(64), nn.GELU())
- self.up4 = nn.Sequential(
- nn.ConvTranspose1d(64, 64, kernel_size=4, stride=2, padding=1),
- nn.BatchNorm1d(64), nn.GELU())
- self.skip4 = nn.Sequential(
- nn.Conv1d(128, 64, kernel_size=3, padding=1),
- nn.BatchNorm1d(64), nn.GELU())
- self.up3 = nn.Sequential(
- nn.ConvTranspose1d(64, 32, kernel_size=4, stride=2, padding=1),
- nn.BatchNorm1d(32), nn.GELU())
- self.skip3 = nn.Sequential(
- nn.Conv1d(64, 32, kernel_size=3, padding=1),
- nn.BatchNorm1d(32), nn.GELU())
- self.up2 = nn.Sequential(
- nn.ConvTranspose1d(32, 16, kernel_size=4, stride=2, padding=1),
- nn.BatchNorm1d(16), nn.GELU())
- self.skip2 = nn.Sequential(
- nn.Conv1d(32, 16, kernel_size=3, padding=1),
- nn.BatchNorm1d(16), nn.GELU())
- self.up1 = nn.Sequential(
- nn.ConvTranspose1d(16, 8, kernel_size=4, stride=2, padding=1),
- nn.BatchNorm1d(8), nn.GELU())
- self.head = nn.Sequential(nn.Conv1d(8, 1, kernel_size=1), nn.Sigmoid())
- @staticmethod
- def _match_size(dec, enc):
- min_len = min(dec.shape[2], enc.shape[2])
- return dec[:, :, :min_len], enc[:, :, :min_len]
- def forward(self, x):
- e1 = self.enc1(x)
- e2 = self.enc2(e1)
- e3 = self.enc3(e2)
- e4 = self.enc4(e3)
- d4 = self.up4(e4)
- d4, e3m = self._match_size(d4, e3)
- d4 = self.skip4(torch.cat([d4, e3m], dim=1))
- d3 = self.up3(d4)
- d3, e2m = self._match_size(d3, e2)
- d3 = self.skip3(torch.cat([d3, e2m], dim=1))
- d2 = self.up2(d3)
- d2, e1m = self._match_size(d2, e1)
- d2 = self.skip2(torch.cat([d2, e1m], dim=1))
- d1 = self.up1(d2)
- return self.head(d1)
- # ═══════════════════════════════════════════════════════════════════════
- # Signal processing: handcrafted evidence
- # ═══════════════════════════════════════════════════════════════════════
- def compute_pointiness_trace(signal_1d, half_win=8):
- """Compute pointiness = prominence² / width at each local maximum.
- For each local max in the signal, computes:
- prominence = peak_value - max(left_valley, right_valley)
- width = number of samples above half-prominence level
- pointiness = prominence² / width
- Returns a trace that is zero everywhere except at local maxima.
- """
- n = len(signal_1d)
- trace = np.zeros(n)
- peaks, _ = find_peaks(signal_1d)
- for loc in peaks:
- if loc < half_win or loc >= n - half_win:
- continue
- peak_val = signal_1d[loc]
- left_valley = np.min(signal_1d[loc - half_win:loc])
- right_valley = np.min(signal_1d[loc + 1:loc + half_win + 1])
- prom = peak_val - max(left_valley, right_valley)
- if prom <= 0:
- continue
- half_prom_level = peak_val - 0.5 * prom
- width = 0
- for j in range(1, half_win + 1):
- if signal_1d[loc - j] > half_prom_level:
- width += 1
- else:
- break
- for j in range(1, half_win + 1):
- if loc + j < n and signal_1d[loc + j] > half_prom_level:
- width += 1
- else:
- break
- if width > 0:
- trace[loc] = prom ** 2 / width
- return trace
- def compute_channel_evidence(signal_1d, fs=FS):
- """Compute handcrafted discharge evidence E_hpp(t) for one channel.
- Steps:
- 1. Pointiness trace on raw signal
- 2. TKEO (Teager-Kaiser Energy Operator) on 20 Hz lowpassed signal
- 3. Z-score normalize both within the window
- 4. Combine: 0.6 × pointiness_z + 0.4 × tkeo_z
- 5. Gaussian smooth (σ=3 samples ≈ 15ms)
- 6. Clip negatives to zero
- """
- n = len(signal_1d)
- if n < 10:
- return np.zeros(n)
- pt = compute_pointiness_trace(signal_1d)
- b_lp, a_lp = butter(4, LOWPASS_HZ / (fs / 2), btype='low')
- try:
- sig_lp = filtfilt(b_lp, a_lp, signal_1d)
- except ValueError:
- sig_lp = signal_1d.copy()
- tkeo = np.zeros(n)
- if n >= 3:
- tkeo[1:-1] = np.abs(sig_lp[1:-1] ** 2 - sig_lp[:-2] * sig_lp[2:])
- def zscore(x):
- m, s = np.mean(x), np.std(x)
- return (x - m) / s if s > 1e-10 else np.zeros_like(x)
- evidence = POINTINESS_WEIGHT * zscore(pt) + TKEO_WEIGHT * zscore(tkeo)
- evidence = gaussian_filter1d(evidence, sigma=SMOOTH_SIGMA_SAMPLES)
- return np.clip(evidence, 0, None)
- # ═══════════════════════════════════════════════════════════════════════
- # Evidence aggregation
- # ═══════════════════════════════════════════════════════════════════════
- def aggregate_evidence(evidence_all, subtype, laterality=None):
- """Aggregate per-channel evidence into a single trace E(t).
- GPD: median across all 18 channels
- LPD with known laterality: median across ipsilateral 8 channels
- LPD without laterality: max(left_median, right_median) at each time point
- """
- if subtype == 'gpd':
- return np.median(evidence_all, axis=0)
- if laterality == 'left':
- return np.median(evidence_all[LEFT_INDICES], axis=0)
- elif laterality == 'right':
- return np.median(evidence_all[RIGHT_INDICES], axis=0)
- else:
- left_med = np.median(evidence_all[LEFT_INDICES], axis=0)
- right_med = np.median(evidence_all[RIGHT_INDICES], axis=0)
- return np.maximum(left_med, right_med)
- def combine_evidence(hpp_evidence, cet_evidence, cet_threshold_pct=80,
- boost_weight=3.0, cet_floor=0.3):
- """Combine handcrafted and CNN evidence with product-boost.
- Steps:
- 1. Normalize both traces to [0, 1]
- 2. Threshold CET: zero out values below the 80th percentile
- (suppresses CET noise floor that causes false positives)
- 3. Product-boost: max(HPP, CET) + boost_weight × HPP × CET
- (amplifies regions where both methods agree)
- """
- hpp_max = np.max(hpp_evidence) if np.max(hpp_evidence) > 0 else 1.0
- cet_max = np.max(cet_evidence) if np.max(cet_evidence) > 0 else 1.0
- hpp_norm = hpp_evidence / hpp_max
- cet_norm = cet_evidence / cet_max
- # Threshold CET to suppress noise floor
- if cet_threshold_pct > 0 and np.any(cet_norm > 0):
- thr = np.percentile(cet_norm[cet_norm > 0], cet_threshold_pct)
- cet_norm = np.where(cet_norm > thr, cet_norm, 0)
- # Floor: zero out very low CET values
- cet_clean = np.where(cet_norm > cet_floor, cet_norm, 0)
- # Product-boost: max + agreement bonus
- base = np.maximum(hpp_norm, cet_clean)
- agreement = hpp_norm * cet_clean
- return base + boost_weight * agreement
- # ═══════════════════════════════════════════════════════════════════════
- # Dynamic programming for discharge timing
- # ═══════════════════════════════════════════════════════════════════════
- def detect_active_interval(evidence, fs=FS):
- """Find the longest contiguous interval where the evidence is active.
- Active = rolling mean (1s window) exceeds 50% of the global rolling max.
- Interval is expanded by 0.5s on each side.
- """
- n = len(evidence)
- win = int(ROLLING_WINDOW_S * fs)
- cs = np.cumsum(evidence)
- rolling = np.zeros(n)
- for i in range(n):
- lo = max(0, i - win // 2)
- hi = min(n, i + win // 2 + 1)
- rolling[i] = (cs[hi - 1] - (cs[lo - 1] if lo > 0 else 0)) / (hi - lo)
- threshold = ACTIVE_THRESHOLD_FRAC * np.max(rolling)
- above = rolling > threshold
- best_start, best_len = 0, 0
- cur_start, cur_len = 0, 0
- for i in range(n):
- if above[i]:
- if cur_len == 0:
- cur_start = i
- cur_len += 1
- else:
- if cur_len > best_len:
- best_start, best_len = cur_start, cur_len
- cur_len = 0
- if cur_len > best_len:
- best_start, best_len = cur_start, cur_len
- if best_len < int(MIN_ACTIVE_SECONDS * fs):
- return 0, n - 1
- expand = int(ACTIVE_EXPAND_S * fs)
- return max(0, best_start - expand), min(n - 1, best_start + best_len - 1 + expand)
- def extract_candidates(evidence, fs, freq_estimate, active_start, active_end):
- """Find candidate discharge peaks within the active interval.
- Two rounds of peak detection:
- 1. Standard: height > 5% of max, min distance = 0.2 × T
- 2. Strong: height > 50% of max, min distance = 0.1 × T (captures obvious peaks)
- Both sets are merged and deduplicated.
- """
- segment = evidence[active_start:active_end + 1]
- if len(segment) < 3:
- return np.array([], dtype=int)
- T = 1.0 / freq_estimate if freq_estimate > 0 else 1.0
- min_dist = max(20, int(0.2 * T * fs))
- min_height = PEAK_HEIGHT_FRAC * np.max(segment)
- peaks, _ = find_peaks(segment, height=min_height, distance=min_dist)
- strong_height = 0.5 * np.max(segment)
- strong_peaks, _ = find_peaks(segment, height=strong_height,
- distance=max(10, int(0.1 * T * fs)))
- peaks = np.unique(np.concatenate([peaks, strong_peaks]))
- return peaks + active_start
- def dp_best_sequence(candidates, evidence, fs, freq_estimate):
- """Find optimal discharge sequence via forward dynamic programming.
- Scoring:
- Node score = evidence[candidate]^1.5 (superlinear: favor strong peaks)
- Edge score = -α × (deviation_from_periodic)² - β × (skips - 1)
- where deviation = (dt - m×T) / (m×T) for skip count m
- New-sequence cost = -λ per candidate
- The DP finds the path through candidates that maximizes:
- Σ(node_scores) + Σ(edge_scores) - λ × path_length
- """
- if len(candidates) == 0:
- return np.array([], dtype=int)
- if len(candidates) == 1:
- return candidates.copy()
- T = 1.0 / freq_estimate
- n = len(candidates)
- raw_scores = np.array([evidence[c] for c in candidates])
- node_scores = raw_scores ** 1.5
- best_score = np.full(n, -np.inf)
- best_prev = np.full(n, -1, dtype=int)
- for i in range(n):
- best_score[i] = node_scores[i] - DP_LAMBDA
- for j in range(1, n):
- for i in range(j):
- dt = (candidates[j] - candidates[i]) / fs
- if dt <= 0 or dt > 4 * T:
- continue
- best_edge = -np.inf
- for m in range(1, MAX_SKIP + 1):
- deviation = (dt - m * T) / (m * T)
- interval_score = -DP_ALPHA * deviation ** 2
- skip_penalty = -DP_BETA * (m - 1)
- edge = interval_score + skip_penalty
- if edge > best_edge:
- best_edge = edge
- total = best_score[i] + best_edge + node_scores[j] - DP_LAMBDA
- if total > best_score[j]:
- best_score[j] = total
- best_prev[j] = i
- path = []
- idx = int(np.argmax(best_score))
- while idx >= 0:
- path.append(idx)
- idx = best_prev[idx]
- path.reverse()
- return candidates[np.array(path)]
- def em_refine(evidence, discharge_samples, fs, freq_estimate):
- """Refine discharge times using template cross-correlation.
- Steps:
- 1. Extract evidence snippets (±150ms) around each detected discharge
- 2. Average to create a discharge template
- 3. Normalized cross-correlation of template with full evidence trace
- 4. Re-run peak detection + DP on the correlation peaks
- """
- n = len(evidence)
- half_win = int(TEMPLATE_HALF_MS / 1000.0 * fs)
- if len(discharge_samples) < 2:
- return discharge_samples
- snippets = []
- for s in discharge_samples:
- lo, hi = s - half_win, s + half_win + 1
- if lo >= 0 and hi <= n:
- snippets.append(evidence[lo:hi])
- if len(snippets) < 2:
- return discharge_samples
- template = np.mean(snippets, axis=0) - np.mean(np.mean(snippets, axis=0))
- t_norm = np.sqrt(np.sum(template ** 2))
- if t_norm < 1e-10:
- return discharge_samples
- corr = np.zeros(n)
- t_len = len(template)
- for i in range(half_win, n - half_win):
- seg = evidence[i - half_win:i + half_win + 1]
- if len(seg) != t_len:
- continue
- seg_c = seg - np.mean(seg)
- s_norm = np.sqrt(np.sum(seg_c ** 2))
- if s_norm < 1e-10:
- continue
- corr[i] = np.dot(template, seg_c) / (t_norm * s_norm)
- T = 1.0 / freq_estimate if freq_estimate > 0 else 1.0
- min_dist = max(30, int(0.3 * T * fs))
- min_height = 0.15 * np.max(corr) if np.max(corr) > 0 else 0
- refined_candidates, _ = find_peaks(corr, height=min_height, distance=min_dist)
- if len(refined_candidates) < 2:
- return discharge_samples
- return dp_best_sequence(refined_candidates, evidence, fs, freq_estimate)
- def per_channel_times(evidence_all, global_samples, fs=FS):
- """Refine global discharge times per channel within ±50ms.
- For each global discharge, finds the local evidence peak in each
- channel within a ±50ms window.
- """
- refine_win = int(CHANNEL_REFINE_MS / 1000.0 * fs)
- n_channels, n_samples = evidence_all.shape
- channel_times = {}
- for ch in range(n_channels):
- ch_times = []
- for gs in global_samples:
- lo = max(0, gs - refine_win)
- hi = min(n_samples, gs + refine_win + 1)
- window = evidence_all[ch, lo:hi]
- if len(window) == 0:
- ch_times.append(gs / fs)
- else:
- ch_times.append((lo + int(np.argmax(window))) / fs)
- channel_times[ch] = ch_times
- return channel_times
- # ═══════════════════════════════════════════════════════════════════════
- # Post-hoc confidence filtering
- # ═══════════════════════════════════════════════════════════════════════
- def posthoc_filter(discharge_samples, evidence, min_evidence_ratio=0.3):
- """Drop detected discharges with evidence below a fraction of the median.
- Removes low-confidence detections where the evidence peak at the
- detected discharge time is less than min_evidence_ratio × median
- peak value across all detections.
- """
- if len(discharge_samples) < 2 or min_evidence_ratio <= 0:
- return discharge_samples
- peak_values = np.array([evidence[int(s)] for s in discharge_samples])
- threshold = min_evidence_ratio * np.median(peak_values)
- mask = peak_values >= threshold
- return discharge_samples[mask]
- # ═══════════════════════════════════════════════════════════════════════
- # ACF frequency estimation (handcrafted, no neural network)
- # ═══════════════════════════════════════════════════════════════════════
- def estimate_frequency_acf(signal_1d, fs=FS):
- """Estimate dominant frequency from autocorrelation of pointiness trace.
- Steps:
- 1. Compute pointiness trace
- 2. Autocorrelation of the pointiness trace
- 3. Find first peak in ACF after min lag (0.4s = max 2.5 Hz)
- 4. Frequency = 1 / lag_at_peak
- """
- pt = compute_pointiness_trace(signal_1d)
- if np.max(pt) < 1e-10:
- return np.nan
- # Autocorrelation
- pt_centered = pt - np.mean(pt)
- n = len(pt_centered)
- acf = np.correlate(pt_centered, pt_centered, mode='full')[n-1:]
- if acf[0] > 0:
- acf = acf / acf[0]
- # Find first peak after minimum lag
- min_lag = int(0.4 * fs) # 0.4s = max 2.5 Hz
- max_lag = min(int(3.0 * fs), len(acf) - 1) # 3.0s = min 0.33 Hz
- if max_lag <= min_lag:
- return np.nan
- segment = acf[min_lag:max_lag + 1]
- peaks, props = find_peaks(segment, height=0.1)
- if len(peaks) == 0:
- return np.nan
- # Take the first (shortest lag) peak
- best_lag = peaks[0] + min_lag
- return fs / best_lag
- # ═══════════════════════════════════════════════════════════════════════
- # Main detector class
- # ═══════════════════════════════════════════════════════════════════════
- class DischargeDetector:
- """Automated periodic discharge detector from raw EEG.
- Loads CNN+Attention (frequency estimation) and CET-UNet (evidence)
- model ensembles on initialization, then detects discharges from raw
- 18-channel bipolar EEG segments.
- Args:
- cet_model_dir: directory containing cet_unet_fold{0-4}.pt
- cnn_model_dir: directory containing cnn_attn_fold{0-4}.pt
- device: 'cpu', 'cuda', or 'mps'
- n_folds: number of ensemble models to load
- """
- def __init__(self, cet_model_dir=None, cnn_model_dir=None,
- device=None, n_folds=5):
- if cet_model_dir is None:
- cet_model_dir = PROJECT_DIR / 'data' / 'cet_cache'
- if cnn_model_dir is None:
- cnn_model_dir = PROJECT_DIR / 'data' / 'pd_channel_cache'
- if device is None:
- if torch.backends.mps.is_available():
- device = torch.device('mps')
- elif torch.cuda.is_available():
- device = torch.device('cuda')
- else:
- device = torch.device('cpu')
- self.device = device
- self.cet_models = self._load_models(
- CETUNet, cet_model_dir, 'cet_unet_fold', n_folds)
- self.cnn_models = self._load_models(
- ChannelPDNetAttention, cnn_model_dir, 'cnn_attn_fold', n_folds)
- def _load_models(self, model_class, model_dir, prefix, n_folds):
- models = []
- model_dir = Path(model_dir)
- for fold in range(n_folds):
- path = model_dir / f'{prefix}{fold}.pt'
- if not path.exists():
- raise FileNotFoundError(f"Model not found: {path}")
- model = model_class()
- model.load_state_dict(torch.load(
- str(path), map_location=self.device, weights_only=True))
- model.to(self.device)
- model.eval()
- models.append(model)
- return models
- @torch.no_grad()
- def estimate_frequency(self, segment_18ch):
- """Estimate discharge frequency from raw EEG using CNN+Attention.
- Runs each channel through the 5-fold CNN+Attention ensemble.
- PD probability is used as weight for frequency averaging
- (PD-weighted mean of log-frequency predictions).
- Returns: float, estimated frequency in Hz, clipped to [0.3, 3.5]
- """
- n_channels = min(segment_18ch.shape[0], 18)
- all_pd_probs = []
- all_log_freqs = []
- for ch in range(n_channels):
- ch_data = segment_18ch[ch].astype(np.float32).copy()
- if not np.all(np.isfinite(ch_data)):
- all_pd_probs.append(0.0)
- all_log_freqs.append(0.0)
- continue
- mu, std = np.mean(ch_data), np.std(ch_data)
- ch_data = (ch_data - mu) / std if std > 1e-8 else ch_data - mu
- x = torch.from_numpy(ch_data[np.newaxis, np.newaxis, :]).to(self.device)
- pd_probs, log_freqs = [], []
- for model in self.cnn_models:
- pd_prob, freq_pred, _ = model(x)
- pd_probs.append(pd_prob.item())
- log_freqs.append(freq_pred.item())
- all_pd_probs.append(np.mean(pd_probs))
- all_log_freqs.append(np.mean(log_freqs))
- pd_weights = np.array(all_pd_probs)
- log_freqs = np.array(all_log_freqs)
- weight_sum = pd_weights.sum()
- if weight_sum > 1e-6:
- weighted_log_freq = np.sum(pd_weights * log_freqs) / weight_sum
- else:
- weighted_log_freq = np.mean(log_freqs)
- return float(np.clip(np.exp(weighted_log_freq), 0.3, 3.5))
- @torch.no_grad()
- def compute_cet_evidence_channel(self, channel_data):
- """Run CET-UNet ensemble on a single channel.
- Returns: numpy array (n_samples,) — evidence in [0, 1]
- """
- x = channel_data.astype(np.float32).copy()
- mu, std = np.mean(x), np.std(x)
- x = (x - mu) / std if std > 1e-8 else x - mu
- x_tensor = torch.from_numpy(x[np.newaxis, np.newaxis, :]).to(self.device)
- predictions = []
- for model in self.cet_models:
- pred = model(x_tensor).squeeze().cpu().numpy()
- predictions.append(pred)
- return np.mean(predictions, axis=0).astype(np.float32)
- def estimate_frequency_acf_multichannel(self, segment_18ch, subtype,
- laterality=None):
- """Estimate frequency from ACF across multiple channels.
- Computes ACF frequency per channel and takes the median across
- relevant channels (all for GPD, lateralized for LPD).
- Returns: float, estimated frequency in Hz (or NaN if no valid estimate)
- """
- n_channels = min(segment_18ch.shape[0], 18)
- b_lp, a_lp = butter(4, LOWPASS_HZ / (FS / 2), btype='low')
- acf_freqs = []
- for ch in range(n_channels):
- try:
- sig = filtfilt(b_lp, a_lp, segment_18ch[ch])
- except ValueError:
- sig = segment_18ch[ch]
- freq = estimate_frequency_acf(sig, FS)
- if np.isfinite(freq):
- acf_freqs.append(freq)
- if not acf_freqs:
- return np.nan
- return float(np.clip(np.median(acf_freqs), 0.3, 3.5))
- def detect(self, segment_18ch, subtype='lpd', laterality=None, refine=True):
- """Detect periodic discharges in a 10-second EEG segment.
- Args:
- segment_18ch: (18, 2000) bipolar EEG at 200 Hz
- subtype: 'lpd' or 'gpd'
- laterality: 'left', 'right', or None
- refine: whether to apply EM template refinement
- Returns:
- dict with:
- global_times: list of discharge times in seconds
- channel_times: dict ch -> list of times
- frequency: float, IPI-derived frequency (Hz)
- freq_estimate_input: float, CNN+ACF ensemble frequency (Hz)
- n_discharges: int
- active_interval: (start_s, end_s)
- """
- n_channels = min(segment_18ch.shape[0], 18)
- n_samples = segment_18ch.shape[1]
- fs = FS
- # 1. Estimate frequency: CNN+ACF ensemble (0.8 CNN + 0.2 ACF)
- cnn_freq = self.estimate_frequency(segment_18ch)
- acf_freq = self.estimate_frequency_acf_multichannel(
- segment_18ch, subtype, laterality)
- if np.isfinite(acf_freq):
- freq_estimate = 0.8 * cnn_freq + 0.2 * acf_freq
- else:
- freq_estimate = cnn_freq
- freq_estimate = float(np.clip(freq_estimate, 0.3, 3.5))
- # 2. Compute per-channel evidence (both handcrafted and CNN)
- hpp_all = np.zeros((n_channels, n_samples))
- cet_all = np.zeros((n_channels, n_samples), dtype=np.float32)
- for ch in range(n_channels):
- hpp_all[ch] = compute_channel_evidence(segment_18ch[ch], fs)
- if np.all(np.isfinite(segment_18ch[ch])):
- cet_all[ch] = self.compute_cet_evidence_channel(segment_18ch[ch])
- # 3. Aggregate across channels
- hpp_agg = aggregate_evidence(hpp_all, subtype, laterality)
- cet_agg = aggregate_evidence(cet_all, subtype, laterality)
- # 4. Combine: product-boosted max(HPP, CET) with CET thresholding
- evidence = combine_evidence(hpp_agg, cet_agg)
- # 5. DP inference
- active_start, active_end = detect_active_interval(evidence, fs)
- candidates = extract_candidates(evidence, fs, freq_estimate,
- active_start, active_end)
- discharge_samples = dp_best_sequence(candidates, evidence, fs,
- freq_estimate)
- # 6. EM refinement
- if refine and len(discharge_samples) >= 3:
- discharge_samples = em_refine(evidence, discharge_samples, fs,
- freq_estimate)
- # 7. Post-hoc confidence filter: drop weak detections
- discharge_samples = posthoc_filter(discharge_samples, evidence)
- # 8. Per-channel timing
- channel_times = per_channel_times(hpp_all, discharge_samples, fs)
- # 8. Output
- global_times = (discharge_samples / fs).tolist() if len(discharge_samples) > 0 else []
- if len(global_times) >= 2:
- ipis = np.diff(global_times)
- ipi_freq = 1.0 / float(np.median(ipis))
- else:
- ipi_freq = np.nan
- return {
- 'global_times': global_times,
- 'channel_times': {int(k): v for k, v in channel_times.items()},
- 'frequency': ipi_freq,
- 'freq_estimate_input': freq_estimate,
- 'n_discharges': len(discharge_samples),
- 'active_interval': (active_start / fs, active_end / fs),
- }
- # ═══════════════════════════════════════════════════════════════════════
- # CLI entry point
- # ═══════════════════════════════════════════════════════════════════════
- if __name__ == '__main__':
- import scipy.io as sio
- print("Discharge Detector — Self-contained implementation")
- print("=" * 60)
- detector = DischargeDetector()
- print(f"Models loaded on {detector.device}")
- # Demo: load first available EEG file
- eeg_dir = PROJECT_DIR / 'data' / 'eeg'
- mat_files = sorted(eeg_dir.glob('*.mat'))
- if mat_files:
- mat = sio.loadmat(str(mat_files[0]))
- data_key = [k for k in mat.keys() if not k.startswith('_')][0]
- seg = mat[data_key]
- if seg.shape[0] > seg.shape[1]:
- seg = seg.T
- seg = seg[:18, :2000]
- print(f"\nProcessing {mat_files[0].name}: {seg.shape}")
- result = detector.detect(seg, subtype='lpd')
- print(f" Discharges found: {result['n_discharges']}")
- print(f" Times: {[f'{t:.3f}' for t in result['global_times'][:10]]}")
- print(f" CNN freq estimate: {result['freq_estimate_input']:.2f} Hz")
- print(f" IPI frequency: {result['frequency']:.2f} Hz")
- else:
- print("\nNo EEG files found in data/eeg/. Provide a .mat file to test.")
discharge_detector.py at commit 58cd045, under CC-BY-NC-4.0 · at the source
Overview
- Department of Neurology, Clinical Data Animation Center, Beth Israel Deaconess Medical Center/Harvard Medical School, Boston, MA, United States of America
- Stanford University, Palo Alto, CA, United States of America
- Department of Neurology, Massachusetts General Hospital/Harvard Medical School, Boston, MA, United States of America
- Department of Neurology, Yale University School of Medicine, New Haven, CT, United States of America
- Department of Neurology, Washington University in St. Louis, St. Louis, MO, United States of America
Abstract
Objective. Periodic discharges (PDs) and rhythmic delta activity (RDA) are common electroencephalographic patterns in critically ill patients that require detailed characterization—includi
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 40 matches between paragraphs and lines of code.
bdsp-core/grond
58cd04551393b2b2282baf051ed87cfb2f5ba366, 28 September 2026Availability: 1 check, the latest on 4 November 2026: the link answers
- 4 November 2026: the link answers
436 files
- code/
archive/ , Python, 132 linesauto_assign_rda_freq.py - code/
archive/ , Python, 281 linesbipd_retrain.py - code/
archive/ , Python, 1 linedl/ __init__.py - code/
archive/ , Python, 474 linesdl/ build_round2_viewer.py - code/
archive/ , Python, 197 linesdl/ data_loader.py - code/
archive/ , Python, 523 linesdl/ evaluate.py - code/
archive/ , Python, 180 linesdl/ extract_external.py - code/
archive/ , Python, 160 linesdl/ generate_annotation_pngs .py - code/
archive/ , Python, 287 linesdl/ generate_round2_pngs.py - code/
archive/ , Python, 242 linesdl/ generate_weak_labels.py - code/
archive/ , Python, 163 linesdl/ model.py - code/
archive/ , Python, 374 linesdl/ select_for_annotation.py - code/
archive/ , Python, 381 linesdl/ select_round2.py - code/
archive/ , Python, 865 linesdl/ train_dann.py - code/
archive/ , Python, 235 linesdl/ train_phase1.py - code/
archive/ , Python, 483 linesdl/ train_phase2.py - code/
archive/ , Python, 151 linesensemble_AB.py - code/
archive/ , Python, 347 linesexperiments/ misc/ combo_experiments.py - code/
archive/ , Python, 182 linesexperiments/ misc/ experiment_channel_aggre gation.py - code/
archive/ , Python, 315 linesexperiments/ misc/ experiment_frequency_pri ors.py - code/
archive/ , Python, 526 linesexperiments/ misc/ experiment_hps.py - code/
archive/ , Python, 383 linesexperiments/ misc/ experiment_multiscale_ha rmonic.py - code/
archive/ , Python, 187 linesexperiments/ misc/ experiment_peak_equaliza tion.py - code/
archive/ , Python, 120 linesexperiments/ misc/ experiment_preproc_acf_v ariants.py - code/
archive/ , Python, 707 linesexperiments/ misc/ experiment_r5_ensemble_l pd.py - code/
archive/ , Python, 187 linesexperiments/ misc/ experiment_subharmonic_c orrection.py - code/
archive/ , Python, 322 linesexperiments/ misc/ gridsearch_triple_median .py - code/
archive/ , Python, 182 linesexperiments/ misc/ peakcount_crossval.py - code/
archive/ , Python, 418 linesexperiments/ misc/ recompute_new77_features .py - code/
archive/ , Python, 308 linesexperiments/ pd_frequency/ exp_opt_freq_bias.py - code/
archive/ , Python, 248 linesexperiments/ pd_frequency/ exp_opt_freq_features.py - code/
archive/ , Python, 273 linesexperiments/ pd_frequency/ exp_opt_freq_models.py - code/
archive/ , Python, 308 linesexperiments/ pd_frequency/ exp_opt_freq_models_rema ining.py - code/
archive/ , Python, 194 linesexperiments/ pd_frequency/ exp_opt_freq_subtype.py - code/
archive/ , Python, 357 linesexperiments/ pd_frequency/ exp_t1_expanded_features .py - code/
archive/ , Python, 149 linesexperiments/ pd_frequency/ exp_t1_feature_transform s.py - code/
archive/ , Python, 92 linesexperiments/ pd_frequency/ exp_t1_subtype_specific. py - code/
archive/ , Python, 324 linesexperiments/ pd_frequency/ exp_t2_combined.py - code/
archive/ , Python, 109 linesexperiments/ pd_frequency/ exp_t2_knn_feature.py - code/
archive/ , Python, 83 linesexperiments/ pd_frequency/ exp_t2_tree_models.py - code/
archive/ , Python, 337 linesexperiments/ pd_timing/ exp_opt_lat_class.py - code/
archive/ , Python, 500 linesexperiments/ pd_timing/ exp_opt_lat_features.py - code/
archive/ , Python, 406 linesexperiments/ pd_timing/ exp_opt_subtype_class.py - code/
archive/ , Python, 252 linesexperiments/ pd_timing/ exp_t3_cnn_embeddings.py - code/
archive/ , Python, 653 linesexperiments/ pd_timing/ exp_t3_timing.py - code/
archive/ , Python, 481 linesexperiments/ pd_timing/ exp_t4_spike_frequency.p y - code/
archive/ , Python, 762 linesexperiments/ rda/ analyze_error_patterns.p y - code/
archive/ , Python, 57 linesexperiments/ rda/ analyze_rda.py - code/
archive/ , Python, 278 linesexperiments/ rda/ exp_rda_lowpass.py - code/
archive/ , Python, 391 linesexperiments/ rda/ exp_rda_task_a.py - code/
archive/ , Python, 453 linesexperiments/ rda/ exp_rda_task_bc.py - code/
archive/ , Python, 663 linesexperiments/ rounds/ r10_expanded_evaluation. py - code/
archive/ , Python, 544 linesexperiments/ rounds/ r11_bootstrap_evaluation .py - code/
archive/ , Python, 625 linesexperiments/ rounds/ r12_full_evaluation.py - code/
archive/ , Python, 353 linesexperiments/ rounds/ r3_fft_cepstral.py - code/
archive/ , Python, 356 linesexperiments/ rounds/ r4_alternans.py - code/
archive/ , Python, 392 linesexperiments/ rounds/ r4_combiner_v2.py - code/
archive/ , Python, 373 linesexperiments/ rounds/ r4_matched_filter_yin_sr h.py - code/
archive/ , Python, 195 linesexperiments/ rounds/ r4_salience_aggregation. py - code/
archive/ , Python, 646 linesexperiments/ rounds/ r5_expanded_banks.py - code/
archive/ , Python, 585 linesexperiments/ rounds/ r5_ordinal_regression.py - code/
archive/ , Python, 640 linesexperiments/ rounds/ r5_rich_features.py - code/
archive/ , Python, 558 linesexperiments/ rounds/ r5_target_transforms.py - code/
archive/ , Python, 503 linesexperiments/ rounds/ r6_ged_spatial_filter.py - code/
archive/ , Python, 640 linesexperiments/ rounds/ r6_mega_combiner.py - code/
archive/ , Python, 575 linesexperiments/ rounds/ r6_phase_coherence.py - code/
archive/ , Python, 511 linesexperiments/ rounds/ r6_tkeo.py - code/
archive/ , Python, 336 linesexperiments/ rounds/ r7_build_large_template_ bank.py - code/
archive/ , Python, 531 linesexperiments/ rounds/ r7_dtw_template.py - code/
archive/ , Python, 643 linesexperiments/ rounds/ r7_expert_spatial.py - code/
archive/ , Python, 799 linesexperiments/ rounds/ r7_hmm_comb.py - code/
archive/ , Python, 538 linesexperiments/ rounds/ r7_mega_combiner.py - code/
archive/ , Python, 787 linesexperiments/ rounds/ r7_multi_montage_ged.py - code/
archive/ , Python, 661 linesexperiments/ rounds/ r9_lopo_experiments.py - code/
archive/ , Python, 274 linesexperiments/ rounds/ round2_anchor_experiment .py - code/
archive/ , Python, 341 linesexperiments/ rounds/ round2_combiner.py - code/
archive/ , Python, 450 linesexperiments/ rounds/ round4_combiner.py - code/
archive/ , Python, 250 linesgenerate_figures.py - code/
archive/ , Python, 547 linesgenerate_final_figures.p y - code/
archive/ , Python, 330 linesgenerate_freq_comparison _scatter.py - code/
archive/ , Python, 178 linesgenerate_freq_scatterplo t.py - code/
archive/ , Python, 656 linesgenerate_hilbert_cv_figu re.py - code/
archive/ , Python, 466 linesgenerate_paper_figure.py - code/
archive/ , Python, 1,174 linesgenerate_paper_figures.p y - code/
archive/ , Python, 91 linesgenerate_round3_images.p y - code/
archive/ , Python, 111 linesgenerate_round4_images.p y - code/
archive/ , Python, 982 lineshemi_detector_experiment s/ confidence_weighted_freq .py - code/
archive/ , Python, 347 lineshemi_detector_experiment s/ eval_freq_improvements.p y - code/
archive/ , Python, 304 lineshemi_detector_experiment s/ eval_v3_contest.py - code/
archive/ , Python, 374 lineshemi_detector_experiment s/ exp0_baseline.py - code/
archive/ , Python, 217 lineshemi_detector_experiment s/ full_comparison.py - code/
archive/ , Python, 1,063 lineshemi_detector_experiment s/ gpd_freq_contest.py - code/
archive/ , Python, 716 lineshemi_detector_experiment s/ optimization_swarm.py - code/
archive/ , Python, 337 lineshemi_detector_experiment s/ pretrain_mae.py - code/
archive/ , Python, 581 lineshemi_detector_experiment s/ retrain_contest.py - code/
archive/ , Python, 2,088 lineshemi_detector_experiment s/ run_e4_e7_experiments.py - code/
archive/ , Python, 1,338 lineshemi_detector_experiment s/ run_e5_e6_e7.py - code/
archive/ , Python, 434 lineshemi_detector_experiment s/ train_hemi_cet_v3.py - code/
archive/ , Python, 755 lineshemi_detector_experiment s/ train_variants.py - code/
archive/ , Python, 194 linesipi_histogram.py - code/
archive/ , Python, 1 linelateralization_contest/ __init__.py - code/
archive/ , Python, 216 lineslateralization_contest/ apply_review_and_reeval. py - code/
archive/ , Python, 105 lineslateralization_contest/ base.py - code/
archive/ , Python, 586 lineslateralization_contest/ features_v3.py - code/
archive/ , Python, 281 lineslateralization_contest/ generate_freq_review_vie wer.py - code/
archive/ , Python, 520 lineslateralization_contest/ generate_lateral_reviewe r.py - code/
archive/ , Python, 385 lineslateralization_contest/ generate_laterality_view er.py - code/
archive/ , Python, 580 lineslateralization_contest/ generate_lpd_laterality_ reviewer.py - code/
archive/ , Python, 507 lineslateralization_contest/ generate_lrda_side_viewe r.py - code/
archive/ , Python, 391 lineslateralization_contest/ harness.py - code/
archive/ , Python, 401 lineslateralization_contest/ harness_v2.py - code/
archive/ , Python, 301 lineslateralization_contest/ harness_v4.py - code/
archive/ , Python, 168 lineslateralization_contest/ methods_advanced.py - code/
archive/ , Python, 187 lineslateralization_contest/ methods_fit.py - code/
archive/ , Python, 113 lineslateralization_contest/ methods_power.py - code/
archive/ , Python, 140 lineslateralization_contest/ methods_rhythm.py - code/
archive/ , Python, 93 lineslateralization_contest/ methods_v2_ensembles.py - code/
archive/ , Python, 155 lineslateralization_contest/ methods_v2_tier1.py - code/
archive/ , Python, 138 lineslateralization_contest/ methods_v2_tier2.py - code/
archive/ , Python, 337 lineslateralization_contest/ methods_v2_tier3.py - code/
archive/ , Python, 402 lineslateralization_contest/ methods_v4_unified.py - code/
archive/ , Python, 1,111 lineslateralization_contest/ methods_v4_unified2.py - code/
archive/ , Python, 156 lineslateralization_contest/ methods_waveform.py - code/
archive/ , Python, 530 lineslateralization_contest/ ml_classify.py - code/
archive/ , Python, 308 lineslateralization_contest/ ml_classify_v3.py - code/
archive/ , Python, 297 lineslateralization_contest/ optimize_bandpass.py - code/
archive/ , Python, 72 lineslateralization_contest/ run_batch.py - code/
archive/ , Python, 70 lineslateralization_contest/ run_batch_v2.py - code/
archive/ , Python, 132 lineslateralization_contest/ run_ensembles_v2.py - code/
archive/ , Python, 202 lineslateralization_contest/ run_multiband.py - code/
archive/ , Python, 114 lineslateralization_contest/ run_v4.py - code/
archive/ , Python, 470 lineslrda_laterality_v2_eval. py - code/
archive/ , Python, 433 lineslrda_laterality_v3_eval. py - code/
archive/ , Python, 372 linesml_regression.py - code/
archive/ , Python, 130 linesoptimization_harnesses/ run_rda_contest.py - code/
archive/ , Python, 157 linesoptimization_harnesses/ run_spatial_contest.py - code/
archive/ , Python, 623 linesoptimize_lg_classificati on.py - code/
archive/ , Python, 447 linespd_channel_detector_vari ants/ build_channel_dataset.py - code/
archive/ , Python, 156 linespd_channel_detector_vari ants/ channel_features.py - code/
archive/ , Python, 174 linespd_channel_detector_vari ants/ train_baseline.py - code/
archive/ , Python, 772 linespd_channel_detector_vari ants/ train_cnn.py - code/
archive/ , Python, 828 linespd_channel_detector_vari ants/ train_cnn_attention_hifr eq.py - code/
archive/ , Python, 1,259 linespd_channel_detector_vari ants/ train_cnn_attention_phas e2.py - code/
archive/ , Python, 652 linespd_channel_detector_vari ants/ train_cnn_attention_v3.p y - code/
archive/ , Python, 606 linespd_channel_detector_vari ants/ train_discharge_detector .py - code/
archive/ , Python, 690 linespd_channel_detector_vari ants/ validate_quality_predict or.py - code/
archive/ , Python, 677 linespd_channel_detector_vari ants/ visualize_discharges.py - code/
archive/ , Python, 1 linerda_contest/ __init__.py - code/
archive/ , Python, 60 linesrda_contest/ base.py - code/
archive/ , Python, 334 linesrda_contest/ harness.py - code/
archive/ , Python, 371 linesrda_contest/ methods_ar.py - code/
archive/ , Python, 242 linesrda_contest/ methods_cnn.py - code/
archive/ , Python, 302 linesrda_contest/ methods_existing.py - code/
archive/ , Python, 274 linesrda_contest/ methods_spectral.py - code/
archive/ , Python, 242 linesrda_contest/ methods_v2.py - code/
archive/ , Python, 286 linesrda_contest/ methods_v3.py - code/
archive/ , Python, 398 linesrda_contest/ methods_v4.py - code/
archive/ , Python, 444 linesrda_contest/ methods_v5.py - code/
archive/ , Python, 97 linesregenerate_images.py - code/
archive/ , Python, 184 linesrun_baselines.py - code/
archive/ , Python, 1 linespatial_contest/ __init__.py - code/
archive/ , Python, 124 linesspatial_contest/ base.py - code/
archive/ , Python, 511 linesspatial_contest/ harness.py - code/
archive/ , Python, 62 linesspatial_contest/ methods_baseline.py - code/
archive/ , Python, 250 linesspatial_contest/ methods_crosschannel.py - code/
archive/ , Python, 226 linesspatial_contest/ methods_hybrid.py - code/
archive/ , Python, 267 linesspatial_contest/ methods_model.py - code/
archive/ , Python, 170 linesspatial_contest/ methods_signal.py - code/
archive/ , Python, 107 linesspatial_contest/ run_comparison.py - code/
archive/ , Python, 420 linesspatial_contest/ voltage_csd_comparison.p y - code/
archive/ , Python, 583 linessuperseded_labelers/ generate_freq_annotation _viewer.py - code/
archive/ , Python, 124 linessuperseded_labelers/ generate_hpp_labeler_rem aining.py - code/
archive/ , Python, 270 linessuperseded_labelers/ generate_rda_freq_viewer .py - code/
archive/ , Python, 868 linessuperseded_labelers/ generate_rda_labeling.py - code/
archive/ , Python, 250 linessuperseded_labelers/ generate_rda_review_view er.py - code/
archive/ , Python, 897 linessuperseded_labelers/ generate_spatial_extent_ labeler.py - code/
archive/ , Python, 33 linesupdate_dashboard_v2.py - code/
archive/ , Python, 323 linesw05_v2.py - code/
bipd_detector.py , Python, 968 lines, 1 match - code/
browse_results.py , Python, 595 lines - code/
cet_model/ , Python, 1 line__init__.py - code/
cet_model/ , Python, 837 linesauto_pipeline.py - code/
cet_model/ , Python, 235 lines, 1 matchcet.py - code/
cet_model/ , Python, 665 linescet_hpp_pipeline.py - code/
cet_model/ , Python, 303 lineseval_all_methods.py - code/
cet_model/ , Python, 170 lineseval_consolidated.py - code/
cet_model/ , Python, 562 lineseval_improvements.py - code/
cet_model/ , Python, 461 lineseval_iterative_freq.py - code/
cet_model/ , Python, 561 lineseval_learned_combine.py - code/
cet_model/ , Python, 350 lineseval_posthoc_filter.py - code/
cet_model/ , Python, 1,091 linesgenerate_evidence_compar ison_viewer.py - code/
cet_model/ , Python, 80 lineslrda_crnn.py - code/
cet_model/ , Python, 202 lines, 1 matchlrda_crnn_dataset.py - code/
cet_model/ , Python, 812 linesparameter_sweep.py - code/
cet_model/ , Python, 677 linestrain_cet.py - code/
cet_model/ , Python, 399 lines, 1 matchtrain_cet_v3.py - code/
cet_model/ , Python, 202 linestrain_lrda_crnn.py - code/
data_management/ , Python, 215 linesapply_lrda_corrections.p y - code/
data_management/ , Python, 52 linesbackup_labels.py - code/
data_management/ , Python, 389 lines, 1 matchbuild_grond_h5_bank.py - code/
data_management/ , Python, 445 linesbuild_round3_viewer.py - code/
data_management/ , Python, 479 linesbuild_round4_viewer.py - code/
data_management/ , Python, 427 linesbuild_segment_labels.py - code/
data_management/ , Python, 254 linesbuild_template_banks.py - code/
data_management/ , Python, 497 linesdata_cleanup.py - code/
data_management/ , Python, 243 linesdownload_iiic_segments.p y - code/
data_management/ , Python, 622 linesdownload_s3_segments.py - code/
data_management/ , Python, 164 linesextract_bank_gt_aligned. py - code/
data_management/ , Python, 118 linesextract_bank_unfiltered. py - code/
data_management/ , Python, 72 linesfix_annotation_paths.py - code/
data_management/ , Python, 283 linesfix_recovery_offsets.py - code/
data_management/ , Python, 369 linesharvest_bipd_segments.py - code/
data_management/ , Python, 166 linesharvest_iiic_other_segme nts.py - code/
data_management/ , Python, 567 linesharvest_iiic_rda_segment s.py - code/
data_management/ , Python, 587 linesharvest_lpd_segments.py - code/
data_management/ , Python, 717 linesharvest_seizure_lpd_cand idates.py - code/
data_management/ , Python, 348 linesingest_independent_exper t_v1.py - code/
data_management/ , Python, 314 lineslabel_status_report.py - code/
data_management/ , Python, 241 linesmatch_per_patient_unfilt ered.py - code/
data_management/ , Python, 185 linesmatch_recovered_to_bank. py - code/
data_management/ , Python, 481 linesmigrate_to_unified_label s.py - code/
data_management/ , Python, 88 linesprecompute_bank_envelope s.py - code/
data_management/ , Python, 149 linesrecover_from_alex_data.p y - code/
data_management/ , Python, 242 linesrecover_irr_cohort_from_ s3.py - code/
data_management/ , Python, 223 linesrecover_missing_pd_segme nts.py - code/
data_management/ , Python, 364 linesrecover_pathC_window_fin der.py - code/
data_management/ , Python, 261 linesrecover_pd_segments_from _s3.py - code/
data_management/ , Python, 230 linesrecover_phase3_irr_canon ical.py - code/
data_management/ , Python, 272 linesredownload_monopolar.py - code/
data_management/ , Python, 568 linesreorganize_data.py - code/
data_management/ , Python, 230 linesrestore_monopolar_canoni cal.py - code/
data_management/ , Python, 300 linesscan_eeg_bank_for_missin g_pids.py - code/
data_management/ , Python, 846 linesselect_rda_candidates.py - code/
data_management/ , Python, 324 linesselect_round3_lpd.py - code/
data_management/ , Python, 467 linesselect_round4_candidates .py - code/
discharge_detector.py , Python, 879 lines, 4 matches - code/
discharge_timing.py , Python, 533 lines - code/
e2e_model/ , Python, 1 line__init__.py - code/
e2e_model/ , Python, 263 linesdataset.py - code/
e2e_model/ , Python, 280 linesevaluate_e2e.py - code/
e2e_model/ , Python, 193 linesloss.py - code/
e2e_model/ , Python, 317 linesmodel.py - code/
e2e_model/ , Python, 71 linesprecompute_hpp.py - code/
e2e_model/ , Python, 424 linestrain_e2e.py - code/
eval_rda_spatial.py , Python, 482 lines - code/
evaluation/ , Python, 1,030 lines, 1 matchanalyze_independent_expe rt_v1.py - code/
evaluation/ , Python, 144 lines, 1 matchanalyze_phlbsz_cohort.py - code/
evaluation/ , Python, 106 linescompare_retrain_to_publi shed.py - code/
evaluation/ , Python, 352 lines, 1 matcheval_3way_classification .py - code/
evaluation/ , Python, 304 lineseval_cnn_weighted_eviden ce.py - code/
evaluation/ , Python, 392 lineseval_rda_expanded.py - code/
evaluation/ , Python, 435 lineseval_subtype_classificat ion.py - code/
evaluation/ , Python, 275 lineseval_unified_pipeline.py - code/
evaluation/ , Python, 446 linesevaluate_methods.py - code/
evaluation/ , Python, 303 linesextract_frequency_spatia l_extent.py - code/
evaluation/ , Python, 123 linesextract_pd_with_laterali ty.py - code/
evaluation/ , Python, 124 linesextract_with_laterality. py - code/
evaluation/ , Python, 199 linesfreq_table_cis.py - code/
evaluation/ , Python, 127 linesgenerate_v12_predictions .py - code/
evaluation/ , Python, 87 linesgenerate_v14_predictions .py - code/
evaluation/ , Python, 293 lineslrda_disagreement_triage .py - code/
evaluation/ , Python, 253 lineslrda_features.py - code/
evaluation/ , Python, 181 lineslrda_freq_disagreements. py - code/
evaluation/ , Python, 241 lines, 2 matcheslrda_freq_hyperparam_swe ep.py - code/
evaluation/ , Python, 259 lineslrda_laterality_disagree ment_triage.py - code/
evaluation/ , Python, 205 lines, 1 matchlrda_laterality_features .py - code/
evaluation/ , Python, 504 lines, 2 matcheslrda_laterality_v13_eval .py - code/
evaluation/ , Python, 297 lineslrda_laterality_v14_eval .py - code/
evaluation/ , Python, 225 lineslrda_top_disagreements.p y - code/
evaluation/ , Python, 128 linesrefresh_algo_freq_rda.py - code/
evaluation/ , Python, 164 linesrefresh_pdchar_freq.py - code/
evaluation/ , Python, 177 linesrefresh_pdchar_spatial.p y - code/
evaluation/ , Python, 147 linesrefresh_tautan_freq.py - code/
evaluation/ , Python, 279 linesrun_all_inference.py - code/
evaluation/ , Shell, 94 linesrun_inference_refresh.sh - code/
evaluation/ , Shell, 126 linesrun_phase1_retrain.sh - code/
evaluation/ , Python, 199 linestest_detectors.py - code/
evaluation/ , Python, 148 linestest_laterality_logic.py - code/
evaluation/ , Python, 216 linestrain_lrda_hard_case_cla ssifier.py - code/
evaluation/ , Python, 215 linestrain_lrda_laterality_cl assifier.py - code/
evaluation/ , Python, 148 linesv9_gated_hybrid.py - code/
evaluation/ , Python, 184 linesvalidate_laterality.py - code/
evaluation/ , Python, 179 linesvalidate_pd_laterality.p y - code/
evaluation/ , Python, 373 lines, 1 matchverify_c1_repro.py - code/
evaluation/ , Python, 181 linesverify_local_data.py - code/
evaluation/ , Python, 301 lines, 1 matchverify_v12_holdout.py - code/
generate_test_images.py , Python, 579 lines - code/
generators/ , Python, 397 linesdashboards/ generate_dataset_dashboa rd.py - code/
generators/ , Python, 655 linesdashboards/ generate_mw_catchup.py - code/
generators/ , Python, 725 linesdashboards/ generate_sahar_package.p y - code/
generators/ , Python, 765 lineslabeling/ generate_bipd_labeler.py - code/
generators/ , Python, 2,047 lineslabeling/ generate_expert_freq_lab eler.py - code/
generators/ , Python, 1,985 lineslabeling/ generate_freq_discrepanc y_reviewer.py - code/
generators/ , Python, 885 lineslabeling/ generate_hpp_labeler.py - code/
generators/ , Python, 501 lineslabeling/ generate_laterality_labe ler.py - code/
generators/ , Python, 1,276 lineslabeling/ generate_lpd_laterality_ timing_labeler.py - code/
generators/ , Python, 1,411 lineslabeling/ generate_lrda_labeler.py - code/
generators/ , Python, 1,474 lineslabeling/ generate_pd_freq_timing_ labeler.py - code/
generators/ , Python, 1,237 lines, 1 matchlabeling/ generate_rda_freq_labele r.py - code/
generators/ , Python, 1,209 lines, 2 matcheslabeling/ generate_rda_freq_spatia l_labeler.py - code/
generators/ , Python, 1,451 lineslabeling/ generate_spatial_labeler .py - code/
generators/ , Python, 636 linesreview/ generate_bipd_reviewer.p y - code/
generators/ , Python, 324 linesreview/ generate_bipd_screener.p y - code/
generators/ , Python, 458 linesreview/ generate_consistency_vie wer.py - code/
generators/ , Python, 527 linesreview/ generate_disagreement_vi ewer.py - code/
generators/ , Python, 321 linesreview/ generate_expert_review.p y - code/
generators/ , Python, 478 linesreview/ generate_label_review_vi ewer.py - code/
generators/ , Python, 628 linesreview/ generate_laterality_view er.py - code/
generators/ , Python, 886 linesreview/ generate_misclass_review er.py - code/
generators/ , Python, 215 linesreview/ generate_rejected_lpd_re viewer.py - code/
hemi_detector/ , Python, 1 line__init__.py - code/
hemi_detector/ , Python, 501 linesdataset.py - code/
hemi_detector/ , Python, 501 lineseval_hemi_cet.py - code/
hemi_detector/ , Python, 449 linesevaluate.py - code/
hemi_detector/ , Python, 175 lineshemi_cet.py - code/
hemi_detector/ , Python, 827 linesmodel.py - code/
hemi_detector/ , Python, 667 linestrain.py - code/
hemi_detector/ , Python, 506 lines, 1 matchtrain_hemi_cet.py - code/
imageGeneration/ , Python, 1 line__init__.py - code/
imageGeneration/ , Python, 172 linesplot_events.py - code/
irr_analysis_fulldataset , Jupyter, 1,233 lines.ipynb - code/
irr_analysis_onagreement , Jupyter, 1,279 lines.ipynb - code/
label_pipeline/ , Python, 1 line__init__.py - code/
label_pipeline/ , Python, 426 linesbuild_channel_pseudolabe ls.py - code/
label_pipeline/ , Python, 138 linesevaluate_hpp.py - code/
label_pipeline/ , Python, 1,082 linesgenerate_freq_disagreeme nt_viewer.py - code/
label_pipeline/ , Python, 771 linesgenerate_spatial_review_ viewer.py - code/
label_pipeline/ , Python, 921 linesgenerate_timing_correcti on_viewer.py - code/
label_pipeline/ , Python, 760 linesgenerate_timing_review_v iewer.py - code/
label_pipeline/ , Python, 599 lineshpp_discharge_marking.py - code/
label_pipeline/ , Python, 172 linesmerge_timing_corrections .py - code/
label_pipeline/ , Python, 258 linespredict_channel_involvem ent.py - code/
optimization_harness.py , Python, 269 lines - code/
optimization_harness_v2. , Python, 1,039 linespy - code/
pd_channel_detector/ , Python, 1 line__init__.py - code/
pd_channel_detector/ , Python, 293 linesbuild_channel_dataset_v5 .py - code/
pd_channel_detector/ , Python, 165 lines, 1 matchchannel_cnn.py - code/
pd_channel_detector/ , Python, 124 linesdischarge_detector.py - code/
pd_channel_detector/ , Python, 958 linestrain_cnn_attention.py - code/
pd_consistency_score.py , Python, 248 lines - code/
pd_detector/ , Python, 5 lines__init__.py - code/
pd_detector/ , Python, 244 linescalculate.py - code/
pd_detector/ , Python, 227 lineseegfilt.py - code/
pd_detector/ , Python, 436 linesfind_events.py - code/
pd_detector/ , Python, 300 linespd_detect.py - code/
pd_detector/ , Python, 171 linespd_detect_alternate.py - code/
pd_detector/ , Python, 104 linesplot.py - code/
pd_detector/ , Python, 336 linesutils.py - code/
pd_detector/ , Python, 336 linesutils2.py - code/
pd_detector_alternate/ , Python, 2 lines__init__.py - code/
pd_detector_alternate/ , Python, 424 linespd_detect_alternate.py - code/
pd_pointiness_acf.py , Python, 324 lines - code/
pd_profiler.py , Python, 391 lines, 2 matches - code/
pdnet_v2/ , Python, 1 line__init__.py - code/
pdnet_v2/ , Python, 520 linesdataset.py - code/
pdnet_v2/ , Python, 287 linesevaluate.py - code/
pdnet_v2/ , Python, 233 linesmodel.py - code/
pdnet_v2/ , Python, 556 linestrain.py - code/
rda_detector/ , Python, 3 lines__init__.py - code/
rda_detector/ , Python, 519 linesauto_label_rda_waves.py - code/
rda_detector/ , Python, 988 linesgenerate_rda_wave_review er.py - code/
rda_detector/ , Python, 193 linesrda1a_fft.py - code/
rda_detector/ , Python, 320 linesrda1b_fft.py - code/
rda_detector/ , Python, 277 linesrda2_hht.py - code/
rda_optimization_harness , Python, 834 lines.py - code/
rda_spatial_extent.py , Python, 221 lines, 1 match - code/
unified_model/ , Python, 1 line__init__.py - code/
unified_model/ , Python, 316 lines, 1 matchdataset.py - code/
unified_model/ , Python, 158 lines, 1 matchmodel.py - code/
unified_model/ , Python, 808 linestrain.py - code/
update_dashboard.py , Python, 32 lines - code/
update_rda_dashboard.py , Python, 33 lines - code/
visualization/ , Python, 536 linesfigure_pairwise_agreemen t.py - code/
visualization/ , Python, 682 linesreproduce_paper_figures. py - code/
visualization/ , Python, 423 linesreproduce_results.py - code/
visualization/ , Python, 429 linesvisualize_discharge_timi ng.py - code/
visualization/ , Python, 176 linesvisualize_embeddings.py - code/
visualization/ , Python, 150 linesvisualize_output.py - paper_materials/
archive/ , Python, 156 linescomposite_fig2.py - paper_materials/
archive/ , Python, 105 linesextract_figure_data.py - paper_materials/
archive/ , Python, 669 linesgenerate_fig2_pd_pipelin e.py - paper_materials/
archive/ , Python, 651 linesgenerate_fig2_pd_pipelin e_backup.py - paper_materials/
archive/ , Python, 493 linesgenerate_fig3_rda_pipeli ne.py - paper_materials/
archive/ , Python, 453 linesgenerate_fig5.py - paper_materials/
archive/ , Python, 650 linesgenerate_figure_data.py - paper_materials/
archive/ , Python, 859 linesgenerate_rda_topo_viewer .py - paper_materials/
archive/ , Python, 216 linesimprove_figure_with_gemi ni.py - paper_materials/
archive/ , Python, 519 linesoptimization_logs/ characterization_round0. py - paper_materials/
archive/ , Python, 540 linesoptimization_logs/ characterization_round1. py - paper_materials/
archive/ , Python, 548 linesoptimization_logs/ characterization_round2. py - paper_materials/
archive/ , Python, 335 linesoptimization_logs/ fig1_examples_round0.py - paper_materials/
archive/ , Python, 346 linesoptimization_logs/ fig1_examples_round1.py - paper_materials/
archive/ , Python, 369 linesoptimization_logs/ fig1_examples_round2.py - paper_materials/
archive/ , Python, 571 linesoptimization_logs/ fig2_target/ code_round0.py - paper_materials/
archive/ , Python, 589 linesoptimization_logs/ fig2_target/ code_round1.py - paper_materials/
archive/ , Python, 639 linesoptimization_logs/ fig2_target/ code_round2.py - paper_materials/
archive/ , Python, 542 linesoptimization_logs/ pd_pipeline_round0.py - paper_materials/
archive/ , Python, 566 linesoptimization_logs/ pd_pipeline_round1.py - paper_materials/
archive/ , Python, 568 linesoptimization_logs/ pd_pipeline_round2.py - paper_materials/
archive/ , Python, 576 linesoptimization_logs/ pd_pipeline_round3.py - paper_materials/
archive/ , Python, 470 linesoptimization_logs/ rda_pipeline_round0.py - paper_materials/
archive/ , Python, 500 linesoptimization_logs/ rda_pipeline_round1.py - paper_materials/
archive/ , Python, 504 linesoptimization_logs/ rda_pipeline_round2.py - paper_materials/
archive/ , Python, 422 linesoptimization_logs/ render_figures_round0.py - paper_materials/
archive/ , Python, 459 linesoptimization_logs/ render_figures_round1.py - paper_materials/
archive/ , Python, 504 linesoptimization_logs/ render_figures_round2.py - paper_materials/
archive/ , Python, 520 linesoptimization_logs/ render_figures_round3.py - paper_materials/
archive/ , Python, 213 linesoptimize_fig2_to_target. py - paper_materials/
archive/ , Python, 343 linesoptimize_figures.py - paper_materials/
build_fig2.py , Python, 624 lines - paper_materials/
build_fig3.py , Python, 479 lines - paper_materials/
build_fig_irr_bars.py , Python, 433 lines, 2 matches - paper_materials/
draw_panel_b.py , Python, 125 lines, 1 match - paper_materials/
draw_panel_b_rda.py , Python, 191 lines, 2 matches - paper_materials/
generate_all_figures.py , Python, 184 lines - paper_materials/
generate_all_tables.py , Python, 117 lines - paper_materials/
generate_discharge_topo_ , Python, 887 lines, 2 matchesviewer.py - paper_materials/
generate_fig0_examples.p , Python, 369 lines, 1 matchy - paper_materials/
generate_fig6.py , Python, 212 lines - paper_materials/
generate_fig_independent , Python, 105 lines_expert_freq.py - paper_materials/
generate_fig_irr.py , Python, 1,136 lines - paper_materials/
generate_fig_spatial_sca , Python, 262 linestter.py - paper_materials/
generate_threshold_sweep , Python, 486 lines.py - paper_materials/
independent_expert_tasks , Python, 264 lines/ select_cases.py - paper_materials/
precompute_spatial_cache , Python, 171 lines.py - paper_materials/
render_figures.py , Python, 565 lines - paper_materials/
spatial_cache_utils.py , Python, 77 lines - paper_materials/
tables/ , Python, 260 lines, 1 matchgenerate_table1.py - paper_materials/
tables/ , Python, 274 linesgenerate_table3.py - paper_materials/
tables/ , Python, 115 lines, 1 matchgenerate_table4.py - paper_materials/
tables/ , Python, 193 linesgenerate_table5.py - paper_materials/
tables/ , Python, 119 linesgenerate_table6.py - paper_materials/
tables/ , Python, 144 lines, 1 matchgenerate_table7.py - test_case_images_archive
/ , JavaScript, 1 linemetadata.js - LICENSE.txt, License, 413 lines
- README.md, Text, 461 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 434 scripts, each with its path and the digest of its content;
- 40 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
The data that support the findings of this study are openly available at the following URL/
Algorithm methods and contests available at: https://
Code. The source code for the GROND system—both pipelines, all algorithm contest entries, the montage-ablation scripts, and the figure- and table-generation scripts used to produce this manuscript—is openly available at: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 4 November 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 13 authors, 5 keywords, 7 MeSH terms, 5 funders, 26 references.
Cite
This paper
Jing, J., Sun, C., Zhang, T., Byrd, M., Tăuţan, A., Basovic, L., Hadar, P. N., Fernandes, M. P., Goldenholz, D., Kim, J., Struck, A. F., Zafar, S. F., & Westover, M. B. (2026). GROND: automated characterization of periodic and rhythmic EEG patterns. Journal of neural engineering, 23(6), 066002. https://
BibTeX
@article{jing2026grond,
author = {Jing, Jin and Sun, Chenxi and Zhang, Tianyu and Byrd, Matthew and Tăuţan, Alexandra–Maria and Basovic, Lara and Hadar, Peter N and Fernandes, Marta P and Goldenholz, Daniel and Kim, Jennifer and Struck, Aaron F and Zafar, Sahar F and Westover, M Brandon},
title = {{GROND: automated characterization of periodic and rhythmic EEG patterns}},
journal = {Journal of neural engineering},
year = {2026},
month = nov,
volume = {23},
number = {6},
pages = {066002},
publisher = {IOP Publishing},
issn = {1741-2560},
doi = {10.1088/
url = {https://
pmid = {42863487},
pmcid = {PMC13729739}
}
RIS
TY - JOUR
AU - Jing, Jin
AU - Sun, Chenxi
AU - Zhang, Tianyu
AU - Byrd, Matthew
AU - Tăuţan, Alexandra–Maria
AU - Basovic, Lara
AU - Hadar, Peter N
AU - Fernandes, Marta P
AU - Goldenholz, Daniel
AU - Kim, Jennifer
AU - Struck, Aaron F
AU - Zafar, Sahar F
AU - Westover, M Brandon
TI - GROND: automated characterization of periodic and rhythmic EEG patterns
T2 - Journal of neural engineering
J2 - J Neural Eng
PY - 2026
DA - 2026/
VL - 23
IS - 6
SP - 066002
SN - 1741-2560
PB - IOP Publishing
DO - 10.1088/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1088/
"type": "article-journal",
"title": "GROND: automated characterization of periodic and rhythmic EEG patterns",
"container-title": "Journal of neural engineering",
"author": [
{
"family": "Jing",
"given": "Jin"
},
{
"family": "Sun",
"given": "Chenxi"
},
{
"family": "Zhang",
"given": "Tianyu"
},
{
"family": "Byrd",
"given": "Matthew"
},
{
"family": "Tăuţan",
"given": "Alexandra–Maria"
},
{
"family": "Basovic",
"given": "Lara"
},
{
"family": "Hadar",
"given": "Peter N"
},
{
"family": "Fernandes",
"given": "Marta P"
},
{
"family": "Goldenholz",
"given": "Daniel"
},
{
"family": "Kim",
"given": "Jennifer"
},
{
"family": "Struck",
"given": "Aaron F"
},
{
"family": "Zafar",
"given": "Sahar F"
},
{
"family": "Westover",
"given": "M Brandon"
}
],
"container-title-short":
"volume": "23",
"issue": "6",
"page": "066002",
"DOI": "10.1088/
"PMID": "42863487",
"PMCID": "PMC13729739",
"ISSN": "1741-2560",
"publisher": "IOP Publishing",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
11,
3
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1111/epi.18487 [code]
- Harvard Electroencephalography Database: A comprehensive clinical electroencephalographic resource from four Boston hospitals.Journal: EpilepsiaIn common: MNE-Python, PyTorch, scikit-learn, 4 other tools, EEG, 3 references, 4 authors
- [2] doi:10.1111/epi.18431 [code]
- Automated detection of interictal epileptiform discharges with few electroencephalographic channels.Journal: EpilepsiaIn common: PyTorch, scikit-learn, pandas, 3 other tools, EEG, 1 reference, 2 authors
- [3] doi:10.1186/s40708-025-00270-1 [code]
- Biotuner: A python toolbox integrating music theory and signal processing for harmonic analysis of physiological and natural time series.Journal: Brain informaticsIn common: specparam (formerly FOOOF), Pingouin, Numba, 9 other tools, EEG, 1 reference
- [4] doi:10.3390/biomedicines11030685 [code]
- Sample Size Analysis for Machine Learning Clinical Validation Studies.Journal: BiomedicinesIn common: seaborn, scikit-learn, pandas, 3 other tools, 2 authors
- [5] doi:10.7554/elife.101071 [code]
- Modulation of aperiodic EEG activity provides sensitive index of cognitive state changes during working memory task.Journal: eLifeIn common: specparam (formerly FOOOF), Pingouin, Numba, 9 other tools, EEG
- [6] doi:10.7554/elife.110588 [code]
- Opening the black box toward a modular approach to spike sorting.Journal: eLifeIn common: LightGBM, Numba, scikit-image, 9 other tools, methods / tools
- [7] doi:10.64898/2026.01.23.701239 [code]
- Opening the black box: a modular approach to spike sortingJournal: bioRxiv (preprint)In common: LightGBM, Numba, scikit-image, 9 other tools, methods / tools
- [8] doi:10.1111/jsr.70309 [code]
- An Electroencephalographic Study of Sleep Spindle and Infraslow Oscillation in Children With Autism Spectrum Disorder.Journal: Journal of sleep researchIn common: Pingouin, MNE-Python, seaborn, 5 other tools, EEG, author M Brandon Westover
- [9] doi:10.1590/0004-282x-anp-2022-s113
- Status epilepticus: review on diagnosis, monitoring and treatment.Journal: Arquivos de neuro-psiquiatriaIn common: EEG, 7 references
- [10] doi:10.1016/j.isci.2023.107522 [code]
- Deep learning for risk-based stratification of cognitively impaired individuals.Journal: iScienceIn common: LightGBM, Pingouin, scikit-image, 8 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 434 scripts, and 40 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:e4be03f17c23d07e…
Add the badge to the code's README
If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.
Markdown
<!-- oscr-badge:start -->
[](https://openscicode.org/badge/doi/10.1088/1741-2552/aeb331)
<!-- oscr-badge:end -->reStructuredText
.. oscr-badge:start
.. image:: https://openscicode.org/badge/doi/10.1088/1741-2552/aeb331.svg
:target: https://openscicode.org/badge/doi/10.1088/1741-2552/aeb331
:alt: OSCR
.. oscr-badge:endHTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1088/1741-2552/aeb331"><img src="https://openscicode.org/badge/doi/10.1088/1741-2552/aeb331.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".
If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.
Report a mismatch between the code and the paper · Report a code error (signed in)
Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.
Report a reproduction failure (signed in)
Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
