19: Beyond CCEP: one tool, many modalities

Fit scalp ERP and TMS-EEG with the same calls; only the preset changes.

Introduction

The first eighteen tutorials used CCEP (intracranial electrical stimulation), but nothing in evoparam is tied to it. The same TrialSet, the same fit, the same existence test and reports fit any evoked response: scalp ERP, somatosensory evoked potentials (SEP), TMS-EEG (TEP), deep-brain ERNA, cochlear and spinal ECAP, animal field potentials. What changes between them is the preset: the time window, the component names, the background shape and the artifact method. There are eleven.

This tutorial fits two very different modalities with the same two lines each, then lists what the presets differ on.

import warnings

from evoparam import models, plots, presets, reports
from evoparam.sim import simulate_erp, simulate_tep

A scalp ERP

simulate_erp makes an averaged scalp ERP: a P1, an N1 and a P3, on a slow drift, at 500 Hz. No stimulation artifact, so the erp preset does no artifact removal; its window is 50 to 700 ms and its components are the classic ERP peaks (P1, N1, N170, P2, P3, N400, MMN, N2pc, LRP, ERN).

erp_trials, _ = simulate_erp(60, n_channels=1, snr=5.0, seed=0)
print(erp_trials)

erp_fit = models.fit(erp_trials, "erp", random_state=0)
erp_table = erp_fit.to_dataframe().dropna(subset=["label"])
print(erp_table[["label", "latency", "amplitude", "fwhm"]].round(3).to_string(index=False))

fig = plots.plot_fit(erp_fit.get("Cz"))
ok: 3 component(s)
<TrialSet | 60 trials × 1 channels × 500 times, -0.200 - 0.798 s, 500 Hz, uV, human, erp>
label  latency  amplitude  fwhm
   P1    0.095     39.841 0.042
   N1    0.161    -42.389 0.056
   P3    0.544     41.030 0.187

The P1, N1 and P3 come back at their true latencies, with the right sign. The only thing that told evoparam this was an ERP and not a CCEP was the string "erp".

A TMS-EEG response (TEP)

simulate_tep makes a TMS-EEG response: the N15, P30, N45, P60, N100 and P180 components, at 5 kHz, with the things that make TEPs hard: a pulse artifact, a slow decay artifact and a muscle artifact. The tep preset cleans them (interpolation plus a subspace projection across channels) before fitting. We run the whole chain with run_pipeline, as in tutorial 08.

tep_trials, _ = simulate_tep(40, n_channels=4, seed=0)
print(tep_trials)

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    tep_result = reports.run_pipeline(tep_trials, "tep", random_state=0)

tep_table = tep_result.group.to_dataframe().dropna(subset=["label"])
tep_columns = ["channel", "label", "latency", "amplitude"]
print(tep_table[tep_columns].round(3).head(8).to_string(index=False))
<TrialSet | 40 trials × 4 channels × 3000 times, -0.200 - 0.400 s, 5000 Hz, uV, human, tep>
channel label  latency  amplitude
     Cz  P180    0.208      1.083
     Pz            NaN        NaN
     Fz    C1    0.200     -1.152
     C3    C1    0.110      3.588
     C3    C2    0.198     -6.970

Same run_pipeline call as the CCEP tutorial, a different preset. The slow decay artifact that would swamp an early component is handled by the preset, not by you. TEP is the hardest modality: the early N15 and P30 sit right after the pulse, and clean recovery of the later N45 and N100 is still being validated, so read the TEP labels with care and lean on the existence test and the figures.

What the presets differ on

Every preset sets the same handful of fields for its modality. Side by side, they show what actually changes from one evoked response to another: the time scale (microseconds for ECAP, hundreds of ms for ERP), the component names, and the artifact method (none for scalp, template subtraction for CCEP, periodic template for ERNA).

for name in ["erp", "sep", "ccep", "tep", "erna", "ecap"]:
    preset = presets.get(name)
    labels = ", ".join(list(preset.component_labels)[:4])
    window = str(preset.fit_window)
    method = str(preset.artifact_method)
    print(f"  {name:5}  window {window:<16}  artifact {method:<22}  e.g. {labels}")
erp    window (0.05, 0.7)       artifact None                    e.g. P1, N1, N170, P2
sep    window (0.01, 0.06)      artifact interpolate             e.g. N20, P25, N33, P37
ccep   window (0.009, 0.5)      artifact template+tail:joint     e.g. N1, N2, delayed
tep    window (0.012, 0.3)      artifact interpolate+subspace    e.g. N15, P30, N45, P60
erna   window (0.0015, 0.0077)  artifact period_template         e.g. ERNA
ecap   window (0.0003, 0.003)   artifact paradigm:alternating    e.g. N1, P2

presets.list() is the full registry with each preset’s validation status. presets.get(name) returns one, and presets.derive(base, ...) makes your own from a close one (a new window, an extra component) without starting from scratch, as the presets tutorial shows.

Conclusion

evoparam is one engine with a preset per modality. Picking "erp", "tep", "ccep" or any of the eleven sets the window, the components, the background and the artifact method; everything you learned on CCEP, loading data, cleaning, existence, trials, time-frequency, statistics and hierarchical models, works the same on every other modality. When none fits, derive builds a custom preset from the nearest one.

Total running time of the script: (0 minutes 1.842 seconds)

Gallery generated by Sphinx-Gallery