Thoughts in Motion
Team (contributor) · 2023–24
Overview
Thoughts in Motion lets a user act on a virtual world without touching anything. It reads two different biosignals, EEG from the brain and EMG from the muscles, and within the EEG it decodes two different signatures: one driven by what you look at, one by what you imagine doing.
It is also, and mainly, a tool. The project became Hugo Devoille’s Master’s thesis (Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction, November 2024, advised by Dr. Xiao Xiao in the Human Learning group), and its stated goal is to lower the barrier for newcomers: an open-source platform that retrieves, processes and transmits biosignals across software, so a designer can use an EEG headset without first becoming a signal-processing engineer.

SSVEP is now Online.Where it came from
The first version was a single Python script, written for one specific VR experiment. It pulled sensor data over LSL, filtered it, analysed it, and sent the result to Unity so that looking at the right thing would open a door. It worked, and it was welded shut: no options, no interface, no way to reuse it for a different sensor or a different application.
Thoughts in Motion is the generalization of that script. Same job, but with the knobs exposed and the application-specific parts taken out.
The project splits across two repositories. A Python acquisition and inference app turns raw sensor
streams into predictions, and a 3D front-end consumes them. Between the two sits a plain socket. That
seam is the whole design: the neuroscience side and the game side can be rebuilt independently, as long
as numbers keep arriving on port 12345.
The pipeline
EEG / EMG headset
↓ LSL stream (pylsl: resolve_stream → StreamInlet)
Python + Tkinter app Main.py
↓ MNE: RawArray → notch + band-pass → fixed-length epochs
↓ scikit-learn model (loaded from a .joblib file) → predict()
↓ socket, localhost:12345, comma-separated values
Unity scene / Three.js scene
↓ SSVEP stimulus, doors, interactions
1 · Acquisition, the Python app
The entry point is a Tkinter config panel. You start by picking the kind of signal you want to work with, and everything after that adapts to your choice. Adding a GUI to a real-time acquisition loop is what forced multithreading into the project: starting, stopping and talking between threads is the part that costs, not the widgets.

The EEG path

- Headset and montage. Two lab headsets are supported, and picking one builds the matching MNE
Info. The Muse 2 gives 5 channels at 256 Hz (TP9, AF7, AF8, TP10plus an auxiliary). The Bitbrain Versatile 32 gives 32, snapped to thestandard_1020montage. Naming and positioning channels correctly is not cosmetic: an electrode’s location is what ties a measurement to a brain region, so a mislabelled montage quietly invalidates the analysis. - Resolve Stream finds the LSL stream on the network and opens an inlet. Preview Stream pulls one second, 256 samples, and dumps it as a dataframe. Fastest way to answer the only question that matters at that moment: is this real signal, or is an electrode floating?
- Filters. A notch against mains hum, which sits in the 45–50 Hz range in Europe, and a band-pass. Both optional, both entered as plain values, both applied to the live epoch rather than to a stored recording.
- From SciPy to MNE. The original script built its filters by hand with NumPy and SciPy. The rewrite moved to MNE, a library written specifically for EEG, which brings epoch segmentation and preprocessing as first-class operations instead of maths you re-derive each time.
- Model. Any classifier serialized with joblib can be dropped in by path. The app does not own the
model. You train it elsewhere, you point at the
.joblib, it loads. - Epoch duration sets the window fed to the classifier, and with it the latency of the whole
interface. MNE builds
make_fixed_length_epochsof that length, so every prediction costs exactly that much delay. - Launch Server opens the socket and starts pushing predictions to whoever connects.
Accuracy against latency
EEG has a poor signal-to-noise ratio, which pushes you toward heavier models. Deep networks read complex patterns better, and they also compete for the same machine that is filtering, segmenting and streaming in real time. Lighter machine-learning models cost far less and see less.
Leaving that arbitration to the user, rather than hard-coding a model, is the most defensible design decision in the project. The person who knows whether their application can afford 400 ms of inference is the person building the application.
The EMG path
Same acquisition skeleton, much simpler maths, because EMG has a far better signal-to-noise ratio than
EEG. Rectification is a numpy.abs away, and there is no model at all: a calibration instead. You
press Calibration, contract the muscle as hard as you can for five seconds, and the app keeps the
maximum value it saw. From then on you give it a ratio of that max, and anything below the resulting
threshold is sent as 0.
That single number is why the EMG path works on a different body on a different day. The interface is calibrated in percent of your own maximum, never in volts.
2 · Rendering, the Three.js scene
The interactive world exists twice: a Unity project, and a Three.js mirror of it that runs in a browser. The web version is the one you can show someone on the spot, with no build and no headset. It ships three cameras and a Cycle camera button: an exterior orbit view, and two viewpoints inside the ship.

GLTFLoader.Inside the first room, two red spheres stand in for the two BCI events. In the browser demo a raycast from the pointer triggers them, which is the honest way to demo a signal you cannot produce on command. Hovering a sphere plays the exact reaction that the classifier’s output plays in the real build.
Sphere 1, SSVEP. Steady-state visually evoked potentials: stare at something flickering at a fixed frequency and your visual cortex starts oscillating at that same frequency, which a decoder can pick out of the spectrum. Trigger it and the room’s two ceiling lights begin blinking on two different periods, 67 ms and 100 ms. Two distinguishable stimuli, so the classifier can tell which one you were looking at rather than only that you were looking.
Sphere 2, ERD/ERS. Event-related desynchronization and synchronization: imagining a movement, without performing it, drops the power of the sensorimotor µ and β rhythms. No stimulus needed, since the intent comes from you and not from the screen. Trigger it and the bulkhead door slides open.

ERD/ERS are detected, doors are opening. Two HUD corners, one per decoder, so you can always see why nothing moved.Running both signatures side by side is what the word “hybrid” is doing here. SSVEP is fast and reliable but needs you to be looking at the stimulus. Motor imagery is slower and noisier but works with your eyes anywhere. Alone, neither makes a comfortable interface.
3 · Hardware


The lab cap is accurate and slow. Gel, impedance checks, twenty minutes before anything happens, and a subject who cannot move much. The printed frame that preceded it traded signal quality per electrode for setup time: positions stay reproducible between sessions and you are running in a couple of minutes. Both answers are defensible, and which one you want depends entirely on whether your bottleneck is precision or the number of sessions you can fit in a day.
The Muse 2 sits at the far end of that scale, with 5 channels and a consumer price. Supporting both it and the Bitbrain is the point of the tool: the same GUI, the same pipeline, whatever hardware the lab or the practitioner happens to own.
4 · What it was tested on
Two Unity applications validated the platform end to end.
The first is a 2D platformer driven by EMG, where the player contracts a bicep to make the character jump. It is my own project, and it served as one of the two validation cases in Hugo’s thesis: contractions were interpreted correctly and reached Unity with low enough latency for the gameplay to stay responsive.
The second is an EEG reflex game: the player shoots at targets by blinking one eye. Different signal, different decoder, same path from headset to game.
Limits, and what would come next
The thesis is clear-eyed about what is missing. Tkinter was the fastest way to get an interface, and
it is also the reason the project carries so much threading complexity; PyQt or Kivy would cost
less to maintain and look like software from this decade. Preprocessing stays basic, with spatial
filtering the obvious next addition for EEG. Support for ECG and EDA sensors would widen the
range of applications. And training models inside the GUI, rather than importing a .joblib from
elsewhere, would close the loop.
My part in it
Small, and worth being precise about. Hugo Devoille built the core: turning raw EEG into a usable signal. That is the hard half of a brain-computer interface, and the half that took a researcher’s expertise. I worked on the other side, making the decoder’s output mean something inside the 3D scene, and I ran test sessions wearing the Bitbrain Versatile 32. Most of the time I was the subject rather than the author.
The one concrete thing I can point to is the EMG platformer: I built it on top of this platform, and it ended up as one of the two applications used to validate the project in Hugo’s thesis.
The project stays on this site anyway, because it is where the rest of my work starts. Hugo introduced me to neuroscience through it, and both the EMG game controller and my Master’s thesis grew out of that. I would rather credit him properly than inflate my share.
Team project. The repository belongs to Hugo Devoille (linked above), and the Three.js visualization lives in a companion repo. Technical details on this page are drawn from his Master’s thesis, Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction (2024).
Überblick
Thoughts in Motion lässt eine Person auf eine virtuelle Welt einwirken, ohne etwas zu berühren. Das System liest zwei verschiedene Biosignale, EEG aus dem Gehirn und EMG aus den Muskeln, und innerhalb des EEG dekodiert es zwei verschiedene Signaturen: eine, die davon abhängt, was du ansiehst, und eine, die davon abhängt, was du dir vorstellst zu tun.
Es ist außerdem, und vor allem, ein Werkzeug. Aus dem Projekt wurde Hugo Devoilles Masterarbeit (Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction, November 2024, betreut von Dr. Xiao Xiao in der Human-Learning-Gruppe), und ihr erklärtes Ziel ist es, die Einstiegshürde zu senken: eine Open-Source-Plattform, die Biosignale abholt, verarbeitet und zwischen Programmen weitergibt, damit eine Designerin ein EEG-Headset nutzen kann, ohne vorher Signalverarbeitungsingenieurin zu werden.

SSVEP is now Online.Woher es kommt
Die erste Fassung war ein einzelnes Python-Skript, geschrieben für ein bestimmtes VR-Experiment. Es holte Sensordaten über LSL, filterte sie, analysierte sie und schickte das Ergebnis an Unity, damit ein Blick auf das richtige Objekt eine Tür öffnete. Es funktionierte, und es war zugeschweißt: keine Optionen, keine Oberfläche, keine Möglichkeit, es für einen anderen Sensor oder eine andere Anwendung wiederzuverwenden.
Thoughts in Motion ist die Verallgemeinerung dieses Skripts. Dieselbe Aufgabe, aber mit freigelegten Stellschrauben und ohne die anwendungsspezifischen Teile.
Das Projekt verteilt sich auf zwei Repositories. Eine Python-Anwendung für Erfassung und Inferenz verwandelt rohe Sensorströme in Vorhersagen, ein 3D-Frontend konsumiert sie. Dazwischen liegt ein schlichter Socket. Diese Naht ist das ganze Design: die Neurowissenschaftsseite und die Spielseite lassen sich unabhängig neu bauen, solange weiterhin Zahlen auf Port 12345 ankommen.
Die Pipeline
EEG-/EMG-Headset
↓ LSL-Stream (pylsl: resolve_stream → StreamInlet)
Python- und Tkinter-App Main.py
↓ MNE: RawArray → Notch + Bandpass → Epochen fester Länge
↓ scikit-learn-Modell (aus einer .joblib geladen) → predict()
↓ Socket, localhost:12345, kommagetrennte Werte
Unity-Szene / Three.js-Szene
↓ SSVEP-Stimulus, Türen, Interaktionen
1 · Erfassung, die Python-App
Der Einstieg ist ein Tkinter-Konfigurationspanel. Zuerst wählst du die Signalart, mit der du arbeiten willst, und alles Weitere passt sich dieser Wahl an. Eine Oberfläche zu einer Echtzeit-Erfassungsschleife hinzuzufügen ist das, was Multithreading ins Projekt gezwungen hat: Starten, Stoppen und die Kommunikation zwischen Threads kostet, nicht die Widgets.

Der EEG-Weg

- Headset und Montage. Zwei Labor-Headsets werden unterstützt, und die Auswahl baut das passende MNE-
Info. Das Muse 2 liefert 5 Kanäle bei 256 Hz (TP9, AF7, AF8, TP10plus einen Hilfskanal). Das Bitbrain Versatile 32 liefert 32, ausgerichtet auf diestandard_1020-Montage. Kanäle korrekt zu benennen und zu positionieren ist nicht kosmetisch: der Ort einer Elektrode verbindet eine Messung mit einer Hirnregion, eine falsch beschriftete Montage entwertet die Analyse also stillschweigend. - Resolve Stream findet den LSL-Stream im Netz und öffnet einen Inlet. Preview Stream zieht eine Sekunde, 256 Samples, und wirft sie als Dataframe aus. Der schnellste Weg zur einzigen Frage, die in dem Moment zählt: ist das echtes Signal, oder hängt eine Elektrode in der Luft?
- Filter. Ein Notch gegen Netzbrummen, das in Europa im Bereich 45–50 Hz liegt, und ein Bandpass. Beide optional, beide als schlichte Werte eingetragen, beide auf die laufende Epoche angewandt statt auf eine gespeicherte Aufnahme.
- Von SciPy zu MNE. Das ursprüngliche Skript baute seine Filter von Hand mit NumPy und SciPy. Die Neufassung wechselte zu MNE, einer eigens für EEG geschriebenen Bibliothek, die Epochen-Segmentierung und Vorverarbeitung als erstklassige Operationen mitbringt statt als Mathematik, die man jedes Mal neu herleitet.
- Modell. Jeder mit joblib serialisierte Klassifikator lässt sich per Pfad einhängen. Die App besitzt das Modell nicht. Du trainierst es anderswo, zeigst auf die
.joblib, sie wird geladen. - Epochendauer legt das Fenster fest, das an den Klassifikator geht, und damit die Latenz der gesamten Schnittstelle. MNE baut
make_fixed_length_epochsdieser Länge, also kostet jede Vorhersage genau diese Verzögerung. - Launch Server öffnet den Socket und schiebt Vorhersagen an alle, die sich verbinden.
Genauigkeit gegen Latenz
EEG hat ein schlechtes Signal-Rausch-Verhältnis, was zu schwereren Modellen drängt. Tiefe Netze lesen komplexe Muster besser, und sie konkurrieren zugleich um dieselbe Maschine, die in Echtzeit filtert, segmentiert und streamt. Leichtere Machine-Learning-Modelle kosten weit weniger und sehen weniger.
Diese Abwägung der Nutzerin zu überlassen, statt ein Modell festzuschreiben, ist die am besten verteidigbare Designentscheidung des Projekts. Wer weiß, ob die eigene Anwendung 400 ms Inferenz verkraftet, ist die Person, die sie baut.
Der EMG-Weg
Gleiches Erfassungsgerüst, deutlich einfachere Mathematik, denn EMG hat ein weit besseres Signal-Rausch-Verhältnis als EEG. Gleichrichtung ist ein numpy.abs entfernt, und es gibt gar kein Modell: stattdessen eine Kalibrierung. Du drückst Calibration, spannst den Muskel fünf Sekunden so stark wie möglich an, und die App merkt sich den höchsten gesehenen Wert. Danach gibst du ihr ein Verhältnis dieses Maximums, und alles unter der daraus folgenden Schwelle wird als 0 gesendet.
Diese eine Zahl ist der Grund, warum der EMG-Weg an einem anderen Tag an einem anderen Körper funktioniert. Kalibriert wird in Prozent des eigenen Maximums, nie in Volt.
2 · Darstellung, die Three.js-Szene
Die interaktive Welt existiert zweimal: als Unity-Projekt und als Three.js-Spiegelung, die im Browser läuft. Die Webfassung kann man spontan herzeigen, ohne Build und ohne Headset. Sie bringt drei Kameras und einen Cycle camera-Knopf mit: eine Außenansicht im Orbit und zwei Blickpunkte im Schiff.

GLTFLoader.Im ersten Raum stehen zwei rote Kugeln für die beiden BCI-Ereignisse. In der Browser-Demo löst ein Raycast vom Zeiger sie aus, was die ehrliche Art ist, ein Signal vorzuführen, das man nicht auf Kommando erzeugen kann. Über eine Kugel zu fahren spielt genau die Reaktion, die im echten Build die Ausgabe des Klassifikators spielt.
Kugel 1, SSVEP. Steady-State Visually Evoked Potentials: Starrst du auf etwas, das mit fester Frequenz flackert, beginnt dein visueller Kortex mit derselben Frequenz zu schwingen, was ein Dekoder aus dem Spektrum herausziehen kann. Löse sie aus, und die beiden Deckenleuchten blinken in zwei verschiedenen Perioden, 67 ms und 100 ms. Zwei unterscheidbare Reize, damit der Klassifikator sagen kann, welchen du angesehen hast, und nicht nur, dass du hingesehen hast.
Kugel 2, ERD/ERS. Ereigniskorrelierte Desynchronisation und Synchronisation: eine Bewegung nur zu denken, ohne sie auszuführen, senkt die Leistung der sensomotorischen µ- und β-Rhythmen. Kein Reiz nötig, denn die Absicht kommt von dir und nicht vom Bildschirm. Löse sie aus, und das Schott gleitet auf.

ERD/ERS are detected, doors are opening. Zwei HUD-Ecken, eine pro Dekoder, damit man immer sieht, warum sich nichts bewegt hat.Beide Signaturen nebeneinander laufen zu lassen, ist genau das, was „hybrid“ hier meint. SSVEP ist schnell und verlässlich, verlangt aber, dass du auf den Reiz schaust. Motorische Vorstellung ist langsamer und verrauschter, funktioniert aber, wohin deine Augen auch gehen. Allein ergibt keine von beiden eine bequeme Schnittstelle.
3 · Hardware


Die Laborhaube ist genau und langsam. Gel, Impedanzprüfung, zwanzig Minuten, bevor irgendetwas passiert, und eine Versuchsperson, die sich kaum bewegen darf. Das gedruckte Gestell davor tauschte Signalqualität pro Elektrode gegen Aufbauzeit: Positionen bleiben zwischen Sitzungen reproduzierbar, und man läuft nach wenigen Minuten. Beide Antworten sind vertretbar, und welche man will, hängt allein davon ab, ob der Engpass Präzision heißt oder die Zahl der Sitzungen pro Tag.
Das Muse 2 sitzt am anderen Ende dieser Skala, mit 5 Kanälen und Verbraucherpreis. Beides zu unterstützen ist der Sinn des Werkzeugs: dieselbe Oberfläche, dieselbe Pipeline, egal welche Hardware das Labor oder die Praktikerin gerade besitzt.
4 · Woran es getestet wurde
Zwei Unity-Anwendungen haben die Plattform von Anfang bis Ende validiert.
Die erste ist ein 2D-Platformer per EMG, bei dem die Spielerin den Bizeps anspannt, damit die Figur springt. Das ist mein eigenes Projekt, und es diente als einer der beiden Validierungsfälle in Hugos Masterarbeit: Kontraktionen wurden korrekt interpretiert und erreichten Unity mit niedriger genuger Latenz, damit das Spiel reaktionsfreudig blieb.
Die zweite ist ein EEG-Reflexspiel: die Spielerin schießt auf Ziele, indem sie ein Auge zukneift. Anderes Signal, anderer Dekoder, derselbe Weg vom Headset ins Spiel.
Grenzen, und was als Nächstes käme
Die Arbeit benennt klar, was fehlt. Tkinter war der schnellste Weg zu einer Oberfläche, und es ist zugleich der Grund für die viele Threading-Komplexität; PyQt oder Kivy wären billiger im Unterhalt und sähen nach Software aus diesem Jahrzehnt aus. Die Vorverarbeitung bleibt einfach, räumliche Filterung ist die naheliegende nächste Ergänzung für EEG. Unterstützung für EKG- und EDA-Sensoren würde den Anwendungsbereich erweitern. Und Modelle in der Oberfläche zu trainieren, statt eine .joblib von außen zu importieren, würde den Kreis schließen.
Mein Anteil daran
Klein, und das sollte präzise stehen. Hugo Devoille hat den Kern gebaut: rohes EEG in ein brauchbares Signal zu verwandeln. Das ist die schwere Hälfte einer Gehirn-Computer-Schnittstelle, und die, für die es die Expertise eines Forschers brauchte. Ich habe auf der anderen Seite gearbeitet, damit die Ausgabe des Dekoders in der 3D-Szene etwas bedeutet, und ich habe Testsitzungen mit der Bitbrain Versatile 32 durchgeführt. Meistens war ich eher Proband als Autor.
Das eine Konkrete, auf das ich zeigen kann, ist der EMG-Platformer: ich habe ihn auf dieser Plattform gebaut, und er wurde zu einer der beiden Anwendungen, mit denen das Projekt in Hugos Arbeit validiert wurde.
Das Projekt bleibt trotzdem auf dieser Seite, denn dort beginnt meine übrige Arbeit. Hugo hat mich darüber an die Neurowissenschaft herangeführt, und sowohl der EMG-Spielcontroller als auch meine Masterarbeit sind daraus entstanden. Ich nenne lieber seinen Anteil richtig, als meinen aufzublasen.
Teamprojekt. Das Repository gehört Hugo Devoille (oben verlinkt), die Three.js-Visualisierung liegt in einem eigenen Repo. Die technischen Angaben auf dieser Seite stammen aus seiner Masterarbeit Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction (2024).
Vue d’ensemble
Thoughts in Motion permet d’agir sur un monde virtuel sans rien toucher. Le système lit deux biosignaux différents, l’EEG venu du cerveau et l’EMG venu des muscles, et à l’intérieur de l’EEG il décode deux signatures distinctes : l’une dictée par ce que tu regardes, l’autre par ce que tu imagines faire.
C’est aussi, et surtout, un outil. Le projet est devenu le mémoire de master d’Hugo Devoille (Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction, novembre 2024, encadré par Dr. Xiao Xiao au sein du groupe Human Learning), et son objectif affiché est d’abaisser la barrière d’entrée : une plateforme open source qui récupère, traite et transmet des biosignaux entre logiciels, pour qu’un designer puisse utiliser un casque EEG sans devoir d’abord devenir ingénieur en traitement du signal.

SSVEP is now Online.D’où ça vient
La première version était un unique script Python, écrit pour une expérience VR précise. Il tirait les données des capteurs via LSL, les filtrait, les analysait, et envoyait le résultat à Unity pour que regarder le bon élément ouvre une porte. Ça marchait, et c’était soudé : aucune option, aucune interface, aucun moyen de le réutiliser pour un autre capteur ou une autre application.
Thoughts in Motion est la généralisation de ce script. Le même travail, mais avec les réglages exposés et les parties spécifiques à une application retirées.
Le projet se répartit sur deux dépôts. Une application Python d’acquisition et d’inférence transforme les flux bruts en prédictions, et un front-end 3D les consomme. Entre les deux, une simple socket. Cette jointure est tout le design : le côté neurosciences et le côté jeu peuvent être reconstruits séparément, tant que des nombres continuent d’arriver sur le port 12345.
Le pipeline
Casque EEG / EMG
↓ flux LSL (pylsl : resolve_stream → StreamInlet)
Application Python + Tkinter Main.py
↓ MNE : RawArray → notch + passe-bande → epochs de longueur fixe
↓ modèle scikit-learn (chargé depuis un .joblib) → predict()
↓ socket, localhost:12345, valeurs séparées par des virgules
Scène Unity / scène Three.js
↓ stimulus SSVEP, portes, interactions
1 · L’acquisition, l’application Python
Le point d’entrée est un panneau de configuration Tkinter. On commence par choisir le type de signal sur lequel on veut travailler, et tout le reste s’adapte à ce choix. Ajouter une interface graphique à une boucle d’acquisition temps réel est ce qui a imposé le multithreading au projet : démarrer, arrêter et faire dialoguer les threads, voilà ce qui coûte, pas les widgets.

La voie EEG

- Casque et montage. Deux casques du labo sont pris en charge, et en choisir un construit l’
InfoMNE correspondante. Le Muse 2 donne 5 canaux à 256 Hz (TP9, AF7, AF8, TP10plus un auxiliaire). Le Bitbrain Versatile 32 en donne 32, calés sur le montagestandard_1020. Nommer et positionner correctement les canaux n’a rien de cosmétique : l’emplacement d’une électrode est ce qui relie une mesure à une région du cerveau, donc un montage mal étiqueté invalide l’analyse en silence. - Resolve Stream trouve le flux LSL sur le réseau et ouvre une entrée. Preview Stream tire une seconde, 256 échantillons, et l’affiche en dataframe. C’est le moyen le plus rapide de répondre à la seule question qui compte à cet instant : est-ce du vrai signal, ou une électrode qui flotte ?
- Filtres. Un notch contre le ronflement du secteur, qui se situe entre 45 et 50 Hz en Europe, et un passe-bande. Optionnels tous les deux, saisis en valeurs simples, appliqués à l’epoch en direct plutôt qu’à un enregistrement stocké.
- De SciPy à MNE. Le script d’origine construisait ses filtres à la main avec NumPy et SciPy. La réécriture est passée à MNE, une bibliothèque écrite spécifiquement pour l’EEG, qui offre la segmentation en epochs et le prétraitement comme opérations de première classe au lieu de mathématiques à redémontrer chaque fois.
- Modèle. N’importe quel classifieur sérialisé avec joblib peut être déposé par chemin. L’application ne possède pas le modèle : tu l’entraînes ailleurs, tu pointes vers le
.joblib, il se charge. - Durée d’epoch définit la fenêtre envoyée au classifieur, et avec elle la latence de toute l’interface. MNE construit des
make_fixed_length_epochsde cette longueur, donc chaque prédiction coûte exactement ce délai. - Launch Server ouvre la socket et commence à pousser les prédictions vers qui se connecte.
La précision contre la latence
L’EEG a un mauvais rapport signal sur bruit, ce qui pousse vers des modèles plus lourds. Les réseaux profonds lisent mieux les motifs complexes, et ils se disputent aussi la machine qui filtre, segmente et diffuse en temps réel. Les modèles d’apprentissage classiques coûtent beaucoup moins et voient moins.
Laisser cet arbitrage à l’utilisateur, plutôt que de figer un modèle, est la décision de conception la plus défendable du projet. La personne qui sait si son application peut se permettre 400 ms d’inférence, c’est celle qui construit l’application.
La voie EMG
Même squelette d’acquisition, mathématiques bien plus simples, parce que l’EMG a un bien meilleur rapport signal sur bruit que l’EEG. Le redressement tient dans un numpy.abs, et il n’y a aucun modèle : un étalonnage à la place. Tu appuies sur Calibration, tu contractes le muscle aussi fort que possible pendant cinq secondes, et l’application retient la valeur maximale observée. Ensuite tu lui donnes un ratio de ce maximum, et tout ce qui passe sous le seuil obtenu est envoyé à 0.
Ce seul nombre est ce qui rend la voie EMG utilisable sur un autre corps un autre jour. L’interface est étalonnée en pourcentage de ton propre maximum, jamais en volts.
2 · Le rendu, la scène Three.js
Le monde interactif existe deux fois : un projet Unity, et un miroir Three.js qui tourne dans un navigateur. La version web est celle qu’on peut montrer sur-le-champ, sans build ni casque. Elle embarque trois caméras et un bouton Cycle camera : une vue extérieure en orbite, et deux points de vue à l’intérieur du vaisseau.

GLTFLoader.Dans la première salle, deux sphères rouges tiennent lieu des deux événements BCI. Dans la démo web, c’est un raycast depuis le pointeur qui les déclenche, ce qui est la façon honnête de montrer un signal qu’on ne peut pas produire sur commande. Survoler une sphère joue exactement la réaction que joue la sortie du classifieur dans la vraie build.
Sphère 1, SSVEP. Potentiels évoqués visuels stationnaires : fixe quelque chose qui clignote à une fréquence donnée et ton cortex visuel se met à osciller à cette même fréquence, qu’un décodeur peut alors extraire du spectre. Déclenche-la et les deux plafonniers de la salle se mettent à clignoter sur deux périodes différentes, 67 ms et 100 ms. Deux stimuli distinguables, si bien que le classifieur peut dire lequel tu regardais, et pas seulement que tu regardais.
Sphère 2, ERD/ERS. Désynchronisation et synchronisation liées à l’événement : imaginer un mouvement, sans l’exécuter, fait chuter la puissance des rythmes sensorimoteurs µ et β. Aucun stimulus nécessaire, puisque l’intention vient de toi et non de l’écran. Déclenche-la et la porte du sas s’ouvre.

ERD/ERS are detected, doors are opening. Deux coins de HUD, un par décodeur, pour toujours voir pourquoi rien n'a bougé.Faire tourner les deux signatures côte à côte, c’est ce que le mot « hybride » recouvre ici. Le SSVEP est rapide et fiable mais exige que tu regardes le stimulus. L’imagerie motrice est plus lente et plus bruitée mais fonctionne où que se portent tes yeux. Seule, aucune des deux ne fait une interface confortable.
3 · Le matériel


Le bonnet de labo est précis et lent. Gel, vérification d’impédance, vingt minutes avant que quoi que ce soit se passe, et un sujet qui ne peut pas beaucoup bouger. Le cadre imprimé qui l’a précédé échangeait de la qualité de signal par électrode contre du temps d’installation : les positions restent reproductibles d’une séance à l’autre et on tourne en quelques minutes. Les deux réponses se défendent, et le choix dépend entièrement de savoir si ton goulot d’étranglement est la précision ou le nombre de séances que tu peux caser dans une journée.
Le Muse 2 occupe l’autre extrémité de cette échelle : 5 canaux, prix grand public. Prendre en charge les deux est justement le but de l’outil : la même interface, le même pipeline, quel que soit le matériel dont dispose le labo ou le praticien.
4 · Ce sur quoi il a été testé
Deux applications Unity ont validé la plateforme de bout en bout.
La première est un plateformer 2D piloté à l’EMG, où le joueur contracte son biceps pour faire sauter le personnage. C’est mon propre projet, et il a servi de l’un des deux cas de validation du mémoire d’Hugo : les contractions étaient interprétées correctement et atteignaient Unity avec une latence assez basse pour que le gameplay reste réactif.
La seconde est un jeu de réflexes à l’EEG : le joueur tire sur des cibles en clignant d’un œil. Autre signal, autre décodeur, même trajet du casque jusqu’au jeu.
Limites, et la suite
Le mémoire est lucide sur ce qui manque. Tkinter était le chemin le plus rapide vers une interface, et c’est aussi la raison pour laquelle le projet traîne autant de complexité de threading ; PyQt ou Kivy coûteraient moins cher à maintenir et ressembleraient à du logiciel de cette décennie. Le prétraitement reste basique, avec le filtrage spatial comme ajout évident côté EEG. La prise en charge de capteurs ECG et EDA élargirait le champ d’application. Et entraîner les modèles depuis l’interface, plutôt qu’importer un .joblib venu d’ailleurs, refermerait la boucle.
Ma part dans le projet
Modeste, et autant être précis. Hugo Devoille a construit le cœur : transformer l’EEG brut en signal exploitable. C’est la moitié difficile d’une interface cerveau-machine, et celle qui demandait une expertise de chercheur. J’ai travaillé de l’autre côté, à faire en sorte que la sortie du décodeur veuille dire quelque chose dans la scène 3D, et j’ai mené des séances de test en portant le Bitbrain Versatile 32. La plupart du temps, j’étais le sujet plutôt que l’auteur.
La seule chose concrète que je peux montrer, c’est le plateformer EMG : je l’ai construit sur cette plateforme, et il a fini comme l’une des deux applications utilisées pour valider le projet dans le mémoire d’Hugo.
Le projet reste sur ce site quand même, parce que c’est de là que part tout le reste de mon travail. C’est par lui qu’Hugo m’a fait découvrir les neurosciences, et le contrôleur EMG comme mon mémoire de master en sont sortis. Je préfère le créditer correctement plutôt que gonfler ma part.
Projet d’équipe. Le dépôt appartient à Hugo Devoille (lien ci-dessus), et la visualisation Three.js vit dans un dépôt compagnon. Les détails techniques de cette page proviennent de son mémoire de master, Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction (2024).
概要
Thoughts in Motion は、何にも触れずに仮想世界へ働きかけるためのシステムです。脳からの EEG と筋肉からの EMG という2種類の生体信号を読み取り、さらに EEG の中でも2つの異なるシグネチャを解読します。ひとつは「何を見ているか」に、もうひとつは「何をしようと想像しているか」に由来するものです。
そして何より、これはツールです。このプロジェクトは Hugo Devoille の修士論文(Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction、2024年11月、Human Learning グループの Dr. Xiao Xiao 指導)になりました。掲げられた目的は参入障壁を下げることです。生体信号を取得し、処理し、ソフトウェア間で受け渡すオープンソースの基盤があれば、デザイナーは信号処理エンジニアにならなくても EEG ヘッドセットを使えます。

SSVEP is now Online に切り替わります。出発点
最初の版は、ひとつの VR 実験のために書かれた単一の Python スクリプトでした。LSL でセンサーデータを取得し、フィルタをかけ、解析し、結果を Unity へ送って、正しい対象を見ればドアが開くようにする。動きはしましたが、溶接されたような作りでした。オプションもインターフェースもなく、別のセンサーや別の用途に転用する手立てもありません。
Thoughts in Motion はそのスクリプトを一般化したものです。仕事の中身は同じで、つまみが表に出され、特定用途に依存する部分が取り除かれています。
プロジェクトは2つのリポジトリに分かれています。Python の取得・推論アプリが生のセンサーストリームを予測に変え、3D フロントエンドがそれを受け取ります。あいだにあるのは素朴なソケットひとつ。この継ぎ目こそが設計の核心です。ポート 12345 に数値が届き続けるかぎり、神経科学側とゲーム側はそれぞれ独立に作り直せます。
パイプライン
EEG / EMG ヘッドセット
↓ LSL ストリーム(pylsl: resolve_stream → StreamInlet)
Python + Tkinter アプリ Main.py
↓ MNE: RawArray → ノッチ + バンドパス → 固定長エポック
↓ scikit-learn モデル(.joblib から読み込み)→ predict()
↓ ソケット、localhost:12345、カンマ区切りの値
Unity シーン / Three.js シーン
↓ SSVEP 刺激、扉、インタラクション
1 · 取得、Python アプリ
入口は Tkinter の設定パネルです。まず扱いたい信号の種類を選び、そのあとの画面はその選択に合わせて組み替わります。リアルタイムの取得ループに GUI を足したことが、このプロジェクトに マルチスレッド を持ち込みました。手間がかかるのはウィジェットではなく、スレッドの開始・停止・相互通信のほうです。

EEG の経路

- ヘッドセットとモンタージュ。 研究室の2機種に対応しており、選ぶと対応する MNE の
Infoが構築されます。Muse 2 は 256 Hz で5チャンネル(TP9, AF7, AF8, TP10と補助1つ)。Bitbrain Versatile 32 は 32 チャンネルで、standard_1020モンタージュに合わせます。チャンネルの命名と配置を正しく行うのは見た目の問題ではありません。電極の位置こそが計測値と脳部位を結びつけるので、ラベルを取り違えたモンタージュは解析を静かに無効化します。 - Resolve Stream がネットワーク上の LSL ストリームを見つけ、インレットを開きます。Preview Stream は 1 秒分、256 サンプルを引いてデータフレームとして吐き出します。その瞬間に本当に知りたい問い、「これは本物の信号か、それとも電極が浮いているのか」に最速で答える方法です。
- フィルタ。 電源ハム用のノッチ(ヨーロッパでは 45〜50 Hz 帯)とバンドパス。どちらも任意で、素の数値を入力し、保存済みの記録ではなく、いま流れているエポックに適用されます。
- SciPy から MNE へ。 元のスクリプトは NumPy と SciPy でフィルタを手作りしていました。書き直しでは MNE に移行しています。EEG のために書かれたライブラリで、エポック分割も前処理も、毎回導出し直す数式ではなく一級の操作として備わっています。
- モデル。 joblib でシリアライズした分類器なら、パスを指定して差し込めます。アプリはモデルを所有しません。学習はよそで行い、
.joblibを指せば読み込まれます。 - エポック長は分類器へ渡す窓の長さを決め、それがそのままインターフェース全体のレイテンシになります。MNE がその長さで
make_fixed_length_epochsを作るので、予測1回のコストはちょうどその遅延ぶんです。 - Launch Server がソケットを開き、接続してきた相手へ予測を送り始めます。
精度とレイテンシの綱引き
EEG は信号対雑音比が低く、そのぶん重いモデルへ引き寄せられます。深層ネットワークは複雑なパターンをよく読みますが、同時にフィルタリング・分割・リアルタイム送信を行っている同じマシンを奪い合います。軽量な機械学習モデルははるかに安価で、そのぶん見えるものも少なくなります。
モデルを固定せず、この判断を利用者に委ねたことが、このプロジェクトでもっとも擁護しやすい設計判断です。自分のアプリが 400 ms の推論を許容できるかどうかを知っているのは、そのアプリを作っている本人だからです。
EMG の経路
取得の骨組みは同じで、数学はずっと単純です。EMG は EEG よりはるかに信号対雑音比が良いためです。整流は numpy.abs ひとつで済み、モデルは一切使いません。代わりにあるのがキャリブレーションです。Calibration を押し、5秒間できるかぎり強く筋肉を収縮させると、アプリはその間の最大値を保持します。以降はその最大値に対する比率を与え、そこから決まる閾値を下回るものは 0 として送られます。
この数値ひとつがあるおかげで、EMG の経路は別の日に別の人の体でも使えます。インターフェースの基準は自分の最大値に対する割合であって、ボルトではありません。
2 · 描画、Three.js のシーン
インタラクティブな世界は二重に存在します。Unity のプロジェクトと、ブラウザで動くその Three.js 版です。Web 版はビルドもヘッドセットも要らず、その場で人に見せられます。3つのカメラと Cycle camera ボタンを備えていて、外からの周回視点と、船内の2つの視点を切り替えられます。

GLTFLoader で読み込んでいます。最初の部屋には、2つの BCI イベントを代理する赤い球が2つ置かれています。ブラウザのデモではポインタからのレイキャストで発火させます。命じられて出せるような信号ではない以上、これが誠実なデモのやり方だと考えました。球にカーソルを重ねると、実機のビルドで分類器の出力が起こすのとまったく同じ反応が再生されます。
球1、SSVEP。 定常状態視覚誘発電位。一定の周波数で点滅するものを見つめると、視覚野が同じ周波数で振動し始め、デコーダーがそれをスペクトルから取り出せます。発火させると、部屋の天井灯2つが 67 ms と 100 ms という異なる周期で点滅を始めます。区別できる刺激が2つあるので、分類器は「見ていた」ことだけでなく「どちらを見ていたか」まで言えます。
球2、ERD/ERS。 事象関連脱同期・同期。動作を実行せずに想像するだけで、感覚運動野の µ 波と β 波のパワーが下がります。意図は画面ではなく本人から来るので、刺激は不要です。発火させると隔壁の扉が開きます。

ERD/ERS are detected, doors are opening。HUD は左右2隅にデコーダー1つずつ。なぜ何も動かないのかが常に見えます。この2つのシグネチャを並走させることこそ、ここでの「ハイブリッド」の意味です。SSVEP は速くて安定していますが、刺激を見ている必要があります。運動イメージは遅く、ノイズも多い代わりに、目をどこに向けていても機能します。片方だけでは、快適なインターフェースにはなりません。
3 · ハードウェア


研究室のキャップは正確ですが遅いです。ジェルを塗り、インピーダンスを確認し、何かが始まるまでに20分。しかも被験者はあまり動けません。その前に使っていたプリント製フレームは、電極1本あたりの信号品質を犠牲にして準備時間を買っていました。位置がセッション間で再現され、数分で計測に入れます。どちらの答えにも理があり、選択は「ボトルネックが精度なのか、1日に回せるセッション数なのか」だけで決まります。
Muse 2 はその尺度のもう一方の端にあります。5チャンネル、コンシューマー価格。両方をサポートすることこそがこのツールの狙いです。研究室や実務者が手元に持っているハードが何であれ、同じ GUI、同じパイプラインで動きます。
4 · 何で検証したか
2つの Unity アプリケーションが、この基盤を端から端まで検証しました。
ひとつは EMG で動く 2D プラットフォーマーで、プレイヤーが上腕二頭筋に力を入れるとキャラクターがジャンプします。これは私自身のプロジェクトで、Hugo の修士論文における2つの検証事例のひとつになりました。収縮は正しく解釈され、ゲームプレイの反応が損なわれない程度に低い遅延で Unity に届いています。
もうひとつは EEG の反射ゲームです。片目をつぶることで的を撃ちます。信号もデコーダーも違いますが、ヘッドセットからゲームまでの道筋は同じです。
限界と、その先
論文は足りないところをはっきり書いています。Tkinter はインターフェースを用意する最短経路でしたが、同時にこのプロジェクトが抱えるスレッド周りの複雑さの原因でもあります。PyQt や Kivy なら保守は安く済み、見た目もこの十年のソフトウェアらしくなるでしょう。前処理は基本的なままで、EEG には 空間フィルタ の追加が順当な次の一手です。ECG や EDA センサーへの対応は用途を広げます。そして .joblib を外から持ち込むのではなく GUI 内でモデルを学習できるようになれば、輪が閉じます。
このプロジェクトでの私の役割
小さな役割でしたので、正確に書いておきます。中核を作ったのは Hugo Devoille です。生の EEG を使える信号に変換する部分、つまりブレイン・コンピュータ・インターフェースの難しいほうの半分であり、研究者としての専門性が必要な領域を担当しました。私はその反対側で、デコーダーの出力が 3D シーンの中で意味を持つようにする作業を行い、Bitbrain Versatile 32 をかぶってテストセッションを重ねました。多くの時間、私は制作者というより被験者でした。
具体的に示せるものがひとつあります。EMG のプラットフォーマーです。この基盤の上に自分で作ったもので、結果として Hugo の論文でプロジェクトを検証する2つのアプリケーションのうちの1つになりました。
それでもこのプロジェクトをサイトに残しているのは、私のその後の仕事がすべてここから始まっているからです。Hugo はこの企画を通じて私に神経科学を教えてくれました。EMG のゲームコントローラーも修士論文も、そこから育ったものです。自分の取り分を大きく見せるより、彼の貢献を正しく書き残したいと思います。
チームプロジェクトです。リポジトリは Hugo Devoille のもの(上にリンク)で、Three.js の可視化は別リポジトリにあります。このページの技術的な記述は、彼の修士論文 Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction(2024)に基づいています。
概览
Thoughts in Motion 让人不接触任何东西也能作用于虚拟世界。它读取两种不同的生物信号,来自大脑的 EEG 和来自肌肉的 EMG;在 EEG 内部又解码两种不同的特征,一种取决于你在看什么,另一种取决于你在想象做什么。
它更是一件工具。这个项目最终成为 Hugo Devoille 的硕士论文(Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction,2024 年 11 月,由 Human Learning 组的 Dr. Xiao Xiao 指导)。它明确的目标是降低门槛:一个开源平台,负责采集、处理并在软件之间传递生物信号,让设计师不必先成为信号处理工程师,也能用上 EEG 设备。

SSVEP is now Online。它从哪里来
最初的版本只是一个 Python 脚本,为某一个特定的 VR 实验而写。它通过 LSL 拉取传感器数据,滤波、分析,再把结果送给 Unity,好让「看向正确的物体」能打开一扇门。它能用,但它是焊死的:没有选项,没有界面,也没法换个传感器或换个应用继续复用。
Thoughts in Motion 就是这个脚本的一般化。做的事情一样,但旋钮被暴露出来,与具体应用绑定的部分被拆掉了。
项目分在两个仓库里。一个 Python 采集与推理程序把原始传感器流变成预测,一个 3D 前端消费这些预测。两者之间是一个普通的套接字。这道接缝就是整个设计:只要数字持续到达 12345 端口,神经科学那侧和游戏那侧就可以各自重写。
流水线
EEG / EMG 头戴设备
↓ LSL 串流(pylsl:resolve_stream → StreamInlet)
Python + Tkinter 应用 Main.py
↓ MNE:RawArray → 陷波 + 带通 → 定长 epoch
↓ scikit-learn 模型(从 .joblib 加载)→ predict()
↓ 套接字,localhost:12345,逗号分隔的数值
Unity 场景 / Three.js 场景
↓ SSVEP 刺激、舱门、交互
1 · 采集,Python 程序
入口是一个 Tkinter 配置面板。你先选择要处理的信号类型,之后的一切都会按这个选择重新组织。给一个实时采集循环加上图形界面,正是多线程被迫进入这个项目的原因:真正花力气的是线程的启动、停止和相互通信,而不是那些控件。

EEG 路径

- 设备与导联。 支持实验室的两款设备,选中之后会构建对应的 MNE
Info。Muse 2 提供 5 个通道、256 Hz(TP9, AF7, AF8, TP10加一个辅助通道)。Bitbrain Versatile 32 提供 32 个通道,对齐到standard_1020导联体系。正确命名和定位通道不是装饰性的工作:电极的位置正是把一次测量与某个脑区联系起来的东西,所以贴错标签的导联会悄无声息地让整个分析失效。 - Resolve Stream 在网络上找到 LSL 流并打开一个 inlet。Preview Stream 拉取一秒、256 个采样点,以 dataframe 的形式打印出来。这是回答当下唯一要紧问题的最快方式:这是真信号,还是有个电极虚接了?
- 滤波。 一个针对市电工频的陷波(在欧洲落在 45–50 Hz),加一个带通。两者都是可选的,直接填数值,作用于正在流动的 epoch,而不是已存下来的记录。
- 从 SciPy 到 MNE。 最初的脚本用 NumPy 和 SciPy 手工搭滤波器。重写之后改用 MNE,一个专为 EEG 而写的库,把 epoch 分段和预处理做成一等操作,而不是每次都要重新推导的数学。
- 模型。 任何用 joblib 序列化的分类器都能按路径挂进来。程序并不拥有模型:你在别处训练,指向那个
.joblib,它就会被加载。 - epoch 时长决定送进分类器的窗口,也就决定了整个界面的延迟。MNE 按这个长度构建
make_fixed_length_epochs,因此每次预测的代价恰好就是这段延迟。 - Launch Server 打开套接字,开始把预测推给任何连上来的客户端。
精度与延迟的取舍
EEG 的信噪比很差,这会把人推向更重的模型。深度网络更善于读出复杂模式,同时也在和那台正在实时滤波、分段、传输的机器抢资源。较轻的机器学习模型代价小得多,看到的东西也少。
把这个取舍留给使用者,而不是把某个模型写死,是这个项目里最经得起辩护的设计决定。知道自己的应用能不能承受 400 毫秒推理的,正是在做这个应用的人。
EMG 路径
采集骨架相同,数学简单得多,因为 EMG 的信噪比远好于 EEG。整流只差一个 numpy.abs,而且完全不用模型,取而代之的是标定。按下 Calibration,用尽全力收缩肌肉五秒,程序会记住这期间看到的最大值。此后你给它一个该最大值的比例,低于由此得出的阈值的一切都会作为 0 发送。
正是这一个数字,让 EMG 路径能在另一天、另一个人身上照样工作。界面标定的单位是占你自己最大值的百分比,而不是伏特。
2 · 渲染,Three.js 场景
这个交互世界存在两份:一份 Unity 项目,一份在浏览器里跑的 Three.js 镜像。网页版是可以随时给人看的那一份,不需要构建,也不需要头戴设备。它带三个相机和一个 Cycle camera 按钮:一个外部环绕视角,两个船内视角。

GLTFLoader 加载。第一个房间里有两颗红球,代表两种 BCI 事件。在浏览器演示中,它们由指针射线触发——面对一种无法按指令产生的信号,这是诚实的演示做法。把光标移到球上,播放的正是真实构建里分类器输出所触发的同一段反应。
红球 1,SSVEP。 稳态视觉诱发电位:盯着以固定频率闪烁的东西看,你的视觉皮层就会以同样的频率振荡,解码器可以把它从频谱里提取出来。触发之后,房间里两盏顶灯会以两种不同周期闪烁,67 毫秒和 100 毫秒。两个可区分的刺激,于是分类器不仅知道你在看,还能说出你在看哪一个。
红球 2,ERD/ERS。 事件相关去同步与同步:只是想象一个动作而不真的做出来,感觉运动皮层的 µ 波和 β 波功率就会下降。不需要任何刺激,因为意图来自你而不是屏幕。触发之后,隔舱门会滑开。

ERD/ERS are detected, doors are opening。HUD 两角各一个解码器状态,因此你随时能看出为什么什么都没动。让这两种特征并行运行,正是「混合」在这里的含义。SSVEP 快而可靠,但要求你正看着刺激。运动想象更慢、噪声更大,却不管你的眼睛看向哪里都能工作。单独任何一种,都做不出用起来舒服的界面。
3 · 硬件


实验室脑电帽准确但慢。涂凝胶、测阻抗,二十分钟之后才可能有动静,而且被试几乎不能乱动。在它之前的打印框架,用单个电极的信号质量换取了准备时间:位置在不同场次之间保持可复现,几分钟就能开跑。两种答案都站得住,选哪一种完全取决于你的瓶颈是精度,还是一天能跑多少场。
Muse 2 位于这把尺子的另一端:5 个通道,消费级价格。同时支持两者恰恰是这件工具的意义所在——同一套界面、同一条流水线,无论实验室或使用者手上是什么硬件。
4 · 用什么验证的
两个 Unity 应用把这个平台从头到尾验证了一遍。
第一个是由 EMG 驱动的 2D 平台跳跃游戏,玩家收缩二头肌让角色起跳。这是我自己的项目,也成了 Hugo 论文里两个验证案例之一:收缩被正确识别,并以足够低的延迟送达 Unity,游戏手感因此保持了灵敏。
第二个是基于 EEG 的反应游戏:玩家眨一只眼来射击目标。信号不同,解码器不同,但从头戴设备到游戏的路径是同一条。
局限,以及接下来该做什么
论文很清楚地写明了缺什么。Tkinter 是做出界面最快的路,同时也是这个项目背负大量线程复杂度的原因;换成 PyQt 或 Kivy,维护成本更低,也更像这个年代的软件。预处理仍然基础,对 EEG 来说空间滤波是顺理成章的下一步。支持 ECG 和 EDA 传感器则会拓宽应用范围。而如果能在界面内训练模型,而不是从外面导入一个 .joblib,这个闭环就补上了。
我在其中的角色
不大,所以更值得写清楚。核心部分由 Hugo Devoille 完成:把原始 EEG 转成可用信号。那是脑机接口里更难的那一半,也是需要研究者专业积累的那一半。我做的是另一侧的工作,让解码器的输出在 3D 场景里真正产生意义,并且戴着 Bitbrain Versatile 32 跑测试。大部分时间里,我是被试,而不是作者。
有一件具体的事可以指给别人看:那个 EMG 平台跳跃游戏。它是我在这个平台之上做出来的,最终成为 Hugo 论文中用来验证项目的两个应用之一。
即便如此,这个项目仍然留在站上,因为我后来的工作都从这里开始。Hugo 正是通过它把我带进神经科学,EMG 游戏手柄和我的硕士论文都由此生长出来。与其夸大自己的份额,我更愿意把他的贡献写准确。
团队项目。仓库属于 Hugo Devoille(上方链接),Three.js 可视化在配套仓库里。本页的技术细节取自他的硕士论文 Redefining Immersion: Olfactory and Biosignals integration in Human-Computer Interaction(2024)。