例題7-1:VisionEggのサンプルを読む

B: あの、前から疑問に思っていたんですが。

A: ん、何が?

B: AさんはVisionEggのプログラミングをどうやって勉強したんですか?

A: んー。最初はVisionEggのサンプルから初めて、疑問に思ったことはソースコードを直接読んで、かな。まずはサンプルから始めればいいよ。

B: へ? サンプルなんてあったんですか? どこに?

A: sourceforgeのVisionEggのダウンロードページ( http://sourceforge.net/projects/visionegg/files/visionegg/ )になかったか? どれどれ…。ああ、今(2011/06/09現在)はソースを取得しないといけないのか。

../_images/07-1-01.png

B: どれをダウンロードすればいいんですか?

A: visionegg-1.2.1.zipってやつだな。visionegg-1.2.1.tar.gzもファイルをまとめる方式が違うだけで中身は同じなんだが、Windowsの場合zipなら特別なソフトをインストールしなくても扱うことが出来るのでお勧めかな。

B: じゃあダウンロード、っと。

------------------------------ 翌日 ------------------------------

B: あの、AさんAさん。

A: どうした?

B: あの、サンプルのmouseTarget.pyっていうのを見てみたんですが、Aさんのプログラムと全然違うんですけど。

  • 行番号なしのソースファイルはVisionEggの配布サイトからダウンロードしてください。

  1#!/usr/bin/env python
  2"""Control a target with the mouse, get SDL/pygame events."""
  3
  4# Variables to store the mouse position
  5mouse_position = (320.0, 240.0)
  6last_mouse_position = (0.0,0.0)
  7
  8############################
  9#  Import various modules  #
 10############################
 11
 12import VisionEgg
 13VisionEgg.start_default_logging(); VisionEgg.watch_exceptions()
 14
 15from VisionEgg.Core import *
 16from VisionEgg.FlowControl import Presentation, FunctionController
 17from VisionEgg.MoreStimuli import *
 18from VisionEgg.Text import *
 19from math import *
 20import pygame
 21
 22#################################
 23#  Initialize the various bits  #
 24#################################
 25
 26# Initialize OpenGL graphics screen.
 27screen = get_default_screen()
 28
 29# Set the background color to white (RGBA).
 30screen.parameters.bgcolor = (1.0,1.0,1.0,1.0)
 31
 32# Create an instance of the Target2D class with appropriate parameters.
 33target = Target2D(size  = (25.0,10.0),
 34                  anchor = 'center',
 35                  color      = (0.0,0.0,0.0,1.0)) # Set the target color (RGBA) black
 36
 37text = Text( text = "Press Esc to quit, arrow keys to change size of target.",
 38             position = (screen.size[0]/2.0,5),
 39             anchor='bottom',
 40             color = (0.0,0.0,0.0,1.0))
 41
 42# Create a Viewport instance
 43viewport = Viewport(screen=screen, stimuli=[target,text])
 44
 45################
 46#  Math stuff  #
 47################
 48
 49def cross_product(b,c):
 50    """Cross product between vectors, represented as tuples of length 3."""
 51    det_i = b[1]*c[2] - b[2]*c[1]
 52    det_j = b[0]*c[2] - b[2]*c[0]
 53    det_k = b[0]*c[1] - b[1]*c[0]
 54    return (det_i,-det_j,det_k)
 55
 56def mag(b):
 57    """Magnitude of a vector."""
 58    return b[0]**2.0 + b[1]**2.0 + b[2]**2.0
 59
 60def every_frame_func(t=None):
 61    # Get mouse position
 62    global mouse_position, last_mouse_position
 63    just_current_pos = mouse_position
 64    (x,y) = pygame.mouse.get_pos()
 65    y = screen.size[1]-y # convert to OpenGL coords
 66    mouse_position = (x,y)
 67    if just_current_pos != mouse_position:
 68        last_mouse_position = just_current_pos
 69
 70    # Set target position
 71    target.parameters.position = mouse_position
 72
 73    # Set target orientation
 74    b = (float(last_mouse_position[0]-mouse_position[0]),
 75         float(last_mouse_position[1]-mouse_position[1]),
 76         0.0)
 77
 78    orientation_vector = cross_product(b,(0.0,0.0,1.0))
 79    target.parameters.orientation = atan2(orientation_vector[1], orientation_vector[0])/math.pi*180.0
 80
 81    # Set target size
 82    global target_w, target_h
 83    global up, down, left, right
 84
 85    amount = 0.02
 86
 87    if up:
 88        target_w = target_w+(amount*target_w)
 89    elif down:
 90        target_w = target_w-(amount*target_w)
 91    elif right:
 92        target_h = target_h+(amount*target_h)
 93    elif left:
 94        target_h = target_h-(amount*target_h)
 95    target_w = max(target_w,0.0)
 96    target_h = max(target_h,0.0)
 97
 98    target.parameters.size = (target_w, target_h)
 99
100#############################################
101#  Create event handler callback functions  #
102#############################################
103
104# target size global variables
105target_w = 50.0
106target_h = 10.0
107
108# key state global variables
109up = 0
110down = 0
111left = 0
112right = 0
113
114def keydown(event):
115    global up, down, left, right
116    if event.key == pygame.locals.K_ESCAPE:
117        quit(event)
118    elif event.key == pygame.locals.K_UP:
119        up = 1
120    elif event.key == pygame.locals.K_DOWN:
121        down = 1
122    elif event.key == pygame.locals.K_RIGHT:
123        right = 1
124    elif event.key == pygame.locals.K_LEFT:
125        left = 1
126
127def keyup(event):
128    global up, down, left, right
129    if event.key == pygame.locals.K_UP:
130        up = 0
131    elif event.key == pygame.locals.K_DOWN:
132        down = 0
133    elif event.key == pygame.locals.K_RIGHT:
134        right = 0
135    elif event.key == pygame.locals.K_LEFT:
136        left = 0
137
138# Create an instance of the Presentation class.  This contains the
139# the Vision Egg's runtime control abilities.
140p = Presentation(go_duration=('forever',),
141                 viewports=[viewport])
142
143def quit(event):
144    p.parameters.go_duration = (0,'frames')
145
146p.parameters.handle_event_callbacks = [(pygame.locals.QUIT, quit),
147                                       (pygame.locals.KEYDOWN, keydown),
148                                       (pygame.locals.KEYUP, keyup)]
149
150#############################################################
151#  Connect the controllers with the variables they control  #
152#############################################################
153
154p.add_controller(None, None, FunctionController(during_go_func=every_frame_func) )
155
156#######################
157#  Run the stimulus!  #
158#######################
159
160p.go()

A: ああ、これはVisionEgg.FlowControlを使うやり方だな。この方法については今まで全く解説していなかったからな。

B: VisionEgg.FlowControl? なんですかそれ。

A: 簡単にいえば、今までの例題でwhileループを使って刺激を描画したりキー入力を処理していたけど、 こういった実験の流れを管理してくれるクラスだ。

B: えぇ、なんだか便利そうに聞こえますが、なんで今まで教えてくれなかったんですか?

A: いろいろ理由があるけど、一番の理由は 関数の定義が出来ないと利用できない からだ。 つまり、例題5で紹介した関数定義のテクニックを使いこなせるところまでpythonの学習が進んでいないと使えないんだよ。

B: ははあ、なるほど。

A: おまけに例題5-1で「出来るだけ使うな」って言った global が62行目と82行目、83行目、115行目、128行目に出てきている。 変数のスコープのことをある程度理解していないとここでglobalを使う意義がわからないだろうから、さらにハードルが高い。

B: なんだかよくわかりませんが、少しは考えて例題を選んでるんですね。

A: 「少しは」とは失礼な。そんなわけで、関数の定義や変数のスコープは理解しているという前提で、ざっと解説するぞ。 このプログラムの鍵は140行目のVisionEgg.FlowControl.Presentation()だ。これはさっき言った通り実験の流れを管理するクラスのインスタンスを生成するもので、 メソッドgo()を呼ぶと自動的に実験を行ってくれる。160行目だね。問題はどういう風に実験を「流す」のかだけど、それを指定しているのが45行目以降の関数群だ。

B: ふむふむ。

A: まず、60行目のevery_frame_func()。

B: って、いきなり60行目からですか。

A: その手前は小道具だからね。60行目のevery_frame_func()は、画面を描き換えるたびに 必ず実行してほしい処理が書いてある。例題6までのプログラムの書き方では、whileループの中でscreen.clear()、viewport.draw()、swap_buffers()の定番トリオを実行する 前にする処理だね。ただ、定番トリオの処理はPresentationがやってくれるので、ここに書く必要はない。

B: ずいぶんたくさん処理がありますね。

A: このサンプルではマウスとキーボードの操作に合わせて刺激の位置や大きさを変更している。その処理が結構凝ってるからね。 それで、このevery_frame_func()を画面描画のたびに実行してほしいとPresentationに登録するのが154行目のadd_controller()メソッドだ。 こうやって登録しておけば、go()した後は自動的にevery_frame_func()を呼び出してくれる。

B: このプログラム、160行目のgo()でいきなり終わっていますが、go()はいつ終了するんですか?

A: VisionEgg.FlowControl.Presentationのデータ属性parameters.go_durationに設定しておく。 140行目にgo_duration=('forever',)って書いてあるのがそれだな。

B: foreverって、永遠にってことですか?

A: その通り。

B: じゃあ終わんないじゃないですか!

A: 話は最後まで聞きたまえ。parameters.go_durationには時間を指定することもできるし、ひとまず'forever'と設定しておいて、 実行中に「○秒後に終了したい」という状態になったらその秒数を設定しなおすこともできる。このプログラムではESCキーを押すかウィンドウを閉じる操作をしたら終了するように なっているんだが、そういうふうに何秒後に終了するか未定の場合は'forever'にしておくとよい。

B: なるほど。ところで('forever' , )の , は何ですか?

A: ああ、終了までの時間や終了までに表示するフレーム数を指定する場合、数値と単位を並べて指定するんだ。 このプログラムでは144行目に(0,'frames')という値を設定しているのがそれだね。 0フレーム後に終了、すなわち直ちに終了するということだ。秒で指定する場合は(0.5,'seconds')のように書く。 'forever'の場合は単位は要らないわけだ。

B: ははあ。カンマの後ろになにもないのはちょっと気持ち悪いですが、意味はわかりました。

A: 144行目の話が出てきたところで、146行目のparameters.handle_event_callbacksの話をしておくか。 VisionEgg.FlowControl.Presentationを使ったプログラミングでは、キーボードのキー押しといったイベントの処理は自動的に行われる。 ただ、どのイベントが起こったときに、どのような処理を行うのかは当然プログラマが指示してやらないといけない。 それがparameters.handle_event_callbacksというデータ属性の役割で、「処理したいイベント」と「行わせたい処理を記述した関数」をタプルで まとめたものを並べたリストを設定しておく。こうしておくと後はVisionEgg.FlowControl.Presentationがよきにはからってくれる。

B: ええと、登録されているイベントは…、pygame.locals.KEYDOWNは今までの例題で出てきましたよね。 pygame.locals.KEYUPというのもなんとなく想像できますが、pygame.locals.QUITっていうのは何ですか?

A: pygameのウィンドウを閉じる操作をしたときに生じるイベントだ。Windowsならウィンドウの右上の×ボタンを押した時などに生じる。 pygame.locals.QUITが生じると、146行目の設定に従ってquit()という関数が呼ばれる。quit()の定義はすぐ上の143行目だね。 ここでparameters.go_durationを0フレームに設定している。これで直ちにgo()メソッドの動作が停止する。

B: うーん、処理があちこち飛び回ってややこしいなあ。quit()の引数のeventってなんですか?

A: 文字通り、parameters.handle_event_callbacksで呼び出された関数には引数として生じたイベントが渡されるんだ。 114行目のkeydown()関数や127行目のkeyup()が良い例だね。押されていたキーが離されたら、pygame.locals.KEYUPというイベントが生じる。parameters.handle_event_callbacksを通じて keyup()関数が呼び出される。keyup()関数では、引数として渡されたeventのデータ属性keyを見ることによって、どのキーが 離されたかを知ることができる。

B: うーん、うーん。

A: これらの関数ではキー押しの状態をup、down、left、rightという変数に保存しているが、 これらの変数は関数内で値を代入されているから 例題5-1で説明したとおりローカル変数として解釈される。それでは関数が終了すると同時にこれらの変数が放棄されてしまい、後でevery_frame_func()でそれらの値を利用できなくなってしまう。 そこで、115行目、118行目で「これらの変数はglobalである」と宣言して、変数が放棄されるのを防いでいるわけだ

B: うは。ついていけなくなりました。これ、全部マスターしないといけないんですか?

A: いや、この辺りはプログラマの好みが分かれるところだろうね。例題5までに紹介したような whileループを使う方法がわかりやすいという人は、VisionEgg.FlowControl.Presentationをわざわざ使う必要はない。オブジェクト指向の プログラミングが好みの人なんかはVisionEgg.FlowControl.Presentationを使う方がしっくりくるだろうね。

B: オブジェクト指向?

A: うーん、私も完全に独学なのできちんと説明する自信がないんだが。 そうだなあ、下手にいいかげんな説明を書くとアレなんで自分で勉強してくれ。

B: あ、逃げた。

A: 「いいかげんな解説」がモットーだからね。私自身の好みを言えば、今までの例題で紹介してきた 書き方の方がやりやすい。人間相手の心理実験では何種類もの教示を画面に表示したりする必要があるけど、そういう「刺激を表示している」 以外の状態が何種類もある実験のプログラムをVisionEgg.FlowControl.Presentationで書くのは面倒だと思う。 VisionEgg.FlowControl.Presentationでそのあたりをうまく処理するためにはbetwee_presentation()などのメソッドをうまく活用しないといけない。と、思う。 私はVisionEggをいじり始めて早々にそっち方面の努力は放棄してしまったので、興味がある人はぜひ自力で何とかしてほしい。

B: これまた他力本願な。

A: あくまで自分が独学してきたことをまとめているだけだからね。VisionEggの全機能を網羅した 解説を作ろうなんて企んだらただでさえ滞りがちな自分の仕事ができなくなっちまう。

B: ふうん。なんだか大変ですねえ。

A: なんだ、その棒読みなセリフは。ま、とにかくこの程度のことが分かっていれば、VisionEgg.FlowControl.Presentationを使っている サンプルプログラムは一応読めるはず。というわけで、例題7はこれにて終了。

B: へ、例題7-2は?

A: visionegg-1.0-demosには他にも参考になるプログラムがたくさん含まれているが、 それはまた別の例題として取り上げたい。そんなわけで、また次回。