Save and reload prepared EEG windows#

Estimated reading time:7 minutes

Persist real windows with their labels and verify the reloaded samples.

These real Nakanishi2015 SSVEP recordings are distributed as the processed nm000118 release (study). Filtering, downsampling and latency handling were already applied; do not shift the event onsets again. CPU is sufficient. Internet is needed for the first download; EEGDASH_CACHE_DIR keeps downloads across runs.

The explicit subset uses 1 participant(s), about 7.0 MB of signal files.

Before you start#

Install EEGDash and its dependencies. Tutorial 02 introduces the window objects saved here. No earlier output is needed: the script loads real data, writes a persistent prepared directory, then reloads it through a new handle. Keep EEGDASH_CACHE_DIR stable across sessions to reuse the printed path.

import numpy as np
from braindecode.preprocessing import create_windows_from_events

from eegdash.paths import get_default_cache_dir
from eegdash import EEGDashDataset
import json
from importlib.metadata import 
from braindecode.datautil import load_concat_dataset

1. Load and inspect the selected recordings#

An input cache and a prepared-data cache solve different problems. EEGDash’s cache avoids fetching source files again; the prepared output captures the representation and indexing choices used by your analysis. Here we retain the source reference. Tutorial 10’s average-referenced output deliberately has a different directory name.

cache_dir = get_default_cache_dir()
subjects = ["1"]
dataset = EEGDashDataset(
    cache_dir=cache_dir,
    dataset="nm000118",
    subject=subjects,
    session="0",
    run="0",
    task="ssvep",
    n_jobs=1,
)
dataset.description[["subject", "session", "run"]]
subject session run
0 1 0 0


raw = dataset.datasets[0].raw
sfreq = raw.info["sfreq"]
channel_names = raw.ch_names
class_names = sorted(set(raw.annotations.description), key=float)
mapping = {name: index for index, name in enumerate(class_names)}

2. Window the observed trials#

The serialization check covers four-second, non-overlapping windows with annotation-derived labels. Besides (trials, channels, samples) voltages, we retain start-sample and recording identifiers. Matching only the array shape after loading would miss reordered windows or targets. The source event lasts 4.15 seconds; its final 0.15 seconds are unused.

window_size = int(4 * sfreq)
windows = create_windows_from_events(
    dataset,
    mapping=mapping,
    window_size_samples=window_size,
    window_stride_samples=window_size,
    on_last_window="drop",
    preload=True,
)

3. Write a persistent Braindecode dataset and a provenance manifest#

save writes Braindecode’s signal and indexing files into the prepared directory. Depending on the window dataset representation, serialization can store a backing Raw file and window metadata rather than one separate file for every trial. Copy the complete directory when moving the prepared data.

The adjacent JSON manifest explains how to interpret it: the exact query, frequency-to-class mapping, channel order, sample rate and package versions. It lives outside the numbered recording directories so the loader does not mistake it for a recording. This is a record of local preparation settings, not a checksum-based guarantee that a future upstream release is identical. Only this tutorial’s named output is overwritten on rerun. Nothing is deleted at exit: use this path in another process to reuse the windows.

prepared_path = cache_dir / "tutorial_13_nm000118_windows"
prepared_path.mkdir(parents=True, exist_ok=True)
windows.save(str(prepared_path), overwrite=True)
print("Reusable path:", prepared_path.resolve())
manifest = {
    "dataset": "nm000118",
    "subjects": subjects,
    "session": "0",
    "run": "0",
    "task": "ssvep",
    "window_samples": window_size,
    "stride_samples": window_size,
    "on_last_window": "drop",
    "reference": "source release unchanged",
    "mapping": mapping,
    "channels": channel_names,
    "sfreq": sfreq,
    "versions": {name: (name) for name in ["eegdash", "braindecode", "mne"]},
}
prepared_path.with_suffix(".manifest.json").write_text(json.dumps(manifest, indent=2))
Writing /home/runner/eegdash_cache/tutorial_13_nm000118_windows/0/0-raw.fif
Closing /home/runner/eegdash_cache/tutorial_13_nm000118_windows/0/0-raw.fif
[done]
Reusable path: /home/runner/eegdash_cache/tutorial_13_nm000118_windows

657

4. Reload and inspect a window and its metadata#

load_concat_dataset opens the prepared files, not a new catalogue query. preload=True reads their signals into memory for repeated indexing. The example compares one window numerically; this is an inspection, not a full integrity audit. A small tolerance permits float32 serialization.

reloaded = load_concat_dataset(str(prepared_path), preload=True)
original, restored = windows[0], reloaded[0]
np.testing.assert_allclose(restored[0], original[0], rtol=1e-6, atol=1e-12)
print("Original / reloaded target:", original[1], restored[1])
reloaded.get_metadata().head()
Opening raw data file /home/runner/eegdash_cache/tutorial_13_nm000118_windows/0/0-raw.fif...
    Range : 0 ... 218519 =      0.000 ...   853.590 secs
Ready.
Reading 0 ... 218519  =      0.000 ...   853.590 secs...
Original / reloaded target: 0 0
i_window_in_trial i_trial_in_dataset i_start_in_trial i_stop_in_trial target HED subject session run task age group species bci_experience gender sex
0 0 0 50 1074 0 Sensory-event, Experimental-stimulus, Visual-p... 1 0 0 ssvep 28.0 healthy homo sapiens not specified None None
1 0 1 1264 2288 0 Sensory-event, Experimental-stimulus, Visual-p... 1 0 0 ssvep 28.0 healthy homo sapiens not specified None None
2 0 2 2478 3502 0 Sensory-event, Experimental-stimulus, Visual-p... 1 0 0 ssvep 28.0 healthy homo sapiens not specified None None
3 0 3 3692 4716 0 Sensory-event, Experimental-stimulus, Visual-p... 1 0 0 ssvep 28.0 healthy homo sapiens not specified None None
4 0 4 4906 5930 0 Sensory-event, Experimental-stimulus, Visual-p... 1 0 0 ssvep 28.0 healthy homo sapiens not specified None None


Reuse the directory in a new process#

In another script, import load_concat_dataset and call it with the printed prepared directory. Check the adjacent manifest before combining that data with another prepared dataset. For large recordings you can choose preload=False and load samples on access. If you change the query, reference or window duration, give the new preparation its own output name so an earlier analysis remains reproducible. For a complete audit, compare windows and their metadata one at a time rather than stacking all signals.

from braindecode.datautil import load_concat_dataset
prepared = load_concat_dataset("/your/printed/prepared/path", preload=False)
signal, target, crop_indices = prepared[0]