Live collaborative-reasoning dashboard (EEG stream -> intent decoding -> LLM candidates), served by the demo at http://127.0.0.1:8080:
A comprehensive toolkit for EEG-based Motor Imagery classification, implementing deep learning, classical ML, and ensemble approaches with real-time BrainFlow streaming support.
Status: feature-complete. All planned modules are implemented and validated (239 tests green on GitHub Actions CI); no further phases are planned.
This project provides complete implementations of:
- EEGNet v2 - Compact CNN for EEG classification (Lawhern et al. 2018)
- Conformer - Transformer-based EEG classifier
- TCN - Temporal Convolutional Network for time-series EEG
- CSP - Common Spatial Pattern (classical BCI approach)
- Riemannian MDM - Covariance-based classification using Riemannian geometry
- Ensemble - Voting & Stacking with tangent space features
- BrainFlow Real-Time Pipeline - Streaming via BrainFlow; tested on the synthetic board, real board IDs pluggable
Verified Results (PhysioNet Motor Movement: 109-subject dataset, 64ch, 160Hz; results verified on an 8-subject subset)
| Model | Evaluation | Accuracy | Notes |
|---|---|---|---|
| EEGNet v2 (baseline) | 5-fold CV | 45.83% | 4-class (25% random) |
| EEGNet v2 (tuned) | 5-fold CV | 54.32% | Hyperparameter search |
| Conformer | 5-fold CV | 38.43% | |
| TCN | 5-fold CV | 40.35% | |
| Ensemble Voting(soft) | train/test split | 79.29% | EEGNet+Conformer+TCN |
| Ensemble Stacking(tangent) | train/test split | 82.14% | Cohen's Kappa 0.537 |
| Riemannian MDM | single subject | 73.63% |
flowchart LR
subgraph ACQ["Data Acquisition"]
BF["BrainFlow Board - synthetic tested"] --> SM["EEGStreamManager - rolling 200 ms windows"]
end
SM --> MD["MockDecoder"]
SM --> RD["RealDecoder - lazy-loaded EEGNet"]
MD --> IE["IntentEncoder - MI intent to cognitive mode"]
RD --> IE
IE --> CM["ContextManager - state machine + dialogue context"]
CM --> LC["LLMClient"]
subgraph LLM["LLM Backends pluggable"]
LC --> OL["Ollama local"]
LC --> DS["OpenAI-compatible API - DeepSeek with off-peak scheduler"]
LC --> MK["Mock"]
end
LC --> AF["AudioFeedback - cross-platform TTS"]
LC --> VF["VisualFeedback - SSE dashboard - pre-serialized and downsampled"]
VF --> BR["Browser - real-time EEG and chat"]
NeuroDecode/
├── src/ # Core package
│ ├── config.py # Configuration management (YAML + defaults)
│ ├── data/ # Data loading and preprocessing
│ │ ├── loader.py # PhysioNet dataset loader
│ │ └── preprocessing.py # EEG preprocessing pipeline
│ ├── models/ # ML models
│ │ ├── eegnet.py # EEGNet v2 implementation
│ │ ├── conformer.py # Conformer model
│ │ ├── tcn.py # TCN model
│ │ ├── ensemble.py # Voting & Stacking ensemble
│ │ ├── csp.py # CSP classifier
│ │ └── riemann_mdm.py # Riemannian MDM classifier
│ ├── acquisition/ # Data acquisition (BrainFlow streaming)
│ │ ├── brainflow_acquisition.py # BrainFlow board wrapper (synthetic default)
│ │ └── eeg_stream_manager.py # Rolling-window EEG stream manager (200ms batch push)
│ ├── decoders/ # EEG decoders
│ │ ├── mock_decoder.py # Mock decoder for testing
│ │ └── real_decoder.py # EEGNet-based real decoder with auto-architecture inference
│ ├── training/ # Training utilities
│ │ ├── trainer.py # Unified training loop
│ │ └── augment.py # Data augmentation (6 methods)
│ ├── inference/ # Real-time inference
│ │ └── pipeline.py # StreamingBuffer + RealTimePipeline
│ ├── evaluation/ # Metrics
│ │ └── metrics.py # Comprehensive evaluation
│ ├── intent/ # Intent encoding
│ │ ├── intent_encoder.py # MI to cognitive mode mapping
│ │ └── context_manager.py # State machine + dialogue context
│ ├── llm_bridge/ # LLM integration
│ │ └── llm_client.py # Ollama/API/Mock pluggable backend
│ ├── feedback/ # Visual feedback
│ │ └── visual_feedback.py # Flask+SSE real-time web UI
│ └── utils/ # Utilities
│ └── config.py # Configuration management
├── tests/ # Unit tests
│ ├── test_intent_encoder.py # Intent encoder tests
│ ├── test_context_manager.py # Context manager tests
│ ├── test_llm_client.py # LLM client tests
│ ├── test_config.py # Config module tests
│ ├── test_eeg_stream_manager.py # EEG stream manager tests
│ └── test_decoders.py # Decoder module tests
├── scripts/ # Executable scripts
│ ├── realtime_demo.py # Real-time pipeline demo - mock stream or trained model
│ ├── collaborative_reasoning_demo.py # BCI×LLM collaborative demo│ ├── train_eegnet.py # EEGNet training (with anti-collapse measures)
│ ├── train_ensemble.py # Ensemble training
│ ├── train_csp.py # CSP training
│ ├── train_riemann.py # Riemannian training
│ ├── compare_models.py # Model comparison
│ └── tune_eegnet.py # Hyperparameter tuning
├── visualizations/ # Charts (CN + EN)
├── configs/ # Configuration files
│ ├── default.yaml # Default training configuration
│ └── demo.yaml # Collaborative reasoning runtime config
├── outputs/ # Results and checkpoints
├── README.md # This file
└── requirements.txt # Python dependencies
# Create conda environment (if not already done)
conda create -n bci_dev python=3.10
conda activate bci_dev
# Install PyTorch
pip install torch>=2.0.0
# Install MNE and dependencies
pip install mne>=1.0.0 scipy>=1.7.0 numpy>=1.21.0
# Install scikit-learn
pip install scikit-learn>=1.0.0
# Install pyRiemann (for Riemannian classifiers)
pip install pyriemann>=0.3.0
# Install Braindecode (optional, for additional models)
pip install braindecode>=0.8.0
# Install other utilities
pip install pyyaml matplotlib tqdmcd NeuroDecode
pip install -r requirements.txtThe first time you run a script, MNE will attempt to download the PhysioNet Motor Movement/Imagery dataset. This requires internet access.
python scripts/train_eegnet.py --subjects 1 2 3# Basic training (with anti-collapse measures enabled by default)
python scripts/train_eegnet.py --subjects 1 2 3 --epochs 100
# Disable augmentation
python scripts/train_eegnet.py --subjects 1 2 --no_augment --epochs 100
# With cross-validation
python scripts/train_eegnet.py --subjects 1 --cv_folds 5python scripts/train_csp.py --subjects 1 2 3 --n_components 4python scripts/train_riemann.py --subjects 1 2 --metric riemannpython scripts/compare_models.py --subjects 1 2 3 --quick# Run all strategies
python scripts/tune_eegnet.py --all --subjects 1 2
# Run specific strategy
python scripts/tune_eegnet.py --strategy A --augmentations gaussian_noise mixup
python scripts/tune_eegnet.py --strategy B --full_search
python scripts/tune_eegnet.py --strategy C --improvements batchnorm# Simulated streaming data, no hardware needed
python scripts/realtime_demo.py --mock --duration 60
# Real-time predictions with a trained model
python scripts/realtime_demo.py --model_path models/eegnet.pt --duration 120For hardware acquisition, BrainFlowAcquisition accepts a BrainFlow board ID
(defaults to the synthetic board; real boards such as Ganglion/Cyton can be
selected programmatically once the device is connected).
Supports 8 channels at 250Hz, sliding window inference (4s window, 0.5s step). Switch hardware by changing --board parameter only.
Systematically test augmentation methods:
- Gaussian Noise
- Temporal Masking
- Channel Masking
- Time Shifting
- Band Perturbation
- Mixup
Search over key parameters:
- F1 (temporal filters): [4, 8, 16]
- D (depth multiplier): [1, 2, 4]
- Dropout: [0.3, 0.5, 0.7]
- Kernel length: [32, 64, 128]
Test architectural modifications:
- Batch Normalization
- Label Smoothing
- SE Attention
- Combined approaches
The training script includes built-in measures to prevent prediction collapse:
| Measure | Default | Flag to Disable |
|---|---|---|
| Class weighting (balanced) | ON | --no_class_weighting |
| Cosine annealing LR scheduler | ON | --scheduler none |
| Data augmentation | ON | --no_augment |
| Label smoothing (0.1) | ON | --label_smoothing 0.0 |
These measures work together to ensure balanced predictions across all MI classes.
NeuroDecode bridges BCI motor imagery decoding with LLM-powered collaborative reasoning. Instead of treating BCI as a keyboard (one label = one character), we map MI classes to high-level cognitive modes, leveraging the human brain's strength in rapid intuitive selection.
EEG Signal -> BrainFlow -> EEGNet Decoder -> IntentEncoder -> ContextManager
|
LLM Bridge (Ollama/API/Coze/Mock)
|
3 Candidate Responses
|
User BCI Selection (2nd round)
|
Expand -> Visual Feedback (Flask+SSE)
| Motor Imagery Class | Cognitive Mode | Description |
|---|---|---|
| Left Hand | QUERY | Search for knowledge / factual lookup |
| Right Hand | REASON | Logical deduction / calculation / analysis |
| Feet | CREATE | Generate solutions / creative ideas |
| Tongue | REVIEW | Summarize / synthesize current context |
# Install collaborative-reasoning dependencies
pip install -r requirements.txt
# Run collaborative reasoning demo with mock LLM
python scripts/collaborative_reasoning_demo.py --backend mock
# With trained EEGNet decoder
python scripts/collaborative_reasoning_demo.py --backend mock --real-decoder
# With custom config file
python scripts/collaborative_reasoning_demo.py --config configs/demo.yaml
# Open browser to http://127.0.0.1:8080All runtime parameters are externalized to configs/demo.yaml. CLI arguments override config values.
| Section | Parameters | Description |
|---|---|---|
acquisition |
sample_rate, window_size, window_overlap | BrainFlow board settings |
eeg_stream |
window_seconds, push_interval, display_channels | Rolling-window EEG display |
bci |
debounce_frames, confidence_threshold, selection_timeout | Intent decoding parameters |
decoder |
model_sample_rate, bandpass_low/high, n_classes | EEGNet preprocessing config |
llm |
default_backend, ollama/api settings | LLM backend configuration (mock/ollama/api/coze) |
feedback |
host, port | Web UI server settings |
Edit the YAML file directly, no code changes needed.
The demo works out of the box with canned responses (--backend mock).
For real generation, pick one of the backends below — they all implement
the same LLMClient interface, so switching is a one-flag change.
| Backend | Setup | Cost | Runs locally? | Best for |
|---|---|---|---|---|
mock |
none | free | — | 30-second trial, CI, no LLM at all |
ollama |
install Ollama + pull a model | free | yes | privacy, offline use, no API key, own GPU |
api |
get an API key | pay-per-token | no (cloud) | best quality, no GPU needed |
coze |
deploy a Coze agent + token | per plan | no (cloud) | Coze users, agent-side prompt control |
If the chosen backend is unavailable at startup, the demo automatically falls back to mock responses instead of crashing. Check the terminal banner and the UI badge to see which backend is actually live (see Verify which backend is live).
-
Install Ollama from https://ollama.com/download (Windows / macOS / Linux).
-
Pull a model that fits your RAM / VRAM:
Model Download size Suggested when qwen2.5:3b~2 GB 8 GB RAM, laptops qwen2.5:7b~4.7 GB 16 GB RAM (project default) llama3.1:8b~4.9 GB alternative at the same size ollama pull qwen2.5:7b
-
Ollama serves on
http://localhost:11434by default. The desktop app starts the server automatically; on a headless server runollama serve. -
Run the demo:
python scripts/collaborative_reasoning_demo.py --backend ollama --real-decoder
Use
--modeland--hostto override the model name and server URL.
Works with every endpoint that implements /chat/completions.
-
Create an API key at your provider (e.g. https://platform.deepseek.com).
-
Either pass flags directly:
python scripts/collaborative_reasoning_demo.py \ --backend api --real-decoder \ --api-url https://api.deepseek.com/v1 \ --api-key YOUR_KEY \ --api-model deepseek-chat
...or keep the key out of your shell history: copy
.env.exampleto.envand fill in the values — the demo loads.envautomatically.cp .env.example .env # then edit .env python scripts/collaborative_reasoning_demo.py --backend api --real-decoderCommon endpoints:
Provider --api-url--api-modelDeepSeek https://api.deepseek.com/v1deepseek-chatOpenAI https://api.openai.com/v1gpt-4o-miniMoonshot Kimi https://api.moonshot.cn/v1moonshot-v1-8kLocal vLLM http://localhost:8000/v1your served model
Instead of calling a raw model endpoint, NeuroDecode can relay prompts to an agent deployed on Coze (coze.cn / coze.com). The agent forwards the prompt to its underlying model and returns the answer.
-
Deploy an agent on Coze with a plain LLM-relay prompt.
-
Collect three values: the agent service domain (
https://xxxx.coze.site), the project id, and a personal access token (PAT) or project API token. -
Copy
.env.exampleto.envand fill inCOZE_AGENT_DOMAIN,COZE_PROJECT_ID,COZE_API_TOKEN. -
Run:
python scripts/collaborative_reasoning_demo.py --backend coze --real-decoder
Notes:
- The agent sandbox may sleep after ~1 h of inactivity; the first request
after an idle period can take noticeably longer (cold start). If you hit
the LLM wait timeout, add
--llm-wait-timeout 60. - Tokens stay in
.env, which is git-ignored. Never commit real keys.
- Terminal banner at startup:
LLM backend: CozeClient OK(orOllamaClient/APIClient/MockLLMClient). A warning line means the backend was unreachable and mock fallback is active. - Web UI: the Pipeline Stats badge and the footer show the live backend (COZE / OLLAMA / API / MOCK).
| Variable | Used by backend | Meaning |
|---|---|---|
LLM_API_URL |
api |
OpenAI-compatible endpoint |
LLM_API_KEY |
api |
API key |
LLM_API_MODEL |
api |
Model name |
COZE_AGENT_DOMAIN |
coze |
Agent service domain |
COZE_PROJECT_ID |
coze |
Numeric project id |
COZE_API_TOKEN |
coze |
PAT or project API token |
Precedence: CLI flags > real environment variables > .env values.
All secrets live in .env (git-ignored); .env.example documents every key.
pytest tests/ -vThe suite covers intent encoder, context manager, LLM client, config, EEG stream manager, and decoder modules (239 tests passing).
| Module | File | Description |
|---|---|---|
| Config | src/config.py |
YAML + defaults deep-merge configuration management |
| BrainFlowAcquisition | src/acquisition/brainflow_acquisition.py |
BrainFlow board wrapper (synthetic default, pluggable board IDs) |
| EEGStreamManager | src/acquisition/eeg_stream_manager.py |
Rolling-window EEG stream manager (200ms batch push, OOP design) |
| MockDecoder | src/decoders/mock_decoder.py |
Mock EEG decoder for testing without trained model |
| RealDecoder | src/decoders/real_decoder.py |
EEGNet-based decoder with auto-architecture inference + 5-step preprocessing |
| IntentEncoder | src/intent/intent_encoder.py |
MI classification to cognitive mode mapping with debounce + confidence threshold |
| ContextManager | src/intent/context_manager.py |
Thread-safe state machine + dialogue context window |
| LLMClient | src/llm_bridge/llm_client.py |
Pluggable LLM backend: Ollama / OpenAI-compatible API / Coze agent / Mock |
| VisualFeedback | src/feedback/visual_feedback.py |
Flask + SSE real-time web UI with EEG waveform + candidate cards |
| Demo | scripts/collaborative_reasoning_demo.py |
Main entry point, orchestrates full collaborative reasoning pipeline |
Measured on the real code path (VisualFeedback.update_eeg) with
scripts/bench_feedback.py — Windows 11, i7-14650HX, 2026-08-19.
EEG batch producer throughput (8-channel windows):
| Window size | Downsample ×1 | Downsample ×4 | Speedup |
|---|---|---|---|
| 32 × 8ch | 18,369 ev/s | 59,195 ev/s | 3.2× |
| 256 × 8ch | 2,508 ev/s | 9,673 ev/s | 3.9× |
| 512 × 8ch | 1,251 ev/s | 4,962 ev/s | 4.0× |
SSE payload serialization (256-sample window, per event):
| Concurrent clients | Naive (serialize per client) | Pre-serialized (serialize once) | Speedup |
|---|---|---|---|
| 1 | 403.3 µs | 408.8 µs | ~1.0× |
| 2 | 788.7 µs | 394.1 µs | 2.0× |
| 5 | 1,968.5 µs | 394.5 µs | 5.0× |
Network payload: downsample ×4 shrinks each EEG batch event by 74.9% (17,287 B → 4,343 B for a 256-sample window).
Reproduce with: python scripts/bench_feedback.py
from src.training.augment import EEGAugmentor, AugmentationConfig
config = AugmentationConfig(
enabled=True,
temporal_mask={'enabled': True, 'prob': 0.3},
channel_mask={'enabled': True, 'prob': 0.2},
gaussian_noise={'enabled': True, 'prob': 0.3, 'snr_db': 10},
time_shift={'enabled': True, 'prob': 0.2},
band_perturbation={'enabled': True, 'prob': 0.2},
mixup={'enabled': True, 'prob': 0.3, 'alpha': 0.2},
)
augmentor = EEGAugmentor(config, sfreq=128)
X_aug = augmentor.augment(X)from src.data.preprocessing import PreprocessingPipeline, PreprocessingConfig
config = PreprocessingConfig(
bandpass_low=4,
bandpass_high=38,
tmin=-1.0,
tmax=4.0,
baseline=(-1.0, 0.0),
normalize=True,
resample_freq=128,
)
pipeline = PreprocessingPipeline(config)
epochs = pipeline.process_raw(raw)| Model | Evaluation | Accuracy | Notes |
|---|---|---|---|
| EEGNet v2 (baseline) | 5-fold CV | 45.83% | 4-class (25% random) |
| EEGNet v2 (tuned) | 5-fold CV | 54.32% | Hyperparameter search |
| Conformer | 5-fold CV | 38.43% | |
| TCN | 5-fold CV | 40.35% | |
| Ensemble Voting(soft) | train/test split | 79.29% | EEGNet+Conformer+TCN |
| Ensemble Stacking(tangent) | train/test split | 82.14% | Cohen's Kappa 0.537 |
| Riemannian MDM | single subject | 73.63% |
# configs/default.yaml
data:
dataset_path: "./NeuroDecode/data/"
subjects: [1, 2, 3, 4, 5, 6, 7, 8]
runs: [4, 5, 6]
preprocessing:
bandpass_low: 4
bandpass_high: 38
normalize: true
eegnet:
F1: 8
D: 2
kernel_length: 64
dropout_rate: 0.5
epochs: 100
batch_size: 64
learning_rate: 0.001
augmentation:
enabled: true
probability: 0.5
temporal_mask:
enabled: true
prob: 0.3If the PhysioNet dataset fails to download:
# Try setting a proxy if behind firewall
# Use synthetic data for testing: scripts will auto-generate if download fails# Reduce batch size
python scripts/train_eegnet.py --subjects 1 --batch_size 32
# Use CPU if GPU memory is limited
python scripts/train_eegnet.py --subjects 1 --device cpu# Make sure you're in the project root
cd NeuroDecode
export PYTHONPATH="${PYTHONPATH}:$(pwd)"
# Or run scripts directly
python scripts/train_eegnet.py-
Lawhern, V. J., et al. (2018). EEGNet: A compact convolutional neural network for EEG-based brain-computer interfaces. Journal of Neural Engineering.
-
Blankertz, B., et al. (2008). The BCI competition III: Validating alternative approaches to actual EEG problems. IEEE TNSRE.
-
Barachant, A., et al. (2012). Classification of covariance matrices using a Riemannian-based kernel for BCI applications. NeuroImage.
MIT. This project is for educational and research purposes.
