Image from Gyazo

Cortexとは

Python を経由して,Emotiv Epoc+ で計測したデータを取得してみましょう. データを取得するには,Emotivの標準APIであるCortexを利用します. 今回はPythonを利用しますが,このCortexは JSONWebSockets で実装されているため, Python以外のプログラミング言語でも利用可能です. また,Cortexは,EmotivProと一緒にインストールされますが, Cortex UIというソフトウェアで動作確認が可能です. 下図のように対象のデバイスが検出されていればOKです. コンタクト・クオリティは今回も35%とパッとしません(笑).

Image from Gyazo

ライブラリの導入

Pythonはウィンドウズ版のバージョン3.7.2を用いることにします. Cortexでは,データの送受信には WebSockets という技術を用います. ここでは,WebSockets のクライアント(データ受信側)として実装するため, websocket-client ライブラリをインストールしておきます.

$ pip install websocket-client

また,WebSocketsのポート番号は 54231 です. URLには wss://localhost:54321 を指定します. ここで,wss はWebSocketsのプロトコルを表しています.

手順1:認証

Cortexから脳波の生データを取得するには,クライアントIDシークレットで認証が必要です. この,クライアントIDとシークレットは,EMOTIVのユーザページで事前に取得しておきましょう(ライセンス番号も必要).

まずは,CORTEXで用いられるJSON-RPCについて簡単に説明します. 上述したように,サーバとクライアント間のデータのやりとりをJSON形式で行うという仕組みです. クライアントが送信するリクエストの基本フォーマットは下記です. ここで,プロトコルバージョンの jsonrpc は常に 2.0 を指定します. メソッドやパラメータには,認証(authorize)やデータ取得(subscribe)などの文字列を指定します.

{
	"jsonrpc": "2.0",
	"method": メソッド,
	"params":{
		パラメータ: 値
	},
	"id": ID番号
}

また,クライアントが受信するレスポンスの基本フォーマットは下記となります. サーバーからの応答結果は result に格納されています. また,リクエストと同じID番号が付与されています.

{
	"jsonrpc": "2.0",
	"result": 結果,
	"id": ID番号
}

JSON-RPCを用いた認証のためのコードは次のようになります. 認証は authorize メソッドを用います. パラメータとして,事前に取得したクライアントID(client_id), シークレット(client_secret),ライセンス番号(license)を指定しています. また,debitはEmotivが接続可能なクライアントの数を表しています.

サーバーからの応答はresponseに代入され,それを出力すると下記のようになります.

[PyCortex]
{'id': 1, 'jsonrpc': '2.0', 'result': {'_auth': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6ImNvbS5uYW90by1lbW90aXZlLnRlc3QiLCJleHAiOjE1NTA2NTkyNTIsImZpcnN0TmFtZSI6Ik5hb3RvIiwibGFzdE5hbWUiOiJNdWthaSIsImxpY2Vuc2UiOnsiaXNfY29tbWVyY2lhbCI6ZmFsc2UsImxpY2Vuc2VJZCI6IjI0YzliYmYyLThjYTYtNDVmOS04NjFhLTVmNDM2NDE1Y2QxNSIsImxpY2Vuc2VTY29wZSI6MiwibGljZW5zZV9leHBpcmUiOjE1ODA2MDE1OTksImxpY2Vuc2VfaGFyZExpbWl0IjoxNTUyMDg5NTk5LCJsaWNlbnNlX3NvZnRMaW1pdCI6MTU1MTQ4NDc5OX0sImxpY2Vuc2VJZCI6IjI0YzliYmYyLThjYTYtNDVmOS04NjFhLTVmNDM2NDE1Y2QxNSIsImxpY2Vuc2VTY29wZSI6MiwibGljZW5zZV9hZ3JlZW1lbnQiOnsiYWNjZXB0ZWQiOnRydWUsImxpY2Vuc2VfdXJsIjoiaHR0cHM6Ly93d3cuZW1vdGl2Y2xvdWQuY29tL2RiYXBpL3ByaXZhY3kvZG9jL2V1bGEvIn0sImxpY2Vuc2VfZXhwaXJlIjoxNTgwNjAxNTk5LCJsaWNlbnNlX2hhcmRMaW1pdCI6MTU1MjA4OTU5OSwibGljZW5zZV9zb2Z0TGltaXQiOjE1NTE0ODQ3OTksIm5iZiI6MTU1MDQwMDA1MiwidXNlckNsb3VkSWQiOjUzMjM4LCJ1c2VybmFtZSI6Im5hb3RvLWVtb3RpdmUifQ.Ziydg2az9HKjBdlXwR2ggcd21NLhoK6mwtfwN0sY2mg'}}

取得したデータには,これ以降の送受信で用いる認証トークン _auth が含まれています. これを,auth_token という変数に代入しておきます.

auth_token = response["result"]["_auth"]

手順2:セッションの確立

デバイスとのセッションを確立します. セッションの確立は createSession メソッドを用います. パラメータとして,認証トークン(_auth),セッションの状態(status)を指定しています. セッションの状態には,open(セッションの開始),active(データ送受信), close(セッションを閉じる)などが指定できます(いきなりactiveで問題なさそうですが).

次に,デバイスにデータの記録を指示します. データの記録には,updateSession メソッドを用います. パラメータとして,認証トークン(_auth),セッションの状態(status)を指定しています. ここでは,セッションの状態として startRecord を指定します. この他,recordingNamerecordingNoterecordingSubjectの指定も必要なようです(ここのところは,イマイチわかりません).

サーバーからの応答は下記のようになります. 電極の位置番号であるAF3F7などが確認できます.

{'id': 3, 'jsonrpc': '2.0', 'result': {'appId': 'com.naoto-emotive.test', 'headset': {'connectedBy': 'dongle', 'dongle': '6ff', 'firmware': '633', 'id': 'EPOCPLUS-4A2C01C0', 'label': '', 'motionSensors': ['Q0', 'Q1', 'Q2', 'Q3', 'ACCX', 'ACCY', 'ACCZ', 'MAGX', 'MAGY', 'MAGZ'], 'sensors': ['AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 'AF4'], 'settings': {'eegRate': 128, 'eegRes': 16, 'memsRate': 0, 'memsRes': 16, 'mode': 'EPOCPLUS'}, 'status': 'connected'}, 'id': 'a1755548-ec7b-41bf-bbdb-a6ee2174a472', 'license': '24c9bbf2-8ca6-45f9-861a-5f436415cd15', 'logs': {'recordInfos': [{'name': 'test', 'notes': 'note1', 'recordId': 'b9b0a5c0-4a79-4137-aafb-12c8b7d865ea', 'sampleMarkerAFF': [0], 'sampleMarkerEEG': [0], 'startMarkerRecording': '2019-02-18T21:12:29.601887+09:00', 'stopMarkerRecording': '', 'subject': 'subject1'}]}, 'markers': [{'code': 1, 'enums': ['record-1'], 'events': [['2019-02-18T21:12:29.601887+09:00', 1]], 'label': 'test', 'port': 'EmotivProRecording'}], 'owner': 'naoto-emotive', 'profile': '', 'project': '', 'recording': True, 'started': '2019-02-18T21:12:29.585930+09:00', 'status': 'activated', 'stopped': '', 'streams': None, 'subject': 0, 'tags': [], 'title': ''}}

手順3:データの取得

最後に脳波の生データを取得します. データを取得するには,subscribe メソッドを用います. パラメータには,認証トークン(_auth),対象のデータストリーム(streams)を指定しています. ここでは,データストリームとして,脳波を表すeegを指定します.

{'id': 4, 'jsonrpc': '2.0', 'result': [{'eeg': {'cols': ['COUNTER', 'INTERPOLATED', 'RAW_CQ', 'AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 'AF4', 'MARKER_HARDWARE', 'MARKER']}, 'sid': 'a1755548-ec7b-41bf-bbdb-a6ee2174a472'}]}

これで,準備は完了です. あとは,WebSocketからデータを取得すると,脳波の生データが連続して取得できます(サーバーへの送信は不要).

取得されたデータは下記です. 例えば,AF3は4171.026,F7は4155.513のように,正規化された値が取得できていることがわかります(コンタクト・クオリティが低いので値は信用できませんが・・). データの詳細はドキュメントを参照してください.

{'eeg': [33.0, 0.0, 0.0, 4171.026, 4143.718, 4155.513, 4156.41, 4164.103, 3978.205, 4057.564, 4166.026, 4152.564, 4168.59, 4138.333, 4140.385, 4164.744, 4163.205, 0.0, 0.0], 'sid': '00ce38aa-8ce2-4e86-87ff-5ab689385ad9', 'time': 22909.26}

参考書籍