What’s new#
Current 1.8.1 (2026-08-31)#
Enhancements#
braindecode.models.BrainBERT,EpiNT,Brant,PopulationTransformer,BaRISTA,DIVER1andMVPFormeraccept ach_maskof real channels, as returned bybraindecode.datasets.pad_channels_collate(), so one batch can mix recordings with different channel counts; PopT’sch_maskreplaceskey_padding_mask. Add the shared helpersbraindecode.functional.check_ch_mask(),pack_channels(),unpack_channels(),masked_mean()andmasked_group_norm()(#1487 by Julien Gadonneix).CI:
test_forward_in_dtypeno longer runs the models in float16 on the CPU; torch’s CPU float16 kernels killed the test worker without a traceback in about one run in seven (the hangs of the last days), and bfloat16 covers the same dtype checks (#1474 by Bruno Aristimunha).CI: a test that stops answering fails after 10 minutes with a traceback (
pytest-timeout), instead of the job waiting until its own timeout; twice an xdist worker went silent duringtest_forward_in_dtypeand the run idled for 40 minutes to 5 hours (#1473 by Bruno Aristimunha).CI: the shared model tests build a 2-layer ZUNA and a narrow BENDR instead of the released 172M- and 157M-parameter configurations, which took 5.6 GB per test worker to script and serialize and killed CI runners; the registry entry of a model may now carry architecture arguments next to its signal parameters. The released configurations stay covered by
test_pretrained_compat(#1472 by Bruno Aristimunha).CI:
test_core_dependencies_are_importedparsed the whole package once per installed distribution (215 s, the last test to finish); it parses once. The test job stops after 60 minutes instead of 6 hours when a worker hangs (#1471 by Bruno Aristimunha).braindecode.functional.sinusoidal_positional_encoding()takes abase(longest wavelength); PopulationTransformer and SignalJEPA build their tables with it, unchanged (#1458 by Bruno Aristimunha).Remove 59 model tests (950 lines) that only asserted output shapes or that a forward ran, every line of which the shared model suites already cover; coverage of
braindecodeis unchanged (#1467 by Bruno Aristimunha).Add
braindecode.modules.PreNormTransformerBlock(EEGConformer, MIRepNet) andPostNormTransformerBlock(CTNet, MSCFormer), replacing four pairs of private encoder classes; parameters and outputs unchanged (#1464 by Bruno Aristimunha).Add
braindecode.modules.LearnedPositionalEncoding(CTNet, MSCFormer) andSinusoidalPositionalEncoding(BIOT, MEDFormer); the four models’ parameters, buffers and outputs are unchanged (#1463 by Bruno Aristimunha).Add
braindecode.modules.PreNormBlock, the pre-norm transformer block with timm’s parameter names (norm1/attn/norm2/mlp, optionalgamma_1/gamma_2layer scale); EEGPT, EEGDINO, LUNA, NeuroRVQ and NeuroRVQTokenizer build their encoders with it, parameters and outputs unchanged (#1461 by Bruno Aristimunha).Add
braindecode.modules.TimmAttention, multi-head self-attention with timm’s parameter names (qkv/proj, BEiT’sq_bias/v_bias); EEGPT and EEGDINO use it with parameters and outputs unchanged (#1460 by Bruno Aristimunha).Add
braindecode.functional.scaled_dot_product_attention()(the shared SDPA helper, now public, withscaleand anexactop-order mode); ATCNet, AttnSleep, EEGDINO, TCFormer and TMSA-Net compute their attention through it with outputs, losses and gradients unchanged bit for bit (#1457 by Bruno Aristimunha).Add
braindecode.functional.rotary_frequencies(),rotary_angles(),apply_rotary()androtate_half(); BaRISTA, BrainOmni, DIVER-1, EpiNT, LUNA and TCFormer build and apply their rotary embeddings with them (parameters and outputs unchanged), and EEGPT loses a rotary code path it never used (#1456 by Bruno Aristimunha).Add a test that lists every private copy of a standard block (attention, transformer block, MLP, positional encoding, norm, patch embedding, residual) in
braindecode.models, so a new copy fails CI unless it is listed with a reason; the new-model checklist asks for the shared blocks (#1455 by Bruno Aristimunha).Add a test that lists every private copy of a standard block (attention, transformer block, MLP, positional encoding, norm, patch embedding, residual) in
braindecode.models, so a new copy fails CI unless it is listed with a reason; the new-model checklist asks for the shared blocks (#1455 by Bruno Aristimunha).Add
braindecode.functional.patch_count()andpad_or_crop_to_patches()(the patch-tiling helpers nine models andPatchTokenizeralready used privately), now public inbraindecode.functional.patches(#1459 by Bruno Aristimunha).braindecode.models.ZUNAtakesregisters(defaultTrue, the published encoder, unchanged).registers=Falsedrops the learned register before each patch, so the encoder runs on the patch tokens alone (half the tokens), each patch keeps its rotary position, and the head reads the patch outputs. Pretrained weights load unchanged; the register parameter is then unused and frozen (#1468 by Bruno Aristimunha).braindecode.models.ZUNAruns its rotary embedding on HPU through the Gaudi bridge’s fused pairwise RoPE kernel, as a custom op whose backward is the kernel at the opposite angle: identical outputs, gradients rounded to bfloat16 once instead of once per term (within one bfloat16 ulp), and one node insidetorch.compile. Gaudi2 train step at 21 channels x 1280 samples, batch 64: 2.61 -> 1.96 s eager, 1.78 -> 1.25-1.42 s with the transformer block compiled; CPU and other devices unchanged (#1461 by Bruno Aristimunha).braindecode.models.ZUNAruns its rotary embedding in HPU eager mode and inside compiled regions through the Gaudi bridge’s fused pairwise RoPE kernel (lazy mode keeps the formula, which its graph compiler fuses), as a custom op whose backward is the kernel at the opposite angle: identical outputs, gradients rounded to bfloat16 once instead of once per term (within one bfloat16 ulp), and one node insidetorch.compile. Gaudi2 train step at 21 channels x 1280 samples, batch 64: 2.61 -> 1.96 s eager, 1.78 -> 1.25-1.42 s with the transformer block compiled; CPU and other devices unchanged (#1461 by Bruno Aristimunha).braindecode.models.ZUNAtakesregisters(defaultTrue, the published encoder, unchanged).registers=Falsedrops the learned register before each patch, so the encoder runs on the patch tokens alone (half the tokens), each patch keeps its rotary position, and the head reads the patch outputs. Pretrained weights load unchanged; the register parameter is then unused and frozen (#1468 by Bruno Aristimunha).Add
braindecode.modules.TimmMlp, the timm-style MLP (fc1/fc2parameter names) that EEGPT and LUNA each defined privately; their parameters and outputs are unchanged (#1454 by Bruno Aristimunha).Every model has
reset_parameters(), which draws new initial weights with the model’s own scheme: the layers’ PyTorch defaults, then the model’s initialization (_init_parameters), which the constructor also runs. Initial weights at construction are unchanged for every model. A new test checks, for all registered models, that seeded construction is reproducible and finite, thatreset_parametersmatches the constructor’s initialization, and that a model’s own initialization is not a no-op (#1452 by Bruno Aristimunha).Add
braindecode.modules.Residualandbraindecode.modules.PostNormResidual, the residual connections that EEGConformer, MIRepNet, STEEGFormer, CTNet and MSCFormer each defined privately; their parameter names and outputs are unchanged (#1449 by Bruno Aristimunha).Add
braindecode.functional.init_weights_(), a per-layer-type weight initialization applied withmodel.apply. The 24 models that each wrote their own initialization loop (BaRISTA, BENDR, BrainOmni, CBraMod, CSBrain, DANCE, DuIN, EEGPT, LaBraM, LUNA, MAPA, MEDFormer, MVPFormer, NeuroPose, NeuroRVQ, PBT, SensingDynamics, SPARCNet, STEEGFormer, TCFormer, VEMG2Pose, …) now declare their rule with it; the initial weights of every model are unchanged (#1448 by Bruno Aristimunha).braindecode.models.ZUNA: a channel without a location takes the standard 10-05 position of its name (with a warning) instead of raising, and ZUNA warns whensfreqis not its pretraining 256 Hz or when the coordinates are not in metres (they were silently clipped); channels with locations give the same model and output (#1437 by Bruno Aristimunha).braindecode.models.BrainBERTtakeson_non_divisible(default"pad"): a window too short for the centre-frame pooling (n_times < 1401with the defaults) gets the missing STFT frames as zeros, half before and half after the window, with a warning, instead of raising ("crop"/"error"keep raising).braindecode.models.MAPAtakes an input at another integer sampling rate and resamples it to 2048 Hz inside the model withtorchaudio.transforms.Resample, with a warning (normalization="session"still needs its 32 Hz spectrogram). Long enough BrainBERT windows and 2048 Hz MAPA inputs give the same model and output as before (#1434 by Bruno Aristimunha).braindecode.models.ZUNAaccepts windows longer thanmax_seqlenpatches (the release’s 8192 samples): the rotary table is extended to the window instead of raising; shorter windows are unchanged (#1433 by Bruno Aristimunha).The DANCE event-detection tutorial evaluates the published checkpoint and explains that its reported subject-3 score was used for epoch selection, not independent testing (#1406 by Bruno Aristimunha).
braindecode.models.NeuroRVQtakes an input at another integer sampling rate and resamples it to the modality’s rate (EEG 200 Hz) inside the model withtorchaudio.transforms.Resample, with a warning, instead of raising; andon_missing="drop"leaves out input channels with no pretrained slot (default"error"). At 200 Hz with known channels the model and its output are unchanged.braindecode.models.EEGCLIPkeeps its BatchNorm layers on running statistics for a single-example batch in train mode, as the other BatchNorm models do (#1402 by Bruno Aristimunha).braindecode.models.NeuroRVQTokenizertakeson_missingasbraindecode.models.NeuroRVQdoes:"drop"leaves out input channels with no pretrained slot, and the target, reconstruction and codes cover the kept channels; the default"error"keeps the previous behaviour (#1435 by Bruno Aristimunha).Add
braindecode.modules.resolve_channel_positions(): places channels in a name -> position bank by name (case,EEG/-REF,T3-T6aliases, montage-qualified names such asbiosemi128_A1) or as the midpoint of a bipolarA-B;on_missingraises, drops, or takes the bank entry nearest the channel’sloc.braindecode.models.REVEuses it (montage,on_missing); exact bank names give the same positions as before (#1400 by Bruno Aristimunha).Add
braindecode.augmentation.LabelSmoothingandbraindecode.augmentation.functional.label_smoothing(), which replace class-index targets, or existing target distributions, with their convex combination with the uniform distribution over the classes; the signals are unchanged and the smoothed targets train with any distribution-accepting loss (#1401 by Li Qing).braindecode.augmentation.CutMixpastes a random time region of a shuffled example into each example of the batch (Yun et al., 2019) and, likebraindecode.augmentation.Mixup, returns targets forbraindecode.training.mixup_criterion()(#1372 by Li Qing).braindecode.models.EEGCLIP.contrastive_loss()takesdistributed=Trueto use every DDP rank’s pairs as negatives, with the gradient of the loss on the concatenated batch even for uneven per-rank batches; the default local-batch loss is unchanged (#1320 by lindicaphxag-tech).braindecode.functional.fft_conv1d()on CPU computes its per-frequency channel product as one real matmul whose output columns are the (real, imaginary) pairs of the result, read as a complex tensor without a copy: the backward no longer allocates four zero-filled copies of the product. CPU train step ofbraindecode.models.EEGNeX2.6x faster on 60-s windows at 100 Hz, batch 64, 24 threads (0.47x of 1.8.1; it had become 1.17x slower) and 2.5x on 4-s windows at 250 Hz, batch 32, 2 threads;braindecode.models.TIDNet2.0-2.7x,braindecode.models.EEGInceptionMI1.15-1.6x; EEGNeX train-step peak memory halved. Forward-only calls with fewer rows than twice the input channels keep the previous product. Outputs and gradients change by float rounding only (#1362 by Bruno Aristimunha).braindecode.models.AXONruns its unmasked attention (the spatial attention of every block) through the shared SDPA helper, so HPU eager mode uses the Gaudi bridge’s fused SDPA kernel instead of torch’s op-by-op SDPA: Gaudi eager train step 1.09x faster (21 channels x 6000 samples, batch 64); CPU, CUDA and Gaudi lazy mode run the same call as before; Gaudi eager outputs and gradients within master’s own float32 error except single gate-bias gradients of 1e-11 to 1e-9 (up to 5x their few-ulp yardstick) (#1360 by Bruno Aristimunha).braindecode.models.AXONcomputes its banded and full temporal attention from one score matrix: both softmaxes share the query-key product, and one product with the values and onetemporal_outprojection serve their scale-gated mixture (the gate weights sum to one). Gaudi train step 1.10x faster in lazy mode and 1.17x in eager mode, CPU 1.05x (21 channels x 6000 samples, batch 64); outputs and gradients differ from before by float rounding of the new summation order, up to 1.6x master’s own float32-vs-float64 difference on the outputs (more on the ~1e-9 gate-bias gradients), argmax unchanged; the attention-dropout and no-band paths are unchanged (#1359 by Bruno Aristimunha).braindecode.models.EEGInceptionMItakes the channels-last copy for its max pooling only on CPU: on Intel Gaudi (HPU) the layout copies made the eager train step 2.6x slower than in 1.8.1 (now back to 1.8.1’s time); CPU unchanged (#1344 by Bruno Aristimunha).braindecode.models.SignalJEPA,braindecode.models.SignalJEPA_Contextual,braindecode.models.SignalJEPA_PreLocalandbraindecode.models.SignalJEPA_PostLocalcompute the group norm of their feature encoder from mean and variance in HPU eager mode on Intel Gaudi: torch’s group norm costs 120-190 ms per forward and backward there whatever the shape. Gaudi eager train step 0.10x (PreLocal) and 0.17x (PostLocal) at 21 channels x 6000 samples, 0.34x for SignalJEPA at 22 x 1000; same code as before on CPU, CUDA and in HPU lazy mode (#1364 by Bruno Aristimunha).braindecode.models.EEGSymruns its temporal(1, k, 1)convolutions on Intel Gaudi (HPU) asconv1don a time-last copy of the input: Gaudi’s conv3d along time, which is not the innermost axis of EEGSym’s 5-D activations, is 10-50x slower than that conv1d. Gaudi train step 0.47x in eager and 0.57x in lazy mode at 21 channels x 6000 samples; same code as before on CPU and CUDA, same state dict (#1375 by Bruno Aristimunha).braindecode.functional.fft_conv1d()runs on Intel Gaudi (HPU) as overlap-save blocks with the DFT as real matmuls instead of a host FFT, andbraindecode.functional.prefer_fft_conv()picks it there from 8 input channels andbatch_size * n_times * kernel_size >= 8e6: Gaudi eager train step ofbraindecode.models.EEGInceptionMI1.4-2.3x faster andbraindecode.models.EEGNeX2.1x faster on 60-s windows at 100 Hz, batch 64; CPU unchanged; Gaudi outputs and gradients change by float rounding only (#1341 by Bruno Aristimunha).braindecode.models.EEGNeXkeeps its long temporal convolution direct on Intel Gaudi (HPU) when no gradient is computed: the FFT path pays off only through the backward, so evaluation and inference on Gaudi were 1.8x (eager) and 2.2x (lazy) slower than in 1.8.1 on 60-s windows at 100 Hz, batch 64; now back to 1.8.1’s time; training, CPU and GPU unchanged; Gaudi outputs change by float rounding only (#1363 by Bruno Aristimunha).braindecode.models.SignalJEPAandbraindecode.models.SignalJEPA_Contextualrun their transformer encoder in training with the attention through the shared SDPA helper (no_safe_softmaxpass of torch’s math path, #1361), the layers otherwise op for op as torch’s: Gaudi eager train step 0.86x at 21 channels x 6000 samples; bit-identical on CPU, same dropout draws; eval, HPU lazy mode and TorchScript unchanged (#1365 by Bruno Aristimunha).braindecode.models.CTNetcomputes the depthwise spatial convolution of its patch embedding as a matmul over the channel axis: same forward values on CPU (bit-identical outputs and batch-norm statistics), a faster backward than the grouped-convolution kernels. Train step 0.90x on CPU and 0.92x (eager) / 0.81x (lazy) on Intel Gaudi at 21 channels x 6000 samples, batch 64; 0.85x on CPU at 22 x 1000, batch 32; parameters and state dict unchanged, gradients change by float rounding (#1374 by Bruno Aristimunha).braindecode.models.CSBrainruns its region embedding and the region means of every encoder layer for all regions at once (a 0/1 neighbour-selection matmul and one batched matmul per kernel; oneindex_add_) instead of one convolution and one mean per region: about half the operators per forward, CPU forward and train step 1.14-1.17x faster, Gaudi forward 1.30-1.45x and train step 1.36-1.56x faster; outputs change by float rounding only (#1335 by Bruno Aristimunha).braindecode.models.EEGSymruns its batch norms on Intel Gaudi (HPU) on the(batch, filters, -1)view of its 5-D activations: Gaudi’s 5-D batch norm backward with the 12 electrodes innermost is 2-7x slower than the 3-D one. Gaudi eager train step 0.75x on top of #1375 at 21 channels x 6000 samples (lazy 0.95x); samebatch_normcall as before on CPU and CUDA, same state dict (#1376 by Bruno Aristimunha).braindecode.models.DIVER1computes the amplitude spectrum of its spectral embedding on the device on accelerators without complex tensors (Intel Gaudi / HPU) instead of a host FFT round trip: Gaudi forward 1.02-1.12x and train step 1.02-1.10x faster; CPU unchanged (#1334 by Bruno Aristimunha).braindecode.models.DIVER1builds and overlap-adds its STCPE sliding windows with one gather and oneindex_addinstead ofunfold/fold: Gaudi forward 1.25-1.36x and train step 1.13-1.32x faster, CPU 0-1 % faster; outputs and released-checkpoint outputs unchanged, gradients change by float rounding only (#1333 by Bruno Aristimunha).braindecode.models.EEGConformerruns the temporal and the spatial convolution of its patch embedding as one convolution over the channels with the composed weight and bias (the two are linear with nothing in between): no (batch, filters, channels, time) intermediate and 3.4x fewer stem FLOPs per train step. Train step 0.58x on CPU and 0.68x (eager) / 0.74x (lazy) on Intel Gaudi at 21 channels x 6000 samples, batch 64; 0.41x on CPU at 22 x 1000, batch 32; parameters and state dict unchanged, outputs change by float rounding (#1373 by Bruno Aristimunha).braindecode.models.MIRepNetruns the temporal and the spatial convolution of its patch embedding as one convolution over the channels with the composed weight and bias (the two are linear with nothing in between): no (batch, filters, channels, time) intermediate and 4.3x fewer stem FLOPs per train step. Train step 0.65x on CPU and 0.63x (eager) / 0.54x (lazy) on Intel Gaudi at 21 channels x 6000 samples, batch 64; 0.60x on CPU at 22 x 1000, batch 32; parameters and state dict unchanged, outputs change by float rounding (#1377 by Bruno Aristimunha).braindecode.models.LUNAcomputes the three group norms of its patch embedding from mean and variance in HPU eager mode on Intel Gaudi, with the group norm ofbraindecode.models.SignalJEPA(#1364): torch’s group-norm backward was about half of each eager train step there. Gaudi eager train step 0.53x at 21 channels x 6000 samples (batch 64) and 0.31x at 22 x 1000 (batch 32); CPU, CUDA and Gaudi lazy mode run the samegroup_normcall as before (bit-identical on CPU), same state dict (#1379 by Bruno Aristimunha).braindecode.models.MIRepNetmakes the tokens of its patch embedding contiguous. The transposed view carried through every residual add of the encoder (a sum takes the memory layout of its first operand), so each add and layer norm read strided memory. Gaudi eager train step 0.92x at 21 channels x 6000 samples, batch 64 (CPU 0.98x, lazy unchanged); outputs change by float rounding (#1378 by Bruno Aristimunha).braindecode.models.NeuroRVQandbraindecode.models.NeuroRVQTokenizercompute the group norms of their multi-scale patch embedding from mean and variance in HPU eager mode on Intel Gaudi, asbraindecode.models.SignalJEPAdoes (#1364): the backward of torch’s group norm compiled new graphs at every call there (8 per train step, about 70 ms each). Gaudi eager train step 0.22x (NeuroRVQ) and 0.39x (NeuroRVQTokenizer) at 21 channels x 800 samples, batch 64; same code as before on CPU, CUDA and in HPU lazy mode (#1382 by Bruno Aristimunha).braindecode.models.REVEcomputes the 4-D position embedding of its default channel positions once per forward and broadcasts it over the batch instead of computing it for one identical copy per example: 3 ms instead of 10-12 ms of each Gaudi train step (21 channels x 6000 samples, batch 64; step 1.01x); explicitposinputs are unchanged (#1347 by Bruno Aristimunha).braindecode.models.ContraWRcomputes its STFT magnitude on the device on accelerators without complex tensors (Intel Gaudi / HPU) instead of a host STFT and a host-to-device copy of the spectrogram: Gaudi forward 4.2-7.1x and train step 2.4-5.2x faster; CPU unchanged (#1332 by Bruno Aristimunha).braindecode.models.NeuroRVQTokenizersums the per-code counts and vectors of its EMA codebook update withindex_add_instead of a dense one-hot matrix and GEMM: CPU train step 1.3x faster (22 channels, batch 32, 2 threads), Gaudi unchanged; outputs, gradients and token ids unchanged, the updated codebooks change by float rounding only (#1308 by Bruno Aristimunha).braindecode.models.REVEcomputes the two halves of its GEGLU feed-forward as two matrix products on the row halves of the same weight instead of slicing one product, so the activation reads contiguous tensors (HPU copied the strided halves in both passes). Same parameters and state dict; train step 1.11x faster on Gaudi eager and 1.06x on CPU (21 channels x 6000 samples, batch 64), float32 rounding differences only (#1346 by Bruno Aristimunha).braindecode.modules.EMACodebooksums its training EMA statistics per code withindex_add_instead of a dense one-hot matrix and GEMM:braindecode.models.TFMTokenizerCPU train step 5 % faster andbraindecode.models.BrainTokenizer1-3 % (22 channels, batch 32, 2 threads); outputs, gradients and token ids unchanged, the EMA buffers change by float rounding only (#1306 by Bruno Aristimunha).The TUH datasets read the
_date.txtfile kept beside each EDF withjson.load()instead ofpandas.read_json(): 2.9 s -> 0.04 s for the 2993 TUAB date files; descriptions unchanged (#1300 by Bruno Aristimunha).Add
braindecode.models.Guetschel2026, the encoder shared by the 58 MAE and JEPA checkpoints of the EEG masking-geometry study (Guetschel et al., 2026). The checkpoints load withfrom_pretrainedfrom the original Hugging Face repositories, with backbone features identical to the reference implementation, andhub_repo_id()builds their names. The head is a flatten and a linear layer, with an optional fixed Gaussian random projection in between (random_projection, as in the OpenEEGBench probe) (#1260 by Pierre Guetschel).Add
braindecode.models.DuIN(Du-IN, Zheng et al., NeurIPS 2024), an sEEG speech decoder that fuses the channels of a brain region into one token per 100 ms patch before a Transformer encoder. The authors’ per-subject Du-IN checkpoints (MAE and VQ-VAE stages, 12 subjects) are on the Hub atbraindecode/duin-pretrainedand match the upstream encoder to float precision.braindecode.modules.MultiHeadAttentiongainshead_dimandqk_normfor it (#1269 by Adam Mounir).braindecode.models.MetaNeuromotorHandsums the cross-spectral bins of each frequency band with one einsum instead of a broadcast product and a sum: CPU forward and train step 3-7 % faster, Gaudi 2-10 % faster; outputs and gradients differ from before by more than float32 rounding (up to 1.6x master’s own float32-vs-float64 difference, amplified by the eigendecomposition), argmax unchanged (#1330 by Bruno Aristimunha).braindecode.models.MetaNeuromotorHandrotates and half-vectorizes the MPF matrices with one precomputedindex_selectinstead of three rolls, a stack and a permute copy: CPU train step about 2 % faster, Gaudi neutral; outputs, gradients and state dict unchanged bit for bit (#1292 by Bruno Aristimunha).braindecode.models.MetaNeuromotorHandsolves the eigendecompositions of its matrix logarithm in chunks on all CPU threads instead of one matrix after another on one thread (torch’s CPUeigh): train step 2.5x faster at 24 threads (21 channels x 32,000 samples, batch 64), 11-15 % at 2 threads (batch 32), 1.5x on Gaudi (the featurizer runs on the host CPU); bit-identical outputs and gradients (#1336 by Bruno Aristimunha).Add
braindecode.modules.layers.RMSNorm, a drop-intorch.nn.RMSNorm(same parameters and state dict) that runs the Gaudi bridge’s fused RMSNorm kernel on HPU instead of torch’s op-by-op square, mean, rsqrt and products (float32 or bfloat16 input with a weight over the last dimension; other devices unchanged).braindecode.models.REVEuses it: Gaudi train step 1.05x faster in eager mode and 1.03x in lazy mode (21 channels x 6000 samples, batch 64), differences within the Gaudi float32 error (#1348 by Bruno Aristimunha).braindecode.models.CodeBrainskips the attention scores in eval mode on CPU at the defaultswa_window_size=1, where the window keeps only the diagonal and the attention reduces to its value projection: CPU eval forward 1.25x faster at 19 x 6000 (batch 32); training unchanged; outputs unchanged bit for bit (#1293 by Bruno Aristimunha).braindecode.models.CodeBraintakes the same one-wide-window shortcut in training on CPU: the attention weights are exactly one-hot, so the block multiplies the value projection by the diagonal of the dropout mask thattorch.nn.MultiheadAttentiondraws (same RNG stream) instead of computing all scores, their softmax, dropout and product: train step 1.2x faster at 21 x 6000 (batch 64, 24 threads) and 1.3x at 19 x 6000 (batch 32, 2 threads); outputs, gradients and dropout masks unchanged bit for bit (#1339 by Bruno Aristimunha).braindecode.models.CodeBraincomputes its bidirectional long convolution as a linear convolution at a 5-smooth FFT length instead of a circular one of length 2 * seq_len: Gaudi forward 2.1x and train step 1.4x faster (19 channels x 6000 samples, batch 32), CPU forward 1-16 % faster, CPU train step unchanged to 6 % faster; outputs and gradients differ from before by more than float32 rounding (up to 1.9x master’s own float32-vs-float64 difference on the released checkpoint’s logits), argmax unchanged (#1329 by Bruno Aristimunha).braindecode.models.MAPAfolds the (batch, array) axes before its within-array attention, so CPU attention runs as one fused 4-Dtorch.nn.functional.scaled_dot_product_attention()call instead of the 5-D math fallback: CPU forward 1.16x and train step 1.03x faster at 22 contacts (batch 32), 4.2 GB less peak memory in training; Gaudi unchanged; outputs and gradients change by float rounding only (relative differences below 1e-6) (#1294 by Bruno Aristimunha).braindecode.modules.MultiHeadAttention(EEGConformer, CTNet, MSCFormer, MIRepNet, STEEGFormer) andbraindecode.models.ZUNArun attention through the Gaudi bridge’s fused SDPA kernel in HPU eager mode, where torch’s SDPA runs op by op through the full score matrix (without a mask or dropout; lazy mode, CPU and other devices unchanged): Gaudi eager train step STEEGFormer 2.2x and ZUNA 1.7x faster (21 channels x 6000 samples), forward 1.2-2x for the other models; differences within the Gaudi float32 error (#1338 by Bruno Aristimunha).braindecode.models.ZUNArotates the rotary position pairs with one flip, the sign kept in the sine table, instead of a negated strided slice and a stack: bit-identical, Gaudi eager train step 11 % faster at 21 channels x 6,000 samples, lazy mode 2.5 % slower (#1349 by Bruno Aristimunha)braindecode.models.ZUNAbuilds its RMS norms frombraindecode.modules.layers.RMSNorminstead oftorch.nn.RMSNorm(same parameters and state dict), so on HPU they run the Gaudi bridge’s fused kernel: Gaudi train step 1.07x faster in eager mode and 1.03x in lazy mode (21 channels x 6000 samples, batch 2); CPU unchanged (bit-identical), Gaudi differences within the Gaudi float32 error (#1354 by Bruno Aristimunha).braindecode.models.MEDFormercomputes its patch embedding, a kernel spanning alln_timesrows over non-overlapping patches, as one GEMM of the patch-reshaped input with the kernel instead of a conv1d withn_timesinput channels (same parameters and state dict): CPU train step 1.12-1.18x faster (21 channels x 1000 samples, batch 64, 24 threads), Gaudi unchanged; float32 outputs are 3-8x closer to the float64 result than before and differ from before by up to 1.15x the model’s own float32-vs-float64 difference (#1357 by Bruno Aristimunha).braindecode.models.PBTruns its attention through the shared SDPA helper ofbraindecode.modules.MultiHeadAttention, so in HPU eager mode attention without dropout (eval mode,drop_prob=0) uses the Gaudi bridge’s fused SDPA kernel instead of the op-by-op path through the full score matrix: Gaudi eager forward 2.0x faster (21 channels x 6000 samples, batch 64); training with attention dropout, lazy mode, CPU and other devices unchanged (outputs, gradients and dropout draws identical); eval outputs within the Gaudi float32 error (#1350 by Bruno Aristimunha).braindecode.models.DANCEskips the taps of its dilated convolutions that only ever read the zero padding (kernel offset at or beyond the input length): CPU train step 0.92x and Gaudi eager 0.97x (21 channels x 6000 samples, batch 64); outputs change by float rounding only (#1343 by Bruno Aristimunha).braindecode.modules.ChannelMerger(DANCE,BrainModule(use_merger=True)) computes its channel mix as(x^T @ weights^T)^Ton HPU, where Gaudi’s lazy-mode graph compiler failed onweights @ xfeeding a convolution’s backward: DANCE now trains in Gaudi lazy mode (train step 622 ms vs 844 ms in eager mode, 21 channels x 6000 samples, batch 64); other devices run the same code as before (#1351 by Bruno Aristimunha).braindecode.models.BrainModulecomputes its GLU gates asa * sigmoid(b)in Intel Gaudi (HPU) lazy mode, where the graph compiler failed onaten.gluafter the gate’s convolution: BrainModule now trains in Gaudi lazy mode (train step 306 ms vs 554 ms in eager mode, 21 channels x 6000 samples, batch 64); eager mode and other devices run the same code as before (#1371 by Bruno Aristimunha).braindecode.models.MSCFormerruns the temporal and the depthwise spatial convolution of each patch-embedding branch as one convolution over the channels with the composed weight and bias (the two are linear with nothing in between): no (batch, filters, channels, time) intermediate. Train step 0.63x on CPU and 0.63x (eager) / 0.66x (lazy) on Intel Gaudi at 21 channels x 6000 samples, batch 64; 0.60x on CPU at 22 x 1000 (batch 32); outputs and gradients change by float rounding (#1381 by Bruno Aristimunha).braindecode.models.CodeBrainaggregates its residual skip connections as onetorch.stack(skips).sum(0)reduction in Intel Gaudi (HPU) lazy mode, where the graph compiler failed on the backward of the sequential sum (a skip-convolution weight-gradient node landed in two pipeline bundles): CodeBrain now trains in Gaudi lazy mode (train step 3498 ms vs 3691 ms in eager mode, 21 channels x 6000 samples, batch 64); eager mode and other devices run the same code as before (#1387 by Bruno Aristimunha).braindecode.models.CodeBraincomputes its bidirectional long convolution on Intel Gaudi (HPU) as a real-valued DFT (device matmuls against a cached cosine/sine basis) instead of a hosttorch.fftround trip: Gaudi lazy TUEG batch 64 now trains (~3.5 s/step, previously synStatus 26); CPU and CUDA keep thetorch.fftpath (bit-identical), TorchScript and state dict unchanged (#1390 by Bruno Aristimunha).braindecode.models.MSVTNetcomputes the depthwise spatial convolution of each branch as a matmul over the channel axis when gradients are computed (and on CPU): same values, a faster backward than the grouped convolution’s. Gaudi train step 0.86x in eager and 0.87x in lazy mode, CPU 0.88x at 22 channels x 1000 samples (batch 32) and 0.98x at 21 x 6000 (batch 64); evaluation on Gaudi keeps the grouped convolution; outputs and batch-norm statistics unchanged on CPU, gradients change by float rounding (#1380 by Bruno Aristimunha).braindecode.models.MSVTNetwith large inputs (when a branch’s (batch, filters, channels, times) activation would reach 2**26 elements, e.g. 21 channels x 6000 samples at batch 64) applies each branch’s spatial weights to the input first and runs each filter’s temporal kernel on its projections, with the first batch norm’s statistics computed from the input’s autocorrelation, so that activation is never built: same function and state dict, numerical differences only. Train step at 21 x 6000, batch 64: Intel Gaudi 0.57x in eager mode and 0.78x in lazy mode, CPU 0.23x (#1384 by Bruno Aristimunha).braindecode.models.TFMTokenizercomputes the group norms of its frequency- and temporal-path convolutional blocks from mean and variance on Intel Gaudi (HPU), where the graph compiler failed on the native group-norm backward at the TUEG batch size (“SRAM capping too big”): TFMTokenizer now trains on Gaudi (train step about 1.5 s in eager and 8 s in lazy mode, 21 channels x 6000 samples, batch 64); CPU, CUDA and TorchScript run the samegroup_normcall as before (bit-identical on CPU), same state dict (#1386 by Bruno Aristimunha).braindecode.models.Labramandbraindecode.models.BaRISTArun attention through the Gaudi bridge’s fused SDPA kernel in HPU eager mode (without a mask or attention dropout; lazy mode, CPU and other devices unchanged): Gaudi eager train step Labram 8 % and BaRISTA 5 % faster (21 channels x 6000 samples), differences within the Gaudi float32 error (#1342 by Bruno Aristimunha).braindecode.models.DANCEruns its Perceiver attention through the same Gaudi fused SDPA path in HPU eager mode (torch’s SDPA there computes the full score matrix op by op, which made the DANCE Gaudi eager train step 8 % slower than in 1.8.1): Gaudi eager train step 848 -> 763 ms (21 channels x 6000 samples, batch 64); CPU and Gaudi lazy mode unchanged (#1340 by Bruno Aristimunha).braindecode.models.REVEruns its attention through the same helper, so HPU eager mode uses the Gaudi bridge’s fused SDPA kernel instead of torch’s op-by-op SDPA through the full score matrix (lazy mode, CPU and other devices unchanged): Gaudi eager train step 1.42x faster (21 channels x 6000 samples, batch 64); differences within the Gaudi float32 error (#1345 by Bruno Aristimunha).braindecode.models.REVEalso uses the fused SDPA kernel in HPU lazy mode, through a new opt-infused_in_lazyflag of the shared helper (default off: on long sequences such as ZUNA’s the graph compiler’s own SDPA fusion is faster): Gaudi lazy train step 1.04x faster for REVE’s ~700-token sequences (21 channels x 6000 samples, batch 64); differences within the Gaudi float32 error (#1353 by Bruno Aristimunha).braindecode.models.EEGPTandbraindecode.models.LUNAsplit their fused query/key/value projection withunbindinstead of three index selects, whose backward wrote a zero-filled copy of the whole projection gradient per select and summed them: EEGPT train step 1.09x faster on CPU and 1.02-1.03x on Gaudi (21 channels x 6000 samples, batch 64); outputs and gradients unchanged bit for bit (#1352 by Bruno Aristimunha).braindecode.models.EEGPTruns its attention through the shared SDPA helper withfused_in_lazy=True, so on HPU it uses the Gaudi bridge’s fused SDPA kernel in eager and lazy mode instead of torch’s SDPA (op by op through the full score matrix in eager mode); causal attention keeps torch’s call: Gaudi train step 1.21x faster in eager mode and 1.03x in lazy mode (21 channels x 6000 samples, batch 64; lazy mode up to 3 % slower on 256-sample inputs); CPU and other devices unchanged (bit-identical), Gaudi differences within the Gaudi float32 error (#1355 by Bruno Aristimunha).The shared SDPA helper of
braindecode.modules.MultiHeadAttention(EEGConformer, CTNet, MSCFormer, MIRepNet, STEEGFormer) andbraindecode.models.PBTruns attention with dropout on CPU and in HPU eager mode as torch’s math path op for op, without the pass of_safe_softmaxthat zeroes fully masked rows (there are none without a mask): train step PBT 16 % faster in Gaudi eager mode and 9 % on CPU, EEGConformer 3 % / 6 %, MIRepNet 5 % / 3 % (21 channels x 6000 samples, batch 64); outputs, gradients and dropout draws identical on CPU and for PBT on Gaudi, within Gaudi’s own run-to-run variation for the other models; lazy mode, masks, other devices and attention without dropout unchanged (#1361 by Bruno Aristimunha).braindecode.models.NeuroRVQandbraindecode.models.NeuroRVQTokenizerrun their four temporal scales through the shared transformer blocks as one stacked batch on accelerators (CPU, and training with dropout, keep the per-scale loop): Gaudi train step 1.7-2.6x and forward 1.8-3.1x faster; CPU unchanged; outputs and token ids unchanged on CPU, within float rounding on Gaudi (#1312 by Bruno Aristimunha).braindecode.datasets.TUH,braindecode.datasets.TUHAbnormalandbraindecode.datasets.TUHEventsbuild the standard_1005 montage once per process instead of twice per recording forrename_channels/set_montage: 2993 TUAB recordings,rename_channels+set_montage37.5 s -> 14.5 s (2.6x), 23 s less perTUHAbnormalindexing withn_jobs=1; channel names, types, positions and descriptions unchanged (#1299 by Bruno Aristimunha).The attention of
braindecode.models.Labram(shared bybraindecode.models.NeuroRVQandbraindecode.models.NeuroRVQTokenizer) runs throughtorch.nn.functional.scaled_dot_product_attention()with the relative position bias as attn_mask off HPU (the manual path stays on HPU, withreturn_attention=Trueand with attention dropout in training): Labram CPU forward 1.2-1.3x and train step 1.06-1.14x faster, NeuroRVQ / NeuroRVQTokenizer CPU up to 6 % faster, Gaudi unchanged; outputs and gradients differ from before by more than float32 rounding (up to 2.5x master’s own float32-vs-float64 difference on the released Labram checkpoint’s gradients), token ids unchanged in single forward passes (#1328 by Bruno Aristimunha).braindecode.models.LUNAcomputes its rotary self-attention withtorch.nn.functional.scaled_dot_product_attention(): CPU forward 1-4 % faster, train step neutral to 2.4 % faster; outputs and gradients change by float rounding only, within master’s own float32-vs-float64 difference, also with the releasedPulpBio/LUNAweights (#1282 by Bruno Aristimunha).braindecode.models.DANCEcomputes the Perceiver and decoder attention withtorch.nn.functional.scaled_dot_product_attention()instead of an explicit softmax: eval forward 0.7 % faster on CPU and 1.4 % on Gaudi (22 channels, 32 s, batch 32), train step unchanged; outputs and gradients change by float rounding only, within master’s own float32-vs-float64 difference (#1291 by Bruno Aristimunha).braindecode.functional.prefer_fft_conv()picks the FFT for a float32 convolution only if alsoin_channels * kernel_size >= 120: with one or two input channels the direct convolution is up to 2.5x cheaper on CPU;braindecode.models.EEGInceptionMIdecisions and outputs unchanged (#1283 by Bruno Aristimunha).braindecode.models.TIDNetandbraindecode.models.EEGNeXcompute their long temporal convolutions withbraindecode.functional.fft_conv1d()on CPU wherebraindecode.functional.prefer_fft_conv()expects it to be faster (TIDNet in float64): train step 1.8x (TIDNet) and 1.3x (EEGNeX) faster at 22 channels, batch 32 (2 threads); small batches, GPUs and Gaudi keep the direct convolution; outputs and gradients change by float rounding only, as close to the float64 result as master (#1305 by Bruno Aristimunha).braindecode.preprocessing.preprocess()withsave_dirpreprocesses a copy of each recording, so withn_jobs=1the input datasets are no longer loaded and held in memory until the saved copies are reloaded: peak memory on 64 TUAB recordings 1.8 GB -> 0.6 GB (it grew by ~22 MB per recording, ~65 GB for the whole corpus); saved files and returned datasets unchanged (#1301 by Bruno Aristimunha).braindecode.models.BIOTcomputes the spectrograms of all channels with one STFT instead of one per channel: Gaudi forward 1.6x and train step 1.35x faster (22 channels, batch 32), CPU registry forward 3 % faster; outputs and gradients unchanged bit for bit, also with the released weights (#1284 by Bruno Aristimunha).braindecode.models.MVPFormerbuilds its attention masks and channel-shift index once per forward instead of in every layer: forward 19-25 % faster on Gaudi and 2-12 % on CPU, outputs and gradients unchanged bit for bit (#1280 by Bruno Aristimunha).braindecode.models.AttnSleep,braindecode.models.Brant,braindecode.models.MSVTNetandbraindecode.models.PBTno longer compute tensors their forward discards (AttnSleep’s first attention product, Brant’s reconstruction projection, MSVTNet’s branch heads withoutreturn_features, PBT’s class-token index); outputs and gradients unchanged bit for bit (#1281 by Bruno Aristimunha).braindecode.models.NeuroRVQTokenizeraccumulates the-2 * vectors @ codes.Tterm of its code distances inside the GEMM (oneaddmm_) instead of a separate product and subtraction: CPU forward 8 % and train step 3 % faster (22 channels, batch 32, 2 threads); distances and token ids unchanged bit for bit (#1315 by Bruno Aristimunha).braindecode.preprocessing.preprocess()withsave_dirreloads the saved recordings with itsn_jobsinstead of one at a time: TUAB (2993 recordings,n_jobs=16) preprocess 626.8 s -> 386.4 s, the final reload 260.1 s -> 65.4 s; saved files and returned datasets unchanged (#1302 by Bruno Aristimunha).braindecode.models.CSBraincopies each region’s mean feature into its electrodes with one slice assignment per region instead of one per electrode: train step about 10 % and forward 7 % faster on Gaudi (22 channels, batch 32), unchanged on CPU; outputs are unchanged and gradients change by float rounding only, within master’s own float32-vs-float64 difference (#1285 by Bruno Aristimunha).braindecode.models.AXONruns one temporal attention per block instead of two when the window has at mosttemporal_window + 1patches (every window up to 5.5 s at the defaults), where the restricted and full temporal branches are identical: train step 7 % faster on CPU and 12 % on Gaudi (22 channels, 5 s, batch 32); outputs are unchanged and gradients change by float rounding only, within master’s own float32-vs-float64 difference (#1286 by Bruno Aristimunha).braindecode.modules.FilterBankLayerbroadcasts each FIR band filter over the channels instead of repeating it, so its FFT is computed once per band:braindecode.models.FBCNet,braindecode.models.FBMSNet,braindecode.models.FBLightConvNetandbraindecode.models.IFNetsteps 1-10 % faster on CPU and 8-29 % on Gaudi at batch 2, 0-3 % at batch 32; outputs and gradients change by float rounding only at 22 channels, within master’s own float32-vs-float64 difference, and not at all at 3 channels (#1278 by Bruno Aristimunha).braindecode.modules.FilterBankLayercomputes the FFT of the input once for consecutive FIR bands with the same filter length (and moves an HPU input to the CPU and back once for the whole bank instead of once per band); each band is still torchaudio’sfftconvolveat its own FFT length, so outputs are unchanged bit for bit:braindecode.models.FBCNet,braindecode.models.FBMSNetandbraindecode.models.FBLightConvNetGaudi forward 12-20 % and train step 8-18 % faster, CPU forward 1-7 % faster;braindecode.models.IFNetGaudi 1-5 % faster, CPU unchanged (#1324 by Bruno Aristimunha).braindecode.modules.FilterBankLayerfilters all FIR bands with one FFT of the input at a 5-smooth length on CPU (each filter shifted to a common window) and with one directconv1don HPU instead of host FFTs:braindecode.models.FBCNet,braindecode.models.FBMSNet,braindecode.models.FBLightConvNetandbraindecode.models.IFNetGaudi train step 1.1-28x faster, CPU train step 3-41 % faster (21 channels x 6000 samples and 22 x 800); outputs and gradients differ from before by more than float32 rounding (up to 3.6x master’s own float32-vs-float64 difference on CPU, 4.7x on Gaudi), argmax unchanged (#1327 by Bruno Aristimunha).The SEANet convolutions of
braindecode.models.BrainTokenizer(and the tokenizer ofbraindecode.models.BrainOmni) skip the padding calls that only copy their input (the 1x1 convolutions pad nothing, and the zero extension is empty unless the signal is shorter than the padding): BrainTokenizer CPU forward 6-11 % and train step 5-8 % faster, Gaudi (eager) train step 2-4 % faster, BrainOmni CPU 1-5 % faster; outputs, gradients, token ids and released-checkpoint outputs unchanged bit for bit (#1326 by Bruno Aristimunha).The SELU of
braindecode.models.BrainOmniandbraindecode.models.BrainTokenizeris computed as an ELU times a positive-side factor instead of awhereof two branches, so its backward has no zero-filled branch gradients: BrainOmni CPU train step 7-9 % faster (22 channels, batch 32, 2 threads), Gaudi unchanged; outputs, gradients and token ids unchanged bit for bit, still trainable on Gaudi (#1316 by Bruno Aristimunha).braindecode.preprocessing.create_fixed_length_windows()runs its per-recording calls in threads whenn_jobs > 1, instead of pickling every recording to worker processes and back: TUAB (2993 recordings) withn_jobs=852.3 s -> 5.5 s (n_jobs=1: 1.8 s, unchanged); windows unchanged (#1303 by Bruno Aristimunha).braindecode.models.MEDFormeradds its channel embedding once instead of once per granularity, and runs its layer norms and kernel-1 feed-forward convolutions once on all granularities (dropout stays per granularity, same masks): train step 3-7 % faster on CPU and 6-7 % on Gaudi; outputs change by float rounding only (#1288 by Bruno Aristimunha).braindecode.models.MEDFormercomputes its attention scores and outputs as batched matrix products on (batch, heads, length, features) operands instead of twotorch.einsum()calls, and the query, key and value projections of its self-attention as one GEMM on their concatenated weights (same parameters and state dict):torch.compile()with Gaudi’shpu_backendnow compiles and trains MEDFormer (graph compilation failed at the einsum’s reshape before; the compiled Gaudi eager train step is 1.9x faster than the uncompiled one), Gaudi train step 1.15-1.18x faster in lazy mode and 1.06-1.10x in eager mode, CPU 1.05x (21 channels x 1000 samples, batch 64); outputs and gradients change by float rounding only, within the model’s own float32 error (#1356 by Bruno Aristimunha).braindecode.functional.plv_time()copies the real and imaginary phasor parts once instead of in every matmul for each batch matrix:braindecode.models.EEGMinerCPU forward 1.5x and train step 1.4x faster (22 channels, batch 32, 2 threads); outputs and gradients change by float rounding only, within master’s own float32-vs-float64 difference (#1277 by Bruno Aristimunha).braindecode.modules.EMACodebookbuilds its one-hot code matrix only in training, where the EMA update reads it: eval forward ofbraindecode.models.TFMTokenizer5 % faster on CPU (22 channels, batch 32; 208 MB less temporary memory) and ofbraindecode.models.BrainTokenizer/braindecode.models.BrainOmni0.5-2 %; outputs, gradients, buffers and token ids unchanged bit for bit (#1290 by Bruno Aristimunha).braindecode.datasets.BaseConcatDataset.save()with anoffsetno longer lists and stats every entry of the target directory (and no longer warns with that listing):braindecode.preprocessing.preprocess()saves each recording at its offset into one directory, which made the scan quadratic; 2993 TUAB recordings 223 s -> 15 s ofsavecalls and 2992 -> 0 warnings (33 MB); saved files unchanged (#1304 by Bruno Aristimunha).braindecode.datasets.EEGWindowsDatasetreads a window of a lazily loaded FIF recording (whatpreprocess(..., save_dir=...)andbraindecode.datautil.load_concat_dataset()produce) from one slice of the memory-mapped file instead of one 1-s FIF buffer at a time through a 4 MB read buffer: one epoch of 60-s TUAB windows 1.3-1.4x faster from a cold page cache and 1.5-2.1x faster from a warm one (num_workers 0-8), windows unchanged (#1318 by Bruno Aristimunha).braindecode.datautil.load_concat_dataset()keeps the FIF data-buffer tags of each lazily loaded recording (one mneTagobject per 1-s buffer) as one int64 array: 2.9x less memory per 20-min recording (446 -> 153 KiB), 2.8-3.4x less copy-on-write memory per DataLoader worker over an epoch (8 workers, 10,000 recordings: 14.0 -> 4.6 GiB for the whole process tree), reload about 8 % faster withn_jobs=1and 1.7-2x faster withn_jobs=8,gc.collect()2-2.2x faster; windows, batches and mne’s own reads unchanged (#1325 by Bruno Aristimunha).braindecode.datasets.BaseConcatDatasetgives aDataLoadereach batch through__getitems__, which first asks the operating system to start reading every lazily loaded FIF window of the batch (posix_fadvise(POSIX_FADV_WILLNEED)) and then reads them as before, so a worker’s reads of a batch overlap instead of running one after another: from cold files on a network file system, one shuffled epoch of 60-s windows 1.3-2.6x faster (num_workers 4-16) and 3.4-7.7x faster when every window comes from a different file; batches unchanged; no hint whereposix_fadvisedoes not exist (Windows, macOS) (#1337 by Bruno Aristimunha).braindecode.models.EEGInceptionMIruns its max pooling in channels-last layout and pads even kernels once per inception module: CPU forward 1.1-1.25x faster, outputs, gradients and state dict unchanged (#1267 by Bruno Aristimunha).braindecode.models.EEGInceptionMIcomputes its temporal convolutions with an FFT on CPU when the input is float16/bfloat16 orbatch_size * largest_kernel >= 600(newfft_convargument;Noneby default,Falsekeeps the direct convolution bit for bit): float32 CPU steps 1.5-8.8x faster from batch 8 (2 threads), float16/bfloat16 17-117x; outputs change by float rounding (<= 3.6e-7 relative) (#1268 by Bruno Aristimunha).braindecode.models.EEGInceptionMItakes its FFT convolution from the newbraindecode.functional.fft_conv1d()(automatic choice inbraindecode.functional.prefer_fft_conv()) and uses plainpadding="same"convolutions again: direct-path outputs, gradients and state dict unchanged bit for bit, CPU steps 0.98-1.45x master’s (FFT path faster from batch 8) (#1274 by Bruno Aristimunha).Add
braindecode.models.TFMTokenizer, the time-frequency motif tokenizer for single-channel EEG of Pradeepkumar et al. (ICLR 2026) (#1202 by lindicaphxag-tech).Add
braindecode.models.EEGCLIP, a dual encoder that aligns EEG windows and text descriptions with a symmetric contrastive objective; the text encoder is optional and user-supplied (#1200 by lindicaphxag-tech).Add
braindecode.models.EpiNT, a port of the EpiNT epilepsy foundation model for scalp and intracranial EEG (Zhang et al., 2025): a channel-independent transformer with rotary attention over 256-sample patches of a 3072-sample window. The authors’ releasedrepresentations.binloads withload_state_dict(strict=False)(#1266 by Julien Gadonneix).Add
braindecode.augmentation.TrivialAugment, which applies to each example one transform and strength sampled from a label-preserving pool; also listbraindecode.augmentation.BandRotationin the API docs (#1255 by Li Qing).The pretrained-compatibility test now covers every model class with released weights (NeuroRVQ, MAPA, BrainOmni, BrainTokenizer and the SignalJEPA heads added), checks that the list is complete, and runs REVE’s cases without network access (#1252 by Bruno Aristimunha)
braindecode.models.NeuroRVQnow reuses the LaBraM attention block instead of a private copy, and the K-means codebook initialisation inbraindecode.modules.quantizationusestorch.cdist(about 6x faster, lower peak memory, identical codebooks). Outputs and state-dict keys are unchanged. (by Bruno Aristimunha)The K-means codebook initialisation in
braindecode.modules.quantizationsearches the nearest center once per distinct sampled row: a first batch smaller than the 4096 drawn vectors is sampled with replacement, and the first forward of a freshbraindecode.models.BrainOmniorbraindecode.models.BrainTokenizertook about 10 s on CPU. Codebooks are unchanged (#1265 by Bruno Aristimunha).Add
braindecode.models.MAPA, a masked-autoencoder foundation model for intracranial EEG that describes an electrode only by its atlas region and its number along the array it was implanted on, never by its coordinates, so that one pretrained encoder reads a subject it has never seen: three magnitude-spectrogram bands on a shared frame clock are tokenized per contact, offset by a learned region embedding, and mixed by a transformer whose attention stays inside one array and carries a two-axis rotary encoding on the contact number and on time. One instance encodes recordings from different subjects, by passing each one’s electrode metadata toforward, andnormalization="session"takes a spectrogram normalized over the whole recording, which reproduces the reference inputs (#1178 by Julien Gadonneix).braindecode.models.MAPAloads the releasedmapa_vits384withMAPA.from_pretrained("braindecode/mapa-pretrained", ...); the key mapping for the original checkpoint is removed (by Bruno Aristimunha).Add
test/unit_tests/models/test_pretrained_compat.py: every model with released weights is built on a grid of input geometries (canonical montage, permuted order, a 64-channel montage outside the 10-20 vocabulary, coordinates-only channels, names without coordinates, 1 s / 30 s / non-divisible windows) and must forward or raise the error its declared channel strategy implies; unhandled cells are strictxfailmarkers (#1228 by Bruno Aristimunha).Add
channel_strategy("exact","zero","nearest","idw","spline","field","source","region","wiener","latent"; default"native") andchannel_strategy_kwargsto the 19 pretrained models, saved in the config. Abraindecode.modules.ChannelLayer(one matrix per montage) maps any montage onto the channels the backbone consumes, also per call withmodel(x, chs_info=...)."native"keeps every released checkpoint bit-identical with the samestate_dict. It replaces the unreleasedChannelTokenizer; BENDR adapts a non-canonical montage with an MNE spline and SignalJEPA no longer returnsNaNfor names without coordinates. See Channel strategies: any montage in (#1227, #1241 by Bruno Aristimunha).Add registry-wide model contract tests that automatically cover every registered model, checking eval-mode input/state purity, finite batched outputs, batch-permutation equivariance, and
get_config+state_dictreconstruction (#1208 by lindicaphxag-tech).Add
braindecode.models.NeuroRVQ, a channel-aware EEG foundation model with four-scale temporal patch embedding and a pretrained masked-token encoder. The port preserves the released architecture and identifies its CC BY-NC 4.0 license and 200 Hz preprocessing requirements (#1090 by lindicaphxag-tech).Generate a version-scoped
llms.txtand selected Markdown documentation entry points with source-commit attribution and critical-page coverage checks.Add
braindecode.models.NeuroRVQTokenizer, the released NeuroRVQ EEG tokenizer: reconstruction and discrete codes (#1223 by lindicaphxag-tech).Add
braindecode.models.AXON, an axis-factorized EEG foundation model whose layers mix a temporal and a spatial attention path with a per-token gate, with pretrained weights on the Hugging Face Hub (#1182 by Mahir Jain).braindecode.models.BIOT,braindecode.models.BrainBERT,braindecode.models.EEGDINO,braindecode.models.SleepFM,braindecode.models.SleepFMStager,braindecode.models.BrainTokenizerandbraindecode.models.BrainOmniwarn about a sampling rate that differs from the pretrained one through the sharedbraindecode.models.util.warn_if_sfreq_differs; the warning fires in the same cases as before (up to a 1e-6 Hz tolerance), with one wording (#1261 by Bruno Aristimunha).braindecode.models.NeuroRVQandbraindecode.models.NeuroRVQTokenizertakemodality("eeg","ecg","emg","ppg"), the settings of the authors’ four released packages; the seven released checkpoints load withfrom_pretrainedfrombraindecode/neurorvq-{eeg,ecg,emg}-pretrainedandbraindecode/neurorvq-tokenizer-{eeg,ecg,emg,ppg}-pretrained(load_pretrained_weightsis removed). The tokenizer reconstruction now equals the authors’ code (it differed by up to 6e-8);statistic_code_usage=Truereproduces their eval-time code-usage EMA. Builds on #1090 and #1223 by lindicaphxag-tech (#1254 by Bruno Aristimunha).Add
braindecode.models.TMSANet, the motor-imagery convolution and local/global attention model of Zhao and Zhu (2025) (#1209 by lindicaphxag-tech).Add
braindecode.models.SeizureTransformer, the U-shaped convolution and Transformer seizure detector of Wu et al. (2025) that won the 2025 SzCORE seizure detection challenge. It predicts a logit for every time sample. With the authors’ released weights it reproduces their challenge scores onbraindecode.datasets.SIENA(event F1 0.706) (#1236 by Raghav Rathi).Restore acceptance tests on supported Python versions as seeded decoding checks on BNCI2014_001 (held-out accuracy thresholds, a shuffled-label control and a replicability check), run by a dedicated CI job (#1159 by Bruno Aristimunha).
Clarify decoder temporal embedding indexing in
braindecode.models.Labramand cover its one-token-per-temporal-patch behavior (#1155 by Bruno Aristimunha).Add
braindecode.models.SleepFM, a channel-agnostic multimodal PSG foundation encoder, andbraindecode.models.SleepFMStager, its patch-wise sleep-staging model, with the released weights; CC BY-NC 4.0 (#1106 by Fashad Ahmed)Add
braindecode.models.CSBrain, the cross-scale spatiotemporal brain foundation model from Zhou et al. (NeurIPS 2025 Spotlight): multi-scale temporal and per-region embeddings with structured sparse (inter-window and inter-region) attention, channel names mapped to five anatomical regions or an explicitbrain_regionslayout, verified bit-exact against the authors’ released pretrained checkpoint (#1196 by Li Qing).braindecode.models.CSBrainreuses the patch embedding ofbraindecode.models.CBraModinstead of a copy of it; state-dict keys, outputs with loaded weights and same-seed initial weights are unchanged (#1240 by Bruno Aristimunha).Add
head_drop_probtobraindecode.models.CSBrain, the task-head dropout of the reference fine-tuning models (default:drop_prob, so existing models are unchanged), and document the checkpoint key names (#1247 by Bruno Aristimunha).Add
braindecode.models.DIVER1, an any-variate EEG/iEEG foundation model with pretrained encoders and support for varying montages throughbraindecode.models.diver1.channel_metadata_from_chs_info()(#1170 by Julien Gadonneix).braindecode.models.ZUNAaccepts ann_timesthat is not a multiple offine_time_ptsthrough the newon_non_divisibleoption ("pad"or"crop"), forwarded to the sharedbraindecode.modules.PatchTokenizer; the default"error"keeps the previous behaviour (#1190 by Bruno Aristimunha).Add
braindecode.models.MSCFormer, a multi-scale convolutional transformer network for motor imagery decoding from Zhao et al. (2025), adapted from the reference implementation to reuse braindecode’s shared attention and feed-forward blocks. The default attention logit scale reproduces the released source’sembed_dim ** -0.5(numerically verified against the original code, max abs logit diff < 1e-6) and is configurable viaattention_scale(#1186 by Li Qing).Add
braindecode.models.BaRISTA, an intracranial EEG foundation model whose spatial encoding scale is a free choice: electrodes are tokenized channel-wise and space enters as a single learned embedding selected by the electrode coordinate, its atlas parcel or its lobe, before a joint space-time transformer encoder with rotary temporal embeddings (#1173 by Julien Gadonneix).Add
braindecode.models.Brant, a braindecode-native port of the Brant foundation model for intracranial (sEEG/iEEG) signals (Zhang et al., NeurIPS 2023), including in-model spectral features and the shared configuration, feature-return, and head-reset APIs. The official pretrained weights load frombraindecode/brant-pretrained(all tensors verified identical to the official release) (#1100 by Adam Mounir).Add
braindecode.models.BrainTokenizer, the EEG/MEG VQ-VAE tokenizer of BrainOmni (NeurIPS 2025), with the released weights converted tobraindecode/braintokenizer-pretrained(#1043 by Bruno Aristimunha).Add
braindecode.models.PopulationTransformer(PopT, Chau et al. 2024), an iEEG population model over per-electrode features and coordinates, with pretrained weights atbraindecode/popt-pretrained(#1105 by Adam Mounir).Add
braindecode.models.BrainOmni, the BrainOmni downstream classifier on a frozenbraindecode.models.BrainTokenizer, with the released tiny and base weights converted tobraindecode/brainomni-tiny-pretrainedandbraindecode/brainomni-base-pretrained(#1043 by Bruno Aristimunha).Add
braindecode.models.VEMG2Pose,braindecode.models.NeuroPose, andbraindecode.models.SensingDynamicsfor dense hand-pose regression from surface EMG (#1132 by Bruno Aristimunha).Add a bounded SAE intervention tutorial on a frozen pretrained REVE with BNCI2014_001 using optional SAE Lens, without adding an SAE implementation to Braindecode (#1120 by Vandit Shah and Bruno Aristimunha)
Improve CI test scheduling and run documentation examples with bounded parallelism, preserving the test cases and gallery training workloads (#1161 by Bruno Aristimunha).
CI: torch threads per pytest-xdist worker so that threads times workers equal the runner’s vCPUs (one per worker on macOS);
test_model_compiledruns only in the Linux job that restores the TorchInductor cache; a pull request’s docs build executes only the gallery examples it changes (the full gallery runs on master, tags,release/*branches and a weekly schedule), and only non-PR docs runs save the MNE and Hugging Face caches (#1263 by Bruno Aristimunha).Add a bounded SAE activation analysis tutorial on BCI IV 2a (BNCI2014_001) using optional SAE Lens, without adding an SAE implementation to Braindecode (#1152 by Vandit Shah and Bruno Aristimunha)
Add
braindecode.models.BrainBERT, a self-supervised foundation model for intracranial (sEEG/iEEG) signals from Wang et al. (ICLR 2023), with pretrained weights (#1104 by Adam Mounir).Add pull request templates, including an exhaustive checklist for new model contributions covering implementation conventions, registration, documentation, and benchmarking (#1169 by Li Qing).
torch.jit.script()compiles models whose weights are parametrized (max-norm constraints,weight_norm): ATCNet, BDTCN, EEGITNet, EEGNeX, EEGNet, EEGTCNet, FBCNet, FBMSNet, IFNet and TIDNet now script and give the eager output, and so do models built with an intsfreqorinput_window_seconds(as in Hub configs) (#1256 by Bruno Aristimunha).torch.jit.script()compiles MetaNeuromotorHand, NeuroRVQTokenizer, REVE, SleepFM and SleepFMStager and gives the eager output (einops calls written asreshape/permute, NeuroRVQTokenizer’s four scales without a computed-namegetattr). Scripted REVE leaves out the autocast-off block around its Fourier features (TorchScript needs a constant autocast device). Eager outputs and state-dict keys are unchanged (#1271 by Bruno Aristimunha).torch.jit.script()compiles MVPFormer and gives the eager output: its einops calls are written as the reshapes and expands einops ran, anddwt_max_level()finds the level in integers instead of withmath.log2. Eager outputs and state-dict keys are unchanged (#1272 by Bruno Aristimunha).Shrink the slowest unit tests, keeping what each one checks: EEGInceptionMI’s 500 Hz case on 2 windows instead of 64; 1 s DANCE and ZUNA and 5-patch CodeBrain windows in the shared model test geometry; one
EEGClassifierfit per model instead of two; 8 BIOT shape cases instead of 240; 3 spawned processes instead of 14 in the distributed sampler tests (#1264 by Bruno Aristimunha).torch.jit.script()compiles 17 more models and gives the eager output: BENDR, BrainModule, CBraMod, CodeBrain, CSBrain, EEGDINO, LUNA, MIRepNet, MSVTNet, NeuroRVQ, SSTDPN, STEEGFormer, TCFormer and the four SignalJEPA variants. einops calls inforwardare written asreshape/permute,forwarddeclares its dict or tuple return, BrainModule no longer keepsNonein aModuleList(TorchScript dropped it), NeuroRVQ’s branches no longer use a computed-namegetattrand LUNA’s reconstruction head stays eager-only. Eager outputs and state-dict keys are unchanged (#1262 by Bruno Aristimunha).torch.jit.script()compiles BrainOmni, BrainTokenizer, DANCE, EEGPT and Labram and gives the eager output: the shared EMA codebook (braindecode.modules.quantization), DANCE’s Fourier position embedding and Perceiver, and EEGPT no longer use einops functions,**kwargsor@torch.no_grad()methods inforward, and Labram’sch_namesis no longer keyword-only. A scripted BrainOmni needs its tokenizer in eval mode. Eager outputs and state-dict keys are unchanged (#1270 by Bruno Aristimunha).torch.jit.script()compiles BIOT and TFMTokenizer and gives the eager output. They now runbraindecode.modules.linear_attention, the part oflinear_attention_transformerthey used (global linear attention, MIT), whoseforwardtakes no**kwargs; parameter names are unchanged, so released checkpoints load as before (#1273 by Bruno Aristimunha).torch.jit.script()compiles MAPA and gives the eager output, both for the montage given at construction and for another recording’ssensor_indicespassed toforward, so every braindecode model now scripts. The token layout is a tuple of named buffers and the band and region tables are module constants. Eager outputs and state-dict keys are unchanged (#1276 by Bruno Aristimunha).Add
braindecode.models.GCNsNet, a graph convolutional neural network for decoding time-resolved EEG motor imagery signals, from Hou et al. (2022) (#1331 by Seáic Mac Murchadha).braindecode.modules.EMACodebookreuses its distance-matrix storage during nearest-code lookup, avoiding two full-sized arithmetic temporaries without changing distance evaluation order (#1404 by Bruno Aristimunha).
API and behavior changes#
braindecode.datasets.BaseConcatDataset.get_metadata()returns ani_datasetcolumn with the position of the dataset each window comes from, and raises aValueErrorif a description or the window metadata already has that key. The samplers inbraindecode.samplerstake each dataset as one recording; pass the metadata withouti_datasetto group the one-trial datasets ofbraindecode.datasets.create_from_X_y()orbraindecode.datasets.create_from_mne_epochs()by subject, session and run as before. The samplers’infogets arecordingindex level after subject, session and run (#1369 by Raghav Rathi).braindecode.models.Labram,braindecode.models.CBraModandbraindecode.models.LUNAtokenize the time axis with the sharedbraindecode.modules.PatchTokenizerand accept a window that is not a multiple ofpatch_sizeby right zero-padding the last patch (with a warning), through a newon_non_divisible={"pad", "crop", "error"}argument; previously such windows raised a reshape error.braindecode.models.ZUNAnow defaults toon_non_divisible="pad"as well (it was"error"). Outputs and state-dict keys are unchanged for divisible windows, so released checkpoints load as before (#1226 by Bruno Aristimunha).Every patch-based model takes
on_non_divisible={"pad", "crop", "error"}, default"pad": a window that patches do not tile is zero-padded instead of cropped (STEEGFormer, Brant, BaRISTA, SleepFM, REVE) or rejected (NeuroRVQ, DuIN, CSBrain). Tiled windows are unchanged; configs saved without the key load as before (subclasses whose__init__does not take it are left alone) (#1399 by Bruno Aristimunha).braindecode.models.Labramdefaults touse_mean_pooling=Trueagain, the documented value and the readout of the original fine-tuning (fc_normof the mean patch token). #931 had made the [CLS] output, which the pretraining loss never uses, the default so that the pretraining checkpoint loaded strictly. That checkpoint, such as the released weights, now loads into the mean-pooling model as in the original fine-tuning script (normunused,fc_norminitialized). Passuse_mean_pooling=Falseto keep the [CLS] readout, for instance to load a checkpoint fine-tuned with it, which no longer loads into the default model (#1155 by Bruno Aristimunha).Remove
InterpolatedBENDR,InterpolatedBIOT,InterpolatedEEGPT,InterpolatedLaBraM,InterpolatedSignalJEPA,InterpolatedModel,ChannelInterpolationLayerandinterpolated_models_dict: useBENDR(chs_info=..., channel_strategy="spline")instead (#1241 by Bruno Aristimunha).
Requirements#
linear_attention_transformeris no longer a dependency: since #1273 braindecode ships the part BIOT and TFMTokenizer use.einops, which arrived only through it, is now declared directly (#1443 by Bruno Aristimunha).Add
sphinx-markdown-builder==0.6.11andpytest<9.1to the docs extra for version-scoped Markdown exports and their offline contract tests.Require PyTorch and TorchAudio >= 2.4 and remove obsolete attention fallbacks. REVE and ZUNA now import PyTorch’s RMSNorm layer directly, preserving their explicit epsilon values. Intel macOS is no longer supported because PyTorch stopped providing its binary packages after 2.2. (#1174 by Bruno Aristimunha)
Bug fixes#
braindecode.modules.FilterBankLayer, and sobraindecode.models.FBCNet,braindecode.models.FBMSNet,braindecode.models.FBLightConvNetandbraindecode.models.IFNet, script again with PyTorch 2.15, wheretorch.jit.scriptfailed insidetorchaudio.functional.lfilter()(“Expression of type dots cannot be used in a type expression”). Eager filtering still calls torchaudio; the scripted graph runs the same operations as before, so outputs are unchanged (#1501 by Bruno Aristimunha).Model loading validates relative custom weight paths and local symlink containment, without changing Hugging Face’s process-global filename. Serialized module classes are resolved only from shipped PyTorch and Braindecode classes, including the Hub and Pydantic configuration decoders (#1494 by Bruno Aristimunha).
braindecode.models.EpiNTz-scores each channel over the whole window before dropping the samples after the last full patch, as the reference implementation does; it dropped them first, which changed the output whenn_timesis not a multiple ofpatch_size(#1488 by Julien Gadonneix).from_pretrainedofbraindecode.models.DuINloads a checkpoint at anothern_times: the head, the only part sized by the window, keeps its initialization with a warning instead of raising a size mismatch (#1497 by Julien Gadonneix).braindecode.preprocessing.RemoveDCOffset,ReinterpolateRemovedChannelsandRemoveCommonAverageReferenceno longer expose internal duration and channel-location flags in their constructors. The existing keyword-onlyforce_dtypeoption is preserved and documented. Previously saved configurations still load with their original internal flags (#1491 by Bruno Aristimunha).Fix
braindecode.preprocessing.exponential_moving_standardize()andbraindecode.preprocessing.exponential_moving_demean()returning NaN for every sample of a channel after a single NaN sample (since thelfilterrewrite of #950): NaN samples are skipped again, as in the previouspandasimplementation (#1481 by Adam Mounir).Fix
braindecode.preprocessing.create_fixed_length_windows()giving the windows of a dataset with severaltarget_nameapandas.Seriestarget when nomappingis given, whichtorch.utils.data.DataLoadercannot collate: the target is now a list, asbraindecode.datasets.RawDatasetreturns and as with amapping(#1484 by Adam Mounir).braindecode.models.BIOT(200 Hz),braindecode.models.EMG2QwertyNet(2000 Hz) andbraindecode.models.MetaNeuromotorHand(2000 Hz) only use their defaultsfreqwhenn_timesandinput_window_secondsdo not determine it, instead of raisingValueErrorfor consistent inputs such asBIOT(n_times=512, input_window_seconds=2.0). Calls that worked before build the same model (#1485 by Adam Mounir).braindecode.models.EEGInceptionERP,braindecode.models.ATCNetandbraindecode.models.EEGInceptionMIonly use their defaultn_times(1000 for EEGInceptionERP) andsfreq(128 or 250 Hz) when the given geometry does not determine them, instead of raisingValueErrorfor consistent inputs such asEEGInceptionERP(input_window_seconds=1.0, sfreq=128)orATCNet(n_times=640, input_window_seconds=5.0). Calls that worked before build the same model (#1485 by Adam Mounir).Fix
braindecode.datautil.load_concat_dataset()ignoring itstarget_nameargument (it now sets the target of datasets of raws and raises for windowed datasets, as the outdated format did) and reading numeric-looking description strings such as subject"01"back as integers (#1480 by Adam Mounir).Fix
braindecode.datautil.infer_signal_properties()(used bybraindecode.EEGRegressorto setn_outputs) inferring one output for windows with several regression targets: the model was built withn_outputs=1and trained against all targets through broadcasting (#1479 by Adam Mounir).Fix
braindecode.datasets.BIDSEpochsDatasettaking all the annotations of the recording as the targets of the epochs, which fails (All arrays must be of the same length) when an epoch was dropped or the recording has other annotations (e.g.BADsegments): each epoch now takes the annotation at its time zero, or its event name when not every epoch has one (#1478 by Adam Mounir).Fix
braindecode.samplers.SequenceSamplerandbraindecode.samplers.BalancedSequenceSamplerreturning sequences that mix recordings when the windows of a recording are not contiguous in metadata withouti_dataset(e.g. alternating subjects): sequences are now taken from the recording’s own windows; results are unchanged when they are contiguous (#1477 by Adam Mounir).Fix
braindecode.augmentation.AugmentedDataLoaderwithnum_workers > 0drawing the same augmentations at every epoch and in every worker: a worker received a copy of the transform with its numpy generator in the state of the main process (a pickled copy of the global generator for the defaultrandom_state=Nonewith spawned workers, as on macOS and Windows), and that state never advanced. The main process now draws a seed from each generator when an epoch starts, and each worker reseeds its copy from that seed and its worker id, so runs with a fixedrandom_statestay reproducible (#1475 by Adam Mounir).Fix
braindecode.preprocessing.CombineChannels,braindecode.preprocessing.FixStimArtifact,braindecode.preprocessing.InterpolateBridgedElectrodes,braindecode.preprocessing.MaxwellFilterandbraindecode.preprocessing.RegressArtifactalways failing withAttributeError: MNE object does not have a ... method: these standalone MNE functions were looked up as methods of the recording. They are now called on it (#1482 by Adam Mounir).Fix cropped
braindecode.EEGClassifieraccuracy callbacks with array inputs in scikit-learn grid searches and pipelines: each array example is scored as one trial, while window datasets retain their existing trial aggregation (#1490 by Bruno Aristimunha).braindecode.visualization.frequency.amplitude_gradients()accepts CUDA tensors and tensors requiring gradients, as well as NumPy arrays, by detaching and moving the input to the CPU before its NumPy FFT (by Anton Soloviev). (by Anton Soloviev).Frequency-domain amplitude attribution preserves existing model parameter gradients, so analysis between optimizer steps does not alter training updates (by Anton Soloviev).
braindecode.visualization.frequency.amplitude_gradients_per_trial()returns each trial’s own gradient independently of the minibatch size, including a short final batch. The batch-mean reduction ofamplitude_gradients()is unchanged (by Anton Soloviev).braindecode.augmentation.AugmentedDataLoaderwithn_augmentation > 0rejects a transform that changes the shape ofXory— e.g.braindecode.augmentation.LabelSmoothing, which replaces class-index targets with distributions — with aValueErrorexplaining thatn_augmentation = 0should be used instead; the combination previously failed withRuntimeError: Tensors must have the same number of dimensionsfrom insidetorch.cat(#1470 by Li Qing).reset_headbuilds the new head in the model’s dtype, device and train/eval mode for every model: 18 models built it in float32 on the CPU (so a.double()or GPU model failed at the next forward) and 33 left it in training mode inside an eval model (#1451 by Bruno Aristimunha).braindecode.models.IFNetnow applies its reference initialization (truncated normal, std 0.01, for the convolutions and the max-norm head; zero biases; unit/zero batch norms): it was called on the model itself instead of throughapply, so it initialized nothing and the layers kept PyTorch’s defaults. This changes IFNet’s initial weights (#1453 by Bruno Aristimunha).Remove dead code:
braindecode/datasets/bids/format.py(an unused copy ofhub_format.py, imported nowhere) and two unused private definitions in MEDFormer and the PhysioNet 2018 dataset (#1444 by Bruno Aristimunha).The DDP tests of EEGCLIP, BrainOmni and NeuroRVQ no longer abort intermittently (SIGABRT during interpreter teardown of a spawned rank): ranks that succeeded exit with
os._exit(0)(#1438 by Bruno Aristimunha).braindecode.augmentation.Mixupkeeps its output in the transform’s float32 dtype instead of the global default dtype, avoiding model dtype mismatches. The functional operation preserves floating input dtypes and retains floating interpolation for integer and boolean inputs (by Anton Soloviev).braindecode.models.SincShallowNetandbraindecode.models.TMSANetraise aValueErrorat construction for input geometries they cannot run, extending the construction-time checks of #1313 to two more models: windows that leave fewer samples thanpool_sizeafter the temporal sinc filter or the average pooling (so the classifier head would get a negative or empty feature width, which previously failed withTrying to create tensor with negative dimension), and a missingn_times, which both models need for their head (#1432 by Li Qing).braindecode.functional.glorot_weight_zero_bias()skipsNoneweights and biases, sobraindecode.models.EEGNetwithbatch_norm_affine=Falseno longer raisesAttributeError, and its docstring now says what it does: it sets BatchNorm weights to one and biases to zero, and leaves the other weights to their layer’s initialization (it has not applied the Glorot initialization its name refers to since 1.0). Initial weights are unchanged (#1426 by Adam Mounir).braindecode.models.MEDFormerpatches its input over time, as in the original model: the port kept the original’s permute from(batch, n_times, n_chans)although braindecode inputs are(batch, n_chans, n_times), and swappedn_chansandn_timesin the patch embedding, so its token convolutions slid over the channels with kernels spanning the whole window, and the number of tokens did not depend onn_times. The token convolution and channel-embedding shapes change, so MEDFormer weights saved with an earlier version do not load (#1431 by Adam Mounir).braindecode.models.Labramwarns when, in the Neural Tokenizer mode (the default), the embedding dimension set by its temporal convolution differs from the givenembed_dim, which it used to replace silently (e.g.patch_size=100gives 104 instead of the default 200), and documents this (#1430 by Adam Mounir).braindecode.models.LUNAapplies itsattn_dropargument to the attention of its transformer blocks instead of ignoring it (the default 0.0 is unchanged), and documents it (#1429 by Adam Mounir).braindecode.models.BIOTwithhop_lengthlarger thansfreqfalls back to an integer hop ofsfreq // 2and stores it inhop_length, instead of passing a float totorch.stft()(which raisedTypeErrorin the forward pass) while keeping the original value in its configuration (#1428 by Adam Mounir).braindecode.training.predict_trials()(andpredict_trialsofbraindecode.EEGClassifier/braindecode.EEGRegressorin cropped mode) returns one target per trial for multi-value window targets, such as several target channels withlast_target_only=True, instead of raisingIndexError; only targets with a time axis are treated as sequences.braindecode.EEGRegressor.predict_trials()withcropped=Falsestacks the targets instead of concatenating them, so scalar targets no longer raiseValueErrorand multi-value targets keep one row per trial (#1427 by Adam Mounir).The docstring of
braindecode.modules.MultiHeadAttention.forward()stated the boolean mask the wrong way round: as intorch.nn.functional.scaled_dot_product_attention(), which receives the mask unchanged,Truemarks a position that takes part in attention andFalseone that is ignored (#1425 by Adam Mounir).braindecode.modules.CrissCrossTransformerEncoderLayerno longer attends across the samples of a batch with its defaultbatch_first=False(its inner attentions always receive batch-first tensors, which the layer builds itself), andnorm_first=Falsenow gives a post-norm layer, as intorch.nn.TransformerEncoderLayer, instead of being ignored.braindecode.models.CBraMod(batch_first=True,norm_first=True) is unchanged (#1424 by Adam Mounir).braindecode.modules.MLPwithnormalize=Truebuilds atorch.nn.LayerNormover the output features of each hidden layer instead of raisingTypeError(#1423 by Adam Mounir).braindecode.models.CTNetappliesfinal_drop_prob(its dropout layer was built but the forward pass flattened the features without it), and its positional encoding covers the actual sequence length, sopool_size_2smaller thanpool_size_1no longer raises a size mismatch; parameter shapes and eval-mode outputs of working configurations are unchanged (#1422 by Adam Mounir).braindecode.models.ATCNetwithconcat=Trueconcatenates the features of all sliding windows as documented, instead of stopping after the first window and failing with a shape error in the classifier; the defaultconcat=Falseoutput is unchanged (#1421 by Adam Mounir).braindecode.models.base.EEGModuleMixin.get_output_shape()runs its probe forward pass in eval mode and restores each module’s mode afterwards, so it no longer updates BatchNorm running statistics or draws dropout masks; models that call it in__init__(e.g.braindecode.models.EEGNet,braindecode.models.EEGConformer) start with untouched BatchNorm statistics (#1420 by Adam Mounir).braindecode.augmentation.SmoothTimeMask,braindecode.augmentation.FrequencyShift, the sensor rotations (braindecode.augmentation.SensorsRotation,braindecode.augmentation.SensorsXRotation,braindecode.augmentation.SensorsYRotation,braindecode.augmentation.SensorsZRotation) andbraindecode.augmentation.FTSurrogaterun on Apple MPS, which has no float64: their parameters are drawn in single precision there and the spherical-spline matrices are computed on CPU in double precision; results on CPU and CUDA are unchanged (#1419 by Adam Mounir).braindecode.augmentation.Composecan be called without a target, like any otherbraindecode.augmentation.Transform, and then returns onlyX:Compose(...)(X, None)used to unpack the batch along its first axis (returning(X[0], X[1])for a batch of two) andCompose(...)(X)raisedTypeError(#1418 by Adam Mounir).braindecode.samplers.DistributedRecordingSampler(andbraindecode.samplers.DistributedRelativePositioningSampler) no longer raiseTypeErroron the first sample with the defaultrandom_state=Noneor aRandomState: torch’sDistributedSamplergetsrandom_stateas its seed only when it is an int, and seed 0 otherwise, the same on every process (#1417 by Adam Mounir).EEGPrep preprocessors that remove EEG channels (
braindecode.preprocessing.RemoveFlatChannels,braindecode.preprocessing.RemoveBadChannels,braindecode.preprocessing.RemoveBadChannelsNoLocs) no longer raiseValueErrorwhen the recording also has non-EEG channels: the original order is restored for the channels that remain, and the input raw is no longer left without its non-EEG channels if the operation fails (#1416 by Adam Mounir).braindecode.preprocessing.create_windows_from_events()checks the window bounds of every trial: it raises when a window would start before the recording (negativetrial_start_offset_sampleson an early trial gave empty windows; withuse_mne_epochs=Truesuch windows are still dropped bymne.Epochs) or when any trial, not only the last one, ends past the recording withtrial_stop_offset_samples; with per-event-type offsets, each trial is checked with the offset of its own event type, so valid inputs are no longer rejected because another type has a larger offset (#1415 by Adam Mounir).set_description(..., overwrite=True)on a windows dataset no longer deletes the overwritten key from the description of the dataset it was windowed from (the two share onepandas.Series), which could make indexing the source dataset raiseKeyErrorwhen the key was its target (#1414 by Adam Mounir).braindecode.datautil.load_concat_dataset()reads a copied or moved dataset folder from the copy: the cachedN-raw.pklis only reused when the fif path it stores is the file next to it, otherwise the fif is read again and the cache re-created. It also skips entries of the folder that are not dataset subdirectories (e.g..DS_Store, a README) instead of raisingValueError(#1413 by Adam Mounir).recording_idsofbraindecode.datasets.TUH(andbraindecode.datasets.TUHAbnormal,braindecode.datasets.TUHEvents) selects recordings by their chronological position, as documented, instead of by their position in the file listing (#1412 by Adam Mounir).braindecode.preprocessing.Preprocessor.deserialize()preserves the supplied configuration so it can be saved and reused for other recordings (by Anton Soloviev).braindecode.augmentation.functional.frequency_shift()accepts scalar, NumPy and CPU tensor shifts for CUDA inputs, preserving gradients to tensor shifts on another device (by Anton Soloviev).braindecode.models.EpiNTacceptschannel_strategyandchannel_strategy_kwargsthrough the shared channel layer, including configuration round trips and native checkpoint loading (#1403 by Bruno Aristimunha).braindecode.datasets.create_from_mne_epochs()no longer shifts targets and window start indices when the inputmne.Epochsstill contain rejected epochs: bad epochs are now dropped on a copy before iterating, so data andepochs.eventsstay aligned (#1411 by Adam Mounir).RevePositionBank.forwardraises for a name it does not hold instead of returning fewer rows than channels (#1400 by Bruno Aristimunha).braindecode.models.Guetschel2026places CPU-computed buffers on the active device insidetorch.devicecontexts on older supported PyTorch versions (by Anton Soloviev).braindecode.models.SincShallowNetnow round-trips throughsave_pretrained/from_pretrained: itswindowbuffer viewed a larger storage, which safetensors refuses, somodel.safetensorswas never written (#1385 by Li Qing).Attention on Intel Gaudi in HPU eager mode no longer returns wrong outputs and gradients at some shapes: the bridge’s fused SDPA kernel, used since #1338 through a shared helper by
braindecode.modules.MultiHeadAttention(EEGConformer, CTNet, MSCFormer, MIRepNet, STEEGFormer),braindecode.models.ZUNA,braindecode.models.DANCE,braindecode.models.Labram,braindecode.models.BaRISTA,braindecode.models.NeuroRVQ,braindecode.models.NeuroRVQTokenizer,braindecode.models.REVE,braindecode.models.PBTandbraindecode.models.EEGPT, received q/k/v as strided head views and gave values unrelated to the right ones for some of them (for example EEGConformer with 64 tokens per window at batch 4). The helper now passes contiguous copies; train-step time unchanged (#1358 by Bruno Aristimunha).braindecode.models.NeuroRVQTokenizernow synchronizes its cold-start EMA codebook weights, counts and initialization state from rank zero before assigning tokens in distributed training. Different rank-local first batches previously produced incompatible codebooks despite later all-reduced EMA statistics (#1319 by lindicaphxag-tech).braindecode.augmentation.MaskEncodingcan mask the last valid interval and a full window (max_mask_ratio=1,n_segments=1), which raisedValueError: high <= 0(#1323 by Anton Soloviev).braindecode.datasets.BaseConcatDataset.save()andbraindecode.preprocessing.preprocess()withoverwrite=Trueraise aValueErrorinstead of deleting a subdirectory a lazily loaded dataset still reads from; saving such a dataset back where it was loaded from lost its recordings, or withids_to_loadin another order wrote wrong data silently (#1367 by Raghav Rathi).braindecode.datasets.TUH,braindecode.datasets.TUHAbnormalandbraindecode.datasets.TUHEventskeep a recording date that FIF cannot store (before 1901-12-13: the year-1 placeholder of undated recordings, the de-identified 1899-12-30 of 16 TUAB v3.0.1 EDFs) in the description only and leave the raw’smeas_dateempty, sobraindecode.preprocessing.preprocess()withsave_dirno longer aborts on them (#1307 by Bruno Aristimunha).The samplers in
braindecode.samplerstook the windows sharingsubject,sessionandrunfor one recording. The two nights of abraindecode.datasets.SleepPhysionetsubject were one recording, sobraindecode.samplers.SequenceSamplerandbraindecode.samplers.BalancedSequenceSamplerbuilt sequences running from one night into the other andbraindecode.samplers.RelativePositioningSamplerraised anIndexError; the same happened to the segments of a TUH session. Windows of BIDS recordings without session or run were dropped, so a dataset without these entities raised an error, and datasets without any of these columns could not be sampled. Each dataset is now one recording, which the newi_datasetcolumn ofget_metadata()marks, andbraindecode.samplers.RelativePositioningSamplerno longer fails when two windows of a recording start at the same sample (#1369 by Raghav Rathi).braindecode.models.MEDFormerruns its patch embedding, a Conv2d kernel as tall asn_times, as the equivalent conv1d withn_timesinput channels: MEDFormer now runs on Gaudi2, whose convolution kernels are limited to 256 rows (it failed to compile forn_times > 256), and its CPU train step is 15-35 % faster; same state dict, outputs change by float rounding only (#1287 by Bruno Aristimunha).braindecode.training.mixup_criterion()usescross_entropyinstead ofnll_loss: the models output logits since #624, so its loss had no lower bound and training withbraindecode.augmentation.Mixupdiverged without an error; log-probabilities still give the same loss (#1389 by Raghav Rathi).braindecode.models.REVE,braindecode.models.CSBrain,braindecode.models.AttnSleep,braindecode.models.BDTCN,braindecode.models.ZUNAandbraindecode.models.DGCNNraise aValueErrorat construction for input geometries they cannot run (channel names missing from REVE’s position bank or windows shorter than its patch, CSBrain windows that are not a multiple ofpatch_size, more than one AttnSleep channel, windows shorter than the BDTCN receptive field, more ZUNA time patches thanmax_seqlen, a single DGCNN channel) instead of failing inforwardor with an unrelated error; thebraindecode.models.USleeperror for an even kernel size namesensure_odd_conv_size(#1313 by Bruno Aristimunha).braindecode.models.SleepStagerBlanco2020,braindecode.models.EEGConformerandbraindecode.models.MSVTNetraise theValueErrornamingn_timesand the input shape when the window is too short for the dummy forward that sizes their head at construction, instead of a torchRuntimeError(#1322 by Bruno Aristimunha).Fix coordinate ordering in
braindecode.augmentation.SensorsRotationwithspherical_splines=False, which raised an error or interpolated the wrong locations (#1295 by Anton Soloviev).braindecode.models.Deep4Netwithstride_before_pool=Trueor an integerfinal_conv_lengthcrashed inforwardfor windows between its approximate and exact minimum length, e.g. 441-681 samples forbraindecode.models.EEGCLIP(2 s at 250 Hz); those windows now shrink the temporal kernels like shorter ones, other windows unchanged bit for bit (#1310 by Bruno Aristimunha).braindecode.models.LUNA,braindecode.models.SignalJEPAandbraindecode.models.SignalJEPA_Contextualgave NaN outputs, andbraindecode.models.DGCNNraisedInput contains NaN, whenchs_infohas channels without a position (MNE stores a NaNloc, e.g. bipolar or EOG/ECG channels): LUNA falls back to its default positions, SignalJEPA starts those channel embeddings at zero and DGCNN looks the names up instandard_1005; outputs with finite positions unchanged bit for bit (#1309 by Bruno Aristimunha).Preserve the final samples in
braindecode.augmentation.SegmentationReconstructionwhen the window length is not divisible by the segment count, instead of replacing them with zeros (#1279 by Anton Soloviev).braindecode.models.MEDFormercrashed inforwardfor windows longer than 5000 samples (e.g. 30 s at 200 Hz): its time-indexed positional table now hasmax(5000, n_times)rows; shorter windows unchanged bit for bit (#1311 by Bruno Aristimunha).Preserve regression windows whose target is at the final recording sample in
braindecode.preprocessing.create_windows_from_target_channels(), independently of the recording time origin (#1296 by Anton Soloviev).braindecode.datautil.load_concat_dataset()closes thetarget_name.jsonand*_kwargs.jsonfiles it reads, which raised twoResourceWarning: unclosed fileper recording (5986 when reloading the 2993 TUAB recordings) (#1314 by Bruno Aristimunha).Every model raises a
ValueErrornamingn_timesand the input shape when called on a window shorter than its layers need, instead of a torchRuntimeError(Output size is too small/Kernel size can't be greater than actual input size) from deep insideforward;braindecode.models.EEGModuleMixin.get_output_shape()already did so at construction (#1317 by Bruno Aristimunha).braindecode.models.SleepStagerBlanco2020raisedin_channels must be divisible by groupsat construction whenn_chansis not a multiple ofn_groups(one channel, or an odd count with the defaultn_groups=2); it now usesgcd(n_chans, n_groups)groups, unchanged for everyn_chansdivisible byn_groups(#1321 by Bruno Aristimunha).Model fixes caught by new CPU-only integration checks (complex tensors, host syncs, kernel gaps, device/dtype follow, training after
inference_mode, deepcopy/pickle): BrainOmni/BrainTokenizer SELU trains on Gaudi, EEGSym pools withavg_pool2d, FBCNet/FBMSNet/FBLightConvNet and LUNA run in float16, EEGMiner and AttnSleep deep-copy after training, Labram and NeuroRVQ pickle, SignalJEPA heads acceptchannel_strategy, and tensors built inforwardfollow the input in CodeBrain, TCFormer, LUNA, MVPFormer, BrainOmni, ZUNA, DIVER1 and EEGDINO; float32 outputs unchanged (#1253 by Bruno Aristimunha)braindecode.models.EEGSymtrains on Gaudi: its depthwise spatialConv3dwith a(1, 1, n_chans)kernel runs as a conv2d on a view, since the Gaudi graph compiler segfaulted on the depthwise conv3d backward; same state dict, outputs unchanged, gradients change by float rounding only (#1289 by Bruno Aristimunha).braindecode.functional.hilbert_freq()returns float16 for float16 input (it returned float32), sobraindecode.models.EEGMinerruns aftermodel.to(torch.float16)(#1259 by Bruno Aristimunha)braindecode.models.BrainOmniandbraindecode.models.BrainTokenizernow run forward on Intel Gaudi (HPU) in lazy mode: the SEANet LSTM input is permuted as a 4D view, which Gaudi compiles; values are unchanged (#1249 by Bruno Aristimunha)braindecode.models.EMG2QwertyNetandbraindecode.models.MetaNeuromotorHandgive correct outputs on Intel Gaudi (HPU) in eager mode: the rotation-invariant MLP rolls a contiguous copy of its input, since Gaudi eager mode rolls a non-contiguous tensor wrongly (#1249 by Bruno Aristimunha)Models now run after
model.to(torch.float64),torch.bfloat16ortorch.float16, and their FFT, STFT and filter-bank front ends run on Intel Gaudi (HPU): the newbraindecode.functional.spectral_input()gives these ops a float32 (at least) input, on the CPU for HPU tensors since PyTorch has no complex bfloat16 and Gaudi no complex dtype; the real result is cast back with.to(x). Used bybraindecode.models.BIOT,braindecode.models.BrainBERT,braindecode.models.Brant,braindecode.models.CBraMod,braindecode.models.CodeBrain,braindecode.models.ContraWR,braindecode.models.DIVER1,braindecode.models.EEGDINO,braindecode.models.EMG2QwertyNet,braindecode.models.LUNA,braindecode.models.MAPA,braindecode.models.MetaNeuromotorHand,braindecode.models.SensingDynamics,braindecode.modules.FilterBankLayer(FBCNet, FBMSNet, FBLightConvNet, IFNet),braindecode.modules.GeneralizedGaussianFilterandbraindecode.functional.hilbert_freq(). Tensors built insideforwardofbraindecode.models.BENDR,braindecode.models.CodeBrain,braindecode.models.DGCNN,braindecode.models.REVE,braindecode.models.SyncNetandbraindecode.models.ZUNAfollow the input’s dtype. CodeBrain can also train after a first forward undertorch.inference_mode(). Float32 outputs and gradients are unchanged (#1246 by Bruno Aristimunha)Delete each passing test’s
tmp_pathso the Windows CI runner no longer runs out of disk (#1251 by Bruno Aristimunha).braindecode.models.BrainOmniandbraindecode.models.BrainTokenizernow type CTF and KIT axial MEG gradiometers as gradiometers, as the released BrainOmni code does; they were typed as magnetometers, which gave them the wrong sensor embedding. Elekta magnetometers and planar gradiometers were already correct. (by Bruno Aristimunha)Fix
braindecode.models.BrainOmni.reset_head()leaving the new head in training mode aftermodel.eval(), so its dropout made inference stochastic; the head now follows the model’s mode (by Bruno Aristimunha).Fix
braindecode.models.BrainTokenizerreconstruction whenwindow_lengthis not a multiple ofprod(ratios): each decoded window is now cropped towindow_lengthbefore the windows are joined, so later windows keep their position. Configurations with a divisible window, such as the released one, are unchanged (by Bruno Aristimunha).braindecode.models.BrainTokenizerandbraindecode.models.BrainOmninow raiseValueErrorforn_filters < 2and for attention dimensions that do not split across heads, instead of a reshape error or anassert; the tokenizer’s sampling rate warning namesBrainTokenizer(by Bruno Aristimunha).braindecode.models.MAPA.input_shapenow returns the spectrogram shape(1, n_chans, 20, n_times)withnormalization="session", soget_output_shape()works in that mode (by Bruno Aristimunha).Fix
from_pretrained()rejecting a caller’schs_info(n_chans=… different from chs_info) andn_times/sfreq(n_times different from input_window_seconds * sfreq): the Hub config filled the geometry arguments the caller omitted, so values from two sources collided. The derived argument is now pinned from the caller’s one. This unblocks loading EEGPT, STEEGFormer, Brant and MVPFormer checkpoints on a montage other than their pretraining dataset’s (#1232 by Bruno Aristimunha).Fix
braindecode.models.CBraModbuilding aLazyLinearhead when the geometry came fromchs_infoorinput_window_secondsinstead ofn_chans/n_times; such a model could not be saved or loaded withfrom_pretrained(“uninitialized parameter”). The head is now a concreteLinearwhenever the geometry is known (#1233 by Bruno Aristimunha).braindecode.models.BrainOmninow loads the released checkpoints’ RoPE cache (cosines only) and keeps PyTorch’s default head initialisation, as the released code does. Unlike the released code, it applies no attention dropout in evaluation mode (#1244 by Bruno Aristimunha).Fix
braindecode.models.CBraModfailing inforwardfor anypatch_sizeother than 200: the spectral reshape hard-coded 101 rFFT bins instead ofpatch_size // 2 + 1(#1240 by Bruno Aristimunha).Fix
from_pretrainedignoring the saved geometry when called with an explicitchs_info=Noneorn_chans=None, andbraindecode.models.Labramwithneural_tokenizer=Falseignoringon_non_divisible(#1250 by Bruno Aristimunha).braindecode.datasets.BaseConcatDataset.set_description()rejects description columns with the wrong number of rows before modifying datasets, instead of silently truncating or partially applying them. An empty mapping remains a no-op (#1405 by Bruno Aristimunha).Fix
braindecode.models.Deep4Netshort-input auto-scaling withsplit_first_layer=Trueso the scaledfilter_time_lengthis used by the actualbraindecode.modules.CombinedConvtemporal kernel instead of retaining the original constructor value. By lindicaphxag-tech.Fix
braindecode.models.Deep4Netwith an explicitfinal_conv_lengthand non_times. The model now skips input-length auto-scaling when the input length is intentionally unspecified, matching the documented contract that onlyfinal_conv_length="auto"requiresn_times. By lindicaphxag-tech.Route the attention of
braindecode.models.EEGDINOandbraindecode.models.Labramthrough theirqkvlinear module instead of reading its weight, so hooks and adapters onqkv(e.g. LoRA) take effect; before, they were skipped silently. Outputs change only by float rounding (#1194 by Bruno Aristimunha)Fix
braindecode.modules.AvgPool2dWithConvto invalidate cached pooling weights when the exact input device changes, preventing stale weights from being reused across CUDA devices or non-CUDA backends with the same dtype. By lindicaphxag-tech.Fix
braindecode.models.ShallowFBCSPNetwithsplit_first_layer=False, which attempted to initialize and remap checkpoint keys through the split-onlyconv_time_spatmodule after the CombinedConv refactor. The unsplit path now initializes and loads its direct temporal convolution as before (#1212 by lindicaphxag-tech).Fix
braindecode.models.Deep4Netwithsplit_first_layer=False, which attempted to initialize and remap checkpoint keys through the split-onlyconv_time_spatmodule after the CombinedConv refactor. The unsplit path now initializes and loads its direct temporal convolution as before (#1207 by lindicaphxag-tech).Make
braindecode.modules.TimeDistributedaccept non-contiguous sequence batches by reshaping rather than requiring view-compatible strides. By lindicaphxag-tech.Make
braindecode.modules.Chomp1dpreserve the input whenchomp_size=0instead of returning an empty time axis. This restoresbraindecode.models.TCNwithkernel_size=1, whose causal padding is zero. By lindicaphxag-tech.Preserve
Deep4Net’s first-block temporal stride when using the mergedCombinedConvpath withstride_before_pool=True; dense-prediction outputs now retain the historical temporal geometry (#1205 by lindicaphxag-tech).Fix
braindecode.modules.CausalConv1dto use explicit left-only causal padding. This preventskernel_size=1from producing an empty time axis and keeps strided/dilated convolutions aligned with the causal reference instead of over-cropping downsampled outputs (#1216 by lindicaphxag-tech).braindecode.EEGClassifier.predict_trials()andbraindecode.EEGRegressor.predict_trials()no longer raise aValueErroron trials of different lengths; they return a list with one prediction array per trial (#1159 by Bruno Aristimunha)Fix
braindecode.models.EEGMineron Intel Gaudi (HPU), part 2 of #1183:braindecode.modules.GeneralizedGaussianFilterandbraindecode.functional.hilbert_freq()now use a real-valued DFT on devices without complex tensors, computed in float32 with autocast disabled (a bf16 matmul DFT gave phase-locking features 20% off and wrong-direction filter gradients). CPU and CUDA keeptorch.fft(#1193 by Bruno Aristimunha)Compute the 4-D Fourier position embedding of
braindecode.models.REVEin float32 with autocast disabled. Intel Gaudi (HPU) autocast downcasts the position × frequency products to bf16 before sin/cos (embedding ~3 % off on Gaudi2); CPU/CUDA results are unchanged (#1192 by Bruno Aristimunha)Fix
braindecode.models.MVPFormeron Intel Gaudi (HPU): the channel-relative shift is now a singletorch.gatherand the grouped-query key repeat works on a contiguous copy. On HPU the previous advanced indexing back-propagated through a host-sideindex_put_with wrong gradients (and took 108 s per 30 s-window training step), and the repeat scrambled the relative keys (features 14-39 % off). CPU and CUDA results are bit-identical (#1189 by Bruno Aristimunha)Fix the positional encoder of
braindecode.models.SignalJEPAandbraindecode.models.SignalJEPA_Contextualon Intel Gaudi (HPU): the time table is now a non-persistent buffer that follows.to(device), and the encoding is built withtorch.catinstead of strided in-place writes (the temporal part was 73 % off on Gaudi2). State-dict keys and CPU/CUDA outputs are unchanged (#1191 by Bruno Aristimunha)Fix
braindecode.EEGRegressortraining on datasets with one target per trial: the(batch,)target is now reshaped to match a(batch, 1)prediction instead of being broadcast to(batch, batch)by the loss, andbraindecode.EEGRegressor.fit()now returnsself(#1180 by Arthur031221).Preserve shared class targets when creating MNE epochs from different event annotations, as in sleep staging. MNE event IDs remain unique. (#1174 by Bruno Aristimunha)
Fix a
FutureWarningraised by MNE >= 1.13 when importingbraindecodeor using models, datasets and augmentations that build on thestandard_1005/standard_1020montages: MNE renamed these montages tocolin27_1005/colin27_1020and will remove the legacy names in MNE 1.14, so their spelling is now resolved against the installed MNE version viabraindecode.util.resolve_montage_name()(#1163 by Li Qing).Fix
braindecode.modules.CombinedConvraisingRuntimeError: self must be a matrixwhenin_chans,n_filters_timeorn_filters_spatis 1, which madebraindecode.models.ShallowFBCSPNetandbraindecode.models.Deep4Netunusable on single-channel data (#1154 by Julien Gadonneix).Fix
braindecode.models.base.EEGModuleMixin.reset_head()leaving the saved configuration on the previous head, so a model saved after changing its number of outputs could not be loaded back. Eighteen models (BENDR, BIOT, CBraMod, EEGDINO, EEGPT, Labram, MetaNeuromotorHand, MVPFormer, REVE, STEEGFormer, ZUNA, the three SignalJEPA classifiers and the Interpolated BENDR, BIOT, EEGPT and LaBraM wrappers) now record the newn_outputs, and BENDR, CBraMod and EEGDINO built as feature extractors now also record that they became classifiers, instead of reloading without their trained head. Existing head-reset train/eval behavior is unchanged (#1181 by Raghav Rathi).Make
braindecode.preprocessing.create_windows_from_events()infer the event mapping once for the whole dataset before the recordings are windowed. Withmapping=Noneandn_jobsabove one, every worker numbered the event descriptions of its own recording from zero, so the same description could receive different integer targets across recordings. By Sarthak Tayal.Make
braindecode.datautil.load_concat_dataset()restore thetargets_fromandlast_target_onlysettings of a savedbraindecode.datasets.EEGWindowsDataset. The loader looked the stored settings up under the nameWindowsDatasetwhile the windowers record them underEEGWindowsDataset, so a dataset windowed withtargets_from="channels"came back reading its targets from the metadata. By Sarthak Tayal.Make
braindecode.datasets.BaseConcatDataset.get_metadata()work on a copy of the metadata of each dataset. The description columns were written into the metadata frame of the dataset itself, replacing any column sharing a name with a description key such astarget. By Sarthak Tayal.Make
braindecode.preprocessing.create_windows_from_events()accept amappingthat sends several event descriptions to the same target when the windows are stored asmne.Epochs, for example to merge sleep stages 3 and 4.mne.Epochsrejects anevent_idwith repeated values since MNE 1.13, so annotations now receive distinct event IDs while their shared targets remain in the window metadata. By Sarthak Tayal.Fix
braindecode.models.STEEGFormeron high-density sensor nets whose electrodes are numbered rather than named for a 10-20 site (e.g. EGI HydroCelE1…E256). A channel name outside the montage vocabulary used to switch the whole montage to the identity mapping, which is meaningless and raisedchan_pos_idx values must be in [0, 145)above 145 channels. Such channels now take the slot of the nearest 10-05 site from theirchs_infoposition, while named channels keep their own slot. Without positions, the identity fallback is kept, and a clear error is raised when it cannot fit (#1185 by Bruno Aristimunha).Fix
braindecode.models.EEGMineron Intel Gaudi (HPU) and undertorch.jit.trace:braindecode.modules.GeneralizedGaussianFilternow clamps its parameters in place undertorch.no_grad()instead of reassigning.data, andbraindecode.functional.hilbert_freq()computes the complex FFT step in float32 for bfloat16 inputs, so the phase features work under bfloat16 autocast (#1183 by Bruno Aristimunha).Fix
braindecode.modules.MaxNormParametrizefailing on Intel Gaudi (HPU), which affects every model with a max-norm weight constraint (e.g.braindecode.models.EEGNet,braindecode.models.ATCNet). The row rescale is now written out instead of callingTensor.renorm; values and gradients are unchanged (#1184 by Bruno Aristimunha).Fix
braindecode.functional.hilbert_freq()withforward_fourier=Truereturning one sample fewer than the input for odd-length signals and doubling the Nyquist coefficient for even-length ones. It now matchesscipy.signal.hilbert()for both.braindecode.functional.plv_time()on time-domain input uses the corrected transform (#1188 by Arthur031221).Fix
braindecode.modules.MaxNormParametrizeproducingNaNoutputs or gradients for zero or very small float16 rows after #1184; the norm and scale are computed in float32 for float16/bfloat16 inputs, while float64 precision is preserved. Safe denominators prevent invalid intermediate gradients, and rows at or belowmax_normare unchanged. Empty tensors pass through and a negativemax_normraises, asTensor.renormdoes (#1187 by Bruno Aristimunha).Keep the channel IDs of
braindecode.models.EEGPT(chans_id) out of the state dict. They are rebuilt fromchs_info, and a checkpoint that still stores them no longer overrides them: the released weights now load on any montage and with the default channel projection, where they failed with a size mismatch, and a montage with the same channel count no longer silently takes the checkpoint’s IDs (#1195 by Bruno Aristimunha).Fix
braindecode.models.Labramso the released weights keep their pretrained time embedding at every window length: it now holds the original 16 absolute time slots (patchpuses slotp) instead of one slot per patch plus one, which matched the released weights only for 15-patch windows. Checkpoints saved with the previous layout load with identical outputs, and windows longer than 16 patches warn that their extra slots keep their initialization (#1155 by Bruno Aristimunha).Fix cropped
braindecode.EEGRegressortraining on a 1-D numpyycomputing its loss on a(batch_size, batch_size)broadcast.braindecode.EEGRegressor.fit()reshapes such ayto(n_trials, 1), andbraindecode.training.CroppedLosssqueezed the time-averaged prediction to(batch_size,). It now keeps the output dimension when the target is 2-D (#1198 by Raghav Rathi).Fix channel resolution in the channel layer:
T3andT7stay distinct; a misspelt strategy raises with the closest name; coordinate-only channels match a target within 15 mm; non-EEG channels raise (or are dropped withdrop_non_eeg=True); legacy names are copies;splineis regularised (reg=1e-3) and a row gain above 2 warns; fewer than four positioned channels raise aValueError(#1241 by Bruno Aristimunha).
Current 1.8.0 (2026-08-31)#
Enhancements#
Add
braindecode.models.MIRepNet, the released downstream convolutional-Transformer encoder and classification head for motor-imagery EEG, with pre-trained weights re-hosted at braindecode/mirepnet-pretrained (#1126 by Bruno Aristimunha).Add a reusable temporal-distributed separable convolution encoder to
braindecode.modules, and centralize output-head replacement for models usingbraindecode.models.base.EEGModuleMixin(#1145 by Bruno Aristimunha)Add
braindecode.datasets.BaseConcatDataset.plot()(every dataset,BIDSDatasetincluded): show a recording in the eegdash-viewer inside a Jupyter cell — serverless (bytes inlined, viewer from CDN), with the synchronized hand-pose panel when a*_desc-pose.jsonsidecar is present (#1133 by Bruno Aristimunha)Add
braindecode.visualization.capture_activations()andbraindecode.visualization.run_with_activation_substitution()to read or replace a submodule’s output during a forward pass (#1138 by Vandit Shah)Preserve the recording-local row of each canonical MNE annotation as
i_trial_in_datasetin event-window metadata, keeping it aligned with targets and annotation extras through event mapping, duration filtering, and dropped short trials. Conflicting annotation extras are dropped with a warning, while conflicting dataset descriptions are rejected during metadata aggregation. (#1130 by Bruno Aristimunha)Models with TorchScript-compatible forward paths can now be passed straight to
torch.jit.script(), without first being rebuilt as a plaintorch.nn.Module.braindecode.models.base.EEGModuleMixinhides its signal-related properties and themappingattribute from TorchScript introspection, so scripting reachesforwardinstead of failing on the class attributes. (#1115 by Aditya Singh)Add a transfer-learning tutorial illustrating the TUAB-to-NMT pathology workflow with offline synthetic recordings (#580 by Mohammad Javad D and Bruno Aristimunha).
API and behavior changes#
Deprecate
drop_last_windowinbraindecode.preprocessing.create_windows_from_events()andbraindecode.preprocessing.create_fixed_length_windows(); it will be removed in version 2.0. The keyword-only replacement,on_last_window, accepts'overlap'or'drop'. Explicit legacyFalsemaps to'overlap'and explicit legacyTruemaps to'drop'; either legacy spelling emits aDeprecationWarning. When no explicit window size or stride is supplied to fixed-length windowing, the policy is immaterial and normalizes to the existing single full-recording window; this also replaces the internal assertion formerly reached by explicit legacyTrue. (#1058 by Michele Romani)
Requirements#
Remove the
rotary_embedding_torchdependency. The rotary frequency generation used bybraindecode.models.LUNAandbraindecode.models.ZUNAis now implemented directly with PyTorch. (#1136 by Bruno Aristimunha)
Bug fixes#
Sample stochastic-depth masks with out-of-place
torch.bernoulli()instead of in-placeTensor.bernoulli_. This keeps the same per-sample Bernoulli mask and scaling while avoiding lazy recipe-cache offset failures on Gaudi accelerators. Thebraindecode.functional.drop_path()documentation now also correctly describesdrop_probas the probability of dropping a path. By Bruno Aristimunha.Make
braindecode.models.LUNArotary attention portable to accelerators that reject concatenations containing empty tensor views, while preserving its pretrained-checkpoint parameter keys and numerical behavior. Also crop mismatchedbraindecode.models.USleepdecoder tensors with views instead of allocating index tensors, avoiding unsupported accelerator lowering without changing the selected samples. (#1136 by Bruno Aristimunha)Fix the docstrings of
braindecode.models.ATCNet,braindecode.models.AttnSleep,braindecode.models.CTNet,braindecode.models.EEGSimpleConv,braindecode.models.IFNet,braindecode.models.SPARCNet,braindecode.models.SleepStagerBlanco2020,braindecode.models.SleepStagerChambon2018andbraindecode.models.TIDNet, which documented parameters their constructors do not accept, either because the parameter was renamed (bn_sizeisbottleneck_size,resamplingisresampling_freq,att_dropoutisatt_drop_prob) or because it was a deprecated alias that has since been removed (n_classes,n_channels,in_chans,input_size_s,input_window_samples). Following those docstrings raisedTypeError. It also corrects stale defaults and types inbraindecode.models.CTNet,braindecode.models.EEGSimpleConv,braindecode.models.IFNet, andbraindecode.models.SleepStagerBlanco2020(#1116 by Aditya Singh).Honor the configurable activation in the adaptive feature recalibration block of
braindecode.models.AttnSleep, and report thed_modelrequired by a non-reference window instead of failing inside layer normalization. The derived source now also carries the complete upstream MIT notice (#1119 by Sarthak Tayal).Make
braindecode.datasets.SleepPhysionetwake cropping retain the complete final sleep annotation without including an extra endpoint sample (#612 by John Muradeli).Fix
braindecode.training.mixup_criterion()treating a plain target tensor of three elements as a mixup(y_a, y_b, lam)triple. The branch was selected onlen(target) == 3, which is also true for the targets of any batch holding three windows, so a validation batch or a last partial batch of that size failed withExpected input batch_size (3) to match target batch_size (0). The mixup branch is now selected on the container type. (#1122 by Sarthak Tayal)Keep the mixing coefficient of
braindecode.augmentation.Mixupon the dtype of the batch. Withbeta_per_sample=Trueit came straight from numpy asfloat64, which upcast the loss returned bybraindecode.training.mixup_criterion()tofloat64while the model stayed infloat32. (#1122 by Sarthak Tayal)Let
braindecode.augmentation.AugmentedDataLoaderused withn_augmentationgreater than zero carry the targets of a transform that mixes them. Batches were concatenated as plain tensors, so combining that option withbraindecode.augmentation.Mixupraisedexpected Tensor as element 1 in argument 0, but got tuple. The triple parts are now concatenated one by one and the clean originals get a mixing coefficient of one. (#1122 by Sarthak Tayal)Let
braindecode.EEGClassifierandbraindecode.EEGRegressortrain with a transform that mixes targets. The loader wrapper cast every target withy.type(...), so a batch carrying the(y_a, y_b, lam)triple ofbraindecode.augmentation.Mixupstopped the fit with'tuple' object has no attribute 'type'before the first batch was seen. The triple parts now follow the same classification/regression dtype contract as a plain target. (#1122 by Sarthak Tayal).Keep
braindecode.preprocessing.EEGPrepcompatible with EEGPrep 0.3, which no longer exposes theeegprep.utilsnamespace used for sampling-rate validation (#1123 by Bruno Aristimunha).Write strict JSON in Hub dataset stores while preserving NaNs in numeric MNE
Infosequences. Unsupported scalar NaNs and infinities are rejected before store creation, and preprocessing kwargs are stored as native strict JSON while legacy string values remain readable (#1128 by Azra Bano).Leave recordings shorter than
n_windowsout of the draw inbraindecode.samplers.BalancedSequenceSamplerinstead of failing on them. Such a recording holds no valid sequence start, which made the sampling range empty and raisedValueError: high <= 0on the first sequence that landed there.braindecode.samplers.SequenceSampleralready skips those recordings. A clear error now names the longest recording when none of them can hold a sequence (#1125 by Sarthak Tayal).Keep
file_idsofbraindecode.samplers.SequenceSamplerinteger. The ids were built from untyped lists, so an empty list coming from a recording too short for a sequence turned the concatenated array intofloat64, against the documented dtype and unusable as an index (#1125 by Sarthak Tayal).Document the
randomizeparameter ofbraindecode.samplers.SequenceSamplerunder its own name. The docstring described it asrandom, which no signature accepts (#1125 by Sarthak Tayal).
Code health#
None yet
Current 1.7.0 (2026-08-01)#
Enhancements#
Add
braindecode.models.ZUNA, a position-aware EEG foundation model from Warner et al. (2026), ported from the publicZyphra/ZUNA1.1encoder with a Braindecode classification head, shared patch tokenization, and construction-time spatial positions compatible with TorchScript andtorch.compile(#1020 by Jon Huml)Add
braindecode.models.util.interpolated_models_dict, a dedicated registry for the interpolated (channel-adapting) models, keeping them separate frombraindecode.models.util.models_dict(#1093 by Bruno Aristimunha)
API and behavior changes#
The
stride_factorparameter ofbraindecode.models.FBLightConvNetis deprecated and will be removed in a future release. The model never read it, its temporal segmentation is set bywin_len, and the reference implementation has no such parameter. Passing it now emits aDeprecationWarningand keeps being ignored. By Sarthak Tayal.Interpolated models (e.g.
InterpolatedBIOT,InterpolatedLaBraM) are no longer included inbraindecode.models.util.models_dict; they now live in the separatebraindecode.models.util.interpolated_models_dictregistry. They remain fully usable and resolvable by name in the skorch wrappers and pydantic configs (#1093 by Bruno Aristimunha)
Requirements#
None yet
Bug fixes#
Fix
braindecode.EEGClassifier.predict_trials(),braindecode.EEGRegressor.predict_trials()andbraindecode.training.scoring.CroppedTrialEpochScoringon the training set raisingAttributeErrororTypeErrorwhen the module was passed as a model name or as an uninstantiated class. Those paths read themoduleconstructor argument instead of the initializedmodule_attribute, so they only worked when an already instantiated module was passed, which the documentation discourages. By Sarthak Tayal.Raise a clear
ValueErrorinbraindecode.models.FBLightConvNetwhenn_timesis shorter thanwin_len. The temporal attention kernel is sized asn_times // win_len, so a short window produced an empty kernel and the constructor died with aZeroDivisionErrorcoming from the weight initialisation. The docstring now also documentswin_lenand reports the correctn_bandsdefault of 9. By Sarthak Tayal.Use
nn.Dropout1dinstead ofnn.Dropout2dfor the channel-wise dropout insidebraindecode.models.BDTCN,braindecode.models.TCNandbraindecode.models.BENDR. Those layers receive(batch, channels, times)activations, whichnn.Dropout2donly handles through a deprecated fallback that warns on every forward pass and is scheduled to be reinterpreted as unbatched input, masking batch items rather than channels. The masking behaviour is unchanged. By Sarthak Tayal.Clarify that
braindecode.training.scoring.predict_trials(),braindecode.EEGClassifier.predict_trials(), andbraindecode.EEGRegressor.predict_trials()return ground-truth dataset targets alongside predictions. By Sarthak Tayal.Make
braindecode.models.EEGMinercompatible with TorchScript across magnitude, correlation, and phase-locking-value feature modes by replacing runtime callable dispatch with a scriptable feature module. (#1101 by Sarthak Tayal)Make the
braindecode.models.REVEposition bank robust on offline / limited-network nodes: it is now cached in the writable MNE data directory (resolved via theREVE_POSITIONS_PATHconfig key, defaulting under~/mne_data) instead of the package folder, so a prefetchedreve_positions.jsonthere is used without any download (#1098 by Bruno Aristimunha)
Code health#
None yet
Current 1.6.1 (2026-07-01)#
Enhancements#
Pin the
%pip install braindecodecell in generated notebooks to the version used to build them (braindecode==X.Y.Zfor stable releases,git+https://github.com/braindecode/braindecode.gitfor dev builds), so notebooks can be reproduced with a matching installation. (#1080 by Fashad Ahmed)Add
braindecode.functional.sinusoidal_positional_encoding(), a shared sine/cosine positional-encoding primitive (handling odd dimensions), and reuse it inbraindecode.models.BIOT,braindecode.models.MEDFormer, andbraindecode.models.STEEGFormerinstead of re-deriving the table in each. Encodings are bit-identical, so model behavior is unchanged. Also addbraindecode.modules.GatedLinearUnit, a configurable GLU-family gate (GEGLU with the defaultnn.GELU, SwiGLU withnn.SiLU;torch.nn.GLUis hard-wired to the sigmoid) for building gated transformer feed-forwards. (#1078 by Bruno Aristimunha)Add
braindecode.models.DANCE, an event detection-and-classification model (CNN encoder + Perceiver bottleneck + DETR-style decoder) that detects a set of(start, end, class)events from long, unaligned EEG windows, with abraindecode.training.DanceLosscriterion, anf1_eventdetection metric, and a runnable tutorial. The re-implemented spatial merger / Perceiver / conv stack are numerically parity-verified against the upstream reference. (#1075 by Bruno Aristimunha)Add an optional spatial Fourier
braindecode.modules.ChannelMerger(withbraindecode.modules.FourierEmb) tobraindecode.models.BrainModuleviause_merger=True, implementing the montage-agnostic spatial attention its docstring described. Also fixes a latent crash for non-integerdilation_growthand sizes the channel accounting in forward order sosubject_layers/STFT combinations work. Default-off, so existing behavior is unchanged. (#1076 by Bruno Aristimunha)Add opt-in electrode positions in the batch via
braindecode.datasets.BaseConcatDataset.set_return_ch_pos()and a cachedch_posaccessor on windowed datasets, plusbraindecode.datasets.pad_channels_collate()to make collections with heterogeneous montages (different channel sets) batchable. Positions and a channel mask are routed into the model’sforwardunderbraindecode.EEGClassifier. Default-off, so the(X, y, crop_inds)contract is unchanged. (#1066 by Bruno Aristimunha)Add a
revisionkeyword argument tobraindecode.datasets.BaseConcatDataset.pull_from_hub()so callers can pin dataset downloads to a specific branch, tag, or commit on the Hugging Face Hub.Clarify the model summary table’s
Typecolumn by usingPredictionfor supervised heads instead of describing them as classification-only. By Sarthak Tayal.Add a
Modalitycolumn (EEG, MEG, sEMG, …) to the model summary table so models can be filtered by the bio-signal they target, and validate it against a controlled vocabulary. By Bhargav Kowshik.Add
braindecode.models.InterpolatedEEGPT, a channel-interpolation variant ofbraindecode.models.EEGPTbuilt withInterpolatedModel(). By Pierre Guetschel.Add
braindecode.models.EEGDINO, the EEG-DINO self-distillation foundation model (Small/Medium/Large) with pretrained S/M weights. By Bruno Aristimunha.Add
braindecode.models.MVPFormer, the multi-variate parallel attention (MVPA) foundation model for heterogeneous multi-variate iEEG, with a db4-wavelet signal encoder computed from first principles (no new dependency). By Bruno Aristimunha.Add
braindecode.models.STEEGFormer, a ViT-based EEG foundation model pre-trained with a masked-autoencoder objective, from Yang et al. (ICLR 2026). By Adam Mounir.Add separate EEGNet and TCN dropout rates to
braindecode.models.EEGTCNetvia the newdrop_prob_eeganddrop_prob_tcnarguments, enabling the source/paper configuration (p_eeg=0.2,p_tcn=0.3). Both default todrop_prob, so existing behavior is unchanged. (#1060 by Bruno Aristimunha)Add an opt-in
conv_max_norm_constargument and asource_optimizer_param_groups()helper tobraindecode.models.ATCNet, exposing the official implementation’s convolution/TCN max-norm constraint (0.6) andL2weight-decay groups (conv/TCN0.009, dense0.5). Defaults preserve current behavior. (#1061 by Bruno Aristimunha)Add causal forward filtering support to
braindecode.modules.FilterBankLayerfor IIR and FIR filter banks viaphase="forward"/phase="causal". Existing zero-phase filtering remains the default. By Bruno Aristimunha.Add
braindecode.models.TCFormer, the Temporal Convolutional Transformer for EEG motor-imagery decoding: a multi-kernel CNN front-end, a grouped-query attention Transformer with rotary positional embeddings, and a grouped temporal convolutional network head. (#1065 by Bruno Aristimunha)Add a
return_featuresoption tobraindecode.models.FBMSNet: when enabled,forward()returns(logits, features)wherefeaturesis the flattened pre-classifier vector (shape(batch, out_channels_spatial * stride_factor)), enabling center-loss training without subclassing. (#1083 by Bruno Aristimunha)Add an
n_augmentationargument tobraindecode.augmentation.AugmentedDataLoaderfor fixed set-expansion: each batch keeps its clean originals and appendsn_augmentationindependently transformed copies (e.g. the EEG-Inception 6x training set withn_augmentation=5); the default0preserves the current in-place behavior. The collate is now a picklable callable, so the loader supportsnum_workers > 0, and several augmentation transforms were vectorized for speed. (#1070 by Bruno Aristimunha)
API and behavior changes#
Removed the deprecated aliases that were scheduled for removal after their deprecation in v1.2/v1.3:
EEGNetv4(usebraindecode.models.EEGNet),SleepStagerEldele2021(usebraindecode.models.AttnSleep),TSceptionV1(usebraindecode.models.TSception), andBNCI2014001(usebraindecode.datasets.BNCI2014_001). (#1045 by Bhargav Kowshik)braindecode.models.EEGTCNetnow inserts aBatchNorm1dafter each TCN convolution (and uses a bias in the residual downsample), matching the original Keras implementation. This closes a documented replicability gap but changes the default architecture and parameter count; passtcn_batch_norm=Falseto recover the previous behavior (e.g. to load checkpoints trained before this change). By Bruno Aristimunha.Change the default
proto_cpt_stdofbraindecode.models.SSTDPNfrom0.01to1.0, matching the sourcetorch.randninitialization of the intra-class compactness prototypes. By Bruno Aristimunha.
Requirements#
Cap the test dependency to
pytest<9.1in thetestsextra.pytest9.1.0 changed theIdMakerconstructor signature, which breakspytest_cases3.10.1 and makes the whole test suite crash at collection time. The cap can be lifted oncepytest_casessupportspytest>=9.1. By Adam Mounir.
Bug fixes#
Fix
braindecode.models.SSTDPNrenormalizingproto_sepalong the wrong axis: the inter-class separation prototypes are now constrained per class-row (dim=0) as in the paper/source (||s_i|| <= S), instead of across classes (dim=1). (#1059 by Bruno Aristimunha)Fix
ContraWR,DeepSleepNet, andEEGMinerraising a confusingValueErroronbatch_size=1forward passes in train mode due toBatchNorm1d(and its 2D/3D variants) requiring more than one sample per channel during training. The affected BatchNorm layers now use running statistics whenbatch_size == 1in train mode. By Bruno Aristimunha.Fix the auto-generated standalone-function preprocessors (
braindecode.preprocessing.ComputeCurrentSourceDensity,braindecode.preprocessing.SetBipolarReference, andbraindecode.preprocessing.OversampledTemporalProjection) passing the function name as a string instead of the callable, so they failed to apply. These functions return the modified instance and are now wrapped as callables. Standalone functions that return auxiliary data (e.g. annotations or bad channels) are intentionally left on the existing path for now. (#885 by Yiheng Li)Fix
braindecode.preprocessing.AnnotateAmplitude,braindecode.preprocessing.AnnotateNan,braindecode.preprocessing.AnnotateBreak,braindecode.preprocessing.AnnotateMovement,braindecode.preprocessing.AnnotateMuscleZscore,braindecode.preprocessing.FindBadChannelsLof, andbraindecode.preprocessing.ComputeBridgedElectrodesfailing at apply-time because the underlying MNE functions return auxiliary data (annotations or bad-channel lists) rather than the modified recording. Each preprocessor now stores a wrapper callable that applies the returned side effects (raw.set_annotations/raw.info['bads']) and returns the recording. (#1055 by Bruno Aristimunha)Fix incorrect import path in CONTRIBUTING.md by Yiheng Li
Fix the broken EEGNeX quickstart snippet on the documentation landing page, which kept non-EEG channels (including the STIM trigger), hardcoded a mismatched
n_times, and failed at the default stratified validation split withy=None; it now restricts to EEG channels and letsEEGClassifierinfer the signal dimensions from the data. By Bhargav KowshikFix
EEGClassifierandEEGRegressorsilently skipping all training when the training set is smaller thanbatch_size(with the defaultiterator_train__drop_last=True). Previously every batch was dropped and the model was left untrained without any message; aUserWarningis now raised telling the user to lowerbatch_sizeor setiterator_train__drop_last=False. (#1053 by Bhargav Kowshik)Fix
braindecode.modules.FilterBankLayerproducingNaNinfloat32: the IIR path downcast the float64 filter coefficients to the input dtype beforefiltfilt(), so band-pass banks with poles near the unit circle (e.g. the Chebyshev-II banks used byFBMSNetandFBCNet) diverged. The recursion now runs infloat64and is cast back. (#1067 by Bruno Aristimunha)Fix
braindecode.models.EEGITNetdiverging from the reference implementation: the inception spatial depthwise convolutions and the final dense layer now carry the authors’ max-norm constraints (1.0and0.25), and the dimensionality-reduction convolution uses14filters instead of28. (#1068 by Bruno Aristimunha)Fix
braindecode.models.MSVTNetdropping the main classification head whenreturn_features=True(it returned only the branch predictions, making the paper’s joint deep-supervision loss unreproducible). It now returns(main_logits, stacked_branch_logits), matching the source. (#1069 by Bruno Aristimunha)Fix the multi-scale spatial convolutions in
braindecode.models.EEGSymbeing dense (groups=1) with a bias: the authors’unit_dconvuses grouped/depthwise convolutions without bias, so they now usegroups=out_channelsandbias=False. (#1071 by Bruno Aristimunha)Align
braindecode.models.EEGSymwith the reference implementation: use the authors’ even temporal kernels withpadding="same", ascending scale order, residual filter progression, residual projection blocks, dense single-scale spatial convolutions, channel-merging groups, Keras batch normalization settings, and defaultspatial_resnet_repetitions=1. (#1088 by Bruno Aristimunha)
Code health#
Silence the new “training set smaller than
batch_size” warning (#1053) in thetest_eegneuralnetsignal-argument tests, which intentionally fit tiny mock data to check argument propagation rather than to train. Keeps the warning meaningful by not emitting it on every CI run. (#1056 by Adam Mounir)Install CPU-only PyTorch wheels in the
testsanddocsCI workflows viaUV_TORCH_BACKEND=cpu. GitHub runners have no GPU, so the default CUDA build pulled ~1.8 GiB of unusednvidia-*wheels and contributed to a disk-exhaustion crash. (#1054 by Bhargav Kowshik)Add a monthly scheduled workflow that cuts a stable PyPI release on the 1st of every month, complementing the existing per-push
.devNpipeline. (#1030 by Bruno Aristimunha)Make the monthly release workflow push only the release tag and open a pull request for the version bump, so it never needs to push to the protected
masterbranch. (#1031 by Bruno Aristimunha)
Current 1.5.1 (stable)#
Enhancements#
Add
braindecode.augmentation.BandRotationandbraindecode.augmentation.functional.band_rotation(): per-band circular roll along the channel axis plus inter-band temporal jitter, for surface-EMG inputs shaped(B, num_bands * electrodes_per_band, T). Models small wristband rotation between sessions and relative timing noise between two arms, from the emg2qwerty paper (Sivakumar et al., NeurIPS 2024). (#1013 by Bruno Aristimunha)Build SpecAugment (Park et al., Interspeech 2019) into
braindecode.models.EMG2QwertyNetas a parameter-free submodule gated by a newspec_augmentconstructor flag (defaultFalse). When enabled, applies up ton_time_masks×time_mask_paramtime bands andn_freq_masks×freq_mask_paramfrequency bands on the log-spectrogram duringtrain()only, with masks sampled IID per(sample × band × electrode)triple — same recipe as the upstreamemg2qwerty.transforms.SpecAugmentdataset transform. The mask fill value stays a 0-D on-device tensor (spec.mean()) so the forward pass adds no host round-trip on GPU. Also adds areturn_featuresruntime flag tobraindecode.models.EMG2QwertyNet.forward()(returns{"features": (B, T_out, num_features), "cls_token": None}, BIOT / signal-JEPA convention) and a matchingreturn_featureconstructor flag (returns(emissions, features)tuple, BIOT-style legacy path) so downstream wrappers — such as neuroai’sDownstreamWrapperModel— can pick up the encoder representation viamodel_output_key="features"(dict) ormodel_output_key=1(tuple) without changes to their call site. (#1015 by Bruno Aristimunha)
API and behavior changes#
Restore the per-batch
ch_nameskeyword argument onbraindecode.models.Labram.forward()that was removed in 1.5.0 (#993).ch_namesis now keyword-only and, when provided, case-insensitively matches each name tobraindecode.models.labram.LABRAM_CHANNEL_ORDERand indexes into the canonical position-embedding bank, so callers can forward an arbitrary subset of canonical channels without going throughbraindecode.models.InterpolatedLaBraM. The strictValueErrorraised in 1.5.0 whenchs_infodid not matchLABRAM_CHANNEL_ORDERis downgraded to aUserWarningso that downstream wrappers (e.g. neuroai’s_LabramChannelWrapper) can build the innerbraindecode.models.Labramwith their union channel set and resolve the subset per batch viach_names.
Bug fixes#
Fix
braindecode.augmentation.AmplitudeScalecrashing on the defaultrandom_state=Noneand on thenumpy.random.RandomStateinstance thatbraindecode.augmentation.Transformpasses in viaself.rng. The previoustorch.Generator().manual_seed(...)path was incompatible with both; the implementation now routes throughcheck_random_statelike the rest of the augmentation module. (#1021 by Sarthak Tayal)Fix 404 author link for Jonathan Dan in the changelog (#1022)
Current 1.5.0 (stable)#
Enhancements#
Add
braindecode.datasets.BaseConcatDataset.set_target()to swap any per-window metadata column or per-record description field (e.g. a BIDS entity, a participants.tsv extra) into the dataset’s targetyin one call, replacing the manualfor ds in concat.datasets: ds.metadata.loc[:, 'target'] = ...; ds.y = ...loop. Dispatches on the subdataset type: writesmetadata['target']/ds.yfor windowed records, and pointstarget_nameat the chosen description field for raw records. By Bruno Aristimunha.Redesign the documentation landing page (
docs/index.rst) in a pyhealth.dev-style layout: animated brain → EEG → net hero, fact strip highlighting MOABB / EEGDash interoperability, interactive model-zoo browser sourced frombraindecode/models/summary.csv, Hugging Face Hub integration row, and a tutorial-thumbnail carousel. Addssphinxext-opengraphto docs extras and aSoftwareApplicationJSON-LD block. (#1007 by Bruno Aristimunha)Tutorials now train for a few epochs then load pretrained weights from Hugging Face Hub to show full training curves and metrics. All 9 tutorial checkpoints published to
huggingface.co/braindecode/. The offline training script used to produce the checkpoints is available as a gist: https://gist.github.com/bruAristimunha/27d74c8410fe9d0db258a03f42efa7c6. (#985 by Bruno Aristimunha)Use
F.scaled_dot_product_attentioninbraindecode.modules.MultiHeadAttention, enabling optimized attention kernels (flash-attention on CUDA, memory-efficient backends on other devices). By Léo Burgund and Bruno Aristimunha. (#902)Add experimental channel interpolation feature: new
braindecode.modules.ChannelInterpolationLayerplus thebraindecode.models.InterpolatedModel()class factory project arbitrary user channel sets to a model’s canonical set via an MNE-backed (frozen by default) interpolation matrix. Ship pre-built variantsbraindecode.models.InterpolatedLaBraM,braindecode.models.InterpolatedSignalJEPA, andbraindecode.models.InterpolatedBIOTfor the corresponding pre-trained models. (#993 by Pierre Guetschel)Add a tutorial walking through the experimental
Interpolated*family (Running a Pretrained Model on Any Channel Set with the Channel Layer): failure mode of the vanilla backbones on non-canonical channel sets, one-line fix viabraindecode.models.InterpolatedLaBraM/braindecode.models.InterpolatedBIOT/braindecode.models.InterpolatedSignalJEPA, side-by-side visualisation of thename_matchvsalwaysinterpolation matrices, and thetrainable=Trueflag. (#994 by Pierre Guetschel)Mark deterministic index buffers (
braindecode.models.BIOTencoder’sindexandbraindecode.models.REVE’s positionembeddingbank) as non-persistent. They are rebuilt from__init__arguments on every instantiation, so keeping them instate_dictonly bloated checkpoints and caused spurious mismatches whenn_chans(or the position-bank config) differed between save and load. (#993 by Pierre Guetschel)Add
braindecode.visualizationinterpretability utilities, all built on plain PyTorch autograd with no extra dependencies:saliency(),input_x_gradient(),integrated_gradients(),layer_grad_cam(),project_to_topomap()(thin wrapper aroundmne.viz.plot_topomap()), andcompute_metrics()for quantitative attribution comparison. A new tutorial, Interpretability of EEG Decoders, walks through the full pipeline.Add
braindecode.models.MetaNeuromotorHand, a port of the handwriting decoder from Meta / CTRL-labs’ generic neuromotor interface (Kaifosh, Reardon et al., Nature 2025). The model takes raw 16-channel surface EMG from the wristband at 2 kHz and produces per-token scores for CTC decoding of handwritten text. The pipeline is a fixed multivariate power frequency (MPF) featurizer (channel-wise STFT, cross-spectral density, frequency-band averaging, and SPD matrix logarithm) followed by a circular rotation-invariant MLP and a 15-block causal conformer encoder. Meta’s pretrained checkpoint loads directly viaload_state_dict(after stripping thenetwork.prefix); the port is bit-exact to the upstream reference implementation on real sEMG. Distributed under CC BY-NC 4.0 to match the upstream repository; see the class docstring for the license warning and the pretrained-checkpoint loading recipe. By Bruno Aristimunha.Add
braindecode.models.EMG2QwertyNet, a port of the TDS-Conv-CTC touch-typing decoder fromfacebookresearch/emg2qwerty(Sivakumar et al., NeurIPS 2024). The model takes raw 32-channel surface EMG (two 16-electrode wristbands at 2 kHz) and emits per-frame scores over a 99-class typing vocabulary (98 keys + CTC blank); passlog_softmax=Trueto get log-probabilities directly consumable byCTCLoss. The pipeline is a parameter-free log-spectrogram front-end, per-electrode-per-band BatchNorm, a circular rotation-invariant MLP (one per band), and a stack of Time-Depth-Separable convolutional blocks (Hannun et al., 2019) without temporal padding. The encodernn.Sequentialmirrors upstream’sTDSConvCTCModule.modelindices for parameter-bearing children, so upstream emg2qwerty checkpoints load directly viaload_state_dict(after stripping the PyTorch-Lightningnetwork.prefix); the class-levelmappingonly remaps the head frommodel.4.{weight,bias}tofinal_layer.{weight,bias}. Distributed under CC BY-NC 4.0 to match the upstream repository; see the class docstring for the license warning and the pretrained-checkpoint loading recipe. By Bruno Aristimunha.
API and behavior changes#
braindecode.modules.MultiHeadAttentionnow follows PyTorch’s SDPA mask convention: boolean masks useTrueto ignore a position (previouslyTruemeant keep). The scaling factor is now1/sqrt(head_dim)instead of1/sqrt(emb_size). (#902)braindecode.models.BENDR: remove then_chans_pretrained/chan_proj_max_normparameters and thechannel_projectionlayer; hard-code the 20 pre-training channels as_BENDR_TARGET_CHS_TUPLES. The officialbraindecode/braindecode-bendrcheckpoint has been re-uploaded flat sofrom_pretrainednow loads its 99 weights (previously 0 of 99 matched silently). Also shipsbraindecode.models.InterpolatedBENDR, theInterpolatedModel()wrapper that accepts arbitrary userchs_infoand projects to the canonical 20 BENDR channels (theSCALEtarget has no physical position, so its interpolation row is a spatial spline of the user’s EEG — not the dn3 amplitude statistic). (#992 by Pierre Guetschel)braindecode.models.Labramnow requireschs_infoto matchLABRAM_CHANNEL_ORDERexactly (128 channels, canonical order). Theon_unknown_chsparameter and the forward-timech_namesargument are removed. Users with arbitrary channel sets should migrate tobraindecode.models.InterpolatedLaBraM. (#993 by Pierre Guetschel)
Bug fixes#
Fix
braindecode.models.SyncNetswapped parameter initialization wherephi_ini(phase shift) was usingbeta_init_valuesandbeta(decay) was usingphase_init_values, replaced incorrect.view()reshape with.permute()for proper conv2d filter weight layout, and fixed duplicate default values in docstring (by Sarthak Tayal)Fix
braindecode.models.AttentionBaseNetredundantsuper().__init__()call that ran the parentnn.Module.__init__twice (by Sarthak Tayal)Fix incomplete author email in
braindecode.models.TSceptionheader (by Sarthak Tayal)Fix a time-of-check-time-of-use race in
braindecode.datasets.base._zarr_to_memmap()that caused concurrent workers to repeatedlyrename-replace the published.npycache, producing wasted I/O on local filesystems and.nfsXXXXsilly-rename files plusSIGBUScrashes on NFSv3. The published file is now created exactly once viaos.linkand is never replaced, making the cache safe under arbitrary concurrent access on local POSIX, NFSv3, Lustre and SMB (#986 by Pierre Guetschel)Register
braindecode.models.BIOTencoderindexas a non-trainable buffer instead of a parameter (torch.long), so it is treated as module state rather than trainable weights (#988 by Pierre Guetschel)Fix
TypeError: type 'Any' is not subscriptablewhen importingbraindecode.models.configwithoutnumpydanticinstalled on Python 3.12+ (#871 by Sarthak Tayal)Fix
braindecode.preprocessing.create_fixed_length_windows()crashing when onlywindow_size_samplesis provided withoutwindow_stride_samples, stride now defaults to window size as documented (#990 by Sarthak Tayal)Add
channel_embeddingparameter tobraindecode.models.SignalJEPAandbraindecode.models.SignalJEPA_Contextualto load pre-trained channel embedding weights when fine-tuning on a subset of the pre-training channels. Two new HuggingFace checkpoints are published:braindecode/signal-jepaandbraindecode/signal-jepa_without-chans(#991 by Pierre Guetschel)Bump
openneuro-pyto>=2026.4.0so the docs build picks up upstream PR#308(DatasetFile.key→id). The previous<2026.4pin (#1000) avoided a libc double-free seen with newer releases but brokeexamples/datasets_io/plot_bids_dataset_example.pyagainst the live OpenNeuro 5.0.0 GraphQL schema (Cannot query field "key" on type "DatasetFile") (#1002 by Bruno Aristimunha)Retry transient TLS failures from
physionet.orgwhen fetchingbraindecode.datasets.SleepPhysionet(by Bruno Aristimunha)
Current 1.4.0 (stable)#
Enhancements#
Unlock the SPD model category in the documentation and link to the spd_learn library with intersphinx cross-references to its seven models (SPDNet, EEGSPDNet, TSMNet, TensorCSPNet, PhaseSPDNet, Green, MAtt) (by Bruno Aristimunha)
Hub integration docstring is now model-specific: each model’s documentation shows examples with its own class name instead of a generic
EEGNetv4reference, and existing model notes are preserved (by Bruno Aristimunha)Optimize
braindecode.preprocessing.exponential_moving_standardize()andbraindecode.preprocessing.exponential_moving_demean()by replacing Pandas-based implementation with a more efficient SciPylfilterversion, achieving a ~1.5x speedup (#950 by Léo Burgund)Add an optional
lazyinit path tobraindecode.datasets.base.BaseConcatDatasetto defer cumulative size computation (and any downstream dataset length access) until first access, supporting truly lazy raw loading workflows.Adding
braindecode.models.EEGPTwith HuggingFace Hub integration and pretrained weights available at braindecode/eegpt-pretrained (#908 by Young Truong and Kuntal Kokate)Add
braindecode.models.CBraModmodel (#914 by Pierre Guetschel)Add
braindecode.models.CodeBrainfoundation model with pretrained weight loading from HuggingFace (#964 by Vandit Shah)Expose additional arguments of
mne_bids.read_raw_bids()inbraindecode.datasets.BIDSDataset(#918 by Pierre Guetschel)Populate epochs metadata with the
mne.Annotations’sextrasentries (#918 by Pierre Guetschel)Add
braindecode.datasets.TUHEventsdataset and improve other TUH datasets (#920 and #923 by Pierre Guetschel)Allow overlapping events in
braindecode.preprocessing.create_windows_from_events()(#923 by Pierre Guetschel)Better alignment of channels configuration with the pretrained LABRAM model, including a warning when initializing without channel information and improved documentation (#931 by Young Truong )
Add informative
__repr__and_repr_html_tobraindecode.datasets.RawDataset,braindecode.datasets.base.EEGWindowsDataset,braindecode.datasets.base.WindowsDataset, andbraindecode.datasets.BaseConcatDatasetso that printing or displaying a dataset in a notebook shows channel count/types, sampling frequency, duration/window size, channel names, description summary, and epoch-level metadata including target distribution and extra metadata columns (signal-level details marked*are taken from the first recording).Add
mapping,preload,picks,drop_bad_windows, anddescriptionsparameters tobraindecode.datasets.mne.create_from_mne_epochs()to bring it to parity withbraindecode.datasets.mne.create_from_mne_raw()(#941 by Aman Srivastava).Per-event-type windowing parameters in
braindecode.preprocessing.windowers.create_windows_from_events()(#942 by Pierre Guetschel)Add
use_mne_epochssupport tobraindecode.preprocessing.windowers.create_fixed_length_windows()(#943 by Pierre Guetschel)Faster loading from epochs on disk with
braindecode.datasets.base.WindowsDataset(#944 by Pierre Guetschel)Add possibility to convert
braindecode.datasets.base.EEGWindowsDatasettobraindecode.datasets.base.WindowsDatasetfor efficient storage (#952 by Pierre Guetschel)Add
braindecode.models.DGCNNmodel for EEG emotion recognition using dynamic graph convolutional neural networks (#947 by Vandit Shah)Add
encoder_onlymode tobraindecode.models.BENDRfor 4-chunk temporal pooling (Kostas et al. 2021, Section 2.4), andn_chans_pretrainedparameter with max-norm constrained channel projection for fine-tuning pretrained BENDR on datasets with arbitrary channel counts (by Kuntal Kokate)Add dynamic SDPA fallback in
braindecode.models.reve.ClassicalAttentionto support PyTorch versions without SDPA (by GalAshkenazi1)Add unified
return_featuresparameter toforward()of all 7 foundation models (EEGPT,Labram,REVE,BENDR,BIOT,CBraMod,SignalJEPAvariants). WhenTrue, returns a consistent{"features": Tensor, "cls_token": Tensor | None}dict across all models. Legacy parameters (return_encoder_output,return_feature,return_all_tokens, etc.) continue to work unchanged (by Bruno Aristimunha).Add
reset_head()to all 7 foundation models for replacing the classification head with a new number of outputs.from_pretrained()now automatically callsreset_headwhen the user passes ann_outputsthat differs from the saved config (by Bruno Aristimunha).Enable lazy loading from Zarr when using
pull_from_hub()orload_from_zarrwithpreload=False. Data is decompressed lazily and MNE objects (.raw/.windows) are reconstructed on first access sopreprocess()works on lazy datasets (#978 by Bruno Aristimunha).
API changes#
Add
braindecode.models.base.EEGModuleMixin.get_config()andbraindecode.models.base.EEGModuleMixin.from_config()to all models, enabling full JSON round-trip serialization and reconstruction of any model including all__init__parameters (by Bruno Aristimunha)push_to_hub()now saves all model parameters toconfig.json(previously only 6 EEG-specific parameters were saved; model-specific parameters likeF1,D,drop_probwere lost on reload) (by Bruno Aristimunha)Add
braindecode.modules.Squareactivation module and updatebraindecode.models.ShallowFBCSPNetto usetype[nn.Module]forconv_nonlin(backward-compatible with callable) (by Bruno Aristimunha)Replace
LazyLinearwithLinearinbraindecode.models.CBraModwhen input dimensions are known, improving Hub round-trip compatibility (by Bruno Aristimunha)
Requirements#
Relaxed PyTorch requirement to >=2.0 to support Intel-based Macs (by GalAshkenazi1)
Bugs#
Improve the error message when
from_pretrained()orpush_to_hub()are called without the optionalhuggingface_hubdependency installed. Users now get a clearImportErrorwith installation instructions (pip install 'braindecode[hub]') instead of anAttributeError. (#1024 by @copilot)Replace the vague
license: unknownfield in the auto-generated Hugging Face Hub dataset card with an inferred license (when consistent across all recordings’ descriptions) or aplease-specifyplaceholder with a reminder comment for the user to fill in the dataset’s license. (#1014 by Fashad Ahmed)Fix the documentation header “Cite Braindecode” announcement link: it used a bare
cite.htmlURL, which browsers resolve relative to the current page path and led to 404s (for example frominstall/install.html). The link is now built with Sphinx’spathto()for each page so it always targets the cite page correctly.Fix
braindecode.models.EEGITNetstate dict mapping that pointed bias to the weight key and referenced a nonexistent submodule path, and fix third inception branch using the wrong variable for kernel length (by Sarthak Tayal)Fix
braindecode.models.EEGInceptionMIstate dict mapping typo where the old key wastc.biasinstead offc.bias(by Sarthak Tayal)Fix multi-target channel windowing in
braindecode.preprocessing.windowers.create_windows_from_target_channels()to use the union of valid target positions across all misc channels instead of only the first channel (by Sarthak Tayal)Fix
braindecode.preprocessing.preprocess.filterbank()to preserve info fields (description,line_freq,device_info, etc.) when creating filtered copies, avoiding merge conflicts in MNE when adding channels (#928 by Bruno Aristimunha)[Outdated:] Restrict to ``pandas>=3.0`` due to incompatibility with ``wfdb`` (#919 by Pierre Guetschel)
Fix multiple bugs in Labram positional encoding. Now the braindecode implementation is aligned with the original one (#931 by Pierre Guetschel )
Fix Zenodo citation: update to global concept DOI and add BibTeX/APA citation formats in
docs/cite.rst,README.rst,CITATION.cff, anddocs/conf.py(#937 by Bruno Aristimunha)Fix channel reduction in
braindecode.modules.SqueezeAndExcitationto avoid runtime shape mismatches when the reduced channel count differs from the reduction rate (#889 by Sarthak Tayal)Push large datasets to HuggingFace Hub using
huggingface_hub.upload_large_folder()to avoid limitations, and allow resuming downloads (#945 and #953 by Pierre Guetschel)Fix
braindecode.models.LUNAchannel location embeddings repeated along batch dimension instead of patch dimension inprepare_tokens, and include pretrained weight typo mapping inself.mapping(#887 by Sarthak Tayal)Fix temporal generalization tutorial producing degraded results (peak AUC dropped from ~0.9 to ~0.75): MEG data in SI units (T/m) has variances ~1e-23, so
BatchNorm1d’seps=1e-5dominated the normalization denominator. Now usesepochs.get_data(units="fT/cm")to bring data to a reasonable scale, and removes the misleading “importance of normalization” section whose conclusions were an artifact of the data scale issue (by Bruno Aristimunha)Fix
braindecode.augmentation.BandstopFilternotch center frequency range usingbandwidth/2instead of2*bandwidthto match docstring (#548 by Sarthak Tayal)Fix
braindecode.models.DeepSleepNethardcoded linear layer size that caused a shape mismatch when using input shapes other than the default 1 channel, 3000 timepoints. The FC and BiLSTM input dimensions are now computed dynamically from the CNN output (#755 by Sarthak Tayal)Fix model docstring inheritance:
track_model_init_kwargswrapped__init__with@wrapsbefore theNumpyDocstringInheritanceInitMetametaclass ran, causinginspect.unwrap()to bypass the wrapper and read__doc__=None. This replaced every model’s description with the parent mixin’s and marked all model-specific parameters as “The description is missing” whenDOCSTRING_INHERITANCE_ENABLE=1was set during documentation builds (#971 by Bruno Aristimunha)Expose
max_nbytesdirectly onbraindecode.preprocessing.preprocess.preprocess()and turn the crypticmmap can't resize a readonlyfailure (raised when joblib memory-maps a preloaded array that a preprocessor later tries to resize) into an actionable error explaining how to fix it: passmax_nbytes=Noneto disable memory mapping, or supply asave_dirso data is reloaded withpreload=False(#325 by Sarthak Tayal)
Code health#
Reorder model categories in documentation to follow the progression: Convolution, Filterbank, Interpretability, Recurrent, Attention/Transformer, SPD, Graph Neural Network, Channel, and Foundation Model (#962 by Bruno Aristimunha)
Fix documentation build warnings and errors: correct numpydoc section underlines in
braindecode.models.EEGSymandbraindecode.models.SSTDPN, strip upstream.. rubric::directives from MNE and MOABB docstrings that caused Sphinx errors, fix RST title levels inwhats_new.rst, correct bibtex key for EEGPT, and ensureconf.pyprioritises the local package onsys.path(by Bruno Aristimunha)Remove deprecated
torch.irfftfallback inbraindecode.visualization.gradients.compute_amplitude_gradients_for_X(), now usestorch.fft.irfftdirectly since braindecode requirestorch>=2.2(by Sarthak Tayal)
Current 1.3.2 (stable)#
Enhancements#
Add an optional
lazyinit path tobraindecode.datasets.base.BaseConcatDatasetto defer cumulative size computation (and any downstream dataset length access) until first access, supporting truly lazy raw loading workflows.Adding Hugging Face Hub integration documentation with comprehensive examples for loading pre-trained models (
braindecode.models.BIOT,braindecode.models.Labram,braindecode.models.REVE,braindecode.models.LUNA,braindecode.models.BENDR,braindecode.models.SignalJEPA) and pushing models to the Hub (#879 by Bruno Aristimunha)Adding
braindecode.models.REVEwith comprehensive documentation covering architecture details, 4D positional encoding, and pretraining scale (#866 by Jonathan Lys )Adding augmentation
braindecode.augmentation.AmplitudeScale(#848 by Bruna Lopes)Adding augmentation
braindecode.augmentation.ChannelsReref(#848 by Bruna Lopes)Added Patched Brain Transformer from Klein T et al 2025 at
braindecode.models.PBT(#787 by José Mauricio)Adding
braindecode.models.SSTDPN(#790 by Can Han and Bruno Aristimunha )Adding
braindecode.models.BrainModule(#743 by Bruno Aristimunha )Added
braindecode.models.BENDR(#802 by Bruno Aristimunha )Adding a tutorial for fine-tuning a pre-trained foundation model (#794 by Pierre Guetschel)
Added 25 new MNE preprocessing functions, expanding from 6 to 31 total preprocessing classes. New functions include:
braindecode.preprocessing.NotchFilter,braindecode.preprocessing.SavgolFilter,braindecode.preprocessing.ApplyHilbert,braindecode.preprocessing.InterpolateBads,braindecode.preprocessing.InterpolateTo,braindecode.preprocessing.RenameChannels,braindecode.preprocessing.ReorderChannels,braindecode.preprocessing.EqualizeChannels,braindecode.preprocessing.AddChannels,braindecode.preprocessing.SetMontage,braindecode.preprocessing.AddReferenceChannels,braindecode.preprocessing.AddProj,braindecode.preprocessing.ApplyProj,braindecode.preprocessing.DelProj,braindecode.preprocessing.ComputeCurrentSourceDensity,braindecode.preprocessing.FixStimArtifact,braindecode.preprocessing.CropByAnnotations,braindecode.preprocessing.Anonymize,braindecode.preprocessing.SetAnnotations,braindecode.preprocessing.SetChannelTypes,braindecode.preprocessing.SetMeasDate,braindecode.preprocessing.FixMagCoilTypes,braindecode.preprocessing.ApplyGradientCompensation,braindecode.preprocessing.AddEvents, andbraindecode.preprocessing.Rescale(#801 by Bruno Aristimunha)Adding artifact removal preprocessing steps, incl.
braindecode.preprocessing.EEGPrep(#796 by Christian Kothe)Including huggingface hub mixin to all models (#795 by Kuntal Kokate )
Better inheritance between the different dataset classes (#806 by Pierre Guetschel )
Fix minor documentation issues in Labram (#808 by Matthew Chen)
Including missed tag in all the models and creating some test for it (#811 by Bruno Aristimunha )
Added
braindecode.models.LUNA(#802 by Bruno Aristimunha, Thorir Mar Ingolfsson)Improve
braindecode.preprocessing.Preprocessorserialisation (#813 by Pierre Guetschel )Modify verbosity of create_window_from_events (#814 by Matthew Chen)
Including SIENA dataset and CHB_MIT datasets (#818 by Jonathan Dan and Bruno Aristimunha )
Modify the interface of FBMSNet for consistency with FBCNet + Unit Tests (#819 by Matthew Chen)
Adding
braindecode.models.MEDFormer(#821 by Bruno Aristimunha )Adding
braindecode.models.EEGSymmodel (#697 by Pierre Guetschel and Bruno Aristimunha )Adding tutorial for
braindecode.preprocessing.windowers.create_fixed_length_windows()(#746 by Aniela Bulicz and Arina Schippers and Aron van Zuijlen)Including interface with Hugging Face Hub for dataset sharing and loading (#820 by Kuntal Kokate and Bruno Aristimunha )
Adding a CITATION.cff file, allowing researchers to directly cite the Braindecode library via a Zenodo DOI (#847 and #872 by Pierre Guetschel )
Add automatically-generated pydantic models configs (#850 by Pierre Guetschel )
Refactor: standardized parameter names in transformer and LabRam modules (#850 by José Mauricio)
Added a tutorial for temporal generalization (#857 by Matthew Chen)
Including colab buttons to all tutorials (#861 by Bruno Aristimunha )
Adding example Pydantic and Exca (#858 by Pierre Guetschel)
Renaming the model category “Foundation Models” from “Large Brain Models (LBM)” (#867 by Bruno Aristimunha )
Make the dataset at huggingface hub more bids compatible (#871 by Bruno Aristimunha )
Reorganized BIDS-related modules into
braindecode.datasets.bidssubpackage for better code organization. The subpackage includes: dataset loading (datasets.py), Hub format utilities (hub_format.py), Hub integration (hub.py), and validation utilities (hub_validation.py) (#871 by Bruno Aristimunha)Changed metadata storage in Zarr format from JSON attributes to TSV files (
metadata.tsv) for better scalability with large datasets (#871 by Bruno Aristimunha)Improved JSON serialization in zarr.json by storing
descriptionandinfoas proper JSON objects instead of stringified JSON, with NaN/Inf sanitization for valid JSON output (#871 by Bruno Aristimunha)
API changes#
BIDS and Hub modules moved to
braindecode.datasets.bidssubpackage:braindecode.datasets.bids.hub,braindecode.datasets.bids.hub_format,braindecode.datasets.bids.datasets,braindecode.datasets.bids.hub_validation(#871 by Bruno Aristimunha)Deprecating the old naming of MOABB Dataset name (#826 by Bruno Aristimunha)
Exposing the
braindecode.datautil.infer_signal_properties()utility function (#856 by Pierre Guetschel)Deprecating the old naming of MOABB Dataset name #826 by Bruno Aristimunha
Drop support for Python 3.10 and increase support to Python 3.13 and python 3.14 (#840 by Bruno Aristimunha)
Model config helpers now soft-import
pydantic/numpydantic; if the optional dependencies are missing the module skips config generation and warns to installpip install braindecode[pydantic].
Bugs#
Fixing the issues with
braindecode.models.ATCNet(#782 by Hamdi Altaheri)Fixing expired path when loading a preprocessed dataset that was moved (#786 by Pierre Guetschel)
Fix OverflowError sometimes when saving description to a json file (#789 by Pierre Guetschel)
Small adjustment in
braindecode.models.Labrammodel to allow pre-loading of weights (#792 by Bruno Aristimunha)Fixing many warnings for generating the documentation (#805 by Bruno Aristimunha)
Fix test compatibility with scikit-learn 1.8+ by adding
__sklearn_tags__support to test mocks (#868 by Bruno Aristimunha)Increase the mne-bids version and fixing API changes (#907 by Bruno Aristimunha)
Current 1.2#
Enhancements#
Improving the docstring for
braindecode.models.EEGNetv4(#768 by Bruno Aristimunha)Improving the docstring for
braindecode.models.EEGConformer(#769 by Bruno Aristimunha)Experimental BIDS Iterable Dataset support (#766 by Bruno Aristimunha and Pierre Guetschel)
Improving the docstring for
braindecode.models.ATCNet(#771 by Bruno Aristimunha)Improving the docstring for
braindecode.models.AttentionBaseNet(#772 by Bruno Aristimunha)Improving the docstring for
braindecode.models.EEGNeX(#773 by Bruno Aristimunha)Massive refactor of the models webpage (#774 by Bruno Aristimunha)
Massive refactor of the models webpage (#775 by Bruno Aristimunha), creating the models categorization, table and many details.
API changes#
Using the name from the original name and deprecation models that we create for no reason, models #775 by Bruno Aristimunha
Deprecated the version name in
braindecode.models.EEGNetv4in favour ofbraindecode.models.EEGNetv.Deprecated the version name in
braindecode.models.SleepStagerEldele2021in favour ofbraindecode.models.AttnSleep.Deprecated the version name in
braindecode.models.TSceptionV1in favour ofbraindecode.models.TSception.
Version 1.1.1#
Enhancements#
Massive refactor of the model webpage
Bugs#
Fixing several models to be be compatibility with small windows size(#756 by Bruno Aristimunha)
Fix initialization of
braindecode.models.Deep4Net(#752 by Bruno Aristimunha)Fix initialization of
braindecode.models.ATCNet(#754 by Bruno Aristimunha)Fix Labram model for small input (#757 by Bruno Aristimunha)
Fix the CTNet parameters to make the embedding configuration easy (#760 by Bruno Aristimunha)
Version 1.0#
Enhancements#
Make sure all the models start at train model (#745 by Bruno Aristimunha)
Enable more models to be pytorch compatible (#732 by Bruno Aristimunha and Lucas Heck)
Making the braindecode.models compatibility with torch compile, torch export and torch jit (#729 by Bruno Aristimunha and Pierre Guetschel)
Reorder the modules, functional and re-organize the codebase (#728 by Bruno Aristimunha)
Adding
braindecode.models.IFNet(#725 by Bruno Aristimunha )Adding
braindecode.models.FBMSNet(#724 by Bruno Aristimunha )Adding
braindecode.models.FBCNet(#722 by Bruno Aristimunha )Adding
braindecode.models.FBLightConvNet(#723 by Bruno Aristimunha )Added dropdown menus for selecting paradigm, type, and hyperparameters on the model summary page. (#718 by Ganasekhar Kalla)
Adding model page (#715 by Lucas Heck)
Inference of shape for string module when using
skorch.helper.SliceDataset(#716 by Bruno Aristimunha)Fix error when using n_njobs > 1 on Windows (#700 by Arnaud Delorme)
Adding
braindecode.models.AttentionBaseNet(#572 by Bruno Aristimunha and Martin Wimpff)Adding
braindecode.datasets.NMTdataset (#443 by Mohammad Javad D and Bruno Aristimunha)Adding an integration test for all the models (:gh: 570 by Bruno Aristimunha)
Adding
braindecode.models.BIOT(#573 by Bruno Aristimunha)Adding
braindecode.models.Labram(#578 by Bruno Aristimunha)Applying black to the codebase (#579 by Bruno Aristimunha)
Adding
braindecode.models.EEGSimpleConv(#581 by Yassine El Ouahidi and Bruno Aristimunha)Increasing the coverage of the tests (#592 by Bruno Aristimunha)
Adding cache and pre-processing option to
braindecode.datasets.MOABBDataset(#582 by Bruno Aristimunha)Add type hints to datasets (#590 by Pierre Guetschel)
Add channel names and montage to
braindecode.datasets.TUH(#593 by Pierre Guetschel)Add
offsetarg tobraindecode.preprocessing.preprocess()(#599 by Pierre Guetschel)Add type hints to preprocessing (#600 by Pierre Guetschel)
Add
mypytype checks to pre-commit and CI (#606 by Pierre Guetschel)Code clarity changes in windowers.py (#615 by John Muradeli)
Adding SegmentationReconstruction augmentation
braindecode.augmentation.SegmentationReconstruction(#608 by Gustavo Rodrigues)Add two models
braindecode.models.ContraWRandbraindecode.models.SPARCNet(#611 by Bruno Aristimunha)Add Sleep Physionet 18 dataset (#621 by Hubert Banville and Bruno Aristimunha)
Optimize the CI by executing only the last commit (#612 by Bruno Aristimunha)
Add experimental lazy_metadata parameter to
braindecode.preprocessing.create_fixed_length_windows()(#597 by Pierre Guetschel)Increasing moabb version to 1.1.0 (#632 by Bruno Aristimunha)
Add MaskEncoding augmentation
braindecode.augmentation.MaskEncoding(#631 by Gustavo Rodrigues)Adding
braindecode.models.EEGNex(#635 by Bruno Aristimunha )Adding
braindecode.models.TSception(#641 by Bruno Aristimunha )Adding
braindecode.models.EEGTCNet(#640 by Bruno Aristimunha )Ensure consistency in the last layer using tests (#642 by Bruno Aristimunha )
Ensuring consistency on the expose of the activation function (#637 by Bruno Aristimunha )
Adding
braindecode.models.SyncNet(#643 by Bruno Aristimunha )Adding
braindecode.models.MSVTNet(#659 by Bruno Aristimunha )Creating the FilterBanklayer module for new models (#656 by Bruno Aristimunha )
Including PytorchAudio as dependency and remove copied code (#661 by Bruno Aristimunha)
Adding
braindecode.models.EEGMiner(#667 by Bruno Aristimunha )Adding
braindecode.models.CTNet(#666 by Bruno Aristimunha )Fix warnings not being suppressed when creating a TUHAbnormal dataset in parallel (#670 by Aphel)
Exposing
braindecode.models.EEGITNethyper-parameters (#672 by Bruno Aristimunha)Adding
braindecode.models.SincShallowNet(#678 by Bruno Aristimunha )Adding
braindecode.models.SCCNet(#679 by Bruno Aristimunha )Fix error when using NMT dataset with n_jobs > 1 (#690 by Aphel)
Adding support for distributed samplers (#695 by Young Truong)
Adding
braindecode.datasets.BIDSDatasetandbraindecode.datasets.BIDSEpochsDatasetplus tutorial (#701 #702 #704 by Pierre Guetschel )Add
braindecode.models.SignalJEPAplus downstream architectures (#703 by Pierre Guetschel )Various improvements on documentation:
Bugs#
Making the
braindecode.models.SCCNetmore compatible with paper instead of source code (#742 by Bruno Aristimunha)Making the
braindecode.models.EEGNeXandbraindecode.models.CTNetmore compatible with paper instead of source code (#740 by Bruno Aristimunha)Exposing extra variable to avoid problem with the parallel process (#736 by Pierre Guetschel)
Fixing the IFNet (#739 by Bruno Aristimunha)
Fix dead links in the documentation (#727 by Lucas Heck)
Fix annotations bug for moabb datasets with non-zero interval (:gh: 561 by Daniel Wilson)
Fix deprecated test and changing the what’s new checker (:gh: 569 by Bruno Aristimunha)
Fix issue with coverage CI and adding a condition on the test for avoid HTML errors (:gh: 591 by Bruno Aristimunha)
Constraint the version of mne (:gh: 594 by Bruno Aristimunha)
Fix type errors (#606 by Pierre Guetschel)
Warn when applying preprocessing steps on a
braindecode.datasets.base.EEGWindowsDataset(#607 by Pierre Guetschel)Fix matplotlib colormaps deprecation (#608 by Bruno Aristimunha)
Ensure mypy to work for every commit (#619 by Bruno Aristimunha)
Deprecate moabb version 1.0.0 because of incorrect epoching (#627 by Pierre Guetschel)
Fixing tutorial benchmark lazy eager loagin (:gh:`` by Bruno Aristimunha and Aphel)
Improve doc build’s time with better caching (#693 by Thomas Moreau)
Fixing the MOABBDataset to work with the cache (#694 by Bruno Aristimunha)
API changes#
Removing deprecated variable
use_log_softmaxfrombraindecode.models.base(#729 by Bruno Aristimunha)Removing function
squeze_final_outputwith Expression because of not compatibility with torch script (#729 by Bruno Aristimunha)Expose the
use_mne_epochs parameterofbraindecode.preprocessing.create_windows_from_events()(#607 by Pierre Guetschel)Parameter
use_log_softmaxis default as False for all the models in (#624 by Bruno Aristimunha)Normalizing the parameters for dropout as part of normalization of model parameters (#624 by Bruno Aristimunha)
Removing
use_log_softmaxand old parameters (#671 by Bruno Aristimunha)Moving
braindecode.models.TCNandbraindecode.models.HybridNetto module and creatingbraindecode.models.BDTCN(#673 by Bruno Aristimunha)Removing dead parameters from
braindecode.models.EEGNetv4(#676 by Bruno Aristimunha)Including Linear Layer at the end
braindecode.models.EEGNetv4(#680 by Bruno Aristimunha)Expose
copy_dataparameter inbraindecode.preprocessing.preprocess()(#736 by Pierre Guetschel)
Version 0.8 (11-2022)#
Enhancements#
Adding
braindecode.models.EEGInceptionMInetwork for motor imagery (#428 by Cedric Rommel)Adding
braindecode.models.ATCNetnetwork for motor imagery (#429 by Cedric Rommel)Adding to
braindecode.datasets.tuh.TUHcompatibility with version 3.0 of TUH dataset (#431 by Mohammad Javad D, Bruno Aristimunha, Robin Tibor Schirrmeister, Lukas Gemein, Denis A. Engemann and Oskar Størmer)Adding
braindecode.models.DeepSleepNetnetwork for sleep staging (#417 by Théo Gnassounou)Adding
braindecode.models.EEGConformernetwork (#454 by Yonghao Song and Bruno Aristimunha)Adding einops in the requirements (#466 by Bruno Aristimunha)
Have moabb as an extra dependency (#467 by Marco Zamboni)
Replacing the replacing Pytorch layers to Rearrange from einops #468 (#468 by Bruno Aristimunha)
Refactoring the documentation and creating a sub-structure for the examples (#470 by Denis A. Engemann and Bruno Aristimunha)
Solving issues with slow conda and splitting the doc and test .yml to speed the CI. (#479 by Bruno Aristimunha)
Improving the GitHub Actions CI and solving the skorch compatibility in the examples (#472 by Bruno Aristimunha)
Changing the documentation order (#489 by Bruno Aristimunha)
Improve the documentation for the Temple University Hospital (TUH) EEG Corpus with discrete targets (#485 by Pierre Guetschel and Bruno Aristimunha)
Improving documentation for MOABB dataset, Trialwise Decoding & Cropped Decoding (#490 by Daniel Wilson)
Improving the documentation for the sleep stage examples (#487 by Bruno Aristimunha)
Improving the tutorial Hyperparameter tuning with scikit-learn (#473 by Bruno Aristimunha)
Add
braindecode.models.base.EEGModuleMixinbase class for all braindecode models (#488 by Pierre Guetschel)Normalize all models common parameters and leaving the old ones as deprecated (#488 by Pierre Guetschel)
Improving the tutorial with a Data Augmentation Search (#495 by Sylvain Chevallier)
Improving documentation for “Split Dataset” and “Process a big data EEG resource” examples (#494 by Bruna Lopes)
Improving documentation for the Convolutional neural network regression model on fake data (#491 by Sara Sedlar)
Enforcing the eval mode in the function predict trial. (#497 by Bruno Aristimunha)
Adding extra requirements for pip install, update doc, removing conda env file (#505 by Sylvain Chevallier)
Add models user-friendly representation with torchinfo tables to
braindecode.models.base.EEGModuleMixin(#488 by Maciej Śliwowski)Merged temporal and spatial convolutions for Deep4 and ShallowFBCSP (by Daniel Wilson and Sara Sedlar)
Enabling data augmentation of single inputs (with no batch dimension). (#503 by Cedric Rommel)
Adding randomize parameter to
braindecode.samplers.SequenceSampler(#504 by Théo Gnassounou.)Creating new preprocessor objects based on mne’s raw/Epochs methods
braindecode.preprocessing.Resample,braindecode.preprocessing.DropChannels,braindecode.preprocessing.SetEEGReference,braindecode.preprocessing.Filter,braindecode.preprocessing.Pick,braindecode.preprocessing.Crop(#500 by Bruna Lopes and Bruno Aristimunha)Moving
braindecode.models.util.get_output_shapeandbraindecode.models.util.to_dense_prediction_model()tobraindecode.models.base.EEGModuleMixin(#514 by Maciej Śliwowski)Automatically populate signal-related parameters in
braindecode.EEGClassifierandbraindecode.EEGRegressor(#517 by Pierre Guetschel)Adding a pure PyTorch tutorial (#523 by Remi Delbouys and Bruno Aristimunha)
Add
models_dicttobraindecode.models.util(#524 by Pierre Guetschel)Keep using mne.Raw after windowing to speed up windowing, do not create mne.Epochs (#515 by Robin Tibor Schirrmeister)
Changing
braindecode.models.Deep4Netfinal_conv_length default value to ‘auto’ (#535 by Maciej Śliwowski)Add support for
mne.Epochsinbraindecode.EEGClassifierandbraindecode.EEGRegressor(#529 by Pierre Guetschel)Allow passing only the name of a braindecode model to
braindecode.EEGClassifierandbraindecode.EEGRegressor(#528 by Pierre Guetschel)Standardizing models’ last layer names (#520 by Bruna Lopes and Pierre Guetschel)
Add basic training example with MNE epochs (#539 by Pierre Guetschel)
Log validation accuracy in
braindecode.EEGClassifier(#541 by Pierre Guetschel)Better type hints in
braindecode.augmentation.base(#551 by Valentin Iovene)Support for MOABB 1.0.0 and switch to pyproject.toml (#553 by Sylvain Chevallier)
Adding pre-commit hooks (#556 by Bruno Aristimunha)
Bugs#
Fixing conda env in the CI (#461 by Bruno Aristimunha)
Fixing E231 missing whitespace after ‘,’ untraceable error in old flake8 (#460 by Bruno Aristimunha)
Removing deprecation warning due to torch transposition in
braindecode.augmentation.functional._frequency_shift()(#446 by Matthieu Terris)Fix padding’s device in
braindecode.models.EEGResNet(#451 by Pierre Guetschel)Fix skorch version issue (#465 by Marco Zamboni)
Fix wrong kernel_size dtype when running torchinfo in
braindecode.models.USleep(#538 by Maciej Śliwowski)Fix bug when using GPU and channel shuffle transform (#546 by Robin Tibor Schirrmeister)
API changes#
Renaming the
braindecode.models.EEGInceptionnetwork asbraindecode.models.EEGInceptionERP(#428 by Cedric Rommel)Removing support for Python 3.7 (#397 by Bruno Aristimunha)
Removing the LogSoftmax layer from the models and adding deprecated warnings and temporary flags (#513 by Sara Sedlar)
Version 0.7 (10-2022)#
Enhancements#
Adding EEG-Inception Network
braindecode.models.EEGInception(#390 by Bruno Aristimunha and Cedric Rommel)Adding EEG-ITNet Network
braindecode.models.EEGITNet(#400 by Ghaith Bouallegue)Allowing target_names as list for BaseDataset (#371 by Mohammad Javad D and Robin Tibor Schirrmeister)
Adding tutorial with GridSearchCV for data augmentation on the BCIC IV 2a with module braindecode.augmentation (#389 by Bruno Aristimunha and Cedric Rommel)
Adding tutorial with GridSearchCV to exemplify how to tune hyperparameters, for instance with the learning rate (#349 by Lukas Gemein and by Bruno Aristimunha)
Adding tutorial with a Unified Validation scheme (#378 by Bruno Aristimunha and Martin Wimpff)
Adding verbose parameter to
braindecode.preprocessing.create_windows_from_events(),braindecode.preprocessing.create_windows_from_target_channels(), andbraindecode.preprocessing.create_fixed_length_windows()(#391 by Lukas Gemein)Enable augmentation on GPU within
AugmentedDataloadervia a new device parameter (#406 by Martin Wimpff, Bruno Aristimunha and Cedric Rommel)Adding randomize parameter to
braindecode.samplers.SequenceSampler(#504 by Théo Gnassounou.)
Bugs#
Fixing parameter subject_ids to recoding_ids in TUHAbnormal example (#402 by Bruno Aristimunha and Lukas Gemein)
Bug fix
braindecode.augmentation.functional.ft_surrogate()and add option to sample independently per-channel (#409 by Martin Wimpff and Cedric Rommel)
API changes#
Renaming the method get_params to get_augmentation_params in augmentation classes. This makes the Transform module compatible with scikit-learn cloning mechanism (#388 by Bruno Aristimunha and Alex Gramfort)
Delaying the deprecation of the preprocessing scale function
braindecode.preprocessing.scale()and updates tutorials where the function were used. (#413 by Bruno Aristimunha)Removing deprecated functions and classes
braindecode.preprocessing.zscore(),braindecode.datautil.MNEPreprocandbraindecode.datautil.NumpyPreproc(#415 by Bruno Aristimunha)Setting iterator_train__drop_last=True by default for
braindecode.EEGClassifierandbraindecode.EEGRegressor(#411 by Robin Tibor Schirrmeister)
Version 0.6 (2021-12-06)#
Enhancements#
Adding
braindecode.samplers.SequenceSampleralong with support for returning sequences of windows inbraindecode.datasets.BaseConcatDatasetand an updated sleep staging example to show how to train on sequences of windows (#263 by Hubert Banville)Adding Thinker Invariance Network
braindecode.models.TIDNet(#170 by Ann-Kathrin Kiessner, Daniel Wilson, Henrik Bonsmann, Vytautas Jankauskas)Adding a confusion matrix plot generator
braindecode.visualization.plot_confusion_matrix()(#274 by Ann-Kathrin Kiessner, Daniel Wilson, Henrik Bonsmann, Vytautas Jankauskas)Adding data Augmentation module (#254 by Cedric Rommel, Alex Gramfort and Thomas Moreau)
Adding Mixup augmentation
braindecode.augmentation.Mixup(#254 by Simon Brandt)Adding saving of preprocessing and windowing choices in
braindecode.preprocessing.preprocess(),braindecode.preprocessing.create_windows_from_events()andbraindecode.preprocessing.create_fixed_length_windows()to datasets to facilitate reproducibility (#287 by Lukas Gemein)Adding
braindecode.models.util.aggregate_probas()to perform self-ensembling of predictions with sequence-to-sequence models (#294 by Hubert Banville)Adding
braindecode.training.scoring.predict_trials()to generate trialwise predictions after cropped training (#312 by Lukas Gemein)Preprocessing and windowing choices are now saved on the level of individual datasets (#288 by Lukas Gemein)
Serialization now happens entirely on dataset level creating subsets for individual datasets that contain ‘fif’ and ‘json’ files (#288 Lukas Gemein)
Instantiation of TUH
braindecode.datasets.tuh.TUHand TUHAbnormalbraindecode.datasets.tuh.TUHAbnormal, as well as loadingbraindecode.datautil.serialization.load_concat_dataset()of stored datasets now support multiple workers (#288 by Lukas Gemein)Adding balanced sampling of sequences of windows with
braindecode.samplers.BalancedSequenceSampleras proposed in U-Sleep paper (#295 by Théo Gnassounou and Hubert Banville)braindecode.preprocessing.preprocess()can now work in parallel and serialize datasets to enable lazy-loading (i.e. preload=False) (#277 by Hubert Banville)Adding
braindecode.models.TimeDistributedto apply a module on a sequence (#318 by Hubert Banville)Adding time series targets decoding together with
braindecode.datasets.BCICompetitionIVDataset4and fingers flexion decoding from ECoG examples (#261 by Maciej Śliwowski and Mohammed Fattouh)Make EEGClassifier and EEGRegressor cloneable for scikit-learn (#347 by Lukas Gemein, Robin Tibor Schirrmeister, Maciej Śliwowski and Alex Gramfort)
Allow to raise a warning when a few trials are shorter than the windows length, instead of raising an error and stopping all computation. (#353 by Cedric Rommel)
Setting torch.backends.cudnn.benchmark in
braindecode.util.set_random_seeds(), adding warning and more info to the docstring to improve reproducibility (#333 by Maciej Śliwowski)Adding option to pass arguments through
braindecode.datasets.MOABBDataset(#365 by Pierre Guetschel)Adding a possibility to use a dict to split a BaseConcatDataset in
braindecode.datasets.BaseConcatDataset.split()(#367 by Alex Gramfort)Adding
cropparameter tobraindecode.datasets.SleepPhysionetdataset to speed up examples (#367 by Alex Gramfort)
Bugs#
Correctly computing recording length in
braindecode.preprocessing.windowers.create_fixed_length_windows()in case recording was cropped (#304 by Lukas Gemein)Fixing
braindecode.datasets.SleepPhysionetto allow serialization and avoid mismatch in channel names attributes (#327 by Hubert Banville)Propagating target_transform to all datasets when using
braindecode.datasets.BaseConcatDataset.subset()(#261 by Maciej Śliwowski)
API changes#
Removing the default sampling frequency sfreq value in
braindecode.datasets.create_windows_from_events()(#256 by Daniel Wilson, Henrik Bonsmann, Vytautas Jankauskas)Made windowing arguments optional in
braindecode.preprocessing.windowers.create_fixed_length_windows()&braindecode.preprocessing.windowers.create_windows_from_events()(#269 by Daniel Wilson, Henrik Bonsmann, Vytautas Jankauskas)Deprecating preprocessing functions
braindecode.preprocessing.zscore()andbraindecode.preprocessing.scale()in favour of sklearn’s implementation (#292 by Hubert Banville)braindecode.preprocessing.preprocess()now returns abraindecode.dataset.BaseConcatDatasetobject (#277 by Hubert Banville)
Version 0.5.1 (2021-07-14)#
Enhancements#
Adding n_jobs parameter to windowers
braindecode.datautil.create_windows_from_events()andbraindecode.datautil.create_fixed_length_windows()to allow for parallelization of the windowing process (#199 by Hubert Banville)Adding support for on-the-fly transforms (#198 by Hubert Banville)
Unifying preprocessors under the
braindecode.datautil.Preprocessorclass (#197 by Hubert Banville)Adding self-supervised learning example on the Sleep Physionet dataset along with new sampler module braindecode.samplers (#178 by Hubert Banville)
Adding sleep staging example on the Sleep Physionet dataset (#161 by Hubert Banville)
Adding new parameters to windowers
braindecode.datautil.create_windows_from_events()andbraindecode.datautil.create_fixed_length_windows()for finer control over epoching (#152 by Hubert Banville)Adding Temporal Convolutional Network
braindecode.models.TCN(#138 by Lukas Gemein)Adding option to use BaseConcatDataset as input to BaseConcatDataset (#142 by Lukas Gemein)
Adding a simplified API for splitting of BaseConcatDataset: parameters property and split_ids in
braindecode.datasets.BaseConcatDataset.split()are replaced by by (#147 by Lukas Gemein)Adding a preprocessor that realizes a filterbank:
braindecode.datautil.filterbank()(#158 by Lukas Gemein)Removing code duplicate in BaseDataset and WindowsDataset (#159 by Lukas Gemein)
Only load data if needed during preprocessing (e.g., allow timecrop without loading) (#164 by Robin Tibor Schirrmeister)
Adding option to sort filtered channels by frequency band for the filterbank in
braindecode.datautil.filterbank()(#185 by Lukas Gemein)Adding the USleep model
braindecode.models.USleep(#282 by Théo Gnassounou and Omar Chehab)Adding
braindecode.models.SleepStagerEldele2021andbraindecode.models.SleepStagerBlanco2020models for sleep staging (#341 by Divyesh Narayanan)
Bugs#
Amplitude gradients are correctly computed for layers with multiple filters (before, they were accidentally summed over all previous filters in the layer) (#167 by Robin Tibor Schirrmeister)
braindecode.models.get_output_shape()andbraindecode.visualization.compute_amplitude_gradients()assume 3d, not 4d inputs (#166 by Robin Tibor Schirrmeister)Fixing windower functions when the continuous data has been cropped (#152 by Hubert Banville)
Fixing incorrect usage of recording ids in TUHAbnormal (#146 by Lukas Gemein)
Adding check for correct input dimensions (4d) in TCN (#169 by Lukas Gemein)
Fixing
braindecode.datautil.create_windows_from_events()when window_size is not given but there is atrial_stop_offset_samples(#148 by Lukas Gemein)Fixing
braindecode.classifier.EEGClassifier.predict_proba()andbraindecode.regressor.EEGRegressor.predict()behavior in the cropped mode (#171 by Maciej Śliwowski)Freeze torch random generator for scoring functions for reproducibility (#155 by Robin Tibor Schirrmeister)
Make EEGResNet work for
final_pool_length='auto'(#223 by Robin Tibor Schirrmeister and Maciej Śliwowski)
API changes#
Preprocessor classes
braindecode.datautil.MNEPreprocandbraindecode.datautil.NumpyPreprocare deprecated in favor ofbraindecode.datautil.Preprocessor(#197 by Hubert Banville)Parameter stop_offset_samples of
braindecode.datautil.create_fixed_length_windows()must now be set to None instead of 0 to indicate the end of the recording (#152 by Hubert Banville)