EEG seizure prediction · Epilepsy pipeline

SparseEvidenceGNNCore — forward pass (dense_edge_gru config)

Architecture read of Epilepsy/pipelines/cwt_gnn_classifiers.py — the config run_pipelines.py's DENSE_EDGE_GRU_PARAMS actually drives: event_mode="dense", dense_edge_temporal_mode="rnn". One raw EEG epoch in, one set of class logits out.

Raw EEG epoch Multichannel EEG recording window non-trainable · per channel, independently Continuous wavelet transform (CWT) Each channel convolved with a Morlet wavelet across scales — one complex time-frequency map per channel, computed before any cross-channel step: Wx(f, t) = x(τ) · ψ*f,t(τ) dτ one map per channel non-trainable · per channel-pair Cross-wavelet coherence (WCT) Combines each pair's CWTs into a smoothed, normalized coherence magnitude and phase, across frequency and time: R2xy(f,t) = |S(Wxy)|2 S(|Wx|2) · S(|Wy|2) φxy(f,t) = arg(Wxy(f,t)) This magnitude/phase pair feeds the surrogate-significance test next. channel i channel j coherence(f, t) freq ↑ time → non-trainable COI mask + surrogate significance Phase-randomized surrogate null establishes a per-(edge,freq) significance threshold. Stack: [coh, sinφ, cosφ, significance], zeroed outside the cone of influence. masked (edge effects) non-trainable · cached once/trial Smooth + downsample Gaussian smoothing, then downsampled in time before the GRU trainable _DenseEdgeGRUTemporal Per-edge nn.GRU, weight-shared (edges folded into batch dim), consumes the full downsampled sequence with memory; final hidden state = pooled edge summary. Repacks to events_padded / src / dst / freq_idx / valid_mask (one row per edge). GRU x_t h_t data flow Repack per-edge features concat( edge features [coh, sinφ, cosφ, sig] ) → per-edge message vector fed directly into sparse_message_mlp, one vector per edge message trainable sparse_message_mlp Linear layer → activation → per-edge message aggregation _aggregate_events → scatter to destination channel nodes event_aggregation ∈ {"mean","concat","gated_softmax"} — canonical run uses "concat" Produces one evidence vector per channel node (or a stack of them, for "concat") dst inactive when n_hops=1 _propagate_hops — (n_hops − 1) extra message-passing rounds. Canonical config: n_hops=1 (skipped). trainable Flatten → sparse_classifier Linear layer → class logits logits output Class logits + event_density (aux) event_density: a fixed constant in "dense" mode — not a meaningful signal here, kept for API parity with "sparse" mode. flat (aux) predicted class illustrative supplements — not a code diagram what the data looks like The graph the GRU messages ride on Channels are nodes. Every ordered pair (i,j) is its own directed edge, carrying [coh, sinφ, cosφ, sig]. C1 C2 C3 C4 C5 C6 C6→C1 and C1→C6 are separate edges — opposite-sign phase, same coherence magnitude. Every channel pair contributes two directed edges. what the data looks like How the per-edge GRU recurs One shared GRU cell, stepped across downsampled timesteps per edge. Hidden state feeds back as input. GRU cell (shared across edges) x_t (edge feature at t) h_t h_(t−1) fed back as next input unrolled over time: h0 h1 h2 h_n final hidden state = pooled edge summary, passed on to repack per-edge features above. Orange loop = the recurrence: each step's output becomes part of the next step's input. what the data looks like Wavelet coherence, cross-spectrum Two channel signals → per-frequency phase & coherence between them, at every timestep. ch i ch j coherence(f, t): rows = frequency, cols = time — one such grid per directed edge, per trial. phase(i→j) phase(j→i) phase(j→i) = −phase(i→j), same |coherence| — the antisymmetric part the direction-bug fix relies on.
Trainable module
Non-trainable / cached
Tensor / data flow
Aggregation (scatter to nodes)
Output
Source: Epilepsy/pipelines/cwt_gnn_classifiers.py (SparseEvidenceGNNCore.forward, _dense_edge_features, _DenseEdgeGRUTemporal) — merged 2026-08-16 from the former xwt_phase_gnn_classifier.py / sparse_evidence_gnn_classifier.py split. This diagram covers only the code path run_pipelines.py actually instantiates for the epilepsy dense-edge-GRU pipeline; the XWTPhaseGNN* classes and the "sparse" / "temporal_graph" event modes earlier in the same file are separate, unused-here code paths kept for parity with the BCI sibling file.