.. title:: Pythonで心理実験 - 例題12-1


例題12-1:Müller-Lyer錯視の実験
==============================================

**A:** 今回は久々に実験プログラムを題材に取り上げます。例題5-5以来ですね。こういうのこそまさに「例題」という名称にふさわしいと思います。

**B:** ちょ、Aさん、なんですかいきなり。

**A:** B君のそのツッコミも例題11-2からほとんど変化がありませんね。作者が投げやりになっている様子が目に浮かぶようです。 さて、ではさっそく解説に入りましょう。今回の題材は…

**B:** ははぁ、また次回予告と違うこと始めたのが後ろめたいんですね?

**A:** っとっと、ごふごふ。い、いったい何の話かね。それは。

**B:** はいはい。グラフの描画は自分で勉強しときますんで、気の済むようにしてください。

**A:** むむっ、グラフの描画は今回の例題でも取り上げるぞ。確かに予告していた通りの内容ではないんだが…

**B:** まあまあ。実際の実験プログラムの例を出すことも大事でしょうしね。

**A:** B君にそんなフォローされるようではおしまいだな。まあ、実際問題B君の言う通りなんだが。 今回の題材は錯視といえば必ず出てくる:Müller-Lyer錯視、大学心理学を学んだ人のほとんどは初級実験でお目にかかったことだろう。

**B:** ぼくが受けた時はやりませんでしたが…

**A:** だから「ほとんど」って言ってるだろ。たった一件の反例で否定するな。

**B:** へえ、じゃあAさんは何例くらい知ってるんですか?

**A:** ええと、私の出身校と、初めて非常勤をした大学と、…、4校かな。

**B:** Aさんこそ「ほとんど」っていうには無理があるような気がしますが。

**A:** うるさい。とにかく始めるぞ。ええと、読者の皆様、サンプルプログラムは長くなるので最後にまとめて掲載して、解説しておきたいポイントをこれから挙げていきます。 私自身がpythonを勉強し始めたころに書いたものなので、今ならもっとうまい書き方があるよなあと思う点もありますが、敢えてそのまま残してあります。

**B:** 書きなおすのが面倒くさいだけじゃないのかねぇ。


コマンドライン引数
~~~~~~~~~~~~~~~~~~

**B:** ええと、まず最初のこれは何ですかね。

**A:** これはWindowsのコマンドシェルなどのCUI(Character User Interface)が好きな人向けだね。 こんな風にコマンドを打ち込んでpythonスクリプトを起動したときに、スクリプト名の後ろにつけた引数をスクリプトから参照する方法だ。

.. code-block:: doscon


    D:\work>python experiment.py SubjectName color 120


**A:** この例ではexperiment.pyがpythonスクリプトのファイル名で、後ろに続く"SubjectName"、"color"、"120"の3つが引数だ。

**B:** 引数というのは関数のところでも出てきましたね。同じようなものだと思えばいいんですかね。

**A:** まあ、そうかな。ただ、pythonの関数と違って渡された引数をスクリプト内で何という名前の変数で受け取ればいいのかこれではわからないよね。 pythonでコマンドライン引数を受け取るには、sysモジュールimportして、sys.argvというリストを参照すればいい。上の例では、以下のような値が格納されている。

.. csv-table::
    :delim: $

    sys.argv[0]$experiment.py
    sys.argv[1]$SubjectName
    sys.argv[2]$color
    sys.argv[3]$120


**B:** ふむふむ。sys.argv[0]にはスクリプト名そのものが入ってるんですね。

**A:** そう。コマンドライン引数がひとつもない(0個)の場合でもスクリプト名は必ず存在するので、 **引数が0個の時のsys.argvの長さは1になる** という点に注意してほしい。同様に、 **n個の引数がある時はsys.argvの長さはn+1個** だ。 引数の個数で処理を振り分けるときにうっかり間違えやすい。まあ、間違えてたらプログラムが正常に動かないのですぐ気付くとは思うが。
**B:** なるほど。メモメモ。

**A:** この例で注意してほしいのは、最後の"120"だ。 **sys.argvでは引数が文字列として渡される** 。 つまり、「1」と「2」と「0」という三文字の文字列として渡されているんだね。数値として処理するためにはint(sys.argv[3])などとする必要がある。

**B:** 面倒くさいですねえ。

**A:** プログラマが文字列と数値のどっちを意図してんのなんかなんてpythonインタプリタにわかるわけないだろ。 コマンドラインは文字列なんだから、余計なことはせずにそのまま渡してくれる方がいい。

**B:** はあ、そんなもんですかね。

**A:** サンプルプログラムでは、53から64行目でコマンドライン引数がなければ被験者名などの入力するダイアログを表示し、あれば第1引数の名前でデータ出力ファイルを開くという処理をしているので見てみてほしい。


プラットフォームの判別
~~~~~~~~~~~~~~~~~~~~~~

**B:** 続いてプラットフォームの判別ということですが、これは?

**A:** この場合のプラットフォームってのはWindowsとかLinuxとか、pythonスクリプトを実行している環境のことだ。 例えば日本語を表示するときのフォントファイルなど、OSによってスクリプトの実行に必要なパラメータが異なる場合がある。 どちらのプラットフォームで実行できるスクリプトを用意しなければいけない時に便利な機能だ。

**B:** うーん、便利と言われると便利そうな気もしますが、そもそもWindowsとLinuxで同じプログラムを実行しなきゃいけないなんてことあるんですかね?

**A:** 複数の研究拠点で共同研究する場合とか、サンプルプログラムを配布する場合とかなんかがそうだな。

**B:** じゃ、このコーナーのサンプルプログラムなんてまさにぴったりじゃないですか。なんで今までのサンプルではそのプラットフォームの判別?とやらをしてなかったんですか?

**A:** そんなの、面倒くさいからに決まっておろう。

**B:** あー、開き直りましたね。

**A:** おうよ。そこまで気を使ってたら面倒くさくってここまで続かなかっただろうよ。とにかく、プラットフォームを判定するにはsysモジュールをimportしてsys.platformを参照する。

**B:** またsysモジュールですか。

**A:** サンプルプログラムの69から76行目で、プラットフォームがWin32か否かでフォントファイル名を切り替える例を示している。 Win32じゃなければUbuntuと決め打ちしているので、Macとか使ってる人はうまくやっちゃってください。

**B:** 相変わらずAさんはMacに厳しいなあ。なんか恨みでもあるんですか?

**A:** だってMac持ってないんだもの。このコーナーのためだけにMac買うほど裕福じゃないし。さて、次行くぞ、次。


刺激の位置と回転角度の指定
~~~~~~~~~~~~~~~~~~~~~~~~~~

**B:** ええと、これは今までの例題で出てきませんでしたっけ。

**A:** うーむ。例題1でちらっと触れて、その後ろくな解説なしに例題7や8で使ったりしていたんだが、ちゃんと解説したことがなかったなと思って。 特にanchorとorientationを両方指定したい場合にちょっと混乱することがあるので、いつかちゃんと触れておかなきゃなと思っていた。

**B:** anchorは刺激の位置を指定するときにどこを基準にするか、でしたね。orientationはどれだけ回転するか。

**A:** そう。まず、anchorに指定できる「位置」にはどのような種類があるかって点なんだが、これ、VisionEggのhelpに書いてあると思ってたんで詳しく解説していなかったんだけど、改めて確認したら書いてないのよね。 じゃあ私はどこで見たんだったけな?と思ってあれこれ調べたらVisionEggのメーリングリストだった。 これは大事な情報なのにhelpに書かれていないってのはちょっとまずいので、ここにちゃんと載せておこうと思って。こんな感じだ。

.. csv-table::
    :delim: $

    anchorに指定できる位置 左上$'upperleft'$上$'top'$右上$'upperright'
    左$'left'$中央$'center'$右$'right'
    左下$'lowerleft'$下$'bottom'$右下$'lowerright'


**B:** はあ、覚えてさえいれば、特に難しいところはなさそうですね。

**A:** ところがだな、これがorientationとかangleと組み合わされるとちょっと厄介なんだ。この例を見てくれ。 VisionEgg.Text.Text(左の"A")とVisionEgg.MoreStimuli.Target2D(右の正方形)を表示したところなのだが、それぞれanchorはcenter、淡い黄色はangle=0、黄色はangle=45、淡い青色はorientation=0、青色はangle=45が指定されている。


.. figure:: img/12-1-01.png

anchor='center'の場合

**B:** へ、angleとorientationって何が違うんでしたっけ?

**A:** ややこしいんだが、angleはVisionEgg.Text.Textで回転角度を指定する引数、orientationはVisionEgg.MoreStimuli.Target2Dで回転角度を指定する引数だ。

**B:** 指定する引数の名前が違うなんて全然気づいてなかった。

**A:** とにかく、黒い線の交点がpositionに指定されている位置で、いずれの刺激も黒い線の交点に中心が一致していて(anchor='center')、濃い色の刺激は淡い刺激より45度回転している。それはいいかな?

**B:** はい、そりゃそういう風に指定したんですから当たり前ですよね。

**A:** うむ。では続いてanchor='lowerright'にするとどうなるか見てみよう。


.. figure:: img/12-1-02.png

anchor='lowerright'の場合

**B:** ん? なんだか変だな。でも何が変なんだろう?

**A:** まず、anchorを右下に指定したんだから、刺激の右下が黒線の交点と一致するように刺激が配置される。そこまではOK?

**B:** はい。

**A:** 問題はここからだが、VisionEgg.Text.Textでは文字列の右下を中心に回転している。それに対して、VisionEgg.MoreStimuli.Target2Dは図形の中央を中心にして回転しているんだ。

**B:** あー、なるほど。でも、なんで?

**A:** うーん、正直なところ意図がよくわからんな。もう一例見ておこうか。次はanchor='top'だ。


.. figure:: img/12-1-03.png

anchor='top'の場合

**B:** やっぱり文字は上を中心に回転していて、正方形は中央を中心にして回転してますね…。やっぱり納得いかないなあ。

**A:** とにかく、回転中心の決め方が違うので、正方形の上に文字を重ねた刺激を制作して、それを回転させたいとか思った時には注意する必要がある。

**B:** きちんと重なるように座標を計算しないといけないってことですよね。うげぇ、面倒くさそう。

**A:** **anchor='center'なら回転中心は文字でも正方形でも黒線の交点(=position)と一致するんだから、回転させた刺激を重ねるときは全部'center'にすればいいんだよ** 。

**B:** あ、そうか。なるほど。

**A:** サンプルプログラムでは、148から151行目、Müller-Lyer図形の矢羽を描画するところがこの問題と関係がある。 もしTarget2DもTextと同じようにanchorの位置が回転の中心となるならば、右側の矢羽のanchorをleft、左側をrightにしてpositionを主線の端に一致するように指定しておけば、もっと簡単に描画できるんだが、 残念ながらそのようになっていないので、anchor='center'として矢羽と主線がぴったり合うように矢羽の中心の座標を計算している。 面倒だがまあ仕方がないな。なお、anchorとorientation、positionの関係がいまいちよくわからない人のために、上の図を描画するサンプルプログラム( `12-1a.py <source/12-1a.py>`_ )を用意しておいたので参考にしてほしい。

**B:** 三角関数ですね。高校生の時はこんなの大学生になっても使うとは思ってなかったなあ。

**A:** サンプルプログラムの残りの部分は今までの例題を見てきた人なら大体わかるはず。

**B:** ええと…、これ、例題7で出てきたPresentationを使ってたりとか、例題5のキー入力待ち関数っぽいのとか、いろいろ入り混じってますねえ。 キー押しをチェックしてるところ(175行目以降)のgKeys['UP']ってのはあまり見たことがないような?

**A:** それは例題3-3で出てきた「辞書型」の変数だな。最初に言ったように、私自身が試行錯誤していたころのプログラムだから、とにかくいろいろな機能を使ってみている。

**B:** …。最後のグラフの描画の部分がよくわかりませんねえ。っていうか、Aさん「次はグラフの描き方をやるぞ」って何度も予告しては放棄して、まだほとんど解説してくれてないじゃないですか!

**A:** うむ。実はそこも今回解説するつもりだったのだがな。間抜けな作者がこの原稿を書き始めてから「ちょっと例題12-1で全部解説するのはムリ」って気づいたらしいんだな。だから本来予定していなかった例題12-2を設定して、そこでグラフの描画の部分を解説するらしい。

**B:** らしい、ってAさん…。

**A:** そんなわけで、次回に続きます。


+ 行番号なしのソースファイルをダウンロード→ `12-1.py <source/12-1.py>`_


.. literalinclude:: source/12-1.py
    :language: python
    :encoding: shift-jis
    :linenos:
    :lineno-match: