TheEyeTribeでキャリブレーションするときにEyeTribe UIというアプリを使います(SDKで自作するてもありますが)。
USB接続されたTheEyeTribeから視線データを取得したり、キャリブレーション等の制御データを送るのはサーバーアプリ(Application/EyeTribe/EyeTribe)が担当(仲介)しています。このサーバーアプリがEyeTribe UIを含むユーザー開発のアプリとネットワーク通信でデータをやりとりします。形式はJSON形式です。その構成を図示したのが公式HPの以下の図です。ちなみにSDKではJSONをパーズする部分はラッピングしてくれていますのでいちいちJSONを変換する必要はありません。
EyeTribe UIでは、受け取ったデータ(JSON)をファイルに保存する機能があります。この保存したデータをパーズして視線データを再生する簡易アプリをProcessingで作りました。まだまだ未完成ですが(一番まずいのは、受信時刻timestampとは全く関係なくProcessingのframeRateで再生しているだけなので本当の視線の動きとは異なる点ですね)、今後、実験で利用していこうと考えています。
EyeTribe UIで記録したa.jsonというファイルがあったとします。これをsketchのdataフォルダーにいれます。
import java.util.List; import java.util.ArrayList; List<TET_JSONData> tet_list = new ArrayList<TET_JSONData>(); int p = 0; void setup() { size(displayWidth, displayHeight); String[] data = loadStrings("a.json"); for (String d : data) { try { tet_list.add(new TET_JSONData(d)); } catch(Exception e) {} //for removing heartbeat data } } void draw() { TET_JSONData tet = tet_list.get(p++); p %= tet_list.size(); background(0); ellipse(tet.avg_x, tet.avg_y, 15, 15); } boolean sketchFullScreen() { return true; }
class TET_JSONData { String category; String request; int statuscode; float avg_x; float avg_y; boolean fix; int state; int time; String timestamp; float left_avg_x; float left_avg_y; float left_pcenterx; float left_pcentery; int left_psize; float left_raw_x; float left_raw_y; float raw_x; float raw_y; float right_avg_x; float right_avg_y; float right_pcenter_x; float right_pcenter_y; int right_psize; float right_raw_x; float right_raw_y; public TET_JSONData(String data) throws Exception { JSONObject json = parseJSONObject(data); category = json.getString("category"); if (! "tracker".equals(category)) throw new Exception(); request = json.getString("request"); statuscode = json.getInt("statuscode"); JSONObject frame = json.getJSONObject("values").getJSONObject("frame"); JSONObject avg = frame.getJSONObject("avg"); avg_x = avg.getFloat("x"); avg_y = avg.getFloat("y"); fix = frame.getBoolean("fix"); state = frame.getInt("state"); time = frame.getInt("time"); timestamp = frame.getString("timestamp"); JSONObject lefteye = frame.getJSONObject("lefteye"); JSONObject left_avg = lefteye.getJSONObject("avg"); left_avg_x = left_avg.getFloat("x"); left_avg_y = left_avg.getFloat("y"); JSONObject left_pcenter = lefteye.getJSONObject("pcenter"); left_pcenterx = left_pcenter.getFloat("x"); left_pcentery = left_pcenter.getFloat("y"); left_psize = lefteye.getInt("psize"); JSONObject left_raw = lefteye.getJSONObject("raw"); left_raw_x = left_raw.getFloat("x"); left_raw_y = left_raw.getFloat("y"); JSONObject raw = frame.getJSONObject("raw"); raw_x = raw.getFloat("x"); raw_y = raw.getFloat("y"); JSONObject righteye = frame.getJSONObject("righteye"); JSONObject right_avg = righteye.getJSONObject("avg"); right_avg_x = right_avg.getFloat("x"); right_avg_y = right_avg.getFloat("y"); JSONObject right_pcenter = righteye.getJSONObject("pcenter"); right_pcenter_x = right_pcenter.getFloat("x"); right_pcenter_y = right_pcenter.getFloat("y"); right_psize = righteye.getInt("psize"); JSONObject right_raw = righteye.getJSONObject("raw"); right_raw_x = right_raw.getFloat("x"); right_raw_y = right_raw.getFloat("y"); } }
ちなみに、上記のちょっと工夫というか対処していることがあります。
EyeTribe UIの出力ファイルは 1つ1つのデータはJSONなんですが、残念ながらJSON配列になっていません。。。一例として、以下に3個のデータを示します。
{"category":"tracker","request":"get","statuscode":200,"values":{"frame":{"avg":{"x":1067.4683,"y":105.1672},"fix":true,"lefteye":{"avg":{"x":1072.8102,"y":88.3571},"pcenter":{"x":0.3705,"y":0.3781},"psize":34.1114,"raw":{"x":1031.5234,"y":129.2825}},"raw":{"x":1049.0,"y":129.6045},"righteye":{"avg":{"x":1062.1265,"y":121.9773},"pcenter":{"x":0.6988,"y":0.4019},"psize":31.2253,"raw":{"x":1066.4764,"y":129.9264}},"state":7,"time":240111901,"timestamp":"2015-01-09 19:49:05.053"}}}
{"category":"tracker","request":"get","statuscode":200,"values":{"frame":{"avg":{"x":1066.6683,"y":103.2254},"fix":true,"lefteye":{"avg":{"x":1073.4294,"y":86.3255},"pcenter":{"x":0.3709,"y":0.3774},"psize":33.7428,"raw":{"x":1080.9175,"y":49.2287}},"raw":{"x":1053.10,"y":74.6391},"righteye":{"avg":{"x":1059.9071,"y":120.1253},"pcenter":{"x":0.6986,"y":0.4016},"psize":31.2981,"raw":{"x":1025.2825,"y":100.0495}},"state":7,"time":240111912,"timestamp":"2015-01-09 19:49:05.064"}}}
{"category":"tracker","request":"get","statuscode":200,"values":{"frame":{"avg":{"x":1067.7034,"y":101.2610},"fix":true,"lefteye":{"avg":{"x":1075.8940,"y":86.1478},"pcenter":{"x":0.3712,"y":0.3776},"psize":33.5070,"raw":{"x":1114.3296,"y":85.9053}},"raw":{"x":1084.3044,"y":75.3505},"righteye":{"avg":{"x":1059.5129,"y":116.3742},"pcenter":{"x":0.6989,"y":0.4013},"psize":30.7790,"raw":{"x":1054.2792,"y":64.7958}},"state":7,"time":240111935,"timestamp":"2015-01-09 19:49:05.087"}}}
このデータが配列になっていればloadJSONArrayが使えるので、EyeTribe UIの出力ファイルをそのまま指定すればいいのですが上記の理由でできないので、loadStringsで1行ずつ持ってきてそれをloadJSONObjectに与えようとしましたが、引数はストリームだけ。。。せっかくloadStringsで文字列を取り出しているのにまた遅いファイルに書き込むのも馬鹿らしいです。。。
で、もしかして。。。とGitHub上のProcessingのソースを調べてみました。するとPAppletクラスにparseJSONObjectがあったんですね。。。ドキュメントにはないのですが。。。
public JSONObject parseJSONObject(String input) { return new JSONObject(new StringReader(input)); }
とりあえず動いているのでヨシとしましょう。
ああ、また2時だ。。。早く寝ないと。