Biosignal Acquisition & Processing: ECG, EEG, EMG

Capturing the body's electrical signals. Learn signal characteristics (amplitude, bandwidth) of ECG, EEG, EMG and EOG, electrodes and the electrode–skin interface, biopotential amplifiers (instrumentation amp, CMRR, right-leg drive, isolation), noise sources (mains, motion, baseline wander, EMG), filtering (high-pass, notch, low-pass), the ECG waves and intervals, QRS detection (Pan–Tompkins), heart-rate variability — with a complete Python QRS detector.

Biosignals are tiny (ECG ~1 mV, EEG ~10–100 µV) and buried in noise: 50/60 Hz mains pickup can be volts, motion shifts the baseline, muscle activity overlaps the frequencies of interest. Good biosignal systems combine careful electrodes, high-CMRR amplifiers, patient-safety isolation and digital processing.

Once acquired, algorithms extract clinical information: heart rate, arrhythmias, sleep stages, seizures, muscle fatigue. These are the foundation of patient monitors, Holter recorders, smartwatches and brain–computer interfaces.

An analogy

Recording an ECG is like trying to hear a whisper in a noisy factory: you use two ears (differential electrodes) so the factory noise that reaches both cancels out, then tune your hearing to the whisper's pitch (filtering).

Signal characteristics

Know the signal before designing the hardware:

Electrodes and amplifiers

Ag/AgCl gel electrodes convert ionic current in tissue to electronic current with low, stable half-cell potential; dry electrodes are convenient but noisier. The electrode–skin interface has a large, variable impedance and DC offset (up to ±300 mV), so the front end is an instrumentation amplifier with high input impedance, CMRR > 100 dB, modest first-stage gain (to avoid saturation), then high-pass filtering and further gain. A right-leg drive circuit actively cancels common-mode voltage; isolation barriers protect the patient.

Filtering and artefacts

High-pass (0.05–0.5 Hz) removes baseline wander from breathing and electrode drift; notch or adaptive filters remove 50/60 Hz; low-pass removes EMG and prevents aliasing. Filters must not distort diagnostic features: a 0.5 Hz high-pass can distort ST segments, so diagnostic ECGs use 0.05 Hz or linear-phase digital filters.

Feature extraction and clinical analysis

ECG intervals (PR, QRS, QT/QTc) and segments (ST elevation for infarction) are measured automatically; arrhythmia detection classifies beats (normal, PVC, AF). HRV in time (SDNN, RMSSD) and frequency (LF/HF) domains reflects autonomic balance. EEG analysis uses band powers, spectrograms and event-related potentials; EMG uses RMS amplitude and median frequency (fatigue). Standards: IEC 60601-2-25/27 for ECG, AAMI EC57 for algorithm testing on MIT-BIH databases.

Formulas

Worked example: Heart rate

Given: RR interval 0.75 s

Find: HR.

Answer: 80 bpm

Worked example: QTc

Given: QT 400 ms, HR 90 bpm

Find: QTc (Bazett).

Answer: 490 ms (prolonged, > 470 ms)

Worked example: Common-mode error

Given: V_cm = 1 V mains, CMRR = 100 dB

Find: Equivalent input error.

Answer: 10 µV — acceptable for ECG, too much for EEG

Pan–Tompkins-style QRS detector on a noisy synthetic ECG

import math, random
random.seed(3)
# Synthetic ECG + noise, band-pass by moving averages, Pan-Tompkins-style R-peak detection
fs = 250
N = fs * 10
hr = 72
rr = 60 / hr
def beat(t):                       # crude PQRST as Gaussians
    return (0.1*math.exp(-((t-0.2)/0.025)**2) - 0.1*math.exp(-((t-0.28)/0.01)**2)
            + 1.0*math.exp(-((t-0.3)/0.01)**2) - 0.25*math.exp(-((t-0.32)/0.01)**2)
            + 0.3*math.exp(-((t-0.55)/0.04)**2))
x = [beat((n/fs) % rr) + 0.3*math.sin(2*math.pi*0.3*n/fs) + 0.05*math.sin(2*math.pi*50*n/fs)
     + random.gauss(0, 0.03) for n in range(N)]
def ma(sig, w):
    out, s = [], 0.0
    for i, v in enumerate(sig):
        s += v
        if i >= w: s -= sig[i - w]
        out.append(s / min(i + 1, w))
    return out
baseline = ma(x, int(0.6 * fs))                       # removes breathing wander
hp = [a - b for a, b in zip(x, baseline)]
lp = ma(hp, 5)                                        # 5-sample MA: notch at 50 Hz
d = [0.0] + [lp[i] - lp[i-1] for i in range(1, N)]    # derivative
sq = [v * v for v in d]
integ = ma(sq, int(0.15 * fs))                        # moving-window integration
thr = 0.3 * max(integ)
peaks, refractory = [], int(0.25 * fs)
i = 0
while i < N:
    if integ[i] > thr:
        j = max(range(i, min(i + refractory, N)), key=lambda k: integ[k])
        peaks.append(j); i = j + refractory
    else:
        i += 1
rrs = [(peaks[k+1] - peaks[k]) / fs for k in range(len(peaks) - 1)]
print(f"detected {len(peaks)} beats in 10 s, mean RR {sum(rrs)/len(rrs):.3f} s -> HR {60/(sum(rrs)/len(rrs)):.1f} bpm")
# detected 12 beats in 10 s, mean RR 0.833 s -> HR 72.1 bpm

The pipeline mirrors the classic Pan–Tompkins algorithm (1985): remove baseline wander, low-pass and notch the mains, differentiate to emphasise the steep QRS, square, integrate over ~150 ms, then threshold with a refractory period. It correctly recovers 72 bpm despite breathing wander, 50 Hz hum and noise. Clinical detectors add adaptive thresholds and search-back.

Common mistakes

Applications

Key takeaways

Going further