Garrick Tse

All work

Selected work

Neurocalm

An EEG system that reads a live signal from OpenBCI hardware and scores focus, load and anomaly against a baseline calibrated to the person wearing it. Built by a team of five.

Year
2025 to 26
Role
Team of five
Stack
Python, BrainFlow, FastAPI, WebSockets, React, SQLite

The problem

Relaxation advice is generic. Everyone is told to breathe slowly. For some people that works, for others it does nothing, and there is normally no way to tell which group you are in except by how you feel afterwards. If you can measure what a technique actually does to someone's brain activity, the recommendation stops being a guess.

The engineering difficulty is that EEG is a continuous, noisy stream. It is not a value you fetch, it is a signal you follow, and everything downstream has to be built for that.

What I built

A local Python service reads from OpenBCI hardware through BrainFlow, computes band powers from the raw signal, and streams them over a WebSocket. A React dashboard turns those into three running figures, focus, load and anomaly, plotted live against each other as the signal arrives.

A session starts with calibration: thirty seconds of rest recorded as a personal baseline, so every score afterwards is relative to that person rather than to a population average. Readings are then grouped by what the wearer is actually doing, across meetings, studying, lectures and a health journal, because the same load figure means something different in a lecture than it does in a meeting.

Events are written to SQLite with a timestamp, mode, scores and context, and that history is what the reinforcement learning component trains against. A synthetic board mode stands in for the hardware, so the system runs end to end without an OpenBCI headset attached.

This was a five person project. My work was on the Python service and the data path between the hardware and the dashboard.

A live session: focus, load and anomaly scores above the real-time band power chart
The same dashboard with no board attached, showing the disconnected state rather than stale numbers
Calibration. Thirty seconds of rest is recorded as a personal baseline before any score is reported
Account sign-in, which scopes recorded sessions to one person

Decisions

Stream it, do not poll it.

Band powers are computed continuously from a live signal. Polling an endpoint would have imposed a second, arbitrary sampling rate on data that already has one, and would have left the visualisation lagging the person wearing the headset. A WebSocket keeps the dashboard at the rate of the signal instead of the rate of a timer.

Keep signal processing out of the browser.

Band power computation happens in Python next to the hardware, through BrainFlow, and the browser receives derived values. That keeps a numerical pipeline in the language that has the libraries for it, and it turns the dashboard into a rendering problem rather than a signal processing one.

Make the hardware optional.

The synthetic board path went in early. Five people cannot share one headset, and a system that only runs with specific hardware attached is a system exactly one person can work on at a time.