Writing

Reading EEG signals as a rendering pipeline

If consciousness produces measurable electrical patterns, then perception might be understood as a renderer.

EEG signals are messy. They are microvolt-range electrical potentials measured on the scalp, filtered through bone and tissue, contaminated by muscle artifacts and eye blinks and power-line noise. A single electrode picks up the summed activity of millions of neurons firing asynchronously. It should be impossible to extract meaning from this. And yet, the signal is there — faint, noisy, but real.

I started reading about EEG because of an analogy that wouldn't leave me alone. The rendering pipeline in a GPU transforms a scene description into pixels through a series of stages: geometry processing, rasterization, fragment shading, output merging. Each stage applies a transformation to the data passing through it. The pipeline is a signal chain.

EEG processing is also a signal chain. Electrodes → amplification → filtering → artifact rejection → feature extraction → interpretation. The stages are different, but the structure — raw data passing through sequential transformations toward a readable output — is the same shape.

The pipeline analogy

Think of a vertex shader. It takes raw geometry data — positions, normals, texture coordinates — and transforms it into clip space. It applies matrices. It accounts for perspective. It prepares the data for the next stage.

EEG preprocessing does something similar. The raw signal is transformed: a high-pass filter removes DC drift, a notch filter removes 50/60 Hz line noise, independent component analysis separates overlapping sources. Each stage applies a matrix to the data — literally, in the case of ICA — and prepares it for the next stage.

The vertex shader doesn't know what the fragment shader will do with its output. The ICA stage doesn't know what the classifier will do with its components. The pipeline is modular, and each stage has a contract with the next.

A concrete example

# EEG preprocessing as a shader-like pipeline
def preprocess(raw_signal, fs=256):
    # Stage 1: High-pass filter (remove DC drift)
    filtered = butter_highpass(raw_signal, cutoff=0.5, fs=fs)
    # Stage 2: Notch filter (remove line noise)  
    clean = notch(filtered, freq=50, fs=fs)
    # Stage 3: ICA (separate sources)
    components = fast_ica(clean, n_components=20)
    # Stage 4: Artifact rejection
    valid = reject_artifacts(components)
    return valid

# This is structurally identical to a render pass sequence:
# geometry → vertex → rasterize → fragment → output

What the GPU perspective brings to EEG

Thinking about EEG in rendering terms forces two useful constraints:

1. Every stage must have a defined input and output format. GPU pipelines crash on type mismatches. EEG pipelines should too. If your ICA expects a certain number of channels and your filter produces a different count, that should be a compile-time error, not a runtime surprise.

2. Latency matters at every stage. A fragment shader that takes 16ms to execute drops your frame rate below 60fps. An EEG processing stage that takes 200ms makes real-time BCI impossible. Thinking in frame budgets — allocating milliseconds per stage — makes performance explicit rather than accidental.

The rate problem

Here's where the analogy gets interesting. A GPU renders at 60 frames per second. EEG is typically sampled at 256 Hz — 256 measurements per second per electrode. With 32 electrodes, that's 8,192 measurements per second. Each measurement is a floating-point value.

This is a stream processing problem. It needs to run continuously, with predictable latency, on a bounded compute budget. The worst thing you can do is buffer too much data and then batch-process it — your latency becomes unpredictable, and real-time BCI becomes impossible.

The GPU has already solved this problem. Its entire architecture is designed around processing streams of data with fixed latency. The insight isn't that EEG processing should run on a GPU (though it can). The insight is that the architecture of real-time signal processing is the same regardless of whether the signal is pixels or microvolts.

Where this is going

I'm building a browser-based EEG viewer — a tool that renders multi-channel neural signals as real-time waveforms using WebGL instanced geometry. Each electrode channel is a line strip. Each sample is a vertex. The entire pipeline runs on the GPU: signal data goes into a buffer, a vertex shader maps it to screen coordinates, a fragment shader applies color based on amplitude.

The CPU never touches the signal data after initialization. Every sample is processed in parallel, on the GPU, at the refresh rate of the display. This is overkill for a viewer. But it's the right architecture for a real-time BCI system, and building the viewer is how I learn the constraints.

The next step is classification — training a model to recognize patterns in the rendered signal and feeding those classifications back into the system. But that's a different essay.