Skip to content

Chapter 8: Apply This to Your Own Data

Everything from Chapters 1–7, applied to your own .edf recording. This notebook runs entirely on your machine — only edit EDF_PATH below; nothing here uploads or transmits your data anywhere.

Your file's known quirks, already accounted for below (from earlier inspection):

  • 32 EEG channels, referenced to CPz, named like EEG Fp1-CPz — needs renaming before a standard montage will apply (Chapter 3).
  • Includes mastoids M1/M2.
  • Sampling rate 1000 Hz, ~944 s duration.
  • raw.annotations contained entries like Impedance ... — these are amplifier impedance checks, not task events. Before epoching means anything, you need to confirm what your real stimulus/response triggers look like (check raw.annotations again with fresh eyes, or ask whoever ran the recording about the marker scheme used).
import mne
import matplotlib.pyplot as plt
import re

%matplotlib inline
mne.set_log_level("WARNING")

1. Load (Chapter 3)

EDF_PATH = r"PUT_YOUR_FILE_PATH_HERE.edf"  # <-- edit this line

raw = mne.io.read_raw_edf(EDF_PATH, preload=True)
raw

2. Inspect

print("Channel names:", raw.ch_names)
print("Sampling rate (Hz):", raw.info["sfreq"])
print("Duration (s):", raw.times[-1])
print("Annotations:", raw.annotations)

3. Rename channels and apply montage (Chapter 3)

Stripping the EEG prefix and -CPz reference suffix so channel names match the standard 10-20 names (EEG Fp1-CPzFp1). This is a labeling fix only — it does not change the underlying signal or its reference.

raw.rename_channels(lambda name: re.sub(r"^EEG\s+", "", name).split("-")[0])
print(raw.ch_names)

montage = mne.channels.make_standard_montage("standard_1020")
raw.set_montage(montage, on_missing="warn")

4. Visualize and mark bad channels (Chapter 3)

raw.plot(n_channels=20, duration=10, scalings="auto")
plt.show()
raw.info["bads"] = []  # <-- fill in any bad channel names you noticed above

5. Filter (Chapter 4)

LINE_FREQ = 50  # <-- set to 60 if your mains power is 60 Hz

raw_filt = raw.copy().filter(l_freq=1.0, h_freq=40.0)
raw_filt.notch_filter(freqs=LINE_FREQ)

raw_filt.compute_psd(picks="eeg").plot()
plt.show()

6. Artifact removal with ICA (Chapter 5)

Your file has no dedicated EOG channel, so we fall back to visual inspection — look for a component with a strong, symmetric, frontal topography (classic eye-blink signature).

ica = mne.preprocessing.ICA(n_components=15, random_state=97, max_iter="auto")
ica.fit(raw_filt)
ica.plot_components()
plt.show()
ica.plot_sources(raw_filt)
plt.show()
ica.exclude = []  # <-- fill in with component indices to remove, e.g. [0, 3]

raw_clean = raw_filt.copy()
ica.apply(raw_clean)

raw_clean.plot(n_channels=20, duration=10, scalings="auto")
plt.show()

7. Find events (Chapter 6)

Stop and check this carefully before continuing. Earlier inspection showed your raw.annotations contained impedance-check markers, not real task events. Run the cell below and look at what event_id actually contains — if it's just impedance/setup markers, your real triggers live somewhere else (a status/trigger channel, or a separate log file from your experiment software), and you'll need to adapt this step before epoching means anything.

events, event_id = mne.events_from_annotations(raw_clean)
print("Event codes found:", event_id)
print("Number of events:", len(events))

8. Epoch (Chapter 6)

Adjust event_id below to only the real condition(s) you care about once you know your true event labels (e.g. event_id={"stimulus/target": 1}), and adjust tmin/tmax to match your experiment design.

epochs = mne.Epochs(
    raw_clean,
    events,
    event_id=event_id,
    tmin=-0.2,
    tmax=0.8,
    baseline=(-0.2, 0.0),
    preload=True,
    reject=None,  # add peak-to-peak thresholds once you've seen typical amplitudes, e.g. dict(eeg=150e-6)
)
epochs

9. Average into an ERP (Chapter 7)

evoked = epochs.average()
evoked.plot()
plt.show()
# Once you have real, meaningful condition labels, compare them like this:
# evoked_condA = epochs["real_label_a"].average()
# evoked_condB = epochs["real_label_b"].average()
# mne.viz.plot_compare_evokeds({"condition A": evoked_condA, "condition B": evoked_condB})

Where to go from here

As you run this, describe what you observe at each step (channel names, event labels, number of epochs, what plots look like) — text/printed output only, never the data file itself — and we'll debug and refine together. Once real events are wired in and you're getting a plausible ERP, natural next steps: measuring specific component peaks (Chapter 7's get_peak()), comparing conditions properly, and eventually formal statistics (mne.stats, briefly mentioned in Chapter 7).