projects
Oculy
Reading the voltage across your eyeball and turning it into a game controller.
View on GitHubThere is a standing voltage across your eyeball. The cornea sits positive against the retina, about a millivolt of difference, and when your eye rotates that whole electric field rotates with it. Put two electrodes on your temples and you can read the swing as it happens. Oculy was a five-person Launchpad team turning that into a controller: electrodes on, look left, and the paddle moves left.

The rig is a BITalino biosignal board with electrodes at the temples and a reference on the mastoid bone behind the ear, streaming into OpenSignals. Two channels, one horizontal and one vertical, plus blinks, which announce themselves as sharp spikes rather than plateaus. Then the problem that shaped the rest of the project: the EOG data coming off the dedicated sensor boards was far too noisy to train anything on. So we recorded the same eye signal through the board's EEG channel instead — identical physiology, much cleaner front end — and that switch is what made a usable dataset possible at all.


Getting labeled data was harder than it sounds. Recording ran in OpenSignals while a separate keypress script logged what movement was being performed, and the two never lined up — a person pressing a key always lags the saccade they're trying to mark, by a variable amount. So the team built a small editing tool to crop recordings and drag segment boundaries onto the real transitions in the trace. I ran recording sessions and contributed my own subject data. Fixing labels is the least glamorous work on the project and it moved accuracy more than any modeling decision did.

The obvious approach is thresholding, and it half works. Eye movements produce predictable polarity — look right, the cornea swings toward the right electrode, the voltage goes positive — so you draw cutoffs and classify. It holds up until the subject changes. Baseline, amplitude, and electrode placement all shift from person to person and session to session, and a threshold tuned on one recording means nothing on the next. A 1D convolutional network sidesteps that by sliding filters along the time axis and keying on shape instead of absolute voltage: how fast the deflection rises, how wide the plateau is, what the envelope looks like.
I owned the model. It ended up small on purpose — a single 1D convolution with 64 filters at kernel size 7, then batch norm, max pooling, a 64-unit dense layer, dropout at 0.5, and an output over three classes: left, right, and stare. Adam at 1e-3, batch size 16, cross-entropy, with the learning rate stepped down on plateau. Three things came out of the training runs. One convolution layer beat three, because the signal is a handful of characteristic shapes rather than a deep hierarchy and the extra depth just memorized faster. An 80/10/10 split beat both 70/15/15 and 60/20/20. And merging recordings across subjects and cleaning up the labels improved things more than any architecture change I tried.


Worth being straight about what that means. The validation set is twenty-six windows drawn from the same handful of recording sessions the model trained on, so a clean diagonal demonstrates that left, right, and stare are separable from a single channel with a very small network — and not much beyond that. Overfitting was the standing problem all semester, and a result like this is what overfitting looks like when your held-out data isn't held out far enough. The honest next step was never a bigger model; it was more subjects, more electrode placements, and a test set from sessions the model had never seen.
Live inference closed the loop. A TCP socket into OpenSignals, JSON packets decoded into frames as they arrive, one-second windows handed to the trained model, and each prediction written out to a file the game reads. The game integrates those predictions as a position rather than treating them as discrete jumps, so a brief flick back to center doesn't cancel out a sustained look — which is the difference between a paddle that drifts where you're looking and one that twitches.