Note
Go to the end to download the full example code.
13: Loading your own data¶
Turn a numpy array, an MNE object or a file into a TrialSet that evoparam can fit.
Introduction¶
The first twelve tutorials used made-up data from evoparam.sim, so that we always
knew the truth. Your own recording comes as one of three things:
a numpy array you built yourself (
from_array);an MNE object (
Epochs,Evoked),read_mne;a file on disk (BrainVision, EDF, FIF, NWB, BIDS folder),
read.
All three give the same object, a TrialSet: trials x channels x time, with the
sampling rate, the channel names, the units and the time of the stimulus. Every later
step (clean, fit, test) works on that object, whatever it came from.
import numpy as np
from evoparam import models, reports
from evoparam.io import from_array, read_mne
1. From a numpy array¶
This is the lowest level and the one that always works. You give the data as an array
shaped (n_trials, n_channels, n_times) (one trial, one channel and one curve are
also allowed), the sampling rate in Hz, and the time of the first sample. Here we build
20 trials of 2 channels: channel RESP has a small negative bump at 25 ms (an N1),
channel FLAT has only noise.
sfreq = 1024.0
times = np.arange(-0.2, 0.5, 1.0 / sfreq)
rng = np.random.default_rng(0)
bump = -60.0 * np.exp(-0.5 * ((times - 0.025) / 0.004) ** 2)
data = rng.normal(0.0, 10.0, size=(20, 2, times.size))
data[:, 0, :] += bump
ts = from_array(data, sfreq=sfreq, times=times, ch_names=["RESP", "FLAT"], units="uV")
print(ts)
<TrialSet | 20 trials × 2 channels × 717 times, -0.200 - 0.499 s, 1024 Hz, uV, human>
times sets the sampling rate and the stimulus time together: t = 0 is the pulse,
negative times are the baseline. If you pass times, do not pass sfreq as well;
evoparam reads the rate from the spacing of the samples. units="uV" tells evoparam
the amplitudes are microvolts, so the reports carry the right unit.
The object is ready to fit. We use models.fit with no preset: it picks a generic
setting from the sampling rate. The N1 of RESP comes back near 25 ms; FLAT gets
no bump.
group = models.fit(ts, window=(0.005, 0.3), random_state=0)
table = group.to_dataframe().dropna(subset=["label"])
print(table[["channel", "label", "latency", "amplitude", "fwhm"]].round(4).to_string(index=False))
channel label latency amplitude fwhm
RESP 0.0251 -61.4999 0.0095
RESP 0.1627 8.7954 0.0020
FLAT NaN NaN NaN
2. From an MNE object¶
If your data are already in MNE (Epochs or Evoked), read_mne converts them
without going through a file. evoparam reads the sampling rate, the channel names, the
times (including the baseline) and the units from the object. Here we make a small
EpochsArray from the same array, then read it back.
import mne # noqa: E402
info = mne.create_info(["RESP", "FLAT"], sfreq=sfreq, ch_types="eeg")
epochs = mne.EpochsArray(data * 1e-6, info, tmin=float(times[0]), verbose="error") # MNE: volts
ts_mne = read_mne(epochs)
print(ts_mne)
<TrialSet | 20 trials × 2 channels × 717 times, -0.200 - 0.499 s, 1024 Hz, uV, human>
MNE stores EEG in volts; evoparam keeps microvolts, so the numbers match what we started
with. read_mne also accepts an Evoked (one averaged curve) and a
units="uV" keyword if your object is already in microvolts.
3. From a file¶
read is the single door for files. It guesses the format from the name, or you can
name it with format=:
from evoparam.io import read
ts = read("session.vhdr") # BrainVision
ts = read("recording.edf") # EDF / EDF+
ts = read("epochs-epo.fif") # MNE FIF
ts = read("file.nwb") # NWB
ts = read("trials.npz") # a saved evoparam array
A continuous file (not yet cut into trials) has no stimulus times. Find them from the
signal with detect_events (tutorial 14), or pass the event times you already have.
For a whole BIDS dataset, read_bids reads one subject, session, task and run,
and can read a few pulses straight from the server without downloading the file
(remote=True), as shown in tutorial 08.
4. Saving the result¶
After a fit, write_bids_derivatives writes the parameters next to the data as BIDS
derivatives (TSV tables plus a JSON with every setting), so the result is self-describing
and can be re-read. The reporting tutorial (12) covers the methods text, the JSON and
the HTML report. Here we only show the call; it returns the list of files written.
from evoparam.io import write_bids_derivatives
result = reports.run_pipeline(ts, "ccep", random_state=0)
files = write_bids_derivatives(result, subject="01", task="SPESclin", out="derivatives/")
Conclusion¶
Whatever the source, the first step is always to get a TrialSet: from_array for
a numpy array, read_mne for an MNE object, read or read_bids for a file or a
dataset. Set the units and the stimulus time right at this step; everything downstream
trusts them. From here, the next tutorials clean the trials, test for a response, and
fit the bumps exactly as they did on the made-up data.
_ = reports # noqa: B018
Total running time of the script: (0 minutes 0.722 seconds)