Note
Go to the end to download the full example code or to run this example in your browser via Binder.
Save and reload prepared EEG windows#
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 version
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"]]
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: version(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
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]