Tobii製アイトラッカーで動画再生中の視線追跡（1/3，SDKを用いたアイトラッカー動作）

次回の記事：
　　[2/3，動画再生中の視線追跡](https://qiita.com/takahashiMamo/items/da88d9e5b5c0bccd9804)

# はじめに
[前回のオドボール課題実施プログラム](https://qiita.com/nnahito/items/2ab3ad0f3adacc3314e6)に引き続き，今回もPythonでプログラム開発していきます．前回は脳波測定実験を実施するためのプログラムだったわけですが，今回は視線追跡です．とある計画のためにTobii社製のアイトラッカー，[Tobii Pro スパーク](https://www.tobii.com/ja/products/eye-trackers/screen-based/tobii-pro-spark)を入手しました．Tobii社のアイトラッカーは，研究用製品とゲーミング用製品に大別され，前者はProの文字を名前に冠しています．自分が使用するスパークも研究用で，その中でエントリーモデルではありますがゲームミング用製品よりいいお値段したのが実のところです．
　しかし，このアイトラッカーでデータを取得し結果を可視化するまでをトータルで支援してくれる純正ソフトウェア，[Tobii Pro ラボ](https://www.tobii.com/ja/products/software/data-analysis-tools/tobii-pro-lab)もこれまた高価でありこちらは現状導入できていません．では，Tobii Pro ラボなしではスパークを使えないかというとそうではなく，[Tobii Pro SDK](https://www.tobii.com/ja/products/software/applications-and-developer-kits/tobii-pro-sdk)という各種言語でのアプリ開発キットが無償で配布されています，そこで自分はこれで動作プログラムを構築して使用することにしました．
　この記事では，動画を再生している最中の，視聴者の視線を記録するプログラムを頑張ってつくっていきます．使用言語は前回に引き続きPythonです．上記リンク先のTobii Pro SDKページには， **「使用するためには適切なプログラミングの知識が必要です。サポート対応はハードウェア上のトラブル及びバグ等の対応に限られ、プログラミングに関するコンサルタントサービスが必要な場合は有償となります（英語のみ）。 」** とありますので自力でなんとか完成させなければなりません．以前自分はPythonには不慣れですし，また[こちらが公式ドキュメンテーション](https://developer.tobiipro.com/index.html)ですが御覧の通りサンプルプログラムはわずかしか配布されていません．でも今の自分にはGPTがあるじゃない！なんだかできそうな気がするぞ．というわけで挑戦します．

本記事は長文になる見込みで連載の可能性がありますのでご容赦ください．今回もGPTの力に頼りますが，前回もそうでしたが結論に至るまでのプロンプトに関する議論は本記事の対象外とさせていただきます．プログラム開発の戦略・方針を記述していきます．
　また，プログラム作成には筆者の研究室の所属学生A君が大いに協力してくれました．長文を見越して先にここに感謝を記しておきます．

# まずは動かしてみる

## 事前準備：Eye Tracker Manager
Tobii製アイトラッカーを入手したらまずはPCに[Tobii Pro Eye Tracker Manager](https://www.tobii.com/ja/products/software/applications-and-developer-kits/tobii-pro-eye-tracker-manager)をインストールします（無料）．これが使用アイトラッカー製品に応じてPCにデバイスドライバをインストールしてくれ，また視線追跡の校正をしてくれます．ただし，自分の環境ではManagerによるドライバインストールには[runtime](https://connect.tobii.com/s/spark-downloads?language=en_US)の追加インストールが必要でした．必須かどうかは未確認です（あまり説明書を端から端まで読んでいないので）．インストールがうまくいったら，Managerによる校正は完了すると思います．

## SDKで動作させてみる
ここまで準備できたら，プログラム開発です．いくつか言語がありますが自分はPythonなので[これ](https://developer.tobiipro.com/python.html
)に従います．
　[Getting started](https://developer.tobiipro.com/python/python-getting-started.html)にはイロイロ書いていますが大事なのはSDKの導入方法．早い話`pip`でインストールできます→`pip install tobii_research`．
　**次の[Step-by-step guide](https://developer.tobiipro.com/python/python-step-by-step-guide.html)はとにかく動かすための最低限のコマンドが説明されているので重要です** ．ここで説明されていることをまとめると，試しに下のようなスクリプトを実行してみてね，という話です：

```py
import tobii_research as tr
import time

# アイトラッカー検出
found_eyetrackers = tr.find_all_eyetrackers()
my_eyetracker = found_eyetrackers[0]
#print("Address: " + my_eyetracker.address)
#print("Model: " + my_eyetracker.model)
#print("Name (It's OK if this is empty): " + my_eyetracker.device_name)
#print("Serial number: " + my_eyetracker.serial_number)

def gaze_data_callback(gaze_data):
    # Print gaze points of left and right eye
    print("Left eye: ({gaze_left_eye}) \t Right eye: ({gaze_right_eye})".format(
        gaze_left_eye=gaze_data['left_gaze_point_on_display_area'],
        gaze_right_eye=gaze_data['right_gaze_point_on_display_area']))

my_eyetracker.subscribe_to(tr.EYETRACKER_GAZE_DATA, gaze_data_callback, as_dictionary=True)
time.sleep(5)
my_eyetracker.unsubscribe_from(tr.EYETRACKER_GAZE_DATA, gaze_data_callback)
```

途中の4行をコメントアウトしているのは，ただの値の表示であり任意だからです．

SDKがインストールできていたら，importでコマンドを使えるようになります．
　アイトラッカー検出コマンドの2行はもう「おまじない」と思っていいでしょう．使用するアイトラッカーが変数`my_eyetracker`に代入されます．
　`gaze_data_callback`関数にてアイトラッカーにより取得された視線データが取り扱われます．変数`gaze_data`に注視点の座標が両目分格納されており（ **実際は+その他もろもろの情報も含まれます（重要）** ．必要になり次第解説します）それをコンソールに`print`してくれます．
　この関数は`my_eyetracker.subscribe_to`により実行され，`my_eyetracker.unsubscribe_from`により停止されます．その間に`time.sleep(5)`により5秒待機を指示されているので，結局アイトラッカーは5秒にわたり取得した注視点座標を画面に書き出し続けてくれます．

以下，このサンプルを編集していき，まずは動画のことはさておき，自分の意図する視線追跡プログラムにしていくことにします．

# 改造1：キーボードボタンで動作開始

ボタン一つおすだけで好きなタイミングで視線レコードを開始できるようにします．これはあっさりGPTに教えてもらって解決させましたが，`keyboard`モジュールを知っている人は秒でクリアできると思います．
　原理は簡単で，ホットキー`s`をストライクすると関数`toggle_streaming`が実行されます．そしてその中には，先ほど使用した5秒間の視線座標表示コマンドが格納してあります．

```py
import tobii_research as tr
import time
import keyboard

# アイトラッカー動作時間
waittime = 5

# アイトラッカー検出
found_eyetrackers = tr.find_all_eyetrackers()
my_eyetracker = found_eyetrackers[0]

def gaze_data_callback(gaze_data):
    # Print gaze points of left and right eye
    print("Left eye: ({gaze_left_eye}) \t Right eye: ({gaze_right_eye})".format(
        gaze_left_eye=gaze_data['left_gaze_point_on_display_area'],
        gaze_right_eye=gaze_data['right_gaze_point_on_display_area']))

# キーボードのsキーでストリーミングを開始/停止する
def toggle_streaming():
    my_eyetracker.subscribe_to(tr.EYETRACKER_GAZE_DATA, gaze_data_callback, as_dictionary=True)
    time.sleep(waittime)
    my_eyetracker.unsubscribe_from(tr.EYETRACKER_GAZE_DATA, gaze_data_callback)

keyboard.add_hotkey('s', toggle_streaming)

# キーボード入力を監視
keyboard.wait()
```

# 改造2：視線データのCSV出力

取得した視線データはゆくゆくは本格的な眼球運動や動画視聴者の興味関心分析に使いたいところです．そのためにポスト処理のため，視線データを書き出すようにしておきましょう．完成品は下の通りですが，これを自力の情報収集だけで解決するのは，上のkeyboardよりはるかに大変だったろうと思います．GPTがあってよかった．

```py
import tobii_research as tr
import time
import keyboard

# アイトラッカー動作時間
waittime = 5

# アイトラッカー検出
found_eyetrackers = tr.find_all_eyetrackers()
my_eyetracker = found_eyetrackers[0]
gaze_data_list = [] # 視線データを格納するリスト

def gaze_data_callback(gaze_data): 
    gaze_data_list.append(gaze_data)

# キーボードのsキーでストリーミングを開始/停止する
def toggle_streaming():
    my_eyetracker.subscribe_to(tr.EYETRACKER_GAZE_DATA, gaze_data_callback, as_dictionary=True)
    time.sleep(waittime)
    my_eyetracker.unsubscribe_from(tr.EYETRACKER_GAZE_DATA, gaze_data_callback)
    # CSVファイルに視線データを書き込む
    with open('gaze_data.csv', 'w', newline='') as csvfile:
        fieldnames = ['left_eye_x', 'left_eye_y', 'right_eye_x', 'right_eye_y']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for data in gaze_data_list:
            left_eye_x = data['left_gaze_point_on_display_area'][0]
            left_eye_y = data['left_gaze_point_on_display_area'][1]
            right_eye_x = data['right_gaze_point_on_display_area'][0]
            right_eye_y = data['right_gaze_point_on_display_area'][1]
            # csv書き込み
            writer.writerow({
                'left_eye_x': left_eye_x,
                'left_eye_y': left_eye_y,
                'right_eye_x': right_eye_x,
                'right_eye_y': right_eye_y
            })

keyboard.add_hotkey('s', toggle_streaming)

# キーボード入力を監視
keyboard.wait()
```

まず，`gaze_data_callback`で指示するアイトラッカー動作内容ですが，今回はcsv書き出しのめに変数に格納していきます．そのため今までの`print`コマンドはもはやいません．繰り返しですがアイトラッカーが取得したデータは変数`gaze_data`内に収まっているのでした．そこで最終的な書き出しのために，`.append`により`gaze_data_list`にどんどん放り込んで保管していきます．`gaze_data_list`はあらかじめ定義され存在していなければならないことに注意→`gaze_data_list = []`．
　アイトラッカーが停止したら，csvファイルが書き出しモードで開かれます．これにヘッダー文字列が書き込まれたあと，その下に`gaze_data_list`の要素数だけforループで取得データが書き込まれていきます．
　コンソールに表示されていたのは，`gaze_data['left_gaze_point_on_display_area']`と`gaze_data['right_gaze_point_on_display_area']`であり，それぞれ左目・右目の注視点座標だったわけですが，これらはさらに2つの要素から構成されており，0番目がx（水平）座標，1番目がy（垂直）座標となります．というわけで左目x，左目y，右目x，右目yの順に，4列に並べて書き出すことにしています．

結果は下のようになります． **すべての値は0から1の間の値となりますが，座標(0,0)はディスプレイ左上端，(1,1)は右下端に対応します** ．詳細は[こちら](https://developer.tobiipro.com/commonconcepts/calibration.html)．
　また，仕様なのか最初の数データはどうしてもnanになります．またデータの途中にnanが含まれてしまうこともあるので注意が必要です（瞬き？）．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3345878/7cb48674-e732-5f1f-10a3-8f36ebd2320e.png)


# 本記事はここまで．次回に続く

というわけで，動画再生中の視聴者の視線を記録する，という最終目標のもと，今回はアイトラッカーの実際の準備，動作コマンドを解説しました．次回の記事では動画の再生機能を追加していきます．