EEG + sEMG Fusion Motor Imagery BCI → Real-time Bilateral VR Avatar Arm Control on Meta Quest 3
This study develops a hybrid BCI (Brain-Computer Interface) system that simultaneously utilizes EEG (electroencephalography) and sEMG (surface electromyography).
Using only motor imagery (MI) signals, the system classifies left/right intent and controls both arms of a VR avatar in real time.
- Dual-stream deep learning model: EEGNet-based CNN (EEG) + 2-layer BiLSTM (sEMG), fused with Softmax Attention Fusion
- Preprocessing ablation study: Performance comparison across four preprocessing strategies (baseline / wideband / narrowband / gamma)
- Bias analysis and correction: Mitigation of Right MI overprediction bias using Hemispheric Flip Augmentation and Logit Calibration
- Real-time BCI-VR integration: ONNX model + WebSocket server → bilateral avatar IK arm control in Unity (target latency < 100 ms)
- Cross-dataset transfer learning: Zero-shot evaluation on BCI Competition IV 2a (9 subjects) using a model trained on GigaDB (52 subjects)
- Cho et al. 2017 | gigadb.org/dataset/100295 | CC BY 4.0
- 52 subjects (19 female, mean age 24.8)
- EEG 64ch + sEMG 4ch, 512 Hz, Biosemi ActiveTwo
- Files:
s01.mat~s52.mat(MATLAB v5) - Labels: 0 = Rest, 1 = Left MI, 2 = Right MI
- 9 subjects, EEG 22ch, 250 Hz,
.gdfformat - Used only for cross-dataset generalization evaluation of the GigaDB-trained model
Dataset files are not included in this repository due to file size limitations.
Please download them directly from the links above and place them underGigaDB_100295/andBCICIV_2a_gdf/in the project root.
MI-BCI/
├── src/ # Python source code
│ ├── train_flip_full.py # Full LOSO retraining (Hemispheric Flip Augmentation)
│ ├── train_flip_aug.py # Flip Augmentation training (subset subjects)
│ ├── inference.py # Sliding-window real-time inference engine
│ ├── export_onnx.py # PyTorch → ONNX conversion (opset 17)
│ ├── server_onnx.py # ONNX-based WebSocket server (Unity integration)
│ ├── websocket_server.py # PyTorch-based WebSocket server
│ ├── ablation_study.py # Performance comparison of 4 preprocessing strategies
│ ├── statistical_tests.py # Wilcoxon signed-rank + ITR bootstrap CI
│ ├── wilcoxon_analysis.py # Additional Wilcoxon analysis
│ ├── attention_analysis.py # Softmax Attention weights vs EMG SNR analysis
│ ├── bias_analysis.py # Right MI bias analysis
│ ├── bias_fix_report.py # Bias correction result report generator
│ ├── calibration.py # Post-hoc Logit Calibration
│ ├── subgroup_analysis.py # Subject subgroup analysis
│ ├── transfer_bcic2a.py # GigaDB → BCI IV 2a transfer learning/evaluation
│ ├── latency_bench.py # Inference latency benchmark
│ └── test_ws_client.py # WebSocket client test
│
├── notebooks/ # Jupyter notebooks for Colab execution
│ ├── S1_S2_Preprocessing_MemberA.ipynb # Data loading & preprocessing (Member A)
│ ├── S3_Model_Training_MemberA.ipynb # Model training & LOSO CV
│ ├── S4_Ablation_Study_Colab.ipynb # Preprocessing ablation study
│ ├── S5_Attention_Analysis_Colab.ipynb # Attention analysis & XAI
│ ├── S5_Bias_Fix_Colab.ipynb # Bias correction experiment
│ └── S6_Transfer_BCIC2a.ipynb # Cross-dataset transfer evaluation
│
├── unity/ # Unity C# scripts
│ ├── AvatarController.cs # WebSocket input → avatar IK control
│ ├── BCIExperimentManager.cs # Experiment manager (trial start/end, events)
│ └── BCISessionLogger.cs # Session data logging
│
├── BCI-VR/ # Unity project (Meta Quest 3)
│ ├── Assets/
│ ├── Packages/
│ └── ProjectSettings/
│
├── BCI_Research/
│ ├── preprocessed/ # Preprocessed HDF5 files (excluded from git — large)
│ └── results/
│ ├── ablation/ # Ablation results CSV/JSON/PNG ✓ (tracked in git)
│ ├── attention/ # Attention analysis results ✓ (tracked in git)
│ ├── calibration/ # Calibration results ✓ (tracked in git)
│ ├── checkpoints_A/ # Model checkpoints .pt (excluded from git — 212 MB)
│ ├── onnx/ # ONNX files (excluded from git — 212 MB)
│ └── vr_sessions/ # VR session logs (excluded from git)
│
└── generate_progress_report.py # Auto-generates project progress report
EEG Input (64ch × 2304) sEMG Input (4ch × 288)
│ │
┌────▼──────┐ ┌──────▼──────┐
│ EEGNet │ │ BiLSTM ×2 │
│ (CNN) │ │ hidden=128 │
└────┬──────┘ └──────┬──────┘
│ h_EEG (256-dim) │ h_EMG (256-dim)
└──────────────┬───────────────────┘
│
┌───────▼─────────┐
│ Softmax Attention│
│ Fusion Layer │
│ w_EEG + w_EMG=1 │
└───────┬─────────┘
│ F_fused (256-dim)
┌───────▼─────────┐
│ Classifier │
│ 256→128→2 │
│ ELU, Dropout │
└───────┬─────────┘
│
Left MI / Right MI
| Hyperparameter | Value |
|---|---|
| EEGNet F1 / D | 8 / 2 |
| BiLSTM hidden | 128, 2 layers, bidirectional |
| Fusion | Softmax Attention (weighted sum, sum=1) |
| Classifier Dropout | 0.3 |
| Loss | Cross-Entropy + L2 (λ=1e-4) |
| Optimizer | Adam, lr=1e-3 |
| Batch size / Epochs | 32 / 200 (early stop patience=20) |
| CV | LOSO (Leave-One-Subject-Out, 52 subjects) |
| Monitor | val F1-macro |
Four team members each use different preprocessing parameters.
| Parameter | A — baseline_v4 | B — wideband | C — narrowband | D — gamma |
|---|---|---|---|---|
| BPF | 4–40 Hz | 1–45 Hz | 8–30 Hz | 4–50 Hz |
| ICA components | 25 | 20 | 30 | 15 |
| epoch_tmin | −0.5 s | 0.0 s | −1.0 s | −0.5 s |
| epoch_tmax | 4.0 s | 4.0 s | 4.0 s | 3.0 s |
| baseline | (−0.5, 0.0) | None | (−1.0, 0.0) | (−0.5, 0.0) |
| EMG window | 50 ms | 100 ms | 25 ms | 200 ms |
| Normalization | z-score | min-max | robust | z-score |
| EOG threshold | r = 0.7 | r = 0.7 | r = 0.6 | r = 0.6 |
Common settings: random_seed=42, identical model architecture, LOSO CV
- CAR (Common Average Re-reference)
- 4th-order Butterworth BPF, zero-phase (
filtfilt) - FastICA / MNE — EOG artifact removal (protect C3/C4/Cz)
- Epoching (trigger-based, baseline correction)
- Adaptive PTP rejection:
threshold = median(ch_ptp) × 10 - Normalization (z-score / min-max / robust)
- μ-band (8–12 Hz), β-band (13–30 Hz) extraction
- Full-wave rectification
- Moving RMS envelope
- BPF 20–124 Hz, notch 60/120 Hz
- Epoching with the same triggers as EEG
- SNR calculation: $\text{SNR} = 20 \log_{10}(\text{RMS}{MI} / \text{RMS}{baseline})$ [dB]
S1 Data Loading & Sync
└─ Load GigaDB .mat files, split EEG/sEMG, cache as HDF5
S2 Preprocessing Ablation (4 members × different parameters)
└─ BCI_Research/preprocessed/member_{A~D}/sub-XX.h5
S3 Model Training & LOSO CV
└─ BCI_Research/results/checkpoints_A/best_sXX.pt
S4 XAI Analysis
└─ DeepSHAP, Grad-CAM, ERD(%) validation
└─ BCI_Research/results/attention/
S5 Bias Analysis & Fix
└─ Right MI overprediction bias analysis
└─ Full retraining with Hemispheric Flip Augmentation
└─ Post-hoc Logit Calibration
S6 Cross-dataset Transfer
└─ GigaDB → BCI IV 2a zero-shot evaluation
└─ BCI_Research/results/transfer_bcic2a/
S7 Real-time BCI-VR Integration
└─ ONNX conversion → WebSocket server → Unity
Python Inference Server Unity (Meta Quest 3)
┌─────────────────────────┐ ┌──────────────────────────┐
│ ONNX Model (bci_sXX) │ │ AvatarController.cs │
│ Sliding Window │ │ ├─ NativeWebSocket │
│ (2048 samples, s=128) │ WebSocket │ ├─ Lerp + EMA (α=0.3) │
│ EMA smoothing (α=0.3) │ ─────────▶│ └─ IK Target Control │
│ min_confidence=0.6 │ JSON │ │
└─────────────────────────┘ └──────────────────────────┘
Latency target: < 100 ms
How to run (real-time server):
# ONNX-based server (recommended)
python src/server_onnx.py --sid 3 --wait_client --min_confidence 0.6
# PyTorch-based server
python src/websocket_server.py --sid 3 --port 8765
# Unity: WebSocket URL = ws://<PC-IP>:8765ONNX I/O spec:
| Tensor | shape | dtype |
|---|---|---|
eeg (input) |
(1, 64, 2304) | float32 |
emg (input) |
(1, 4, 288) | float32 |
logits (output) |
(1, 2) | float32 |
probs (output) |
(1, 2) | float32 |
label (output) |
(1,) | int64 |
| Metric | Description |
|---|---|
| F1-macro | Balanced evaluation across Left / Right classes |
| Cohen's κ | Chance-corrected agreement |
| ITR | Information Transfer Rate (bits/min) |
| Wilcoxon | Non-parametric paired test between conditions |
| Bonferroni | Multiple-comparison correction (6 pairs, α=0.05/6) |
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
# Install dependencies
pip install torch torchvision torchaudio
pip install numpy scipy pandas h5py scikit-learn
pip install mne onnx onnxruntime websockets- Install Unity 2022.3 LTS or later + Meta XR SDK
- Package Manager → Add package from git URL:
https://github.com/endel/NativeWebSocket.git#upm - Copy
unity/AvatarController.cs,unity/BCIExperimentManager.cs,unity/BCISessionLogger.csintoBCI-VR/Assets/Scripts/ - Add
AvatarControllercomponent to the avatar GameObject in your scene - Assign IK target transforms for
leftArmTarget/rightArmTarget - Set
serverUrl=ws://<Python-server-IP>:8765
# Run notebooks/S1_S2_Preprocessing_MemberA.ipynb
# Output: BCI_Research/preprocessed/member_A/sub-XX_member_A.h5# Run notebooks/S3_Model_Training_MemberA.ipynb
# or
python src/train_flip_full.py --drive_root /content/drive/MyDrive/MI-BCI
# Output: BCI_Research/results/checkpoints_A/best_sXX.ptpython src/export_onnx.py --all --out_dir BCI_Research/results/onnxNote:
.ptcheckpoints (212 MB) and.onnxfiles (212 MB) are not included in the repository due to size limits.
They are generated locally after training; share them via external storage such as Google Drive.
python src/statistical_tests.py
# Output: results/ablation/wilcoxon_results.json
# results/ablation/itr_bootstrap.json# Start Python server
python src/server_onnx.py --sid 3 --wait_client
# Press Play in Unity → avatar arms move based on BCI signalssub-01_member_A.h5
├── eeg/
│ ├── epochs (n_epochs, 64, n_times) # Raw EEG epochs
│ ├── mu_epochs (n_epochs, 64, n_times) # 8–12 Hz filtered
│ └── beta_epochs (n_epochs, 64, n_times) # 13–30 Hz filtered
├── emg/
│ └── epochs (n_epochs, 4, n_times) # sEMG epochs
├── labels (n_epochs,) # 0=Left, 1=Right
└── metadata/ # attrs: full CONFIG stored
[Dataset] Cho et al. (2017). EEG datasets for motor imagery brain–computer interface.
GigaScience, 6(7). https://doi.org/10.1093/gigascience/gix034
This research code is intended for research purposes only.
The GigaDB dataset follows the CC BY 4.0 license.