Как использовать RxJS, Angular и Web Bluetooth вместе с ЭЭГ-гарнитурой, чтобы сделать больше с вашим мозгом

Несколько месяцев назад я наткнулся на Bluetooth Smart EEG Headset. Я сразу осознал его потенциал для некоторых очень интересных вещей: используя Web Bluetooth, я мог напрямую общаться из моего мозга с веб-страницами!

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

Одним из таких новаторских вариантов использования является Muse, потребительский продукт, который должен помочь научить вас медитировать мелодию за 250 долларов, но это также надежное потребительское устройство ЭЭГ, которое поставляется с Bluetooth. И хотя он должен научить вас, как успокаивать ваш разум, мой разум успокоится только после того, как я выясню, как потреблять его данные с моей веб-страницы!

(Если ваш разум тоже не может оставаться спокойным, не стесняйтесь переходить к руководству по коду ниже ;-)

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

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

Я попытался реконструировать протокол Muse Bluetooth, аналогично тому, что я сделал с лампочкой Magic Blue. Примерно через час я понял, что кто-то уже мог это сделать, поэтому я погуглил одно из характерных чисел, которые обнаружил, и нашел эту отличную статью, которая, в свою очередь, указала на эту библиотеку python, созданную Alexandre Barachant, и вдруг у меня появилось все необходимое: так родилась muse-js.

Итак, теперь я могу подключиться к своей гарнитуре Muse через Интернет и получать данные ЭЭГ (а также уровень заряда батареи, акселерометр / гироскоп и т. Д.). Ура!

Итак, что я буду на нем строить?

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

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

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

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

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

Преимущество RxJS заключается в том, что он предлагает набор функций, которые позволяют манипулировать и обрабатывать байты необработанных данных, полученные от гарнитуры Muse, чтобы преобразовать их во что-то более полезное (как мы будем делать в ближайшее время).

Визуализировать

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

Фактически, если у вас есть устройство Muse и браузер с поддержкой веб-Bluetooth, вы можете открыть Демонстрационную страницу и попробовать сами!

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

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

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

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

Мы будем использовать сигнал от этих электродов для нашей программы ЭЭГ «Hello World» - обнаружения морганий путем отслеживания активности глаз.

Давайте начнем!

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

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

Но прежде чем мы займемся чем-либо еще, давайте сначала установим Muse-js в наш проект ...

npm install --save muse-js

… А затем импортировать его в наш код. В этом случае это будет приложение Angular - просто пустой проект, созданный с помощью Angular CLI, но вы также можете следовать вместе с React / VueJS, если хотите, поскольку там будет очень мало кода, специфичного для фреймворка.

Затем мы импортируем muse-js в наш основной компонент приложения:

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

Класс MuseClient взаимодействует с гарнитурой, а channelNames просто обеспечивает удобочитаемое отображение каналов ЭЭГ.

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

this.muse = new MuseClient();

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

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

async onConnectButtonClick() {
  await this.muse.connect();
  this.muse.start();
  // TODO: subscribe to EEG DATA
}

Метод connect() класса MuseClient инициирует соединение с гарнитурой, а затем метод start() дает команду гарнитуре начать выборку данных ЭЭГ и их отправку по проводам.

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

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

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

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!» активности, пока вы не наденете гарнитуру, так как будет много статического шума. Однако, как только вы наденете гарнитуру, вы должны увидеть сообщение «Blink!» сообщения только когда вы моргаете или касаетесь левого глаза:

Вау, это действительно работает!

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

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

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

Теперь мы можем использовать эту leftBlinks наблюдаемую, чтобы визуализировать наши мигания! Мы можем привязаться к нему в наших шаблонах Angular, используя канал async:

<span [hidden]="leftBlinks|async">👁</span>

Приведенный выше код будет скрывать символ глаза всякий раз, когда вы моргаете, или вместо этого мы можем переключить класс CSS, а затем раскрасить или анимировать символ глаза при моргании:

<span [class.blink]=”leftBlinks|async”>👁</span>

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

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

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

Итак, мы сделали это! «Привет, мир» ЭЭГ готов!

Вы можете найти код для полного проекта здесь:



Резюме

Несколько лет назад ЭЭГ была чем-то дорогим, а громоздкое оборудование было доступно только для больниц и исследовательских центров. Сегодня веб-разработчики, такие как мы с вами, могут легко подключать и анализировать данные ЭЭГ с помощью тех же инструментов, которые мы используем изо дня в день для создания веб-сайтов - нашего веб-браузера, RxJS и Angular.

Даже если ЭЭГ - не ваша чашка чая, вы можете ясно увидеть, как новый толчок для всех видов «умных» потребительских товаров создал множество действительно интересных возможностей для разработчиков. Мы живем в захватывающие времена!

p.s. - Большое спасибо Бену Лешу за помощь с кодом RxJS в этих примерах.