Note
Go to the end to download the full example code.
From window labels to events: asynchronous EEG decoding with DANCE#
braindecode defines brain decoding as \(f_\theta: X \to y\) (see the brain decode problem), and is explicit that the definition of \(y\) is broad. Most tutorials in this repository instantiate that mapping at a single point in the space it allows: \(X\) is a window already cut and aligned to a known onset (a motor-imagery cue, a sleep-epoch boundary, a P300 flash), and \(y\) is one categorical label for the whole window.
DANCE instantiates the same \(f_\theta: X \to y\) at a different point:
\(X\) is one long, unaligned recording, and \(y\) is the set of
(start, end, class) events \(f_\theta\) must locate and classify
itself. The definition did not change. Only the shape of \(y\) did.
This tutorial shows how to use the DANCE model
for event detection on continuous, real EEG recordings, with plain PyTorch (no
skorch, no Lightning). We reproduce, at reduced scale, the P300 setting from
the original paper [1], using the Brain Invaders BI2014a dataset [2] loaded
through MOABBDataset.
The pipeline is:
load a few subjects of continuous, real P300 EEG (BI2014a) with
MOABBDataset,preprocess with the exact minimal recipe from the paper: band-pass 0.1-100 Hz, resample to 128 Hz, per-channel robust scaling clamped to
[-16, 16],cut it into fixed-length windows with
create_fixed_length_windows()(W = 32s, matching the paper’s BI2014a configuration),turn each window’s stimulus annotations (
Target/NonTargetflashes) into a DANCE target dict (normalizedstart/endin[0, 1]plus a dense per-token class map),train
DANCEwithDanceLosson a cross-subject split (train on some subjects, evaluate on held-out ones, following the paper’s protocol), andevaluate with the event-level
f1_event()metric and a per-token macro F1 on the dense head (sklearn.metrics.f1_score()).
DANCE exposes two entry points: model.forward(x) returns the dense
per-token logits (B, num_latents, n_outputs), while model.detect(x)
returns the event set {class, start, end, dense}. Training and evaluation
below use detect (the loss needs the query set); a downstream user who only
wants a per-token prediction can call forward directly.
Note
DANCE follows a DETR-style class-0 = background / no-object convention:
real event classes are 1 .. n_outputs - 1 and class 0 is reserved
for “no event”. Here class 1 is a non-target flash and class 2 a
target flash; the target builder and both F1 metrics honour this
convention automatically.
Note
To keep the docs build fast, this tutorial uses only 3 subjects and 2 training epochs, far short of the paper’s full protocol (all subjects, 100 epochs with early stopping). Expect low F1 scores below; see the Conclusion for what changes at full scale.
# Authors: Bruno Aristimunha <b.aristimunha@gmail.com>
#
# License: MIT
random_state = 0
Loading and preprocessing the dataset#
Loading the raw recordings#
BI2014a is a Brain Invaders P300 (oddball) dataset: subjects watch a stream of flashes, most of which are “non-target” and a few “target”. We load 3 subjects: 2 for training, 1 held out for evaluation, following the paper’s cross-subject splitting protocol.
from braindecode.datasets import MOABBDataset
subject_ids = [1, 2, 3]
dataset = MOABBDataset(dataset_name="BI2014a", subject_ids=subject_ids)
0%| | 0.00/60.6M [00:00<?, ?B/s]
0%| | 13.3k/60.6M [00:00<11:25, 88.3kB/s]
0%| | 44.0k/60.6M [00:00<05:54, 171kB/s]
0%| | 95.2k/60.6M [00:00<03:43, 270kB/s]
0%| | 151k/60.6M [00:00<03:02, 331kB/s]
0%|▏ | 236k/60.6M [00:00<02:15, 447kB/s]
1%|▏ | 321k/60.6M [00:00<01:56, 517kB/s]
1%|▎ | 397k/60.6M [00:00<01:50, 542kB/s]
1%|▎ | 479k/60.6M [00:01<01:45, 569kB/s]
1%|▎ | 561k/60.6M [00:01<01:42, 588kB/s]
1%|▍ | 627k/60.6M [00:01<01:46, 562kB/s]
1%|▍ | 692k/60.6M [00:01<01:49, 546kB/s]
1%|▍ | 774k/60.6M [00:01<01:44, 573kB/s]
1%|▌ | 831k/60.6M [00:01<01:52, 533kB/s]
2%|▌ | 922k/60.6M [00:01<01:42, 581kB/s]
2%|▋ | 1.00M/60.6M [00:01<01:39, 596kB/s]
2%|▋ | 1.13M/60.6M [00:02<01:22, 720kB/s]
2%|▊ | 1.22M/60.6M [00:02<01:25, 695kB/s]
2%|▊ | 1.33M/60.6M [00:02<01:18, 751kB/s]
2%|▉ | 1.43M/60.6M [00:02<01:18, 757kB/s]
2%|▉ | 1.51M/60.6M [00:02<01:22, 719kB/s]
3%|█ | 1.61M/60.6M [00:02<01:20, 734kB/s]
3%|█ | 1.70M/60.6M [00:02<01:23, 703kB/s]
3%|█ | 1.78M/60.6M [00:03<01:26, 681kB/s]
3%|█▏ | 1.88M/60.6M [00:03<01:23, 703kB/s]
3%|█▏ | 1.98M/60.6M [00:03<01:20, 724kB/s]
4%|█▎ | 2.12M/60.6M [00:03<01:09, 846kB/s]
4%|█▍ | 2.22M/60.6M [00:03<01:11, 819kB/s]
4%|█▍ | 2.30M/60.6M [00:03<01:16, 764kB/s]
4%|█▍ | 2.39M/60.6M [00:03<01:20, 727kB/s]
4%|█▌ | 2.49M/60.6M [00:03<01:18, 738kB/s]
4%|█▌ | 2.57M/60.6M [00:04<01:22, 706kB/s]
4%|█▋ | 2.65M/60.6M [00:04<01:24, 683kB/s]
5%|█▋ | 2.75M/60.6M [00:04<01:21, 708kB/s]
5%|█▊ | 2.83M/60.6M [00:04<01:24, 687kB/s]
5%|█▊ | 2.93M/60.6M [00:04<01:20, 713kB/s]
5%|█▉ | 3.01M/60.6M [00:04<01:23, 687kB/s]
5%|█▉ | 3.11M/60.6M [00:04<01:21, 708kB/s]
5%|██ | 3.21M/60.6M [00:04<01:19, 724kB/s]
5%|██ | 3.30M/60.6M [00:05<01:21, 702kB/s]
6%|██ | 3.38M/60.6M [00:05<01:24, 680kB/s]
6%|██▏ | 3.48M/60.6M [00:05<01:20, 705kB/s]
6%|██▏ | 3.56M/60.6M [00:05<01:23, 685kB/s]
6%|██▎ | 3.64M/60.6M [00:05<01:24, 670kB/s]
6%|██▍ | 3.81M/60.6M [00:05<01:06, 849kB/s]
7%|██▍ | 3.94M/60.6M [00:05<01:03, 897kB/s]
7%|██▌ | 4.05M/60.6M [00:05<01:03, 889kB/s]
7%|██▌ | 4.15M/60.6M [00:06<01:05, 856kB/s]
7%|██▋ | 4.24M/60.6M [00:06<01:10, 799kB/s]
7%|██▋ | 4.35M/60.6M [00:06<01:08, 820kB/s]
7%|██▊ | 4.43M/60.6M [00:06<01:13, 764kB/s]
7%|██▊ | 4.53M/60.6M [00:06<01:13, 764kB/s]
8%|██▉ | 4.61M/60.6M [00:06<01:17, 724kB/s]
8%|██▉ | 4.70M/60.6M [00:06<01:20, 696kB/s]
8%|███ | 4.85M/60.6M [00:07<01:07, 831kB/s]
8%|███ | 4.93M/60.6M [00:07<01:11, 776kB/s]
8%|███▏ | 5.01M/60.6M [00:07<01:15, 731kB/s]
9%|███▏ | 5.16M/60.6M [00:07<01:05, 852kB/s]
9%|███▎ | 5.24M/60.6M [00:07<01:09, 794kB/s]
9%|███▍ | 5.39M/60.6M [00:07<01:01, 891kB/s]
9%|███▍ | 5.48M/60.6M [00:07<01:06, 833kB/s]
9%|███▍ | 5.57M/60.6M [00:07<01:09, 792kB/s]
9%|███▌ | 5.65M/60.6M [00:08<01:13, 745kB/s]
9%|███▌ | 5.74M/60.6M [00:08<01:16, 713kB/s]
10%|███▋ | 5.88M/60.6M [00:08<01:05, 839kB/s]
10%|███▋ | 5.97M/60.6M [00:08<01:09, 781kB/s]
10%|███▊ | 6.06M/60.6M [00:08<01:10, 769kB/s]
10%|███▊ | 6.15M/60.6M [00:08<01:14, 728kB/s]
10%|███▉ | 6.23M/60.6M [00:08<01:17, 699kB/s]
10%|███▉ | 6.33M/60.6M [00:08<01:15, 716kB/s]
11%|████ | 6.41M/60.6M [00:09<01:18, 694kB/s]
11%|████ | 6.54M/60.6M [00:09<01:08, 791kB/s]
11%|████▏ | 6.62M/60.6M [00:09<01:12, 743kB/s]
11%|████▏ | 6.71M/60.6M [00:09<01:15, 709kB/s]
11%|████▎ | 6.80M/60.6M [00:09<01:13, 727kB/s]
11%|████▎ | 6.94M/60.6M [00:09<01:05, 814kB/s]
12%|████▍ | 7.04M/60.6M [00:09<01:06, 801kB/s]
12%|████▍ | 7.14M/60.6M [00:10<01:07, 788kB/s]
12%|████▌ | 7.22M/60.6M [00:10<01:12, 741kB/s]
12%|████▌ | 7.30M/60.6M [00:10<01:15, 708kB/s]
12%|████▋ | 7.45M/60.6M [00:10<01:03, 840kB/s]
12%|████▋ | 7.56M/60.6M [00:10<01:01, 855kB/s]
13%|████▊ | 7.66M/60.6M [00:10<01:03, 828kB/s]
13%|████▊ | 7.76M/60.6M [00:10<01:05, 806kB/s]
13%|████▉ | 7.91M/60.6M [00:10<00:57, 909kB/s]
13%|█████ | 8.00M/60.6M [00:11<01:02, 846kB/s]
13%|█████ | 8.09M/60.6M [00:11<01:05, 803kB/s]
13%|█████▏ | 8.18M/60.6M [00:11<01:09, 751kB/s]
14%|█████▏ | 8.27M/60.6M [00:11<01:09, 751kB/s]
14%|█████▎ | 8.41M/60.6M [00:11<01:02, 832kB/s]
14%|█████▎ | 8.51M/60.6M [00:11<01:04, 809kB/s]
14%|█████▍ | 8.64M/60.6M [00:11<00:59, 868kB/s]
14%|█████▍ | 8.72M/60.6M [00:11<01:04, 808kB/s]
15%|█████▌ | 8.82M/60.6M [00:12<01:06, 781kB/s]
15%|█████▌ | 8.95M/60.6M [00:12<01:00, 849kB/s]
15%|█████▋ | 9.06M/60.6M [00:12<00:59, 864kB/s]
15%|█████▋ | 9.25M/60.6M [00:12<00:50, 1.02MB/s]
15%|█████▊ | 9.36M/60.6M [00:12<00:52, 982kB/s]
16%|█████▊ | 9.51M/60.6M [00:12<00:49, 1.03MB/s]
16%|██████ | 9.61M/60.6M [00:12<00:53, 958kB/s]
16%|██████ | 9.71M/60.6M [00:13<00:56, 895kB/s]
16%|██████▏ | 9.80M/60.6M [00:13<00:59, 846kB/s]
16%|██████▏ | 9.89M/60.6M [00:13<01:04, 788kB/s]
17%|██████▎ | 10.0M/60.6M [00:13<01:02, 810kB/s]
17%|██████▎ | 10.1M/60.6M [00:13<00:55, 912kB/s]
17%|██████▍ | 10.2M/60.6M [00:13<00:59, 852kB/s]
17%|██████▍ | 10.3M/60.6M [00:13<01:02, 802kB/s]
17%|██████▌ | 10.4M/60.6M [00:13<01:00, 826kB/s]
17%|██████▌ | 10.5M/60.6M [00:14<01:02, 804kB/s]
18%|██████▋ | 10.7M/60.6M [00:14<00:54, 910kB/s]
18%|██████▋ | 10.9M/60.6M [00:14<00:45, 1.09MB/s]
18%|██████▊ | 11.2M/60.6M [00:14<00:33, 1.49MB/s]
19%|██████▉ | 11.4M/60.6M [00:14<00:31, 1.58MB/s]
20%|███████▎ | 12.0M/60.6M [00:14<00:20, 2.32MB/s]
20%|███████▌ | 12.3M/60.6M [00:14<00:19, 2.44MB/s]
22%|████████ | 13.3M/60.6M [00:14<00:12, 3.89MB/s]
23%|████████▍ | 13.9M/60.6M [00:15<00:11, 4.21MB/s]
25%|█████████▍ | 15.4M/60.6M [00:15<00:06, 6.45MB/s]
27%|█████████▉ | 16.3M/60.6M [00:15<00:06, 6.69MB/s]
30%|███████████ | 18.2M/60.6M [00:15<00:04, 8.88MB/s]
32%|████████████ | 19.7M/60.6M [00:15<00:04, 9.73MB/s]
34%|████████████▌ | 20.6M/60.6M [00:15<00:04, 8.94MB/s]
36%|█████████████▏ | 21.5M/60.6M [00:15<00:04, 8.21MB/s]
37%|█████████████▋ | 22.4M/60.6M [00:16<00:06, 6.03MB/s]
38%|██████████████ | 23.0M/60.6M [00:16<00:06, 5.79MB/s]
39%|██████████████▍ | 23.7M/60.6M [00:16<00:06, 5.56MB/s]
40%|██████████████▊ | 24.3M/60.6M [00:16<00:06, 5.47MB/s]
41%|███████████████▎ | 25.1M/60.6M [00:16<00:06, 5.56MB/s]
42%|███████████████▋ | 25.7M/60.6M [00:17<00:11, 2.93MB/s]
43%|████████████████ | 26.3M/60.6M [00:17<00:10, 3.24MB/s]
44%|████████████████▍ | 26.8M/60.6M [00:17<00:09, 3.44MB/s]
45%|████████████████▊ | 27.5M/60.6M [00:17<00:08, 3.93MB/s]
47%|█████████████████▎ | 28.3M/60.6M [00:17<00:07, 4.42MB/s]
48%|█████████████████▊ | 29.2M/60.6M [00:17<00:06, 4.98MB/s]
49%|██████████████████▎ | 29.9M/60.6M [00:17<00:06, 4.95MB/s]
51%|██████████████████▋ | 30.7M/60.6M [00:18<00:06, 4.98MB/s]
51%|███████████████████ | 31.2M/60.6M [00:18<00:06, 4.73MB/s]
55%|████████████████████▍ | 33.5M/60.6M [00:18<00:03, 8.31MB/s]
59%|█████████████████████▊ | 35.8M/60.6M [00:18<00:02, 11.1MB/s]
65%|████████████████████████ | 39.4M/60.6M [00:18<00:01, 15.9MB/s]
70%|█████████████████████████▊ | 42.3M/60.6M [00:18<00:01, 17.9MB/s]
76%|████████████████████████████▎ | 46.3M/60.6M [00:18<00:00, 23.2MB/s]
80%|█████████████████████████████▊ | 48.7M/60.6M [00:18<00:00, 22.3MB/s]
85%|███████████████████████████████▎ | 51.2M/60.6M [00:19<00:00, 23.0MB/s]
90%|█████████████████████████████████▏ | 54.4M/60.6M [00:19<00:00, 24.5MB/s]
97%|███████████████████████████████████▊ | 58.6M/60.6M [00:19<00:00, 26.7MB/s]
0%| | 0.00/60.6M [00:00<?, ?B/s]
100%|██████████████████████████████████████| 60.6M/60.6M [00:00<00:00, 215GB/s]
0%| | 0.00/29.6M [00:00<?, ?B/s]
0%| | 1.02k/29.6M [00:00<1:08:43, 7.17kB/s]
0%| | 14.3k/29.6M [00:00<07:46, 63.3kB/s]
0%| | 47.1k/29.6M [00:00<03:11, 154kB/s]
0%| | 93.2k/29.6M [00:00<02:02, 240kB/s]
0%|▏ | 135k/29.6M [00:00<01:46, 278kB/s]
1%|▏ | 182k/29.6M [00:00<01:33, 315kB/s]
1%|▎ | 248k/29.6M [00:00<01:15, 388kB/s]
1%|▍ | 325k/29.6M [00:00<01:02, 465kB/s]
1%|▌ | 407k/29.6M [00:01<00:55, 528kB/s]
2%|▋ | 488k/29.6M [00:01<00:50, 572kB/s]
2%|▋ | 554k/29.6M [00:01<00:51, 561kB/s]
2%|▊ | 636k/29.6M [00:01<00:48, 597kB/s]
2%|▉ | 701k/29.6M [00:01<00:49, 579kB/s]
3%|█ | 783k/29.6M [00:01<00:47, 606kB/s]
3%|█▏ | 933k/29.6M [00:01<00:36, 791kB/s]
4%|█▎ | 1.05M/29.6M [00:01<00:33, 840kB/s]
4%|█▍ | 1.16M/29.6M [00:02<00:32, 868kB/s]
4%|█▋ | 1.31M/29.6M [00:02<00:29, 969kB/s]
5%|█▊ | 1.41M/29.6M [00:02<00:30, 919kB/s]
5%|█▉ | 1.50M/29.6M [00:02<00:32, 868kB/s]
5%|██ | 1.62M/29.6M [00:02<00:30, 904kB/s]
6%|██▏ | 1.74M/29.6M [00:02<00:30, 917kB/s]
6%|██▎ | 1.84M/29.6M [00:02<00:31, 882kB/s]
7%|██▍ | 1.93M/29.6M [00:02<00:33, 835kB/s]
7%|██▌ | 2.01M/29.6M [00:03<00:34, 790kB/s]
7%|██▊ | 2.17M/29.6M [00:03<00:29, 937kB/s]
8%|██▉ | 2.27M/29.6M [00:03<00:30, 898kB/s]
8%|███ | 2.42M/29.6M [00:03<00:27, 992kB/s]
9%|███▏ | 2.55M/29.6M [00:03<00:26, 1.02MB/s]
9%|███▍ | 2.65M/29.6M [00:03<00:27, 965kB/s]
9%|███▌ | 2.75M/29.6M [00:03<00:29, 914kB/s]
10%|███▋ | 2.84M/29.6M [00:03<00:30, 873kB/s]
10%|███▊ | 2.93M/29.6M [00:04<00:32, 826kB/s]
10%|███▉ | 3.04M/29.6M [00:04<00:31, 844kB/s]
11%|████ | 3.17M/29.6M [00:04<00:28, 912kB/s]
11%|████▏ | 3.32M/29.6M [00:04<00:26, 1.00MB/s]
12%|████▍ | 3.50M/29.6M [00:04<00:22, 1.15MB/s]
12%|████▌ | 3.67M/29.6M [00:04<00:21, 1.21MB/s]
13%|████▋ | 3.79M/29.6M [00:04<00:22, 1.14MB/s]
13%|████▉ | 3.91M/29.6M [00:04<00:23, 1.10MB/s]
14%|█████ | 4.02M/29.6M [00:05<00:24, 1.04MB/s]
14%|█████▎ | 4.13M/29.6M [00:05<00:25, 984kB/s]
15%|█████▍ | 4.31M/29.6M [00:05<00:22, 1.14MB/s]
15%|█████▌ | 4.44M/29.6M [00:05<00:22, 1.12MB/s]
15%|█████▋ | 4.56M/29.6M [00:05<00:23, 1.06MB/s]
16%|█████▉ | 4.75M/29.6M [00:05<00:20, 1.23MB/s]
17%|██████▏ | 4.94M/29.6M [00:05<00:18, 1.31MB/s]
17%|██████▎ | 5.07M/29.6M [00:05<00:19, 1.23MB/s]
18%|██████▍ | 5.19M/29.6M [00:06<00:20, 1.17MB/s]
18%|██████▋ | 5.32M/29.6M [00:06<00:21, 1.12MB/s]
19%|██████▊ | 5.50M/29.6M [00:06<00:19, 1.23MB/s]
19%|███████▏ | 5.74M/29.6M [00:06<00:16, 1.46MB/s]
20%|███████▎ | 5.89M/29.6M [00:06<00:17, 1.38MB/s]
20%|███████▌ | 6.03M/29.6M [00:06<00:18, 1.30MB/s]
21%|███████▋ | 6.16M/29.6M [00:06<00:19, 1.23MB/s]
21%|████████ | 6.28M/29.6M [00:06<00:25, 901kB/s]
22%|████████▏ | 6.38M/29.6M [00:07<00:26, 887kB/s]
22%|████████▎ | 6.48M/29.6M [00:07<00:26, 863kB/s]
22%|████████▍ | 6.60M/29.6M [00:07<00:26, 883kB/s]
23%|████████▌ | 6.70M/29.6M [00:07<00:26, 865kB/s]
23%|████████▊ | 6.81M/29.6M [00:07<00:25, 879kB/s]
23%|████████▉ | 6.91M/29.6M [00:07<00:26, 856kB/s]
24%|█████████ | 7.01M/29.6M [00:07<00:26, 840kB/s]
24%|█████████▏ | 7.13M/29.6M [00:07<00:25, 873kB/s]
24%|█████████▎ | 7.24M/29.6M [00:08<00:25, 890kB/s]
25%|█████████▍ | 7.33M/29.6M [00:08<00:26, 843kB/s]
25%|█████████▌ | 7.45M/29.6M [00:08<00:24, 890kB/s]
26%|█████████▌ | 7.67M/29.6M [00:08<00:19, 1.15MB/s]
26%|█████████▋ | 7.78M/29.6M [00:08<00:20, 1.09MB/s]
27%|█████████▉ | 7.95M/29.6M [00:08<00:18, 1.16MB/s]
27%|██████████▏ | 8.10M/29.6M [00:08<00:18, 1.18MB/s]
28%|██████████▎ | 8.21M/29.6M [00:08<00:19, 1.11MB/s]
28%|██████████▍ | 8.33M/29.6M [00:09<00:20, 1.05MB/s]
29%|██████████▌ | 8.44M/29.6M [00:09<00:20, 1.02MB/s]
29%|██████████▊ | 8.61M/29.6M [00:09<00:18, 1.12MB/s]
29%|██████████▉ | 8.72M/29.6M [00:09<00:19, 1.06MB/s]
30%|███████████ | 8.83M/29.6M [00:09<00:20, 1.00MB/s]
30%|███████████▏ | 8.95M/29.6M [00:09<00:20, 1.00MB/s]
31%|███████████▋ | 9.07M/29.6M [00:09<00:20, 984kB/s]
31%|███████████▊ | 9.18M/29.6M [00:09<00:20, 974kB/s]
31%|███████████▉ | 9.28M/29.6M [00:10<00:22, 922kB/s]
32%|███████████▉ | 9.49M/29.6M [00:10<00:17, 1.17MB/s]
33%|████████████▏ | 9.71M/29.6M [00:10<00:14, 1.34MB/s]
34%|████████████▍ | 9.92M/29.6M [00:10<00:13, 1.46MB/s]
34%|████████████▌ | 10.1M/29.6M [00:10<00:14, 1.38MB/s]
35%|████████████▊ | 10.2M/29.6M [00:10<00:14, 1.30MB/s]
35%|████████████▉ | 10.3M/29.6M [00:10<00:15, 1.23MB/s]
36%|█████████████▏ | 10.5M/29.6M [00:10<00:14, 1.30MB/s]
36%|█████████████▍ | 10.7M/29.6M [00:11<00:13, 1.36MB/s]
37%|█████████████▌ | 10.8M/29.6M [00:11<00:14, 1.28MB/s]
37%|█████████████▋ | 11.0M/29.6M [00:11<00:15, 1.21MB/s]
38%|█████████████▉ | 11.2M/29.6M [00:11<00:13, 1.33MB/s]
38%|██████████████ | 11.3M/29.6M [00:11<00:14, 1.25MB/s]
39%|██████████████▎ | 11.4M/29.6M [00:11<00:15, 1.19MB/s]
39%|██████████████▍ | 11.5M/29.6M [00:11<00:16, 1.12MB/s]
39%|██████████████▌ | 11.6M/29.6M [00:11<00:16, 1.06MB/s]
40%|██████████████▋ | 11.8M/29.6M [00:12<00:17, 1.00MB/s]
40%|███████████████▏ | 11.9M/29.6M [00:12<00:18, 950kB/s]
40%|███████████████▎ | 12.0M/29.6M [00:12<00:19, 903kB/s]
41%|███████████████▍ | 12.0M/29.6M [00:12<00:20, 865kB/s]
41%|███████████████▋ | 12.2M/29.6M [00:12<00:18, 927kB/s]
42%|███████████████▊ | 12.3M/29.6M [00:12<00:19, 894kB/s]
42%|███████████████▉ | 12.4M/29.6M [00:12<00:18, 947kB/s]
42%|████████████████ | 12.5M/29.6M [00:12<00:18, 901kB/s]
43%|████████████████▏ | 12.6M/29.6M [00:13<00:19, 853kB/s]
43%|████████████████▎ | 12.7M/29.6M [00:13<00:19, 861kB/s]
43%|████████████████▍ | 12.8M/29.6M [00:13<00:19, 843kB/s]
44%|████████████████▌ | 12.9M/29.6M [00:13<00:20, 831kB/s]
44%|████████████████▋ | 13.0M/29.6M [00:13<00:21, 786kB/s]
44%|████████████████▉ | 13.1M/29.6M [00:13<00:17, 951kB/s]
45%|█████████████████ | 13.3M/29.6M [00:13<00:16, 986kB/s]
45%|█████████████████▏ | 13.4M/29.6M [00:13<00:16, 971kB/s]
46%|█████████████████▎ | 13.5M/29.6M [00:13<00:17, 919kB/s]
46%|█████████████████▍ | 13.6M/29.6M [00:14<00:17, 927kB/s]
46%|█████████████████▌ | 13.7M/29.6M [00:14<00:18, 879kB/s]
47%|█████████████████▊ | 13.8M/29.6M [00:14<00:17, 914kB/s]
47%|█████████████████▉ | 13.9M/29.6M [00:14<00:17, 880kB/s]
47%|██████████████████ | 14.0M/29.6M [00:14<00:17, 897kB/s]
48%|██████████████████▏ | 14.2M/29.6M [00:14<00:15, 994kB/s]
48%|█████████████████▉ | 14.3M/29.6M [00:14<00:15, 1.02MB/s]
49%|██████████████████▌ | 14.4M/29.6M [00:14<00:15, 993kB/s]
49%|██████████████████▋ | 14.5M/29.6M [00:15<00:15, 980kB/s]
50%|██████████████████▊ | 14.6M/29.6M [00:15<00:16, 929kB/s]
50%|██████████████████▌ | 14.8M/29.6M [00:15<00:14, 1.01MB/s]
51%|██████████████████▊ | 15.0M/29.6M [00:15<00:12, 1.19MB/s]
51%|██████████████████▉ | 15.1M/29.6M [00:15<00:12, 1.13MB/s]
52%|███████████████████ | 15.2M/29.6M [00:15<00:13, 1.10MB/s]
52%|███████████████████▏ | 15.4M/29.6M [00:15<00:13, 1.09MB/s]
52%|███████████████████▍ | 15.5M/29.6M [00:15<00:12, 1.09MB/s]
53%|███████████████████▌ | 15.6M/29.6M [00:16<00:13, 1.04MB/s]
53%|███████████████████▋ | 15.8M/29.6M [00:16<00:12, 1.10MB/s]
54%|███████████████████▊ | 15.9M/29.6M [00:16<00:13, 1.04MB/s]
54%|████████████████████▌ | 16.0M/29.6M [00:16<00:13, 980kB/s]
54%|████████████████████▏ | 16.1M/29.6M [00:16<00:13, 1.01MB/s]
55%|████████████████████▎ | 16.2M/29.6M [00:16<00:12, 1.03MB/s]
55%|████████████████████▍ | 16.4M/29.6M [00:16<00:13, 1.00MB/s]
56%|█████████████████████▏ | 16.5M/29.6M [00:16<00:13, 945kB/s]
56%|█████████████████████▎ | 16.6M/29.6M [00:17<00:13, 939kB/s]
56%|█████████████████████▍ | 16.7M/29.6M [00:17<00:14, 902kB/s]
57%|█████████████████████▌ | 16.8M/29.6M [00:17<00:15, 853kB/s]
57%|█████████████████████▎ | 17.0M/29.6M [00:17<00:10, 1.18MB/s]
58%|█████████████████████▍ | 17.1M/29.6M [00:17<00:11, 1.11MB/s]
58%|█████████████████████▌ | 17.2M/29.6M [00:17<00:11, 1.04MB/s]
59%|█████████████████████▋ | 17.3M/29.6M [00:17<00:12, 1.02MB/s]
59%|██████████████████████▍ | 17.4M/29.6M [00:17<00:12, 961kB/s]
59%|██████████████████████▌ | 17.6M/29.6M [00:18<00:12, 950kB/s]
60%|██████████████████████▋ | 17.7M/29.6M [00:18<00:13, 898kB/s]
60%|██████████████████████▊ | 17.7M/29.6M [00:18<00:13, 849kB/s]
60%|██████████████████████▉ | 17.8M/29.6M [00:18<00:14, 829kB/s]
61%|███████████████████████ | 17.9M/29.6M [00:18<00:14, 783kB/s]
61%|███████████████████████▏ | 18.0M/29.6M [00:18<00:13, 826kB/s]
61%|███████████████████████▎ | 18.2M/29.6M [00:18<00:12, 899kB/s]
62%|███████████████████████▍ | 18.3M/29.6M [00:18<00:12, 910kB/s]
62%|███████████████████████▌ | 18.4M/29.6M [00:19<00:13, 860kB/s]
63%|███████████████████████▊ | 18.5M/29.6M [00:19<00:11, 986kB/s]
63%|███████████████████████▉ | 18.6M/29.6M [00:19<00:11, 933kB/s]
63%|████████████████████████ | 18.7M/29.6M [00:19<00:12, 892kB/s]
64%|████████████████████████▏ | 18.8M/29.6M [00:19<00:11, 910kB/s]
64%|████████████████████████▎ | 18.9M/29.6M [00:19<00:12, 878kB/s]
64%|████████████████████████▍ | 19.1M/29.6M [00:19<00:11, 900kB/s]
65%|████████████████████████▋ | 19.2M/29.6M [00:19<00:11, 911kB/s]
65%|████████████████████████▊ | 19.3M/29.6M [00:19<00:11, 879kB/s]
66%|████████████████████████▉ | 19.4M/29.6M [00:20<00:10, 936kB/s]
66%|█████████████████████████ | 19.5M/29.6M [00:20<00:11, 886kB/s]
66%|█████████████████████████▏ | 19.6M/29.6M [00:20<00:10, 916kB/s]
67%|█████████████████████████▎ | 19.7M/29.6M [00:20<00:10, 962kB/s]
67%|████████████████████████▉ | 19.9M/29.6M [00:20<00:09, 1.04MB/s]
68%|█████████████████████████▋ | 20.0M/29.6M [00:20<00:09, 983kB/s]
68%|█████████████████████████▊ | 20.1M/29.6M [00:20<00:10, 931kB/s]
68%|█████████████████████████▉ | 20.2M/29.6M [00:20<00:10, 921kB/s]
69%|██████████████████████████ | 20.3M/29.6M [00:21<00:10, 925kB/s]
69%|██████████████████████████▏ | 20.4M/29.6M [00:21<00:10, 888kB/s]
69%|██████████████████████████▎ | 20.5M/29.6M [00:21<00:10, 841kB/s]
70%|██████████████████████████▌ | 20.6M/29.6M [00:21<00:09, 897kB/s]
70%|██████████████████████████▋ | 20.8M/29.6M [00:21<00:09, 909kB/s]
71%|██████████████████████████▊ | 20.9M/29.6M [00:21<00:09, 877kB/s]
71%|██████████████████████████▉ | 21.0M/29.6M [00:21<00:09, 940kB/s]
72%|██████████████████████████▍ | 21.2M/29.6M [00:21<00:07, 1.06MB/s]
72%|██████████████████████████▋ | 21.3M/29.6M [00:22<00:07, 1.10MB/s]
72%|██████████████████████████▊ | 21.4M/29.6M [00:22<00:07, 1.04MB/s]
73%|███████████████████████████▋ | 21.5M/29.6M [00:22<00:08, 984kB/s]
73%|███████████████████████████▊ | 21.6M/29.6M [00:22<00:08, 932kB/s]
73%|███████████████████████████▉ | 21.7M/29.6M [00:22<00:08, 891kB/s]
74%|████████████████████████████ | 21.8M/29.6M [00:22<00:08, 904kB/s]
74%|████████████████████████████▏ | 22.0M/29.6M [00:22<00:07, 953kB/s]
75%|███████████████████████████▋ | 22.1M/29.6M [00:22<00:07, 1.03MB/s]
75%|████████████████████████████▌ | 22.2M/29.6M [00:23<00:07, 976kB/s]
76%|████████████████████████████▋ | 22.3M/29.6M [00:23<00:07, 997kB/s]
76%|████████████████████████████▊ | 22.4M/29.6M [00:23<00:07, 942kB/s]
76%|████████████████████████████▉ | 22.5M/29.6M [00:23<00:07, 894kB/s]
77%|█████████████████████████████ | 22.7M/29.6M [00:23<00:07, 946kB/s]
77%|█████████████████████████████▎ | 22.8M/29.6M [00:23<00:07, 943kB/s]
77%|█████████████████████████████▍ | 22.9M/29.6M [00:23<00:07, 901kB/s]
78%|█████████████████████████████▌ | 23.0M/29.6M [00:23<00:07, 853kB/s]
78%|█████████████████████████████▋ | 23.1M/29.6M [00:24<00:07, 861kB/s]
78%|█████████████████████████████▊ | 23.2M/29.6M [00:24<00:07, 882kB/s]
79%|█████████████████████████████▏ | 23.4M/29.6M [00:24<00:06, 1.02MB/s]
80%|█████████████████████████████▌ | 23.6M/29.6M [00:24<00:04, 1.28MB/s]
81%|█████████████████████████████▊ | 23.8M/29.6M [00:24<00:03, 1.46MB/s]
81%|██████████████████████████████ | 24.0M/29.6M [00:24<00:03, 1.47MB/s]
82%|██████████████████████████████▏ | 24.2M/29.6M [00:24<00:03, 1.47MB/s]
82%|██████████████████████████████▍ | 24.3M/29.6M [00:24<00:03, 1.38MB/s]
83%|██████████████████████████████▌ | 24.5M/29.6M [00:25<00:03, 1.31MB/s]
83%|███████████████████████████████▌ | 24.6M/29.6M [00:25<00:05, 956kB/s]
84%|███████████████████████████████▋ | 24.7M/29.6M [00:25<00:05, 937kB/s]
84%|███████████████████████████████▉ | 24.8M/29.6M [00:25<00:04, 978kB/s]
84%|███████████████████████████████▏ | 25.0M/29.6M [00:25<00:04, 1.01MB/s]
85%|████████████████████████████████▏ | 25.1M/29.6M [00:25<00:04, 988kB/s]
85%|████████████████████████████████▎ | 25.2M/29.6M [00:25<00:04, 943kB/s]
86%|████████████████████████████████▌ | 25.3M/29.6M [00:25<00:04, 970kB/s]
86%|████████████████████████████████▋ | 25.4M/29.6M [00:26<00:04, 960kB/s]
87%|████████████████████████████████▏ | 25.8M/29.6M [00:26<00:02, 1.47MB/s]
88%|████████████████████████████████▋ | 26.1M/29.6M [00:26<00:01, 1.88MB/s]
90%|█████████████████████████████████▏ | 26.5M/29.6M [00:26<00:01, 2.41MB/s]
91%|█████████████████████████████████▊ | 27.1M/29.6M [00:26<00:00, 2.94MB/s]
94%|██████████████████████████████████▋ | 27.7M/29.6M [00:26<00:00, 3.67MB/s]
96%|███████████████████████████████████▎ | 28.3M/29.6M [00:26<00:00, 3.96MB/s]
99%|████████████████████████████████████▍| 29.2M/29.6M [00:26<00:00, 4.96MB/s]
29.8MB [00:27, 4.98MB/s]
0%| | 0.00/29.6M [00:00<?, ?B/s]
100%|██████████████████████████████████████| 29.6M/29.6M [00:00<00:00, 171GB/s]
0%| | 0.00/87.9M [00:00<?, ?B/s]
0%| | 13.3k/87.9M [00:00<16:00, 91.5kB/s]
0%| | 41.0k/87.9M [00:00<08:50, 166kB/s]
0%| | 100k/87.9M [00:00<04:45, 307kB/s]
0%| | 155k/87.9M [00:00<04:03, 361kB/s]
0%| | 204k/87.9M [00:00<03:52, 377kB/s]
0%| | 253k/87.9M [00:00<03:46, 386kB/s]
0%|▏ | 304k/87.9M [00:00<03:40, 398kB/s]
0%|▏ | 387k/87.9M [00:00<02:59, 488kB/s]
1%|▏ | 453k/87.9M [00:01<02:53, 504kB/s]
1%|▏ | 537k/87.9M [00:01<02:53, 505kB/s]
1%|▎ | 635k/87.9M [00:01<02:27, 591kB/s]
1%|▎ | 735k/87.9M [00:01<02:12, 656kB/s]
1%|▎ | 834k/87.9M [00:01<02:04, 699kB/s]
1%|▍ | 932k/87.9M [00:01<01:58, 732kB/s]
1%|▍ | 1.10M/87.9M [00:01<01:35, 905kB/s]
1%|▌ | 1.20M/87.9M [00:02<01:38, 878kB/s]
1%|▌ | 1.28M/87.9M [00:02<01:44, 831kB/s]
2%|▌ | 1.38M/87.9M [00:02<01:46, 813kB/s]
2%|▋ | 1.46M/87.9M [00:02<01:52, 770kB/s]
2%|▋ | 1.61M/87.9M [00:02<01:36, 890kB/s]
2%|▊ | 1.74M/87.9M [00:02<01:30, 949kB/s]
2%|▊ | 1.89M/87.9M [00:02<01:23, 1.02MB/s]
2%|▊ | 1.99M/87.9M [00:02<01:28, 970kB/s]
2%|▉ | 2.09M/87.9M [00:03<01:33, 919kB/s]
3%|▉ | 2.20M/87.9M [00:03<01:33, 915kB/s]
3%|▉ | 2.30M/87.9M [00:03<01:36, 885kB/s]
3%|█ | 2.40M/87.9M [00:03<01:39, 861kB/s]
3%|█ | 2.50M/87.9M [00:03<01:41, 843kB/s]
3%|█ | 2.58M/87.9M [00:03<01:46, 800kB/s]
3%|█▏ | 2.66M/87.9M [00:03<01:52, 758kB/s]
3%|█▏ | 2.78M/87.9M [00:03<01:45, 807kB/s]
3%|█▎ | 2.92M/87.9M [00:03<01:31, 931kB/s]
3%|█▎ | 3.02M/87.9M [00:04<01:36, 880kB/s]
4%|█▎ | 3.11M/87.9M [00:04<01:42, 830kB/s]
4%|█▍ | 3.19M/87.9M [00:04<01:47, 788kB/s]
4%|█▍ | 3.39M/87.9M [00:04<01:21, 1.04MB/s]
4%|█▌ | 3.65M/87.9M [00:04<01:01, 1.37MB/s]
4%|█▋ | 3.93M/87.9M [00:04<00:50, 1.65MB/s]
5%|█▊ | 4.37M/87.9M [00:04<00:37, 2.22MB/s]
5%|██ | 4.80M/87.9M [00:04<00:31, 2.62MB/s]
6%|██▎ | 5.55M/87.9M [00:05<00:22, 3.68MB/s]
7%|██▌ | 6.10M/87.9M [00:05<00:20, 3.92MB/s]
8%|███ | 7.28M/87.9M [00:05<00:14, 5.66MB/s]
10%|███▋ | 8.65M/87.9M [00:05<00:10, 7.30MB/s]
12%|████▍ | 10.5M/87.9M [00:05<00:08, 9.64MB/s]
14%|█████▏ | 12.3M/87.9M [00:05<00:06, 11.0MB/s]
16%|██████ | 14.3M/87.9M [00:05<00:05, 12.4MB/s]
18%|██████▋ | 15.8M/87.9M [00:05<00:05, 12.3MB/s]
20%|███████▍ | 17.8M/87.9M [00:06<00:05, 13.4MB/s]
22%|████████ | 19.1M/87.9M [00:06<00:05, 12.3MB/s]
23%|████████▌ | 20.3M/87.9M [00:06<00:07, 9.02MB/s]
24%|████████▉ | 21.4M/87.9M [00:06<00:09, 7.16MB/s]
25%|█████████▎ | 22.2M/87.9M [00:06<00:09, 7.03MB/s]
26%|█████████▋ | 23.0M/87.9M [00:07<00:11, 5.66MB/s]
27%|█████████▉ | 23.6M/87.9M [00:07<00:11, 5.58MB/s]
28%|██████████▏ | 24.2M/87.9M [00:07<00:11, 5.43MB/s]
28%|██████████▍ | 24.8M/87.9M [00:07<00:12, 5.25MB/s]
29%|██████████▋ | 25.4M/87.9M [00:07<00:11, 5.28MB/s]
29%|██████████▉ | 25.9M/87.9M [00:07<00:12, 4.82MB/s]
30%|███████████ | 26.4M/87.9M [00:07<00:12, 4.83MB/s]
31%|███████████▎ | 26.9M/87.9M [00:07<00:13, 4.62MB/s]
31%|███████████▌ | 27.4M/87.9M [00:08<00:13, 4.48MB/s]
32%|███████████▊ | 28.0M/87.9M [00:08<00:12, 4.63MB/s]
33%|████████████ | 28.6M/87.9M [00:08<00:12, 4.73MB/s]
33%|████████████▎ | 29.1M/87.9M [00:08<00:12, 4.66MB/s]
34%|████████████▍ | 29.6M/87.9M [00:08<00:13, 4.47MB/s]
34%|████████████▋ | 30.1M/87.9M [00:08<00:13, 4.35MB/s]
35%|████████████▉ | 30.7M/87.9M [00:08<00:12, 4.54MB/s]
36%|█████████████▏ | 31.3M/87.9M [00:08<00:12, 4.63MB/s]
36%|█████████████▎ | 31.8M/87.9M [00:08<00:12, 4.37MB/s]
37%|█████████████▌ | 32.4M/87.9M [00:09<00:12, 4.51MB/s]
37%|█████████████▊ | 33.0M/87.9M [00:09<00:11, 4.62MB/s]
38%|██████████████ | 33.4M/87.9M [00:09<00:12, 4.41MB/s]
39%|██████████████▎ | 34.1M/87.9M [00:09<00:11, 4.58MB/s]
39%|██████████████▌ | 34.7M/87.9M [00:09<00:12, 4.33MB/s]
40%|██████████████▊ | 35.3M/87.9M [00:09<00:11, 4.46MB/s]
41%|███████████████ | 35.9M/87.9M [00:09<00:11, 4.64MB/s]
41%|███████████████▎ | 36.4M/87.9M [00:10<00:11, 4.37MB/s]
42%|███████████████▌ | 36.8M/87.9M [00:10<00:12, 4.15MB/s]
43%|███████████████▋ | 37.4M/87.9M [00:10<00:12, 4.20MB/s]
43%|███████████████▉ | 37.9M/87.9M [00:10<00:11, 4.35MB/s]
44%|████████████████▏ | 38.5M/87.9M [00:10<00:10, 4.53MB/s]
45%|████████████████▍ | 39.2M/87.9M [00:10<00:10, 4.66MB/s]
45%|████████████████▋ | 39.7M/87.9M [00:10<00:10, 4.67MB/s]
46%|████████████████▉ | 40.2M/87.9M [00:10<00:10, 4.54MB/s]
46%|█████████████████ | 40.7M/87.9M [00:10<00:10, 4.31MB/s]
47%|█████████████████▎ | 41.1M/87.9M [00:11<00:11, 4.15MB/s]
47%|█████████████████▌ | 41.7M/87.9M [00:11<00:10, 4.44MB/s]
48%|█████████████████▊ | 42.4M/87.9M [00:11<00:09, 4.56MB/s]
49%|██████████████████ | 43.0M/87.9M [00:11<00:09, 4.56MB/s]
50%|██████████████████▎ | 43.6M/87.9M [00:11<00:09, 4.75MB/s]
50%|██████████████████▌ | 44.2M/87.9M [00:11<00:09, 4.70MB/s]
51%|██████████████████▊ | 44.8M/87.9M [00:11<00:08, 4.81MB/s]
52%|███████████████████ | 45.3M/87.9M [00:11<00:09, 4.59MB/s]
52%|███████████████████▎ | 45.9M/87.9M [00:12<00:08, 4.81MB/s]
53%|███████████████████▌ | 46.6M/87.9M [00:12<00:08, 4.89MB/s]
54%|████████████████████▏ | 47.9M/87.9M [00:12<00:05, 7.00MB/s]
58%|█████████████████████▎ | 50.7M/87.9M [00:12<00:03, 12.1MB/s]
60%|██████████████████████▎ | 53.1M/87.9M [00:12<00:02, 14.5MB/s]
64%|███████████████████████▋ | 56.3M/87.9M [00:12<00:01, 18.1MB/s]
69%|█████████████████████████▍ | 60.4M/87.9M [00:12<00:01, 22.8MB/s]
73%|███████████████████████████▏ | 64.4M/87.9M [00:12<00:00, 27.3MB/s]
77%|████████████████████████████▋ | 68.0M/87.9M [00:13<00:00, 26.2MB/s]
82%|██████████████████████████████▎ | 72.1M/87.9M [00:13<00:00, 28.0MB/s]
87%|████████████████████████████████ | 76.2M/87.9M [00:13<00:00, 29.6MB/s]
91%|█████████████████████████████████▋ | 80.2M/87.9M [00:13<00:00, 30.2MB/s]
96%|███████████████████████████████████▍ | 84.3M/87.9M [00:13<00:00, 31.1MB/s]
100%|████████████████████████████████████▉| 87.9M/87.9M [00:13<00:00, 30.3MB/s]
0%| | 0.00/87.9M [00:00<?, ?B/s]
100%|██████████████████████████████████████| 87.9M/87.9M [00:00<00:00, 369GB/s]
Preprocessing#
We reproduce the exact minimal preprocessing recipe of the DANCE paper
[1]: band-pass filter between 0.1 and 100 Hz, resample to a common
128 Hz, then a per-channel, per-session robust scaling (subtract the
median, divide by the interquartile range) clamped to [-16, 16]. We
reach for sklearn.preprocessing.robust_scale() rather than
hand-rolling the median/IQR arithmetic. axis=1 scales each channel
(row) using its own median and interquartile range computed across time
(columns), which is exactly the per-channel recipe above.
import numpy as np
from sklearn.preprocessing import robust_scale
from braindecode.preprocessing import Preprocessor, preprocess
def robust_scale_clamp(data):
"""Per-channel median/IQR scaling, clamped to [-16, 16] (DANCE recipe)."""
return np.clip(robust_scale(data, axis=1), -16, 16)
SFREQ = 128.0
preprocessors = [
Preprocessor("pick_types", eeg=True, stim=False),
Preprocessor("filter", l_freq=0.1, h_freq=100.0),
Preprocessor("resample", sfreq=SFREQ),
Preprocessor(robust_scale_clamp, apply_on_array=True),
]
preprocess(dataset, preprocessors)
Cut fixed-length windows and read the flash events#
DANCE consumes long, fixed-length windows rather than per-trial epochs, so
we use create_fixed_length_windows()
instead of create_windows_from_events().
We use W = 32 s and up to 150 events per window, matching the paper’s
BI2014a configuration (mean 45.8 events/window in the full dataset).
from braindecode.preprocessing import create_fixed_length_windows
WINDOW_S, N_CLASSES, NUM_LATENTS, MAX_EVENTS = 32.0, 3, 256, 150
WINDOW_SAMPLES = int(WINDOW_S * SFREQ)
windows_ds = create_fixed_length_windows(
dataset,
window_size_samples=WINDOW_SAMPLES,
window_stride_samples=WINDOW_SAMPLES,
drop_last_window=True,
preload=True,
use_mne_epochs=False, # guarantees EEGWindowsDataset -> (X, y, crop_inds)
)
def bi_annotations_to_events(raw):
"""Read ``(start_s, end_s, class_int)`` flash events from a BI* raw.
Class ``1`` = non-target flash, class ``2`` = target flash (class ``0``
is reserved for background/no-event, per the CLASS-0 CONTRACT).
"""
label_to_class = {"NonTarget": 1, "Target": 2}
events = []
for ann in raw.annotations:
cls = label_to_class.get(str(ann["description"]))
if cls is None:
continue
events.append((float(ann["onset"]), float(ann["onset"] + ann["duration"]), cls))
return events
Map each window’s annotations to DANCE targets#
This is the code that builds the \(y\) from the right-hand panel of the figure above. For one window \(X\), the paper [1] defines the target as a set of events
where \(b_i = (t_{\mathrm{start}}, t_{\mathrm{end}}) \in [0, 1]^2\) are the event’s boundaries normalized to the window duration (exactly the colored spans in the figure), and \(c_i \in \{0, \dots, K\}\) is its class. \(c_i = 0\) is the shared background/no-object class (the unlabeled gray span in the figure); real classes are \(1, \dots, K\) (here \(K = 2\): non-target and target flashes).
Two consequences follow, and both matter for dance_target_builder
below. Padding is indistinguishable from background:
DanceLoss’s matcher expects a fixed-size set
of max_events slots per window, so any slot with no real event defaults
to \(c_i = 0\), the same value that also denotes “no object”. There is
only one id for both, by design, and it never collides because a real
annotation is never given class 0. The dense head also needs a
per-token version of the same set: DANCE trains a per-timestep classifier
over num_latents latent tokens, so we rasterize each event’s
\([t_{\mathrm{start}}, t_{\mathrm{end}})\) span onto that token grid,
and every idle token defaults to the same background class 0.
This matches braindecode’s own upstream reference: the
dance/example/data.py MOABB bridge in facebookresearch/dance uses class 0 for
padding/background, 1 for non-target, and 2 for target on this
exact dataset, with events zero-padded to max_events = 150.
import torch
def dance_target_builder(
annotations, window_onset, window_duration, max_events, num_latents
):
"""Build one window's DANCE target: the event set y and its dense grid."""
start = torch.zeros(max_events)
end = torch.zeros(max_events)
cls = torch.zeros(max_events, dtype=torch.long) # cls[i] = 0 -> background/padding
w0, wd = window_onset, window_duration
kept = 0
for s, e, c in annotations:
# clip the annotation to the window and normalize to [0, 1]
s_c, e_c = max(s, w0), min(e, w0 + wd)
if e_c <= s_c or int(c) == 0 or kept >= max_events:
continue # outside the window, background, or out of query slots
start[kept] = (s_c - w0) / wd
end[kept] = (e_c - w0) / wd
cls[kept] = int(c)
kept += 1
# rasterize the kept events onto the num_latents-token grid: dense[t] is
# the class of whichever event (if any) covers token t
dense = torch.zeros(num_latents, dtype=torch.long)
s_tok = (start * num_latents).clamp(0, num_latents).long()
e_tok = (end * num_latents).clamp(0, num_latents).long()
for i in range(kept):
a, b = int(s_tok[i]), int(e_tok[i])
if a < b:
dense[a:b] = int(cls[i])
return {"start": start, "end": end, "class": cls, "dense": dense}
def dance_collate(batch):
"""Stack ``[(eeg, target_dict), ...]`` into the batched dict schema
:class:`~braindecode.training.DanceLoss` expects.
"""
eeg = torch.stack([b[0] for b in batch])
out = {"eeg": eeg}
for key in ("start", "end", "class", "dense"):
out[key] = torch.stack([b[1][key] for b in batch])
return out
raw_events = {
ds.description["subject"]: bi_annotations_to_events(ds.raw)
for ds in windows_ds.datasets
}
metadata = windows_ds.get_metadata()
samples, sample_subjects = [], []
for i in range(len(windows_ds)):
x, _, crop_inds = windows_ds[i]
eeg = torch.as_tensor(np.asarray(x), dtype=torch.float32)
subject = int(metadata.iloc[i]["subject"])
window_onset = float(crop_inds[1]) / SFREQ # i_start_in_trial / sfreq
target = dance_target_builder(
raw_events[subject],
window_onset=window_onset,
window_duration=WINDOW_S,
max_events=MAX_EVENTS,
num_latents=NUM_LATENTS,
)
samples.append((eeg, target))
sample_subjects.append(subject)
Splitting into train and test sets (cross-subject)#
We hold out the last subject entirely for evaluation, mirroring the paper’s cross-subject splitting protocol (train and test subjects never overlap).
from torch.utils.data import DataLoader
sample_subjects = np.asarray(sample_subjects)
test_subject = subject_ids[-1]
train_idx = np.flatnonzero(sample_subjects != test_subject)
test_idx = np.flatnonzero(sample_subjects == test_subject)
train_samples = [samples[i] for i in train_idx]
test_samples = [samples[i] for i in test_idx]
print(f"{len(train_samples)} train windows, {len(test_samples)} test windows")
train_loader = DataLoader(
train_samples,
batch_size=len(train_samples),
shuffle=True,
collate_fn=dance_collate,
)
test_loader = DataLoader(
test_samples,
batch_size=len(test_samples),
collate_fn=dance_collate,
)
37 train windows, 35 test windows
Create the model and the criterion#
from braindecode.models import DANCE
from braindecode.training import DanceLoss, f1_event
from braindecode.util import set_random_seeds
cuda = torch.cuda.is_available()
device = "cuda" if cuda else "cpu"
set_random_seeds(seed=random_state, cuda=cuda)
chs_info = windows_ds.datasets[0].raw.info["chs"]
model = DANCE(
n_outputs=N_CLASSES,
n_chans=len(chs_info),
chs_info=chs_info,
n_times=WINDOW_SAMPLES,
sfreq=SFREQ,
input_window_seconds=WINDOW_S,
).to(device)
criterion = DanceLoss(num_latents=NUM_LATENTS)
optimizer = torch.optim.Adam(model.parameters(), lr=5e-4)
Train the model#
detect() returns the query set + dense map that DanceLoss
needs; forward() alone would only give the dense per-token logits.
Warning
Kept at 2 epochs and 3 subjects so the docs build stays fast. This is not a converged model: the paper trains on the full dataset (71 subjects) for up to 100 epochs with early stopping. See the Conclusion.
n_epochs = 2
model.train()
for epoch in range(n_epochs):
epoch_loss = 0.0
for batch in train_loader:
batch = {k: v.to(device) for k, v in batch.items()}
optimizer.zero_grad()
out = model.detect(batch["eeg"])
loss, details = criterion(out, batch, duration=WINDOW_S)
loss.backward()
optimizer.step()
epoch_loss += float(loss)
print(f"Epoch {epoch + 1}/{n_epochs}: loss={epoch_loss / len(train_loader):.3f}")
Epoch 1/2: loss=11.305
Epoch 2/2: loss=9.444
Evaluate on the held-out subject#
f1_event() scores the decoded event set (IoU >
0.5 + class match); the per-token macro F1 scores the dense head against
the dense target with sklearn.metrics.f1_score().
def detections_to_events(detections, duration):
"""Decode a DANCE ``detect()`` output into per-window event tuples.
Each query becomes ``(start_s, end_s, class, confidence)`` in seconds
within the window; queries whose argmax class is ``0``
(background/no-object) are dropped, following the CLASS-0 CONTRACT.
"""
probs = torch.softmax(detections["class"], dim=-1) # (B, Q, K+1)
confidence, label = probs.max(dim=-1) # (B, Q), one softmax call for the batch
start = detections["start"] * duration
end = detections["end"] * duration
events = []
for bi in range(label.shape[0]):
keep = label[bi] != 0
events.append(
list(
zip(
start[bi, keep].tolist(),
end[bi, keep].tolist(),
label[bi, keep].tolist(),
confidence[bi, keep].tolist(),
)
)
)
return events
from sklearn.metrics import f1_score
model.eval()
ev_f1s = []
dense_preds, dense_targets = [], []
with torch.no_grad():
for batch in test_loader:
batch = {k: v.to(device) for k, v in batch.items()}
out = model.detect(batch["eeg"])
pred_events = detections_to_events(out, duration=WINDOW_S)
for bi in range(batch["eeg"].shape[0]):
# ground-truth events of this window, in seconds within the window
gt = [
(float(s) * WINDOW_S, float(e) * WINDOW_S, int(c))
for s, e, c in zip(
batch["start"][bi], batch["end"][bi], batch["class"][bi]
)
if int(c) != 0
]
preds = [(s, e, c) for (s, e, c, _conf) in pred_events[bi]]
ev_f1s.append(f1_event(preds, gt, iou_threshold=0.5))
# dense head (B, T, n_outputs) vs dense target (B, T), one token per prediction
dense_preds.append(out["dense"].argmax(-1).reshape(-1).cpu())
dense_targets.append(batch["dense"].reshape(-1).cpu())
dense_preds = torch.cat(dense_preds).numpy()
dense_targets = torch.cat(dense_targets).numpy()
sample_f1 = f1_score(
dense_targets, dense_preds, labels=list(range(N_CLASSES)), average="macro"
)
print(
f"Held-out subject {test_subject}: "
f"F1-event={np.mean(ev_f1s):.3f} F1-sample={sample_f1:.3f}"
)
Held-out subject 3: F1-event=0.014 F1-sample=0.246
Predicting with the official pretrained checkpoint#
Note
braindecode has not yet published an official pretrained checkpoint for DANCE on the Hugging Face Hub, unlike the sleep-staging tutorials. The cell below uses the same loading pattern as the rest of the gallery, so it starts working automatically once a checkpoint is released. Until then it falls back to the model trained above and predicts on one held-out window, so the cell still runs end-to-end. Training longer closes most of that gap: the 2-epoch, 3-subject model above is far from converged, and the paper’s full protocol (all subjects, up to 100 epochs with early stopping) is what a released checkpoint would reflect.
import warnings
repo_id = "braindecode/plot_dance_event_detection"
try:
from huggingface_hub import hf_hub_download
model.load_state_dict(
torch.load(hf_hub_download(repo_id, "model.pt"), map_location=device)
)
print(f"Loaded the official pretrained checkpoint from {repo_id}.")
except Exception as exc:
warnings.warn(
f"Could not load a pretrained checkpoint from {repo_id} ({exc}); "
"predicting with the locally trained short-run model instead.",
stacklevel=2,
)
model.eval()
with torch.no_grad():
example = test_samples[0][0].unsqueeze(0).to(device)
out = model.detect(example)
predicted = detections_to_events(out, duration=WINDOW_S)[0]
print(f"{len(predicted)} events predicted on the first held-out window:")
for s, e, label, conf in sorted(predicted, key=lambda ev: ev[0])[:10]:
print(f" [{s:5.2f}s, {e:5.2f}s] class={label} confidence={conf:.2f}")
Loaded the official pretrained checkpoint from braindecode/plot_dance_event_detection.
51 events predicted on the first held-out window:
[ 0.00s, 0.36s] class=1 confidence=0.99
[ 0.02s, 0.98s] class=2 confidence=0.96
[ 0.02s, 0.82s] class=1 confidence=0.98
[ 0.63s, 1.41s] class=1 confidence=0.86
[ 1.30s, 2.05s] class=1 confidence=0.95
[ 1.87s, 2.74s] class=1 confidence=0.93
[ 2.35s, 3.22s] class=1 confidence=0.75
[ 2.57s, 4.13s] class=2 confidence=0.95
[ 4.68s, 5.70s] class=1 confidence=0.64
[ 5.72s, 6.65s] class=1 confidence=0.85
Conclusion#
This tutorial trained DANCE on real,
continuous P300 EEG, detecting target and non-target flashes directly from
unaligned windows, without ever being told where a flash starts. At this
reduced scale (3 subjects, 2 epochs), the reported F1 scores are low and
should not be compared to the paper’s numbers.
At full scale, following the paper’s protocol (BI2014a, all subjects, up to 100 epochs with early stopping and a OneCycle learning rate schedule), DANCE matches onset-informed models on this dataset without ever using the flash onset, and sets a new state of the art on the harder task of seizure monitoring (Temple University Seizure corpus), where onsets are unknown by construction. That is the core claim: one architecture performing on par with onset-informed baselines while requiring no onset information at all.
References#
Total running time of the script: (6 minutes 22.783 seconds)
Estimated memory usage: 8806 MB