例題18-5:続・Builderで行こう¶
A: さて、前回の続き。本来は全開で済ませるつもりだった内容だけなので短くなる予定。
B: Aさん、全開じゃなくて前回。
A: おっと失礼。前回は文字列を5秒間表示するというところまでやったのだが、経過時間の表示が残っている。これは例題1からずっとおつきあいいただいた方々には「なんだ、簡単じゃん」だと言っていただけるんじゃないかなーと思うが、この例題18から見ていただいている方々には難しいかもしれない。
B: もったいぶってないで早く始めましょうよ。
A: お、おう。前回の続きから始めるので、前回をまだ読んでない方や作成した実験を保存していない方は、前回を読んで「5秒待ちます」という5秒間表示するところまでBuilderで作業を進めてください。Experiment infoのあたりはどーでもいいです。
B: 「前回」ってもう何回出てきたかな…
A: で、経過時間を表示するためのTextコンポーネントを追加します。右側のComponentペインからTextコンポーネントをクリックしてください。
B: なんでText「コンポーネント」で「Component」ペインなんだろ…
A: ええい、ごにょごにょうるさいな。Textコンポーネントをクリックしたら例によってダイアログが開くので、以下のように入力する。
A: さて、説明のためにAからDまでラベルを付けたが、Aは前回説明した通りだから問題なし。Bは、まあわかるでしょ。[ ]の中にカンマ区切りでX座標とY座標を書く。前回ここは[0, 0]のままにした人が多いと思うけど、それと重ならないように適当な値を指定して。この例ではPreferencesでunitsをnormに指定しているので、X座標、Y座標ともに範囲は-1.0から1.0だ。ま、適当に[0, -0.5]くらいにしておけばいいでしょ。 norm以外のunits、特にpixなんかにしちゃってるひとは-0.5だとほとんど重なっちゃうんで各自の設定に合わせて値は調節してくださいね 。
B: 負の値だと中央より下になるんですよね?
A: Y座標については、ね。X座標なら中心より左になる。問題はCとDの項目。まずCについてだが、ここではモロにpythonの文法とBuilderの背後にあるメカニズムについての知識が求められる。 各パラメータを設定する時に、$から書き始めるとそれはpythonの文として解釈されるんだ (注:実際には先頭じゃなくても半角$が含まれているとpythonの文と解釈されるようです)。
B: ん? それはつまり… どういうこと?
A: 前回、「5秒待ちます」という文字列を表示させたときに、この欄に直接「5秒待ちます」と入力すればBuilderが自動的にUnicode文字列と判断して日本語で表示してくれた。B君もありがたいなあって言ってただろう? それに対して、$から書き始めるとBuilderはここに入力されているものを勝手に解釈せずに純粋にpythonのコードとして埋め込もうとする。例えばTextの欄に以下のように入力して実行して見たまえ。
$5秒待ちます
B: どれどれ…。全く動きませんね。エラーメッセージすら出ない。
A: ここから先はある程度pythonを知ってる人向けに書くので、初心者の方はとにかく「$には特別な意味がある」ということだけを押さえて読み飛ばしてほしい。
------------------------------ ここからスキップ ------------------------------
A: $を付けるとpythonのコードとして解釈されるということは、pythonの文法で明示的に文字列であると宣言すれば正常に動作するはずである。確認しよう。まず以下のようにstr文字列として宣言してみる。
$'5秒待ちます'
B: 実行してみます。…ああ、毎度おなじみのUnicodeDecodeErrorが出ますね。じゃあ次のようにすれば大丈夫っていうことですか?
$u'5秒待ちます'
A: 試してみたまえ。
B: どれどれ。…動きますね。ばっちりです。
A: これで理解してもらえたかな。最後にひとつ。先頭に半角の$が入っている文字列を表示したい場合はどうする?
B: え? えーと、えーと…
A: 文字列の中に特殊な文字が出て来る時の定番の書き方があるだろう。
B: あ、 \ ですね! じゃあこう書けばいいのかな?
\$から始まる文字列
A: その通り。動作確認した限りでは、どうも文字列の先頭だけじゃなくて途中に出てきても同じ現象が起きるので、どうやら半角$を文字列中で使う場合は\$と書くようにした方がよさそうだ。
B: へーい。
------------------------------ ここまでスキップ ------------------------------
A: さて、道のりは遠いな。これでCの欄の説明は半分おしまい。残り半分は$に続くpythonの文の意味だ。これは例題18-3をきちんと理解してくれているかどうかがカギだ。B君は当然大丈夫だよな?
B: うっ、プレッシャー。getTime()っていうのは例題18-3で出てきたpsychopy.core.Clock.getTime()ですよね? あのストップウォッチの。
A: よしよし。ご名答。ここからがポイントだが、Builderでは何も指定しなくても自動的に生成されるタイマーがある。$を使ってpythonの文を埋め込めばこれらのタイマーの値を参照できるんだ。
B: これ「ら」? 複数あるんですか?
A: 以下のタイマーが定義される。
globalClock |
実験が開始してからの経過時間を保持している。 |
(routine)Clock |
現在のRoutineが開始してからの経過時間を保持している。(routine)にはRoutine名が入る。例えばtrialというRoutineならtrialClock、practiceというRoutineならpracticeClockという名前になる。 |
routineTimer |
現在のRoutineの終了予定時刻までの残り時間を保持している。終了予定時間が定まらない(参加者がキーを押すまで待つなど)Routineでは参照可能だが値は無意味。 |
B: いま参照しようとしているタイマーはtrialClockですから…現在のRoutineはtrialっていう名前なんですか?
A: 次回説明しようと思っていたんだが、現在編集中のRoutineの名前はRoutineペインの上部のタブに書いてある。
B: あ、確かに。
A: Builderで実験を新規作成したときに標準で作成されるRoutineはtrialという名前になるので覚えておくといい。
A: さて、そこまでわかればもう話は簡単。$がついているからpythonの文として解釈される。で、trialClock.getTime()を評価するとtrial Routineが始まってからの経過時間が得られるんだから、これで目的を達成できることになる。
B: ふー。難しかったですがこれでばっちりですね。
A: いや、最後にもうひとつ。Dの欄の解説が残っている。
B: あ、そういえば。これはなんですか?
A: Builderでは、刺激が更新されるタイミングを以下の三通りから指定しなければいけないんだ。更新しなくてよい刺激に対する処理を極力減らして処理落ちを防ぐためだろうな。
constant |
最初に初期化された時に値が決定され、その後実験の最後まで変更されない。 |
set every repeat |
Routineが呼び出される度に値が更新される。Routineの実行中は更新されない。 |
set every frame |
画面を描画する度に(=毎フレーム)値が更新される。 |
B: set every repeatとset every frameの違いがよくわかりませんが…。
A: set every repeatは次回18-2a.pyをBuilderで再現する時に使うのでその時に詳しく触れるとして、実際の実験の時にはRoutineは条件をかえながら繰り返し実行しないといけないわけで、今回は○○条件でこのRoutineを実行してね!という指定をしたい時にset every repeatを使うといいのだ。
B: ふーむ。次回改めて聞きます。
A: で、18-1.pyを再現するためにはフレーム毎に経過時間を更新しないといけないので、set every frameを指定する。
B: なるほど。
A: なお、初期値ではすべての項目でconstantになっているが、 これを変更し忘れて「先生!刺激が動きません!!」っていうのは定番のボケなので注意するように 。
B: ああー。やってしまう自分が目に浮かぶようだ。
A: これでいよいよ準備OK。じゃあ実行してみよう。
B: おお、ちゃんと動いているっぽいですが小数点以下の桁数がすごいんですけど。
A: trialClock.getTime()の値をそのままぶち込んでいるからな。どうすればいいと思う?
B: うーん。$がついていればpythonの文として解釈されるんですよね。ということは%演算子とかもそのまま使える?
A: そうそう、いいぞ。
B: ということは、%演算子を使ってこれでどうだ!
$'%.2f'%(trialClock.getTime())
A: んー。いいね。実行してみて。
B: おお、ばっちりだ。狙い通り動くと気持ちいいですね!
A: %演算子の意味が分からない人は 例題3-4 をご覧くださいね。例題3は基本的な演算子を網羅しているのでpythonの文法に詳しくない方はぜひどうぞ。
B: これで18-1.pyの再現は達成ですか。
A: そうだね。ここで一区切り、というところだけど、せっかくタイマーの値を利用して刺激を更新するテクニックを紹介したので少し遊んでみよう。「5秒待ちます」の文字列のOrientationにタイマーの値を突っ込んで文字列を回転してみる。Orientationの欄に以下のように設定してみて。Orientationの単位はdegなので、90を掛けると1秒間に90度回転することになる。
trialClock.getTime()*90
B: あれ、先頭の$は?
A: ダイアログをよく見るんだ。Orientationの欄には$がすでについているから$を入力する必要がないんだ 。
B: え? どういうことですか?
A: これだよ、これ。
B: ああっ、全然気づかなかった! 何ですかこれ?!
A: Orientationには角度を表す数値が入るのであって、文字列が入るはずがない。だからわざわざユーザーが$を入力しなくてもpythonの文として解釈されるように最初から$がついているのだ。
B: ああー。じゃ、Textには…やっぱりついていない。だからTextの欄にpythonの文を書く時には$を付ける必要があったんですね?
A: その通り。
B: なるほどなるほどー。全然気にしてなかった。
A: じゃあ実行してみるか。
B: うわっ、キモい。何だか奥行き方向に傾いているように見える。
A: ああ、unitsがnormだから回転したときに縦横比のつじつまを合わせようとして変なことになっているんだな。これは単位を例えばpixにすれば直るはず。これでどうだ。
B: ああ、だいぶ和らぎましたがまだちょっと傾いているような?
A: んー。微妙だな。このあたりは一度ちゃんと検証した方がいいような気がするが、ちょっと今はそこまで踏み込めないな。というわけで今回はこれでおしまい。次回で18-2.pyの再現を目指しつつ、RoutineとFlowを使って「条件をかえてRoutineを繰り返す」ことに挑戦してみたいと思う。
B: へーい。ちゃんとした実験への道のりはまだまだ遠そうですねえ。
A: 最後に読者の皆さんにちょっとした練習問題。「5秒待ちます」の色を最初描画された瞬間は黄色、徐々に白くなって行って5秒後には完全な白になって終了するようにしてみてください。ただし色空間はRGBとします。解答例は以下の通りですが、なぜこうなるかちゃんとわかりますか? わからなかったら$の意味や色の指定方法を再確認してください。ではまた次回。
$[1,1,trialClock.getTime()/2.5-1.0]