Bench modeSteps, parts, and safety only. Big type for a phone at the bench.

Python for signals

The dozen lines of NumPy, Matplotlib, and SciPy you need to load a recording, plot it properly, filter it, and see its spectrum. Plus enough Git to not lose your work.

SpineDecoding / signal processing / ML

You are skimming: the title, the first figure, and the short version. Switch to Read in the header for the full page, or Deep to open every deep dive.

You do not need to be a programmer for Phase 1. You need to load a CSV, plot it with labelled axes, apply a band-pass, and compute a spectrum. That is about a dozen lines and they are all on this page. Everything else in Python you will pick up as you need it.

Install

Python 3 from the official installer or Miniforge. Then in a terminal:

pip install numpy scipy matplotlib mne

Use a plain text editor and the terminal, or VS Code, or a Jupyter notebook. All three are fine. Notebooks are good for exploring and bad for anything you want to run again; when a notebook works, copy the code into a .py file.

Load a recording from the plotter

import numpy as np
d = np.loadtxt('recording.csv', delimiter=',', skiprows=1)
t = d[:, 0] / 1000.0            # first column: milliseconds -> seconds
x = d[:, 1]                     # second column: channel 1, ADC counts
fs = 1.0 / np.median(np.diff(t))
print(f'{len(x)} samples, about {fs:.1f} Hz')

Convert counts to microvolts once, at the top, and never think about it again. For an Arduino on a 5 V reference and a total gain of 2000: one count is 5 V / 1024 / 2000, about 2.44 µV.

uV = (x - x.mean()) * (5.0 / 1024 / 2000) * 1e6

Plot it properly

import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 3))
ax.plot(t, uV, lw=0.6)
ax.set(xlabel='time (s)', ylabel='µV', title='O1 - A1, eyes closed, 2026-09-14')
fig.tight_layout(); fig.savefig('eyes-closed-2026-09-14.png', dpi=150)

Axes with units. A title that says what and when. Saved with a name that sorts by date. Those are the notebook rules from Phase 0 and they apply to every figure you ever make.

Filter it

from scipy import signal
sos = signal.butter(4, [0.5, 40], btype='bandpass', fs=fs, output='sos')
clean = signal.sosfiltfilt(sos, uV)      # zero-phase: forward and backward

sosfiltfilt runs the filter forward and backward so it does not shift your peaks in time. That is only possible after the fact, on a recording; the Arduino cannot do it live. Phase 2’s filters explainer is about exactly this.

See its spectrum

f, pxx = signal.welch(clean, fs=fs, nperseg=int(2 * fs))   # 2 s windows
fig, ax = plt.subplots()
ax.semilogy(f, pxx); ax.set(xlim=(0, 60), xlabel='Hz', ylabel='µV²/Hz', title='Welch PSD')

Two-second windows give 0.5 Hz resolution, which is enough to see alpha’s peak and read off its frequency. Overlay eyes-open and eyes-closed on the same axes with a legend; that overlay is the artifact for Project B.

Six things to know about NumPy

Arrays are the unit; loops over samples are slow and almost never needed. x[a:b] slices. x.mean(), x.std(), np.abs(x), np.argmax(x). Boolean masks: x[(f > 8) & (f < 12)].mean() is alpha band power. Broadcasting: x - x.mean() subtracts from every element. Read the error message; it says which line and usually why.

Git, the minimum

Your code and your CSVs are part of the notebook. Put each project in a folder and, once:

git init
git add .
git commit -m "first working single-channel EEG script"

Then after every working change: git add -A && git commit -m "what changed". Push to a free GitHub repository so a dead laptop does not end the project. That repository is also where your build logs’ code lives, and recruiters look at it. Do not commit anything with another person’s name or recording in it.

Where this goes

Phase 2’s software pipeline project moves from these scripts to MNE-Python, which handles channels, montages, epochs, and topographies for you and is what every lab uses. Phase 3 adds scikit-learn for classifiers. None of it is harder than this page; there is just more of it.

Recall
Why use sosfiltfilt rather than sosfilt on a recording?
It runs the filter forward and then backward, cancelling the phase shift so peaks stay at their true times. It only works offline, on a complete recording.
Recall
What window length in Welch's method gives 0.5 Hz resolution, and why does that matter for alpha?
Two seconds (resolution is one over window length). It is enough to read the individual alpha frequency to half a hertz.