[譯] 響應式腦電波—如何使用 RxJS、Angular、Web 藍牙以及腦電波頭戴設備來讓咱們的大腦作一些更酷的事

原文連接: medium.com/@urish/reac…
本文爲 RxJS 中文社區 翻譯文章,如需轉載,請註明出處,謝謝合做!
若是你也想和咱們一塊兒,翻譯更多優質的 RxJS 文章以奉獻給你們,請點擊【這裏】html

幾個月前,我偶然間發現了一臺藍牙智能腦電波頭戴設備。我忽然意識到它的巨大潛力,使用它能夠作一些超級酷的事情:使用 Web 藍牙,能夠直接用大腦與網頁進行通信!node

腦電波 ( Electroencephalography,簡稱 EEG ) 本質上是監控腦電活動的一種方式。它一般須要在頭皮上放置幾個電極,而後收集關於神經元發射的信息,最後將信息記錄在圖表上。聽起來像是一些想當不錯的數據可供我使用!雖然腦電波主要用於醫療用途,但仍會不時出現一些新穎的使用案例。python

其中一個新穎的使用案例即是 Muse,它是一種消費產品,花費$250即可以幫助你學習如何進行冥想,同時它仍是自帶藍牙、消耗腦電波的實體設備。雖然它可以教會你如何平靜下來,但對我來講,只有弄清楚如何在網頁上消費這些數據後,我才能平靜下來!react

(若是你也沒法保持平靜的話,可選擇略過此部分,直接查看下面的代碼教程 ;-)git

頭戴設備配備 Android 或 IOS 應用,甚至還提供了一個庫,這樣你就能夠獲取原始數據並構建本身的應用,但這個庫只能在原生應用中運行,並且源碼不是開源的 (所以,我想用大腦控制網頁的夢想起初看來是視乎是沒法達成的的)。github

在參加 ng-cruise 時,我遇到了 Alex Castillo,他的演講展現瞭如何將他叫作 OpenBCI 的開源硬件腦電波頭戴設備與 Angular 進行鏈接並將信號可視化。儘管這一切使人印象深入,但他不得不使用 node.js 進行復雜的設置和 Web socket 服務器來傳播數據,這離我想要的還有必定差距。後來在 ng-cruise 的黑客之夜,每一個人都在嘗試使用各類硬件設備來作一些很酷的東西,這些設備中就包括腦電圖設備,因此我天然不會錯過如此良機。web

我嘗試對 Muse 的藍牙協議進行逆向工程,相似於這篇文章所作的。大約進行了一個小時,我想到以前可能有人已經作到了,因此我 google 了我所發現的一個特徵數字,並找到了這篇超棒的文章,反過來這篇文章指出了由 Alexandre Barachant 建立的 python 庫,忽然間,我擁有了我所需的一切:這就是 muse-js 誕生的過程。typescript

因此如今我能夠將 Web 和 Muse 頭戴設備進行鏈接並接受腦電波數據 (還包括電池電量、加速計/陀螺儀,等等)。萬歲!shell

那麼接下來我要用它作什麼呢?npm

硬件

在深刻代碼以前,咱們首先來了解下 Muse 頭戴設備。基本上,它就是一個輕量級的可充電頭帶。它配備了4個腦電波電極:2個在前額,眼睛稍微往上一些,另外2個與耳朵接觸。此外,它還配備了螺旋儀和加速計,這樣能夠計算出頭的方位。我很高興我發現了它還有另一個腦電波傳感器,這樣就能夠鏈接到本身的電極了 (儘管是 Micro USB 接口),我打算儘快進行嘗試。

注意頭帶有兩個版本:2014款和2016款。你想要的確定是2016款,它使用了藍牙低耗能。2014款使用的是經典藍牙,所以沒法與 Web 藍牙一塊兒使用。

Muse 2016: AF7 和 AF8 是前額電極, TP9 和 TP10 是耳電極

使用 RxJS 的響應流

構建庫時,我須要決定如何暴露傳入的腦電波數據。使用 Web 藍牙,每當接收到新的數據包時都會觸發一個事件。每一個數據包包含來自單個電極的12個樣本。我本可讓用戶註冊一個 JavaScript 函數,每當接收到新數據時便調用此函數,但我最後決定使用 RxJS 庫 (JavaScript 的響應式擴展庫),它包括用於轉換,組合和查詢數據流的各類方法。

RxJS 的優點是它提供了一組函數,可以讓你操縱和處理從 Muse 頭戴設備接收到的原始數據字節,以便將其轉換爲更有用的東西 (好比咱們立刻要作的)。

可視化

首先映入腦海的即是使用咱們全新的 muse-js 可視化數據。黑客之夜當晚,Alex 和我開始開發 angular-muse,這是一個 Angular 應用,它能夠將腦電波數據和頭部方向進行可視化。

個人 Muse 數據可視化初始原型

事實上,若是你擁有 Muse 設備和 支持 Web 藍牙的瀏覽器,你即可以實際打開 Demo 頁面親自嘗試!

使用 Muse、 Angular 和 Smoothie Charts 將個人大腦活動進行可視化

這個應用以一種簡單的方式證實了數據是流式傳輸,但老實說,查看數據圖確實可以吸引人,但若是隻是這樣而已,那麼你將很快失去對它的興趣。

關於眨眼

腦電波所作的衆多事情之一即是測量頭皮上不一樣位置的電勢 (電壓)。測量的信號是大腦活動的反作用,可用於檢測通常心理狀態 (如濃度水平、突發刺激的檢測,等等)。

除了大腦活動以外,還可使用稱爲眼球電圖檢查 (幸運的是,個人女友就是驗光師,她可以教我不少這方面的知識) 的技術來檢測眼部運動。Muse 設備有兩個電極位於前額 (在標準的 10-20定位系統中稱爲 AF7 和 AF8),它們靠近雙眼,因此咱們可以垂手可得地監控眼部運動。

咱們的眼睛:角膜前方帶正電,視網膜背部帶負電

咱們將使用這些電極的信號做爲咱們腦電圖程序的 「Hello World」, 該程序會經過監測眼睛活動來檢測眨眼。

開始編碼!

咱們的開發思路以下:咱們從設備中獲取傳入的腦電波樣本流 (如上所述,muse-js 將提供 RxJS Observable),而後過濾出咱們所需的 AF7 電極 (也就是左眼),再而後咱們會在信號中找尋峯值,例如,絕對值超過500mV的樣本意味着發生了大變化。因爲電極在眼睛旁邊,咱們指望眼球的運動產生顯着的電勢差。

雖然這可能不是檢測眨眼最準確的方法,但它對我來講很是有用,而且代碼簡單易行 (就像全部優秀的 「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 藍牙須要一些用戶交互,纔可以啓動鏈接,因此咱們須要添加按鈕,並只有當用戶點擊該按鈕時才實際去鏈接頭戴設備。咱們在 onConnectButtonClick 方法來實現鏈接邏輯:

async onConnectButtonClick() {
  await this.muse.connect();
  this.muse.start();
  // TODO: 訂閱腦電波數據
}複製代碼

MuseClient 類實例的 connect() 方法啓動與頭戴設備的鏈接,start() 方法命令頭戴設備開始對腦電波數據進行採樣並將其發送到電線上。

使用 Web 藍牙與 Muse 頭戴設備配對

接下來咱們須要訂閱 muse.eegReadings observable 上的腦電波數據 (這段代碼放到上面的 TODO 註釋處):

const leftEyeChannel = channelNames.indexOf('AF7');

this.leftBlinks = this.muse.eegReadings
  .filter(r => r.electrode === leftEyeChannel)複製代碼

上面的代碼接收來自設備的腦電波讀數,並過濾出位於左眼上方的 AF7 電極。每一個數據包包含12個樣本,observable 流中每一項都是具備如下結構的對象:

interface EEGReading {
  electrode: number;
  timestamp: number;
  samples: number[];
}複製代碼

electrode 包含電極的數字索引 (使用 channelNames 數組映射出更友好的名稱),timestamp 包含相對於記錄開始時採樣的時間戳,samples 是12個浮點數的數組,每項都是一個腦電波測量,以 mV (微伏) 爲單位。

下一步,咱們只想獲得每一個數據包中的最大值 (例如,最大輸出值的測量)。咱們使用 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!」 出如今控制檯中。緣由是眨眼會另電勢產生變化。爲了必要出現過多的 「Blinks!」,咱們須要進行去抖動過濾 ( debounce ),相似於這篇文章 所作的。

咱們來作最後的補充:咱們再也不將信息打印到控制檯,而是當眨眼時咱們實際發出值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 observable 來對眨眼進行可視化!可使用 async pipe 將它綁定到 Angular 模板中:

<span [hidden]="leftBlinks|async">👁</span>複製代碼

每當眨眼時,上面的代碼會隱藏眼睛符號,或者咱們能夠切換 CSS 類,而後在閃爍時對眼睛符號進行顏色改變或執行動畫:

<span [class.blink]=」leftBlinks|async」>👁</span>複製代碼

不管採用哪一種方式,我建議每次只眨一隻眼睛,這樣能夠確保你能觀察到你的代碼是否正常工做😜!

若是咱們構建的是 React 應用,能夠直接訂閱 observable 並在眨眼時更新組件的 state :

this.leftBlinks.subscribe(value => {
  this.setState({blinking: value});
});複製代碼

如今咱們作到了!腦電波的 「Hello World」 已經完成!

項目的完整代碼在這裏

總結

幾年前,腦電波仍是很昂貴的,笨重的設備只能用於醫院和研究機構。現在,像你我同樣的 Web 開發者均可以使用咱們天天都在使用的開發工具 (瀏覽器、RxJS 和 Angular ) ,垂手可得地來鏈接和分析腦電波數據。

即便腦電波不是你的菜,你能夠清楚地看到,因爲各類「智能」消費品的推進,已經爲開發者創造了一系列真正的好機會。咱們確實生活在一個使人振奮、天天都充滿驚喜的年代!

備註: 十分感謝 Ben Lesh 幫忙完善這些示例中的 RxJS 代碼。

相關文章
相關標籤/搜索