原文連結: medium.com/@urish/reac…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】
幾個月前,我偶然間發現了一臺藍芽智慧腦電波頭戴裝置。我突然意識到它的巨大潛力,使用它可以做一些超級酷的事情:使用 Web 藍芽,可以直接用大腦與網頁進行通訊!
腦電波 ( Electroencephalography,簡稱 EEG ) 本質上是監控腦電活動的一種方式。它通常需要在頭皮上放置幾個電極,然後收集關於神經元發射的資訊,最後將資訊記錄在圖表上。聽起來像是一些想當不錯的資料可供我使用!雖然腦電波主要用於醫療用途,但仍會不時出現一些新穎的使用案例。
其中一個新穎的使用案例便是 Muse,它是一種消費產品,花費$250便可以幫助你學習如何進行冥想,同時它還是自帶藍芽、消耗腦電波的實體裝置。雖然它能夠教會你如何平靜下來,但對我來說,只有弄清楚如何在網頁上消費這些資料後,我才能平靜下來!
(如果你也無法保持平靜的話,可選擇略過此部分,直接檢視下面的程式碼教程 ;-)
頭戴裝置配備 Android 或 IOS 應用,甚至還提供了一個庫,這樣你就可以獲取原始資料並構建自己的應用,但這個庫只能在原生應用中執行,而且原始碼不是開源的 (因此,我想用大腦控制網頁的夢想起初看來是視乎是無法達成的的)。
在參加 ng-cruise 時,我遇到了 Alex Castillo,他的演講展示瞭如何將他叫做 OpenBCI 的開源硬體腦電波頭戴裝置與 Angular 進行連線並將訊號視覺化。儘管這一切令人印象深刻,但他不得不使用 node.js 進行復雜的設定和 Web socket 伺服器來傳播資料,這離我想要的還有一定差距。後來在 ng-cruise 的黑客之夜,每個人都在嘗試使用各種硬體裝置來做一些很酷的東西,這些裝置中就包括腦電圖裝置,所以我自然不會錯過如此良機。
我嘗試對 Muse 的藍芽協議進行逆向工程,類似於這篇文章所做的。大約進行了一個小時,我想到之前可能有人已經做到了,所以我 google 了我所發現的一個特徵數字,並找到了這篇超棒的文章,反過來這篇文章指出了由 Alexandre Barachant 建立的 python 庫,突然間,我擁有了我所需的一切:這就是 muse-js 誕生的過程。
所以現在我可以將 Web 和 Muse 頭戴裝置進行連線並接受腦電波資料 (還包括電池電量、加速計/陀螺儀,等等)。萬歲!
那麼接下來我要用它做什麼呢?
硬體
在深入程式碼之前,我們首先來了解下 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 程式碼。