A standalone service that reads EEG from any LSL (Lab Streaming Layer) stream and publishes the user's attention and relaxation levels in real time. See Plan.md for the design and milestones.
Status: neurostate run reads EEG from an LSL stream, cleans it and publishes attention and
relaxation levels, scaled against a resting baseline it measures at startup. A proper
calibration (milestone M5) and blink handling (M7) aren't built yet. neurostate run --fake
publishes made-up values, for building apps without a cap.
You need uv. It installs Python 3.12 if you don't have it.
uv sync --all-extras--all-extras adds the gui extra (pyqtgraph and Qt, about 250 MB), which only
neurostate check needs. Leave it out on machines that just run the service.
uv run neurostate --help
uv run neurostate mock # fake 32-channel EEG on LSL, until Ctrl+C
uv run neurostate mock --alpha 30 --blinks 15 # strong alpha, 15 blinks per minute
uv run neurostate mock --channels Fp1,Fp2,O1,O2 --line-noise 5
uv run neurostate mock --drops 20 # lose a chunk of samples 20 times a minute
uv run neurostate --config my.yaml mock # settings from a config fileuv run neurostate run --montage brainaccess-midi # from the EEG stream
uv run neurostate run --fake # made-up values: slow waves in attention and relaxation
uv run neurostate run --fake --dropouts # each minute ends with poor signal, then no signalWith a BrainAccess headset, first connect it in BrainAccess Board with LSL streaming on. For
the first minute of good signal, sit still with your eyes open: neurostate measures your
resting baseline meanwhile, and reports the state calibrating. After that, attention and
relaxation say how far you are from that resting state, with 50 meaning no change. To watch
the values, run uv run python examples/python/tcp_client.py in another terminal.
Every 250 ms, neurostate takes the last 2 s of EEG, leaves out flat, noisy and mains-polluted channels, filters and re-references the rest to their average, and skips the window if it goes beyond ±100 µV. Attention comes from beta / (alpha + theta) and frontal theta / parietal alpha; relaxation from the share of alpha, and alpha / beta, over the back of the head.
Apps can receive the values over LSL, JSON over TCP or WebSocket, or the ThinkGear protocol that NeuroSky MindWave apps use, all at once. docs/outputs.md describes the values and each output; examples/ has working clients in Python, C# (including Unity) and JavaScript.
uv run neurostate check # first LSL stream of type EEG
uv run neurostate check --stream MockEEG # a stream by name
uv run neurostate check --montage my-cap.yaml # a montage other than standard 10-20check first prints what the stream reports about itself: name, sampling rate, channel labels,
units, which channels it ignores (for example an accelerometer) and which channels cover each
brain region. It then opens a live view:
- Signals, filtered, coloured by channel status: grey for flat, red for noisy, orange for mains hum.
- Status line: how many good channels each region (frontal, parietal, occipital) has, the received sampling rate, and gaps in the stream.
- Spectrum over the back of the head (O1, Oz, O2). The alpha peak at 8–13 Hz should grow clearly when you close your eyes.
When you close the window (or press Ctrl+C), it prints how many samples arrived and how many were lost.
config/default.yaml lists every setting with its default. Your own config file only needs the keys it changes.
A montage says which of a cap's channels are EEG and which brain region each one covers.
The built-in ones are in src/neurostate/montages/: standard-1020
works for any cap with standard 10-20 labels, and brainaccess-midi for the 16-channel
BrainAccess MIDI as BrainAccess Board streams it. You can also pass a path to your own montage
file.
The mock stream is a regular LSL stream of type EEG, so any LSL tool (LabRecorder,
mne-lsl's viewer, etc.) can see it.
uv run pytest
uv run ruff check
uv run ruff formatCI runs the same checks on Linux and Windows for every push to main and every pull request.