memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

Muse到着

たまたまネットで目にしたMuse。確かこの動画だった。


The Muse Headband, Ariel Garten Interview ...

研究で使えるかも?とネットで購入

で、一昨日くらいに到着。

ちなみにちょっとしたトラブルがあったのでメモ。

郵便屋さんが大学の事務受付に届けに来たらしい(自宅に届いても結局大学に持っていくので送付先を大学にしていた)。不在だったため何度かきたらしい。事務が受け取れなかったのは手数料を支払う必要があったからとか。

え?送料は払っているし、なんで?と思ったら消費税(3300円)と手数料(200円)であることが判明。これを買う人は注意が必要です。

で、開封の儀が以下。でかい。

f:id:ke_takahashi:20150131105307j:plain

最近、業者を通さず購入したEyeXやEyeTribeはいずれも小さかった。開けてみると以下のような感じ。なんか。。。アメリカっぽい。

f:id:ke_takahashi:20150131105333j:plain

で、箱から取り出したのが以下。

f:id:ke_takahashi:20150131105354j:plain

で、昨日は卒業研究発表会だったので何もできず。。。

今日、昼くらいからあれこれ試行錯誤すること5時間くらい!?飯も食わずにとりあえずできたのが以下。とにかく脳波データをプログラムで取り出したかった。それ以降はどうとでもできるので。


Visualizing brain wave spectra measuring with Muse ...

#ちなみに、はてブロのYouTube貼り付け機能が改善されましたね。。。アップロードしてすぐに検索してもヒットせず、いつも手動でタグを作っていた。何のためのブログ補助機能なのかと思っていた。。。助かります。

MuseSDKに含まれるmuse-ioというコマンドラインプログラムを起動すると、muse-ioがMuseからBluetooth経由で受け取ったデータをOSCでアプリにつないでくれます。ちなみに通信はTCPもしくはUDPのようです。

で、いつものProcessingを使うことに。OSCライブラリがあるのでUDPで受けたデータに含まれるOSCをパースしてデータを簡単に取り出すことができます。ライブラリの開発者に感謝します。ちなみにOSCの中にJSONデータが一部含まれているのですが、それもProcessingではJSONObjectとして扱うクラスがあるのでこの処理も楽チンです。

で、とりあえずFFT後のdelta, theta, alpha, beta, gammaの相対値(←の5つの総和で各値を割った値)を取得して、それをProcessingで棒グラフを描きました。ちなみにMuseには4つのセンサーがついているので箇所ごとに脳波データを得ることができます。

muse-ioの起動は以下のような感じです。僕は福岡に住んでいるのでオプションに60hzを指定しました。フィルタリングしてくれるんだそうです。--dspオプションは。。。dspデータを流してくれるようですが、これをしてもできなかったんですよね。。。よくわかりません。とりあえずつけていても問題はなかったのでつけておきます。あ、各メソッドのコメントはMuseSDKのドキュメントからそれらしいのをコピーしています。

muse-io --osc osc.udp://localhost:5001 --60hz --dsp

で、以下がProcessingのソースです。汚いしデータクラスとか作らずに配列で垂れ流しているのでかっこ悪いですが、まぁ、とりあえず動きます。

//see detail about OSC Paths -> https://sites.google.com/a/interaxon.ca/muse-developer-site/museio/osc-paths/osc-paths---v3-6-0

import oscP5.*;
import netP5.*;

OscP5 oscP5;

float eeg[][] = new float [4][5]; //lazy code...

void setup() {
  size(800, 400);
  frameRate(25);
  /* start oscP5, listening for incoming messages at port 5001 */
  oscP5 = new OscP5(this,5001);
//  oscP5.plug(this,"rcv_eeg","/muse/eeg");
//  oscP5.plug(this,"rcv_quantization","/muse/eeg/quantization");
//  oscP5.plug(this,"rcv_dropped_samples","/muse/eeg/dropped_samples");
//  oscP5.plug(this,"rcv_acc","/muse/acc");
//  oscP5.plug(this,"rcv_config","/muse/config");
//  oscP5.plug(this,"rcv_low_freqs_absolute","/muse/elements/low_freqs_absolute");
//  oscP5.plug(this,"rcv_delta_absolute","/muse/elements/delta_absolute");
//  oscP5.plug(this,"rcv_theta_absolute","/muse/elements/theta_absolute");
//  oscP5.plug(this,"rcv_alpha_absolute","/muse/elements/alpha_absolute");
//  oscP5.plug(this,"rcv_beta_absolute","/muse/elements/beta_absolute");
//  oscP5.plug(this,"rcv_gamma_absolute","/muse/elements/gamma_absolute");
  oscP5.plug(this,"rcv_delta_relative","/muse/elements/delta_relative");
  oscP5.plug(this,"rcv_theta_relative","/muse/elements/theta_relative");
  oscP5.plug(this,"rcv_alpha_relative","/muse/elements/alpha_relative");
  oscP5.plug(this,"rcv_beta_relative","/muse/elements/beta_relative");
  oscP5.plug(this,"rcv_gamma_relative","/muse/elements/gamma_relative");
//  oscP5.plug(this,"rcv_delta_score","/muse/elements/delta_score");
//  oscP5.plug(this,"rcv_theta_score","/muse/elements/theta_score");
//  oscP5.plug(this,"rcv_alpha_score","/muse/elements/alpha_score");
//  oscP5.plug(this,"rcv_beta_score","/muse/elements/beta_score");
//  oscP5.plug(this,"rcv_gamma_score","/muse/elements/gamma_score");
//  oscP5.plug(this,"rcv_touching_forehead","/muse/elements/touching_forehead");
//  oscP5.plug(this,"rcv_horseshoe","/muse/elements/horseshoe");
//  oscP5.plug(this,"rcv_is_good","/muse/elements/is_good");
//  oscP5.plug(this,"rcv_blink","/muse/elements/blink");
//  oscP5.plug(this,"rcv_jaw_clench","/muse/elements/jaw_clench");
}

public void rcv_config(String value) {
  JSONObject json = parseJSONObject(value);
  println(json.getInt("battery_millivolts"));
//  println(value);
}

//This is the EEG data converted to microvolts. Range: 0.0 - 1682.0 in microvolts 
public void rcv_eeg(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//To decrease the size of the data, the EEG value is divided by a number before it is sent. This is the number it is divided by.
public void rcv_quantization(int left_ear, int left_forehead, int right_forehead, int right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//Number of EEG samples (all channels = 1 sample) dropped from bluetooth connection issues, 16bit, Range: 0-65535. Position of this message in the message stream indicates where the dropped samples occurred.
public void rcv_dropped_samples(int value) {
  println(value);  
}

//The relative positions specified(forward/back, up/down) are if you are wearing Muse properly on your head. These values are in milli-G's where 1 G is the force of gravity, this is also known as "weight per unit mass" or "acceleration vector". 
public void rcv_acc(float g_forward_backward, float g_up_down, float g_left_right) {
  println(g_forward_backward + ", " + g_up_down + ", " + g_left_right);  
}

//1-8Hz, log band power (dB)
public void rcv_low_freqs_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//1-4Hz, log band power (dB)
public void rcv_delta_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//5-8Hz, log band power (dB)
public void rcv_theta_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//9-13Hz, log band power (dB)
public void rcv_alpha_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//13-30Hz, log band power (dB)
public void rcv_beta_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//30-50Hz, log band power (dB)
public void rcv_gamma_absolute(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//1-4Hz, log band power (dB)
public void rcv_delta_relative(float left_ear, float left_forehead, float right_forehead, float right_ear) {
//  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
  eeg[0][0] = left_ear;
  eeg[1][0] = left_forehead;
  eeg[2][0] = right_forehead;
  eeg[3][0] = right_ear;
}

//5-8Hz, log band power (dB)
public void rcv_theta_relative(float left_ear, float left_forehead, float right_forehead, float right_ear) {
//  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
  eeg[0][1] = left_ear;
  eeg[1][1] = left_forehead;
  eeg[2][1] = right_forehead;
  eeg[3][1] = right_ear;
}

//9-13Hz, log band power (dB)
public void rcv_alpha_relative(float left_ear, float left_forehead, float right_forehead, float right_ear) {
//  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
  eeg[0][2] = left_ear;
  eeg[1][2] = left_forehead;
  eeg[2][2] = right_forehead;
  eeg[3][2] = right_ear;
}

//13-30Hz, log band power (dB)
public void rcv_beta_relative(float left_ear, float left_forehead, float right_forehead, float right_ear) {
//  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
  eeg[0][3] = left_ear;
  eeg[1][3] = left_forehead;
  eeg[2][3] = right_forehead;
  eeg[3][3] = right_ear;
}

//30-50Hz, log band power (dB)
public void rcv_gamma_relative(float left_ear, float left_forehead, float right_forehead, float right_ear) {
//  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
  eeg[0][4] = left_ear;
  eeg[1][4] = left_forehead;
  eeg[2][4] = right_forehead;
  eeg[3][4] = right_ear;
}

//1-4Hz, log band power (dB)
public void rcv_delta_score(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//5-8Hz, log band power (dB)
public void rcv_theta_score(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//9-13Hz, log band power (dB)
public void rcv_alpha_score(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//13-30Hz, log band power (dB)
public void rcv_beta_score(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//30-50Hz, log band power (dB)
public void rcv_gamma_score(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//A boolean value, 1 represents a blink was detected.
public void rcv_blink(int value) {
  println(value);  
}

//A boolean value, 1 represents a jaw clench was detected.
public void rcv_jaw_clench(int value) {
  println(value);  
}

//A boolean value, 1 represents that Muse is on the head correctly.
public void rcv_touching_forehead(int value) {
  println(value);  
}

public void rcv_horseshoe(float left_ear, float left_forehead, float right_forehead, float right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

//Strict data quality indicator for each channel, 0= bad, 1 = good.
public void rcv_is_good(int left_ear, int left_forehead, int right_forehead, int right_ear) {
  println(left_ear + ", " + left_forehead + ", " + right_forehead + ", " + right_ear);  
}

String label1[] = {"left_ear", "left_forehead", "right_forehead", "right_ear"};
String label2[] = {"delta", "theta", "alpha", "beta", "gamma"};
int col[] = {color(255,0,0), color(0,255,0), color(0,255,255), color(255,0,255), color(0,0,255)};

void draw() {
  background(0);

  for (int i = 0; i < 4; i++) {
    fill(255);
    text(label1[i], 0, i * (height / 4) + 10); 
    for (int j = 0; j < 5; j++) {
      fill(255);
      text(label2[j], (j + 1) * (width / 6), i * (height / 4) + 10); 
      fill(col[j]);
      rect((j + 1) * (width / 6), i * (height / 4) + 20 , (width / 6) * eeg[i][j], 20);
    }
  }
}

さて。。。こう書くと非常にスムーズに行ったと思うでしょう?それなら5時間もかかりません。。。とりあえず参考になるかもしれませんので書いておきます。

まず、開発には関係ないですが、iPhoneにアプリを入れてMuseを使用してみました。これ。。。使いにくいですねぇ。。。気持ちはわかる(←誤った使い方をすると正しいデータが得られず製品の評価が下がる)のですが、早く試してみたいという気持ちを思いっきり裏切ってくれます。僕がせっかちなせいもあるのでしょうけどね。

で、お次はSDKのダウンロード。

なんと、ここでいきなりつまづきます。Mac版をダウンロードしようとリンクをクリックするとAccess Deniedとか。。。おいおいorz とりあえずForumがあったので報告しておいたけどいつまでもなおらず。

f:id:ke_takahashi:20150201152937p:plain

しかたがないので、VirtualBoxにいれてあるWin7で試すことに。MuseとPCを接続するにはBluetoothを使う必要が。DVDドライブのように簡単にいくかと思えば。。。ダメ。ここなどを参考に、まずMac側でBluetoothを切にして、以下のコマンドをターミナルから実行しました。

sudo launchctl unload /System/Library/LaunchDaemons/com.apple.blued.plist
kextunload -b com.apple.driver.AppleUSBBluetoothHCIController

あとkextstatを実行するとBluetoothを使っていそうなものがあるのでこれもついでにunload(※ここは自己責任でお願いします。よく調べていないのでやってよいものかがよくわかっていません。

sudo kextunload -b com.apple.iokit.IOBluetoothFamily
sudo kextunload -b com.apple.iokit.IOBluetoothSerialManager
sudo kextunload -b com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport
sudo kextunload -b com.apple.iokit.IOBluetoothHostControllerUSBTransport

続いて、VirtualBoxWin7を起動し、Win側にUSB-Bluetoothを接続するとWin7Bluetooth機器が接続されたことを認識してドライバーをいれる動作が始まります。それが失敗するので手動で選択して「Blutonium BCM2035 Bluetooth 2.4 GHz Single Chip Transceiver」を選びました。とりあえずこれで動作してくれました。Museという装置が表示されるはずです。どこに?忘れました。。。プリンタなどのところだったと思います。その装置アイコンを選択して一度ペアリングしておく必要があります。パスコードを入力どうたらとでますがしなくていい(というかMuse側にはコードを入力する手段がないので)です。

で、Win側でブラウザを開き、MuseSDKのWin版をダウンロードしてインストールします。そして、C:¥Program Files¥Museコマンドプロンプトを開き、上記のmuse-ioのコマンドを実行します。すると、自動的にリトライを何度かしてくれて接続できます。

プログラムを作り始める前に、muse-ioが正常に動作しているか、C:¥Program Files¥Museにあるmuselabを起動します。使い方はこちらにあります。ポート番号欄に5001と入力し、UDPボタンを押すと入力データを見ることができるはずです。UDPで通信できているし、データもある程度変化している様子がわかりましたので、とりあえずMuseとアプリを接続できることが確認できました。

で、あとは自分のアプリを作っていくことになります。ProcessingのOSCライブラリ(oscP5)をダウンロード&インストールします。で、oscP5のサンプルから適当なものを見つけ(oscP5plug)、それを改変して作りました。

で、OSCのプログラムを書いたことがなかったので知りませんでしたが、メッセージ種別(EEGのデータ、本体のバッテリー状態、加速度センサーの値などのこと)はPathで区別するんですね。そのPath情報はここにありました。

OSC屋さん?にはあたりまえなんでしょうね。でも、ddddとかiiとかよくわからないんですよね。あれこれやってわかりましたが。iはint、fはfloat、dはdoubleです。その引数が文字の個数分送られてくるということを表しているんですね。

例えば、以下のようにドキュメントに書いています。

f:id:ke_takahashi:20150201155028p:plain

これは4つのfloat型のデータがこのPath名で送られてきますよ、ということなんですね。あと暗黙なんですがこの4つのデータはLeft Ear(TP9), Left Forehead(FP1), Right Forehead(FP2), Right Ear(TP10)、つまりMuseの4つのセンサーの値を並べて送ってくれる、ということを表しています。TP9とかは電極の位置ですね(参考)。

で、これをあとはチマチマとマッピングしながらProcessigに落とし込んでいきます。単純作業で面白くありませんが。。。

以上が5時間くらいの作業内容ですね。もう疲れた。。。何をやっているんだか。家族が出かけてたのでベイマックス観に行く予定だったんだけどなぁ。。。1日中パジャマを着て過ぎ去ってしまいました。

■2015/2/3 追記

Mac版のMuseSDK、ダウンロードできるようになりました(こちら)。Forumにきちんと返信がありました。明日、出勤したら動作確認してみます。readme.txtには書いていませんが、muse-ioを起動するときに必要なDLL2個(liblo.7.dylib、liblsl.dylib)を/usr/local/lib以下にコピーする必要があるようです。カレントは検索してくれないみたい。

■2015/2/4 追記

Mac版のMuseSDKで動作確認できました。上記のBluetoothの無効化を行ったせいかなかなかParingができなかったのですが「sudo launchctl load /System/Library/LaunchDaemons/com.apple.blued.plist」とした後に認識してくれました。

Mac版のmuse-ioはターミナルでカラー表示できるせいかWin版より若干わかりやすいです(下図参照)。4つのセンサーのノイズ状態が常に表示されますので装着具合をその値をみながら調整できます。

f:id:ke_takahashi:20150204131309p:plain