[Перевод] Реактивные мозговые волны: рассказ о Muse, JS и браузерах

Несколько месяцев назад я наткнулся на интеллектуальную ЭЭГ-гарнитуру с поддержкой Bluetooth и тут же увидел её потенциал в некоторых крайне интересных областях. А именно, эта гарнитура и Web Bluetooth вполне могли позволить напрямую связать мой мозг с веб-страницами.

omllnvb_1yxu2mvt49ec2jintxo.jpeg

ЭЭГ, или электроэнцефалография — это способ мониторинга электрической активности мозга. Обычно при ЭЭГ-исследовании на поверхности головы размещают несколько электродов, которые затем регистрируют электрические явления, сопутствующие работе мозга. Результаты регистрации записывают в виде электроэнцефалограмм. Мне идея поэкспериментировать с этими данными показалась очень интересной. Обычно ЭЭГ применяют в медицинских целях, но в последнее время стали появляться проекты, предусматривающие новые способы использования данных об электрической активности мозга.

Один из таких проектов — Muse. Его создатели продают, примерно за $250, систему, которая обучает медитации. В её состав входит отлично сделанная ЭЭГ-гарнитура, которая поддерживает Bluetooth. Хотя Muse должна обучать тому, как успокаивать разум, мой разум успокоился только после того, как я понял, как мне работать с данными от гарнитуры в веб-браузере.

(Если вы тоже сгораете от нетерпения это узнать — можете перейти сразу к коду).

Гарнитура умеет работать с приложениями для Android и iOS. Кроме того, имеется библиотека, используя которую, можно создать собственное приложение на основе данных, получаемых от гарнитуры. Однако, подходит она лишь для разработки нативных приложений, а её исходный код оказался закрытым (в результате моя мечта об управлении веб-страницами силой мысли, показалась, на первый взгляд, неосуществимой).

Когда я оказался на конференции ng-cruise, я встретил Алекса Кастилло. Он рассказывал о том, как подключил ЭЭГ-гарнитуру OpenBCI к Angular и визуализировал поступающие от неё сигналы. Эта гарнитура создана в рамках проекта с открытым аппаратным обеспечением. Хотя всё это выглядело весьма впечатляюще, ему пришлось использовать довольно сложную систему на базе Node.js и сервера на веб-сокетах для работы с данными от гарнитуры. А это было совсем не то, чего хотелось мне. Однако, позже, был ночной хакатон, где все пытались сделать что-нибудь интересное с разными железками, включая и ЭЭГ-гарнитуру. В результате, что совершенно естественно, я решил воспользоваться появившимся у меня шансом.

Я попытался отреверсить протокол Muse Bluetooth, примерно так же, как я сделал это с лампочкой Magic Blue. Провозившись примерно час, я понял, что кто-нибудь, возможно, уже это сделал. Я поискал по одному из обнаруженных мной номеров характеристик и нашёл эту отличную статью, через которую вышел на библиотеку для Python, которую написал Александр Барачант. В результате, совершенно неожиданно, у меня оказалось всё необходимое. Так появилась библиотека muse-js.

Теперь я мог подключаться к гарнитуре Muse из веб-приложений и работать с поступающими с неё ЭЭГ-данными (а также — считывать уровень заряда батареи, данные акселерометра, гироскопа, и так далее). Сейчас я расскажу о том, что сделал на основе этой библиотеки.

Аппаратное обеспечение


Прежде чем мы погрузимся в код, сначала немного лучше познакомимся с гарнитурой Muse. Она выполнена в виде миниатюрного оголовья со встроенными аккумуляторами. В Muse имеется четыре ЭЭГ-электрода. При надевании гарнитуры два из них находятся на лбу, чуть выше глаз, ещё два — в районе ушей. Кроме того, у неё есть гироскоп и акселерометр, что даёт возможность выяснить ориентацию головы того, кто носит гарнитуру. Мне очень понравилось то, что у Muse есть ещё один ЭЭГ-датчик, который можно подключить к собственному электроду (по Micro USB). В ближайшем будущем я планирую это опробовать.

Обратите внимание на то, что существует две версии этой гарнитуры. Одна — 2014-го года, вторая — 2016-го. Вам, безусловно, больше понравится версия 2016-го, которая использует Bluetooth Low Energy. Гарнитура 2014-го года использует для связи с внешним миром Classic Bluetooth, и, таким образом, её нельзя использовать с Web Bluetooth.

f68d335c4469e14c98883022ef6536c7.png
Гарнитура Muse образца 2016-го года. Электроды AF7 и AF8 расположены на лбу. Электроды TP9 и TP10 — около ушей

Реактивные потоки и RxJS


Когда я создавал библиотеку, мне нужно было выбрать способ представления входящих ЭЭГ-данных. При работе с Web Bluetooth всякий раз при получении нового пакета вызывается событие. Каждый пакет содержит 12 образцов измерений с одного электрода. Я мог бы предусмотреть регистрацию JavaScript-функции, которая вызывалась бы каждый раз при приёме новой порции данных, но я решил реализовать это с использованием библиотеки RxJS (Reactive Extensions Library for JavaScript). Она включает в себя методы для работы с потоками данных. Их можно трансформировать, создавать, из них можно извлекать то, что нужно.
Сильная сторона RxJS заключается в том, что библиотека предлагает набор функций, которые позволяют манипулировать потоками необработанных данных, получаемых от гарнитуры Muse и обрабатывать их, конвертируя в нечто более удобное для дальнейшей работы (чем мы скоро и займёмся).

Визуализация


Первое, что приходит в голову, когда думаешь о том, что можно сделать на базе новой библиотеки muse-js — это визуализация данных. В ходе ночного хакатона Алекс и я начали работать над проектом angular-muse. Это Angular-приложение, которое визуализирует данные ЭЭГ и показывает положение головы.

9e2a0b26905200f47e54a070699acd38.png
Мой первый прототип для визуализации данных Muse

На самом деле, если у вас есть Muse и браузер, который поддерживает Web Bluetooth, вы можете открыть эту страницу и попробовать всё самостоятельно.

3bb3266d544450fd48ee011af6074155.png
Визуализация электрической активности моего мозга с использованием Muse, Angular и Smoothie Charts

С помощью этого приложения можно увидеть, что данные с гарнитуры действительно попадают в браузер. Однако, честно говоря, хотя наблюдение за графиками может быть занятием интересным, вам оно, вероятно, довольно скоро надоест, если с данными больше ничего делать нельзя (интересно, однако, как эта потеря интереса отобразилась бы на графиках…).

В мгновение ока


Одна из операций, выполняемых в ходе ЭЭГ-диагностики — измерение электрического потенциала (вольтажа) в различных областях головы. Измеренный сигнал является побочным эффектом деятельности мозга, он может быть использован для выяснения общего состояния мозга (такого, как уровень концентрации, выявление неожиданных раздражителей, и так далее).

Помимо электроэнцефалографии, которая исследует деятельность мозга, существует такое понятие, как электроокулография (ЭОГ). Это — исследование, в ходе которого можно детектировать движения глаз (к счастью, моя девушка — специалист по коррекции зрения, она объяснила мне что к чему). У Muse есть два электрода, расположенных в районе лба, довольно близко к глазам. (Они называются AF7 и AF8 — в соответствии со стандартной системой позиционирования электродов при ЭЭГ »10–20%») Это даёт нам возможность отслеживать движения глаз.

2394d6c9dbbe02d04e290e6efcd80567.png
Человеческий глаз. Роговица, спереди, заряжена положительно; сетчатка, сзади, заряжена отрицательно

Мы будем использовать сигналы с электродов в нашем электроэнцефалографическом «Hello World» для выявления морганий путём отслеживания активности глаз.

Начало работы


Действовать будем так: берём поток ЭЭГ-данных, поступающих от гарнитуры (muse-js даёт доступ к этим данным как к наблюдаемому объекту RxJS). Затем отбираем только данные от нужного нам электрода, AF7, который находится над левым глазом, и ищем в сигнале пики, то есть, образцы, абсолютное значение которые превышает 500 мкВ, что говорит о большом изменении потенциала в моменты таких пиков. Так как электрод расположен около глаза, мы ожидаем, что движение глазного яблока будет генерировать большую разницу потенциалов.

Хотя это, возможно, и не самый точный метод определения момента моргания, в моём случае он подошёл очень хорошо. Кроме того, код оказался простым, таким, в котором легко было бы разобраться (всё это — признаки любого хорошего «Hello World»).

Прежде чем приступать к работе с данными ЭЭГ, установим muse-js:

npm install --save muse-js


Затем импортируем библиотеку в код. В данном случае речь идёт об Angular-приложении — обычном пустом проекте, созданном с помощью Angular CLI. Вы, если хотите, можете всё это воспроизвести с использованием React или Vue.js, так как в этом примере очень мало кода, который жёстко привязан к фреймворку.

Далее, импортируем muse-js в главный компонент приложения:

import { MuseClient, channelNames } from `muse-js`;


Класс MuseClient отвечает за взаимодействие с гарнитурой, а channelNames — это просто массив, используемый для приведения названий каналов к удобному для работы виду.

В компоненте создаём новый экземпляр MuseClient:

this.muse = new MuseClient();


Теперь вот кое-что посложнее: код для подключения к гарнитуре.

Web Bluetooth требует вмешательства пользователя для того, чтобы инициировать подключение. Поэтому нам надо добавить в компонент кнопку. Мы будем подключаться к гарнитуре только после того, как пользователь щёлкнет по кнопке. Логику подключения к гарнитуре реализуем в методе onConnectButtonClick:

async onConnectButtonClick() {
  await this.muse.connect();
  this.muse.start();
  // TODO: подписаться на данные с гарнитуры
}


Метод connect() объекта MuseClient инициирует соединение с гарнитурой. Затем метод start() передаёт на гарнитуру команду для начала сбора ЭЭГ-данных и передачи их в приложение.

18f2d19321b42dd5da946d5ce7be2c08.png
Подключение к гарнитуре Muse с использованием Web Bluetooth

Следующее, что надо сделать — организовать подписку на данные ЭЭГ, доступные в наблюдаемом объекте muse.eegReadings (это делается там, где в вышеприведённом коде расположен комментарий TODO):

  const leftEyeChannel = channelNames.indexOf('AF7');
  
  this.leftBlinks = this.muse.eegReadings
    .filter(r => r.electrode === leftEyeChannel)


Этот код принимает показатели ЭЭГ, полученные с устройства, и отбирает только те, которые имеют отношение к электроду AF7, который расположен над левым глазом. Каждый пакет содержит 12 образцов, в результате каждый элемент в наблюдаемом потоке — это объект со следующей структурой:

interface EEGReading {
  electrode: number;
  timestamp: number;
  samples: number[];
}


Переменная electrode будет содержать числовой индекс электрода (используйте массив channelNames для того, чтобы привести этот индекс к более понятному названию электрода), timestamp содержит время, относительно начала измерений, когда было сделано измерение, а samples — это массив, содержащий 12 чисел с плавающей точкой, каждый элемент которого содержит одно ЭЭГ-измерение, выраженное в микровольтах (мкВ).

На следующем шаге нам нужно отобрать максимальное значение из каждого пакета (то есть — измерение с максимальных выходным значением). Тут мы обработаем полученный поток с помощью оператор RxJS map для того, чтобы получить нужное значение:

  this.leftBlinks = this.muse.eegReadings
    .filter(r => r.electrode === leftEyeChannel)
    .map(r => Math.max(...r.samples.map(n => Math.abs(n))))


Теперь, так как у нас имеется поток обычных чисел, мы можем его отфильтровать и пропустить лишь значения, которые больше, чем 500. Эти значения, возможно, соответствуют морганию, а именно это нам и нужно:

  this.leftBlinks = this.muse.eegReadings
    .filter(r => r.electrode === leftEyeChannel)
    .map(r => Math.max(...r.samples.map(n => Math.abs(n))))
    .filter(max => max > 500)


На этом этапе у нас есть простой конвейер для обнаружения морганий, основанный на RxJS, но нам ещё нужно подписаться на него для того, чтобы начать получать данные. Начнём с обычного console.log:

 this.leftBlinks.subscribe(value => {
    console.log('Blink!', value);
  });


Если запустить этот код, вы, возможно, увидите много сообщений «Blink!», пока надеваете гарнитуру, так как в это время будет немало статического шума. Как только гарнитура будет надёжно надета, сообщения должны появляться только при моргании:

80deffa92a319c2bfbcb6ad6f16a4eda.gif
Программа реагирует на моргания

Возможно, вы увидите несколько сообщений «Blink!» при каждом моргании. Причина этого заключается в том, что каждое моргание создаёт несколько изменений электрического потенциала. Для того, чтобы избавиться от ненужных сообщений, нужно применить антидребезговый фильтр, похожий на тот, который используется при работе с механическими кнопками на Arduino.

Поэтому добавим в наш проект ещё одно улучшение: вместо вывода необработанных данных в консоль, мы будем выдавать значение 1 при обнаружении моргания, затем будем ждать полсекунды после последнего изменения потенциала, и выдавать 0. Это позволит отфильтровать множественные события «Blink!», которые мы видели:

  this.leftBlinks = this.muse.eegReadings
    .filter(r => r.electrode === leftEyeChannel)
    .map(r => Math.max(...r.samples.map(n => Math.abs(n))))
    .filter(max => max > 500)
    .switchMap(() =>
      Observable.merge(
        Observable.of(1),
        Observable.timer(500).map(() => 0)
      )
    );


В чём же секрет switchMap? А происходит тут следующее: когда поступает новый элемент, switchMap отбрасывает предыдущий поток и вызывает заданную функцию для создания нового потока. Этот новый поток включает в себя два элемента: значение 1, которое мы выдаём немедленно с помощью Observable.of, а затем значение 0, которое выдаётся через 500 миллисекунд, если только, конечно, не поступит новый элемент из строки filter, что перезапускает switchMap и отбрасывает ожидаемое значение 0.

Теперь мы можем использовать этот наблюдаемый объект leftBlinks для визуализации морганий. Эти данные можно связать с шаблоном Angular, используя асинхронный конвейер:

lqfwaidcopaqrinyzjpt2zyycqc.png

Данный код скрывает значок глаза при моргании. Вместо этого можно переключать класс CSS и использовать цвет или анимировать символ глаза при моргании:

syivgqa2hmwrmlng9wl4z05kpym.png

В любом случае, рекомендую, если вы так можете, моргать только одним глазом для проверки кода.

Если создавать, пользуясь тем же подходом, приложение React, можно просто подписаться на этот наблюдаемый объект и обновлять состояние компонента при моргании:

  this.leftBlinks.subscribe(value => {
    this.setState({blinking: value});
  });


Итак, мы это сделали! Вот он, «Hello World» для ЭЭГ-гарнитуры!

1f20cf31e396e0c6dbac1ab7b0981152.gif
Готовый «Hello World»

Здесь можно найти код этого проекта. Хочу выразить огромную благодарность Бену Лешу за помощь с подготовкой RxJS-кода.

Итоги


Несколько лет тому назад слово «электроэнцефалография» вызывало мысли о громоздком, страшно дорогом оборудовании, доступном только больницам да исследовательским учреждениям. Сегодня веб-разработчики, такие же как вы и я, могут работать с данными об электрической активности мозга, используются обычные инструменты: браузер, RxJS, Angular и вполне доступную гарнитуру.

Возможно, ЭЭГ — не ваша тема, но это не может помешать вам увидеть то, как новые поколения «интеллектуальных» устройств создают массу интереснейших возможностей для программистов. Эх, что за времена настали!

Уважаемые читатели! Какие варианты применение ЭЭГ-гарнитур в веб-разработке кажутся вам самыми интересными и перспективными?

© Habrahabr.ru