例題18-3:例題01をPsychoPyで¶
A: …。
B: …えー、ごほん。読者から苦情のメールが来ております。わかっていますね?
A: はい、すみません…。
B: 例題18-2では「公式サンプル」とタイトルに入っているのに、公式サンプルについて全く記述がありませんでした。今後、こういう事がないように深く反省するように。
A: 申し訳ございません…
B: ぼくからもお詫び申し上げます。
A: …それにしてもお詫びするべきのは私やB君じゃなくて作者だと思うのだが。
B: いや、それは僕もそう思うんですが、うかつにそんなことを言って×××××たり×××××たら嫌じゃないですか。
A: うむ。それは嫌だな。耐えるしかないのか。
B: 世の中は理不尽なものなのです。
A: さて、今回の話題だが…
B: あれ? 公式サンプルの件は放置ですか?
A: いや、本当に大したことを言うつもりはなかったんだよ。単にCoderのメニューの"Demos"からCoder用のサンプルがいろいろ見られるので見ておいてね、とかBuilderにも同じように"Demos"があるけどインストール直後の状態だと展開されていないので"Unpack demos..."を選んでホームディレクトリなどに展開してから見てね、とかその程度で済ませるつもりだった。内容まで踏み込もうと思ったらそれだけで数回分の分量になるだろうからな。
B: ふーん。なんでBuilderのデモはいちいち展開しないといけないんでしょうね?
A: 多分Builderのデモはデータの保存などでディスクに書き込みが生じるからだと思うけどね。PsychoPy本体はたいてい管理者権限がないと書き込み出来ない場所にインストールされるから、そこでデモを実行すると「書き込み権限がない」って怒られてデモが動かない。
B: なるほど。
A: え、改めて今日の内容だが…。PsychoPyをなかなか取り上げなかった理由の一つに、PsychoPyは使い方が何通りもあって解説しづらいというのがある。さらに言うと、恐らく「何通りもある使い道」の中で最も希望が多いのが「Builderを使ってスクリプトを書かずにらくらく実験!」だと思うんだが、そりゃ私が一番苦手というか経験のない使い方なんだよ。
B: Aさんがブツブツ言いながらプログラム書いてるところはしょっちゅう見かけますが、Builderの画面なんて今回初めて見ましたからねえ。
A: で、方針なんだが、まず懐かしの例題1を例題1-3までCoderで作ってみる。続いて例題1-2までBuilderで作ってみる。これでPsychoPyの基本的な考え方や操作を概観する。
B: ふむふむ…って、なんでBuilderは1-2までなんですか?
A: 例題1-3をBuilderで作るスマートな方法がまだ思いつかない。泥臭い方法ならすぐにでも出来るんだが。
B: 泥臭くても紹介しておいた方がいいんじゃないんですか。
A: ま、その時考える。で、その後は多分Coderを中心にもう少し複雑な話をすることになるんじゃないかと思う。
B: これまでの例から言って予定通りにいかないに決まっているような…
A: さて、いよいよ今回の内容だが、例題1-1「5秒待つ」をPsychoPyで書き直すと以下のようになる。
行番号なしのソースファイルをダウンロード→ 18-1.py
1#coding: utf-8
2from psychopy import visual, core, event
3
4myWin = visual.Window(units='norm')
5msg = visual.TextStim(myWin, text = u'5秒待ちます',pos=(0, 0))
6counter = visual.TextStim(myWin,text='####',pos=(0, -0.5))
7
8trialClock = core.Clock()
9t = 0
10while t < 5.0:
11 t=trialClock.getTime()
12 counter.setText('%.2f' % (t))
13 msg.draw()
14 counter.draw()
15 myWin.flip()
A: 比較のために01-1.pyも載せておこう。ちなみに1行目を見ればわかるようにPsychoPyのサンプルはUTF-8、VisionEggのサンプルはShift-JISでコーディングしてあるが、これは PsychoPyのCoderがUTF-8でなければ日本語を含むファイルを正常に開けない からこのようにしている。 Coderを使わずにテキストエディタで直接作成してpythonのスクリプトとして実行する場合はPsychoPyでもShift-JISを使っても問題ない 。
1#!/usr/bin/env python
2# -*- coding:shift_jis -*-
3from VisionEgg import *
4from VisionEgg.Core import *
5from pygame import *
6
7
8VisionEgg.start_default_logging()
9VisionEgg.watch_exceptions()
10VisionEgg.config.VISIONEGG_GUI_INIT = 1
11
12screen = get_default_screen()
13
14ct = st = VisionEgg.time_func()
15while ct-st<5.0:
16 ct = VisionEgg.time_func()
17 event.get()
18 screen.clear()
19 swap_buffers()
B: あー。懐かしいなあ。でもこれ、PsychoPyのとVisionEggのとずいぶん違うんじゃないですか? 18-1.pyの方は「u'5秒待ちます'」とか文字列が入っているし。
A: うむ。まったく同じでも面白くないかと思って。両者の対応している部分を列挙してみよう。
# PsychoPy(18-1.py) VisionEgg(01-1.py)
# モジュールの読み込み
# 2行目 3-5行目
from psychopy import visual, core, event from VisionEgg import *
from VisionEgg.Core import *
from pygame import *
# スクリーンの生成
# 4行目 12行目
myWin = visual.Window(units='norm') screen = get_default_screen()
# 時計の初期化
# 8行目 必要なし
trialClock = core.Clock()
# 時刻の取得
# 11行目 14行目など
trialClock.getTime() VisionEgg.time_func()
# スクリーンの消去
# 必要なし 18行目
screen.clear()
# バッファのスワップ
#15行目 19行目
myWin.flip() swap_buffers()
B: ふーむ、似ているような、でも似ていないような…。
A: いっぺんに説明するとややこしいから少しずついこう。まず、PsychoPyの機能を利用するためにはPsychoPyのモジュールをimportする必要がある。もしPsychoPyに興味があっていきなり例題18から読んでるけどpythonなんてまるっきりわかりません!という方がいたら例題1も併せて読んでくださいね。要するに…、と。私がいうより復習もかねてB君に説明してもらった方がいいかな?
B: へっ? 僕がですか?
A: importとは何だ?
B: えー、ええと。誰かが書いてくれた便利なプログラムをまとめたモジュールというのがあって、それをpythonに取り込んでpythonをパワーアップする…ような何か?です。
A: なんだよ、「ような何か」って。まあ大体いいだろう。ここではpsychopyからvisual、core、eventというモジュールを読み込んでいる。visualは視覚刺激をどうのこうのするモジュールで、coreは時計やらなんやら。eventはキーボードやマウスの入力を扱うモジュールだ。18-1.pyではeventは利用していないがすぐ後に紹介する18-2a.pyで使うので読み込んである。
B: …自分だって「どうのこうの」とか「なんやら」とか言ってるじゃないですか。
A: まあまあ、それはそれ、これはこれ。続いて4行目のpsychopy.visual.Window()は刺激を描画するためのスクリーンを生成する。VisionEggのVisionEgg.Core.get_default_screen()に相当する。VisionEggとの大きな違いはVisionEggでおなじみの画面の解像度などを設定するダイアログが出てこないことだ。だからスクリプト内で解像度などの設定をすべて済ませておく必要がある。
B: へえ、それってどうやるんですか?
A: psychopy.visual.Window()の引数として与えるのだが…。どうするかな。VisionEggをやらずにいきなりPsychoPyへ来ている人に情報を出しすぎると混乱するかなと思って触れるつもりはなかったんだが、主要なパラメータを一応書いておくか。良くわからない方はとりあえず飛ばしてくださっても問題ありません。
引数 |
概要 |
size |
ウィンドウのサイズをタプルで指定する。デフォルト値は(800,600)。Monitor Centerの設定と違うサイズを指定するとnormなどの単位を使っている時に面倒なことが生じるので注意。 |
color |
背景色をタプルで指定する。デフォルト値は(0,0,0)。標準では3つの値がそれぞれR、G、Bに対応する。 各値は-1から1の範囲で指定する。0から1ではないので注意! |
fullscr |
フルスクリーンモードでウィンドウを開くか否かを指定する。何も指定しなければPreferenceに従う。Preferenceの設定にかかわらず強制的にフルスクリーンモードにしたければTrue、しなくなければFalseを指定すればよい。 |
allowGUI |
ウィンドウの枠やマウスカーソルを表示するか否かを指定する。何も指定しなければPreferenceに従う。Preferenceの設定にかかわらず強制的に表示したければTrue、しなくなければFalseを指定すればよい。 |
monitor |
Monitor Centerで管理しているディスプレイの設定を適用する。Monitor Centerに登録しているディスプレイ名を文字列として与えるとよい。 |
units |
刺激描画に標準で用いる単位を指定する。何も指定しなければPreferenceに従う。Preferenceの設定にかかわらず特定の単位を使用したい場合はその単位(norm, height, pix, cm, deg)を文字列として与えるとよい。 |
B: 結構たくさんありますね。
A: これでもだいぶ省略したんだがな。後はガンマ補正がどうとか色空間がどうとか、そういうパラメータが多いので、そっち系の人は頑張ってヘルプを読んでくださいねっ☆ と。
B: …なんですかその☆は。
A: そこはスルーしろよ。あと、刺激を描画した後に実際に描画するためにスワップという作業が必要なのは 例題1-4 、 例題11-2 で説明した通りだが、この作業はflip()というメソッドを用いる。18-1.pyの15行目だね。
B: ふむふむ。
A: VisionEggに慣れている人にちょっと注意してもらいたいのは、VisionEggのswap_buffers()が関数だったのに対して、PsychoPyのflip()はpsychopy.visual.Windowのクラスメソッドだということだ。PsychoPyではとにかくpsychopy.visual.Windowオブジェクトがさまざまな処理の起点となる。
B: はあ。よくわかりませんがとにかくmyWin.flip()と書けばいいんですよね。
A: psychopy.visual.WindowオブジェクトがmyWinという変数に格納されていれば、な。まああまり慣れていない方はB君が言うようにとにかくこう書くんだと覚えておくとよいかも知れません。ちなみにVisionEggの公式サンプルでは刺激描画スクリーンはscreenという変数に格納されることが多いですが、PsychoPyの公式サンプルではWinとかmyWinとかいう名前の変数に格納されることが多いような気がします。
B: WinはWindowのWinですよね。
A: だね。さて、ここまでは単にVisionEggのサンプルをそのままPsychoPyに対応させることが出来たんだが、ここでいよいよそのまま対応させられないものが出て来る。それが時刻の取得だ。
B: さっきの対応表でさらっと飛ばしたやつですね。
A: 直感的に言おう。 VisionEggのVisionEgg.time_func()はシンプルな時計である。PsychoPyのpsychopy.core.Clockはストップウォッチである。
B: ?? なんですかいきなり。
A: VisionEgg.time_func()を呼ぶと、今の時刻がわかる。だから、刺激を表示してから5秒経ったかなあ、という事を知りたければ、刺激を表示した時刻をメモしておいて、引き算をして経過時間を求めなければならない。これが例題1-1で言った事だ。
B: 懐かしいなあ。そうでした。
A: 一方、PsychoPyのpsychopy.core.Clockはストップウォッチのように、それを見ても現在の時刻はわからない。しかし、計測を始めてから何秒経ったかは一目でわかる。だから、今回のように「5秒待つ」という動作をさせたいときには、単にストップウォッチを見て、5秒経過したかどうかを確認すればよい。以上を踏まえて18-1.pyの8行目以降を見てほしい。 まず8行目でストップウォッチを用意して、計測をスタートする。そして、11行目でgetTime()メソッドを用いて何秒経過したかを変数tに格納している。単に5秒待つだけなら別に変数に代入する必要はないのだが、現在の経過時間を画面に表示するためにこのようにしている。 で、10行目のwhile文でtの値が5.0を超えているか否かでループを終えるかどうかを判定している。
B: getTime()の戻り値の単位は秒なんですか?
A: その通り。
B: なるほど…。心理実験だと何秒経ったか?が問題になることが多いので、PsychoPyの方が便利な気がします。
A: んー。まあそれに関しては同意するかな。 あと、 怖い人 から厳しい指摘をいただくかもしれないんで念のため言っておくと、VisionEgg.time_func()が返すのはある時点からの経過時間であって何時何分何秒とかいう時刻ではない。 Windowsの場合はpythonのプロセスが起動してからの時刻かな? そういう意味ではpsychopy.core.Clockとそう違いはないんだが、実験プログラムを書くときの考え方としては上記のような考え方でいいと思う。
B: 怖い人ですか。誰のことだろう。
A: さて、この時刻に関する話をしたところで18-1.pyはおしまい。次のサンプル18-2a.pyへ進もう。
B: へ? 文字列の表示とか、説明していないことがいっぱいあると思うんですが。
A: それは18-2a.pyで触れるのでパス。
B: …例題のつくり方が悪いんじゃないですか? ちょっといい加減すぎるような。
A: んー。そういわれるとちょっと心苦しいな。もう少し吟味したかったんだが、ほら、そろそろ後学期も始まるしな。作者も焦ってんだろ。
B: 仕方ないおっさんですねえ。
A: というわけで18-2a.pyだ。これは 例題1-2 の01-2.pyに対応する。参考のために再掲…と言いたいところだが、45行もあるしどっちがどっちかwebページ上では見分けにくいから必要があれば例題1-2を別窓で開いて見比べてみてほしい。
B: AさんAさん、例題1-2が何をするスクリプトなのか言わないと、この例題から読んだ人にはわからないんじゃないですか。
A: おっと、ナイス指摘。ありがとう。次の18-2a.pyでは画面の中央に文字列を表示します。スペースキーを押すごとに文字列が更新され、3番目の文が終わったら「再生してください」というメッセージを5秒間表示して終了します。例題1ではリーディングスパンテストを段階的に作成していて、ここではとりあえず以下の処理を達成することを目指しています。
参加者が読み終わるまで文を表示しておく
参加者がひとつの文を読み終わったら実験者がスペースキーを押して直ちに次の文を表示する
3文読み終えたら参加者がキーワードを再生するための時間を5秒間確保する
後で再生すべきキーワードを示す下線を引くのは難しいのでそこはとりあえず保留しておく(^^;
B: 今度は顔文字か。さっきの☆といい今日のAさんはちょっと壊れてるな…
行番号なしのソースファイルをダウンロード→ 18-2a.py
1#coding: utf-8
2from psychopy import visual, core, event
3
4sentenceList = [u'お腹すいたなぁ',u'あ、財布忘れた',u'しょんぼり']
5
6myWin = visual.Window(units='norm')
7msg = visual.TextStim(myWin, pos=(0, 0))
8
9for s in sentenceList:
10 msg.setText(s)
11 while True:
12 if 'space' in event.getKeys():
13 break
14 msg.draw()
15 myWin.flip()
16
17
18msg.setText(u'再生してください')
19
20trialClock = core.Clock()
21while trialClock.getTime() < 5.0:
22 msg.draw()
23 myWin.flip()
B: VisionEgg版が45行なのにPsychoPy版は23行しかないんですね。
A: うむ。理由はいろいろあるが、順番に話そう。まず文字列を描画する方法。VisionEggではVisionEgg.Text.Textというクラスを用いたが、これに対応するのがpsychopy.visual.TextStimだ。6行目だね。引数に表示するテキストや表示位置を指定するのは同じだが、ここでVisionEggと大きく違う点があるのでひとつ指摘しておきたい。 psychopyの視覚刺激のコンストラクタは、第一引数にpsychopy.visual.Windowのインスタンスを受け取る 。VisionEggではViewportを用いて刺激とスクリーンが結び付けられたが、PsychoPyにはViewportにあたるものがないので視覚刺激のオブジェクトを生成する時にどのウィンドウに結び付けるのか指定しておかないといけないという事だな。
B: …なんだかよくわかりませんけど、とにかく第一引数にmyWinと書けばいいんですね?
A: しつこく繰り返すが、psychopy.visual.WindowオブジェクトがmyWinという変数に格納されているのであれば、それでいい。
B: こだわるなあ。
A: 重要だからな。この例題18から読み始めた人にはかなり難しい記述が続くと思うが、例題1を見ていただいたりpythonのテキストなどを見ていただきながら少しずつ慣れて欲しい。
B: で、6行目ですが…その他の引数はpos=(0,0)だけ?
A: そうだな。posは刺激の位置を指定する引数。あとtextという引数で表示する文字列を指定できるが、この例題18-2では後で表示する文字列を入れ替えるのでここで指定する必要はない。
B: 他にはどんな引数を指定できるんですか?
A: んー。これもまだ慣れてないかたはスルーしていただくということで、代表的なものを。他にもantialiasとかboldとかitalicとか面白いのがあるから詳しく知りたい人は例によってヘルプを見てください。
引数 |
概要 |
text |
表示する文字列を指定する。 |
pos |
位置をタプルで指定する。デフォルト値は(0.0, 0.0)、すなわちスクリーンの中心 |
color |
フォントの色を指定する。デフォルト値は(1,1,1)。(1,0,0)といったタプルやリストで指定する方法の他、HTMLのような'#FF0047'といった表記やwebカラーによる表記も受け付ける。 |
opacity |
透明度を指定する。0.0が完全に透明で、1.0が完全に不透明。デフォルト値は1.0。 |
units |
この刺激では親ウィンドウの単位と異なる単位を指定したい場合に単位を指定する。何も指定しなければ親ウィンドウの単位に従う。 |
ori |
文字列を回転する。単位はdegで時計回り(反時計回りではない!)。デフォルト値は0.0。 |
alignHoriz |
posで指定した位置が文字列のどこに対応するかを指定する。leftなら左端、rightなら右端、centerなら中央。デフォルト値はcenter。 |
arignVert |
posで指定した位置が文字列のどこに対応するかを指定する。topなら上端、bottomなら下端、centerなら中央。デフォルト値はcenter。 |
height |
文字の高さを指定する。デフォルト値はNone。 |
wrapWidth |
textで指定された文字列がこの幅より広い場合は自動的に折り返される。デフォルト値はNone。 |
font |
使用するフォントを指定する。VisionEggと異なりフォントファイル名ではなくOSに登録されているフォント名を用いる。例えば日本語Windowsでメイリオを指定したいときにはfont='メイリオ'、MS明朝を指定したい場合はfont='MS 明朝'と書く。 フォント名の文字コードに注意! OSに登録されているフォント名のコードがmbcsでスクリプトの文字コードがmbcs以外であればフォント名はu'メイリオ'.enchode('mbcs')と書く必要がある。 |
B: んんん? 特に最後のfontというのはよくわからないぞ…? OSに登録されているフォント名の、文字コード?
A: ビギナーの方もいると思うと無茶な内容かな、と思うんだけどわかっている人向けの記述という事で。ある程度pythonに慣れている人はこれでわかると信じたい。
B: 僕はちょっと自信ないです…。encodeとか見覚えはあるんですが。
A: フォント関連で言っておくと、VisionEggとは異なり PsychoPyでは標準で使用されるフォントで日本語が表示できるようだ 。特にこのフォントが使いたいとかいう目的がなければフォントを指定しなきゃならん事はないと思う。
B: フォントの指定は結構面倒くさかったですからね。これは便利。
A: さて、前置きが長くなってしまったが、とにかくこれで文字列を描画する準備が出来た。で、肝心の描画だが、これも文字列に限らず他の視覚刺激に共通だから覚えておいてほしい。PsychoPyの場合、 描画したい刺激はウィンドウをflip()する前に全てdraw()する必要がある 。自動的にdraw()してくれるAutoDrawという機能もあるが、それはいずれ紹介する。
B: ふんふん。
A: 従って、VisionEggのparameters.onにあたるような属性は存在しない。VisionEggならparameters.onにTrueかFalseを設定して刺激を描画するか否かをコントロールしていたが、それはPsychoPyではdrawを呼ぶか呼ばないかでコントロールすることになる。
B: ふーん。VisionEggでもVisionEgg.Core.Viewport.draw()をswapする前に毎回読んでいましたから、あまり違和感はないかも。
A: まあ、その辺の感じ方は人それぞれかな。私はやっぱりViewportを使う方がしっくりくるのだが…。とにかく、さっきの時刻の計測の違いと、このdrawの違い。そしてあとキー入力などのイベント処理の違いを押さえれば、VisionEggな人は簡単にPsychoPyに移行できると思う。Gaborパッチはどう描くの?とか個別の問題はあるが、実験プログラムの骨格部分はこれで書けるはずだ。
B: えっ、そんな大胆発言しちゃっていいんですか。
A: PsychoPyらしいというか、PsychoPyならではのスクリプトを書きたいと思うのなら、もっと覚えるべきことはある。だが、単にVisionEggが最近更新されないから乗り換えようかなぁとか思っている人は、後の部分は自分流に書いてしまえばいいのだ。
B: はー。そんなもんですか。
A: 自分が書きたいように書けばいいのさ。そして 動くプログラムこそが美しいプログラムである 。動いてほしいように動けばそれでいいのさ。
B: Aさんのソレ、ひさしぶりに聞きましたね。
A: で、もうひとつの相違点であるイベント処理だが…
B: あ、なんだかAさんがまとめに入ったので終わったような気がしていました。
A: おいおい、しっかりしてくれよ。
B: っと、そういえば文字列を入れ替える処理を説明していただいていませんね。10行目。
A: む? そうだっけか?
B: 聞、い、て、い、ま、せ、ん。Aさんこそしっかりしてくださいよ。
A: あー。ちょっと先を急ぎすぎてまた飛ばしてしまうところだった。いかんいかん。 B君の言うとおり、9行目のfor文でsentenceListに格納された文それぞれについて処理を繰り返している。で、どんな処理を繰り返しているかというと、10行目で文字列を描画するためにTextStimに設定して、11行目以降のwhile文でスペースキーが押されるのを待ちながらflip()をし続ける。10行目の処理がVisionEggと大きく異なるのは、TextStimのクラスメソッドであるsetTextを使って文字列を設定しているところだな。このサイトのVisionEggのサンプルプログラムではparameters.textというデータ属性に直接手を突っ込んで書き換えるという 下品 な事をしていたが、PsychoPyでは行儀よくクラスメソッドを使って書き換えるんだ。
B: 今まで教えてもらっていたやり方は 下品 なんですか?
A: ん? まあ上品ではないわなあ。
B: Aさんの上品とか下品とかいうセンスがわかりません…。
A: PsychoPyでも下品なやり方が出来るのかどうか面倒くさいので試していない。とにかく、 PsychoPyでは視覚刺激オブジェクトを生成した後でパラメータを変更したいときにはクラスメソッドを使う 。例えば引数posで指定したものを後で変更したければsetPos()、oriを後で変更したければsetOri()、textを後で変更したければsetText。 だいたいfooを後で変更したければsetFooという対応になっている のでわかりやすい。
B: ふむ、なるほど。
A: さて、やっとキー入力のイベントについてだが。これは12行目がカギだ。psychopy.event.getKey()という関数はpygame.event.get()と似ているが、以下の点で異なる。
キー押しイベントだけが報告される。マウス等のイベントが報告されないのは当然として、キーを放したというイベントも報告されない。
押されたキーの名前を示す文字列を並べたリストが戻り値として得られる。pygame.localsに相当するものは必要ない。
B: キー放しが報告されないのか。それで大丈夫なのかな。
A: 今までの例題でpygame.locals.KEYDOWN以外の定数を使ったか? ていうかキー放しに相当するpygame.localsの定数ちゃんと覚えてるか?
B: はっはっは、そんなの覚えてるわけないじゃないですか。
A: 開き直るんじゃない。例題3-2、例題7-1、例題12-4で出てきているから後で確認しておくように。
B: へーい。 …ってそもそもキー放しのイベントなんて使わないよね?っていう話をしてたんですよね。今まで全然使わなかったんだし、確認しなくてもいいんじゃないんですか?
A: 屁理屈言うな。そういうことを覚えておくといざという時に役に立つんだぞ。
B: えー。(疑いのまなざし)
A: ま、まあともかく。キーの名前が文字列で返ってくるので、12行目にあるようにin演算子でチェックしたいキーの名前が含まれているかどうかを確認することが出来る。今までのVisionEggのサンプルと比べるとシンプルだな。
B: スペースキーが'space'という名前なのはわかりますが、他のキーはどうなんですかね。一覧表とかないんですか?
A: まだちゃんと確認できていないんだが、私が今まで使った範囲ではpygame.keyのcommon nameに一致しているように思う。一覧表は長大になるのでpygameのドキュメントのpygame.keyのページ( http://www.pygame.org/docs/ref/key.html )を見てくれ。
B: ほいほい。
A: ちなみにこのgetKeysはtimeStampedという引数にpsychopy.core.Clockのインスタンスを与えてやると、ストップウォッチさながらに押したキーとその時のタイムスタンプを並べたタプルを返してくれる。反応時間を測りたいときなんかは便利な機能だね。いずれ解説することもあると思う。腕に自信がある人は自分でどんな値が戻ってくるのか試してみてね。
B: だいぶAさんも疲れてきましたね。今回は長丁場だ…。
A: うん。もういい加減終わりたい気持ちでいっぱいなんだけど、あともう一つスクリプトが残っているんだ。
B: げー。
A: 次の18-3.pyでは、 例題1-3 のように「実験っぽくする」。長いスクリプトだが解説すべきことはひとつだけだから安心しろ。えー、例題1-3をご存知でない方向けに補足しておきますと、例題1-3では以下の事を目指します。
例題1-2で作ったものはリーディングスパンテストの一試行に相当するが、これを拡張して実際の実験のように複数の試行を連続して実施する。
後で再生すべきキーワードを示す下線を引く部分は相変わらず保留(^^;
行番号なしのソースファイルをダウンロード→ 18-3.py
1#coding: utf-8
2from psychopy import visual, core, event
3import random
4
5sentenceList = [u'それは、ゆれながら水銀のように光って上に上がった',
6 u'二人の子供が、青い湖のそばで遊んでいた',
7 u'祖母は黙って家の外を眺めるような目つきをしていた',
8 u'ドライアイスは冷凍食品を冷やすのにちょうどよい',
9 u'人間は氷期と間氷期を何度も経てゆっくりと進化してきた',
10 u'この色は実際は桜の皮から取り出した色なのだった',
11 u'上の面や横の面は、青く黒く金属のように見える',
12 u'これは現実世界で起こっている出来事と同じである',
13 u'一番下の弟が、まぶしそうに目を動かしながら尋ねた',
14 u'さまざまな工夫をこらして、西洋の言葉を学ぼうとした',
15 u'彼は、人々の信頼に答えようと、昼も夜も働いた',
16 u'農民たちは稲も麦も豊かに実ってくれるものと期待した',
17 u'その男は会議で熱弁をふるって警告を発した',
18 u'彼はかぜをひいて下宿で寝ていたが、知らせを聞いてはね起きた',
19 u'その子供は目を丸くして、分からないという表情をした',
20 u'地上に降った雨は海へ流れていくが、雪は降り積もる',
21 u'文法が分かるからといって、英語が通じるとは限らない',
22 u'父が娘あての手紙に、しっかり勉強するようにと書いた',
23 u'彼も、科学的な調査の結果を見せられては、反論できなかった',
24 u'厳しい寒さの中を、私は20年ぶりに故郷へと帰った',
25 u'夫が車椅子に乗るようになってから、12年が過ぎた',
26 u'物事に対する自分の心の動きに注意深く目を向けよう',
27 u'ある人から鈴をもらい、私はそれを椅子にぶら下げた。',
28 u'小学生たちは、元気に夏休みの一日一日を過ごしていった',
29 u'突然の知らせに二人は声も出ず、座り込んでしまった',
30 u'公園で昼寝をしていたら、大きな蜂に刺された',
31 u'用語の中には、漢字で日本語に訳されているものもある',
32 u'私たちは、日ごろさまざまな問題に出会う',
33 u'その技術のレベルはしろうとの域をはるかに超えている',
34 u'男は今日は海に出るのはよした方がいいとその子に注意した',
35 u'電車に乗り遅れたので母に送ってもらった',
36 u'彼はぶっきらぼうだが、根はいいやつだと思う',
37 u'妹が帰ってくる日、私と弟は家庭菜園のかぼちゃを全部収穫した',
38 u'野球が初めて日本に伝えられたのは明治5年ごろである',
39 u'死んだ父親は筆まめな人で、頻繁に手紙をよこした',
40 u'気がつくとボートは、浜辺に打ち上げられていた',
41 u'転校生は彼女と目があったとたんに、友達になれそうだなと思った',
42 u'その人は美しい色の糸で織った着物を見せてくれた',
43 u'少女がそこで見たのは、信じられないような事件だった',
44 u'近くの駅からその町の駅までは、特急でおよそ3時間かかる',
45 u'そのパイロットは昔から真夜中に星空を眺めるのが好きだった',
46 u'彼はその日から、道のでこぼこを通るのが楽しみとなった',
47 u'船乗りは子猫を丘の上の自分の家に連れて帰った',
48 u'子供達は、とても月が明るいので皆で外へ出かけた',
49 u'祖父はひと月後に、永遠にまぶたを閉じたのである',
50 u'この時突然、私の脳裏に子供の頃の光景が浮かんできた',
51 u'その日は、久しぶりに朝から夕方まで雨が降り続いた',
52 u'昼食をとった後、私はぶらぶらとその辺を散歩した',
53 u'茶の間に座っていた父は、はだしで表へ飛び出した',
54 u'降りしきる雨に、池の堤防はもろくもくずれた',
55 u'世界には2000以上の言語があると言われている',
56 u'彼には妻はなく、内気な妹と二人で暮らしている',
57 u'その朝早く、私はわが家の門の前に立っていた',
58 u'母親は封筒を初めて見たとき、ひどくびっくりした',
59 u'その日は、山小屋には羊飼いも誰も来ていなかった',
60 u'彼はゆっくりと白い自転車を走らせて運動場を回った']
61
62setSizes = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5]
63sentenceIndex = range(len(sentenceList))
64random.shuffle(sentenceIndex)
65myWin = visual.Window()
66msg = visual.TextStim(myWin, pos=(0, 0))
67
68currentSentence = 0
69trialClock = core.Clock()
70
71for maxSentences in setSizes:
72 trial = 1
73 while trial <= maxSentences:
74 msg.setText(sentenceList[currentSentence])
75 event.clearEvents(eventType='keyboard')
76 while True:
77 if 'space' in event.getKeys():
78 break
79 msg.draw()
80 myWin.flip()
81 trial += 1
82 currentSentence += 1
83
84 msg.setText(u'再生してください')
85
86 trialClock.reset()
87 while trialClock.getTime() < maxSentences * 5.0:
88 msg.draw()
89 myWin.flip()
90
B: うーん、これも懐かしい。
A: リーディングスパンテストは2文条件、3文条件、と音読する文の数がどんどん増えていき、それに伴って再生に使える時間も1文あたり5秒ずつ伸びていくという特徴を持っている。これをどのように実現しているのか、その仕組みをきちんと説明すると長くなりますので、初心者の方は 例題1-3 をご覧くださいませ。
B: (懐かしさにひたっている)
A: さて、そのあたりの説明をざっくり省略してしまうと、ここで説明すべきことはひとつだけ。いや、ふたつかな? 第一の点は86行目。先ほど言った通りPsychoPyの時計はストップウォッチ。当然0にリセットする機能がある。それがこのreset()メソッド。reset()を呼ぶとストップウォッチの値が0になるので、参加者がキーワードを再生する時間を測り始める直前にreset()すればストップウォッチを何個も用意しなくても使いまわせる。
B: 同時に複数の時間を測りたいときには複数のストップウォッチを用意できるんですよね?
A: もちろん出来る。例えばある試行が始まってから何秒経ったかを測ると同時に今のブロックが始まってから何秒経ったかを測りたい場合とかだな。
B: すごく便利そう…と思ったけど、VisionEggの場合とあまり変わらないかも。
A: そうだね。試行やらブロックやらが始まった時刻を保持した変数を用意するか、それとも試行やらブロックが始まってからの時間を測るストップウォッチを保持した変数を用意するか、だけの違いだからね。まあそのちょっとした違いがささーっとスクリプトを書くときにミスの原因になったりするんだが。
B: 大抵の人はVisionEggとPsychoPyの二刀流なんていう変態なことはしないと思うんで大丈夫だと思いますが。
A: まあね。あと、ちょっと補足しておくと、87行目から89行目の参加者の再生待ちのwhileループでは、先に言ったようにwhile文の条件式の中に直接getTime()を書いてある。18-1.pyのように現在の経過時間を表示しないのならこれで十分だ。
B: おお、そういえばそんな話しましたね。長かったのですっかり忘れていました。
A: そうだな。こんなに長い話は久しぶりにした気がする。第二の点はさらっと済ませるか。75行目のpsychopy.event.clearEvents。これは文字通りイベントを消去するメソッドで、「再生してください」とメッセージが表示されている間にスペースキーをげしげし押してしまうと、キー押しイベントが蓄積されてしまって次の試行が始まった瞬間に二番目の文に進むのを防ぐためのものだ。
B: あー。それはやってしまいそうな気がする。すごーくやってしまいそうな気がする。目に浮かぶようだ。
A: ???と思われる方は、ぜひ75行目をコメントアウトしてこのサンプルを実行し、「再生してください」と表示されている間にスペースキーを押してみてください(75行目の"event"の直前に半角の#を挿入する)。正常に動作していればソースコードの5行目から60行目に入力されている文がこの順番に表示されるはずですが、一文飛ばされて表示されてしまうはずです。
B: 引数のeventTypeというのは…?
A: キーボードのイベントは消したいがマウス操作に関するイベントは消したくない、という場合に指定する。値は'mouse', 'joystick', 'keyboard', Noneのいずれかで、指定した値のイベントが消える。75行目ならキーボードに関するイベントだけが消えるわけだね。Noneならすべてのイベントが消去される。省略するとNoneが指定されたことになる。 B: ふむふむ、なるほど。
A: さて、ずいぶん長くなってきたし、そろそろ例題18-3はおひらきにするかな。
B: …。
A: ん? 何?
B: いや、18-2a.pyなんですが。18-2a.pyっていうことはいつものAさんのやり方からすれば18-2b.pyとか18-2c.pyがあるんじゃないんですか?
A: …バレた?
B: 当然。
A: んー。疲れたからこのまましらばっくれようと思ったんだがなあ。ダメ?
B: そりゃもう、Aさんの良心にお任せします。
A: んぐぅ、そんなこと言うか?
B: いや、僕がどうこう言うこっちゃないでしょ。
A: …はぁ、少しだけ解説するか。VisionEggとPsychoPyの違いのひとつなんだが、どうも静止画を出してただ参加者の反応を待っているようなときに、PsychoPyは画面をスワップしたりイベントを取得したりしなくても「アプリケーションは応答していません」状態にならないみたいなんだよな。長時間のテストはまだしていないが、少なくともVisionEggのように短時間でそうなったりはしない。そこで、いちいちflip()し続けなくてもこういう書き方がPsychoPyならできるよ、という例のつもりで用意した。
行番号なしのソースファイルをダウンロード→ 18-2b.py
1#coding: utf-8
2from psychopy import visual, core, event
3
4sentenceList = [u'お腹すいたなぁ',u'あ、財布忘れた',u'しょんぼり']
5
6myWin = visual.Window()
7msg = visual.TextStim(myWin, pos=(0, 0))
8
9for s in sentenceList:
10 msg.setText(s)
11 msg.draw()
12 myWin.flip()
13 key = []
14 while 'space' not in key:
15 key = event.waitKeys()
16
17msg.setText(u'再生してください')
18
19countDownClock = core.CountdownTimer()
20countDownClock.reset()
21countDownClock.add(5.0)
22while countDownClock.getTime() > 0.0:
23 msg.draw()
24 myWin.flip()
A: ポイントは11行目から15行目。18-2a.pyと見比べてもらうとよくわかるんだが、draw()とflip()は各文を最初に画面に描画する時に一回実行しているだけで、後はひたすらキー入力を待っている。そのキー入力を待つ部分でも、psychopy.event.waitKeys()というキーが押されるまでひたすら待つ関数を使っている。押されたキーがspaceじゃなかった場合に再び待たないといけないのでwhileを使っているが、全体的に無駄(?)な繰り返しが減っていることがわかるだろう。
B: ふむ。今までVisionEggしか知りませんでしたら不自然に思いませんでしたが、やっている作業を直感的にスクリプトに落とし込むとこっちの方が自然なのかもしれませんねえ。
A: あとは遊びで5秒測るのにpsychopy.core.CountdownTimerというクラスを使用している。19行目。これは通常のpsychopy.core.Clockと違って値がどんどん減っていく。20行目で時間を0にして、21行目で5秒足して、そして22行目からのwhileループを「残り時間が0秒になるまで」という条件文で回している。普通のpsychopy.core.Clockがあれば別に必要ないものなんだけど、多分こういうのを用意しているのはBuilderの存在と関係あるんだろうな。
B: ふーん。多機能で面白いですが、ややこしいですね…。AさんがVisionEggの方が好きというのもちょびっとだけ共感できるかも。
A: ちょびっとなのかよ。まあ、何通りもやり方を示すのは初心者には混乱の元になると思うんでどうしようか迷ったんだが、VisionEggをすでに使っている人にはPsychoPyにはこういう使い方もあるということを見て欲しくて。全般的に今回の話は初心者向けなのか、VisionEggに習熟した人向けなのか、ターゲットが曖昧になってしまった。
B: 初心者向けにしたらそもそも長すぎだと思いますが。pythonの文法的なことを全然説明していませんし。
A: ま、そりゃ例題1-1から1-3の3回で扱った内容を一気に話したわけだからな。
B: 次は初心者の方にも配慮した分量、内容にするべきかと。
A: ん。次は大丈夫だな。何しろ次回で扱うのはBuilder。Builderは私もズブの初心者なのでその辺は全く問題ないと思われる。
B: ズブの初心者って…そんな人に習っていいのか不安になってきた。
A: じゃあ次回はB君に解説を頼もう。しっかり予習してきてくれたまえ。
B: 遠慮しときます。
A: というわけで、次回は今回やった「とりあえず5秒待つ」、「キーが押されたら次の文を表示する」といった動作をBuilderで作ってみます。乞うご期待。