例題16-3:とっても大切な砂嵐(+おまけにお手軽ダイアログ)¶
A: さて、今日は台風が接近して大変なことになっているのでVisionEgg.Dotsの紹介でもするか。
B: なんですかそりゃ。台風と何の関係が?
A: いや、VisionEgg.Dotsの回に「とっても大切な砂嵐」って仮題つけてたんだけど、ちょうど台風で予定が狂ったんで研究室のPCじゃなくても出来るお気軽な仕事をしようかと思って。
B: 「ちょうど」っていうのがよくわかりませんが。まさか台風→強風→…?
A: …。さて、本題に入るか。
B: (あきれ顔)
行番号なしのソースファイルをダウンロード→ 16-4.py
1# -*- coding:shift-jis -*-
2import VisionEgg
3import VisionEgg.Core
4
5import pygame
6import VisionEgg.Dots
7import VisionEgg.Text
8
9screen = VisionEgg.Core.get_default_screen()
10sx,sy = screen.size
11stim = [VisionEgg.Dots.DotArea2D(position=(sx/6,sy/4)),
12 VisionEgg.Dots.DotArea2D(position=(sx/6,3*sy/4),signal_direction_deg=135,color=(0.5,1,0.5)),
13 VisionEgg.Dots.DotArea2D(position=(3*sx/6,sy/4),dot_lifespan_sec=1.0,color=(1,0.8,0.8)),
14 VisionEgg.Dots.DotArea2D(position=(3*sx/6,3*sy/4),velocity_pixels_per_sec=30.0,color=(0.8,0.8,1)),
15 VisionEgg.Dots.DotArea2D(position=(5*sx/6,sy/4),dot_size=8.0,color=(1,0.5,1)),
16 VisionEgg.Dots.DotArea2D(position=(5*sx/6,3*sy/4),num_dots=300,color=(1,1,0.5)),
17 VisionEgg.Text.Text(position=(sx/6,sy/4),text='default',anchor='center',font_size=20),
18 VisionEgg.Text.Text(position=(sx/6,3*sy/4),text='signal_direction_deg=135',anchor='center',font_size=20),
19 VisionEgg.Text.Text(position=(3*sx/6,sy/4),text='dot_lifespan_sec=1.0',anchor='center',font_size=20),
20 VisionEgg.Text.Text(position=(3*sx/6,3*sy/4),text='velocity_pixels_per_sec=30.0',anchor='center',font_size=20),
21 VisionEgg.Text.Text(position=(5*sx/6,sy/4),text='dot_size=8.0',anchor='center',font_size=20),
22 VisionEgg.Text.Text(position=(5*sx/6,3*sy/4),text='num_dots=300',anchor='center',font_size=20)]
23
24viewport = VisionEgg.Core.Viewport(screen=screen,stimuli=stim)
25
26ct = st = VisionEgg.time_func()
27while ct-st < 5:
28 ct = VisionEgg.time_func()
29 e = pygame.event.get()
30 screen.clear()
31 viewport.draw()
32 VisionEgg.Core.swap_buffers()
33
34screen.close()
B: なんか11行目からの刺激の定義に圧倒されますが、そこ以外はすごく単純ですね。例題1の「5秒待つ」とほとんど変わらないかも。
A: そりゃ5秒刺激を表示しているだけだからな。今回の主役はVisionEgg.Dots.DotArea2D。これはランダムに運動する点の中で一部の点が一様に、すなわち同一方向に同一速度で運動するという刺激だ。運動知覚の研究者御用達だな。
B: こりゃまたマニアックな刺激ですね…。ランダムドットというとランダムドットステレオグラム(RDS)を思い出しますが、RDSは描けないんですか?
A: 残念ながらこいつはRDS用じゃないな。さて、今回は単にこういう刺激がVisionEggに用意されているよというだけの話で、解説することはほとんどない。 実行すると左下に表示されるのがデフォルトの状態。これにsignal_direction_deg、dot_lifespan_sec、velocity_pixels_per_sec、dot_size、num_dotsといったパラメータを指定したものがそれぞれ配置されている。 指定したオプションがテキストで画面上に書かれているし、この刺激を使おうっちゅー人は解説しなくてもすぐにわかるでしょ。以上、解説おしまい。
B: うわ、毎度ひどい解説だけど今回は極悪ですな。
A: そうそう、当然だけどこれらのパラメータは組み合わせられるからね。今回はこれでお終い…と言いたいところだけど、せっかくなんでちょっとお遊び。1秒間刺激が提示されるんで、一様な運動が上下左右4方向のいずれだったかカーソルキーを押して反応してほしい。 反応直後に正解の方向が矢印で表示される。矢印の色が反応が正しければ白、間違っていれば赤になる。 一様な運動をする点の割合(signal_fraction)が1.0から0.1ずつ下がっていって、一度でも間違えるか0.1でも正解したらプログラムは終了する。
B: 極限法の下降系列を一回だけするようなもんですね。
A: うん。本当はちゃんとした極限法のプログラムを作るつもりだったんだが、面倒臭くなってここで挫折した。
B: だは、せっかくちょっと見直しかけたのに…。
A: 代わりと言っては何だが、まだサンプルプログラムで使ったことがなかったVisionEgg.MoreStimuli.ArrowとtkMessageBoxの使用例を示してみた。 VisionEgg.MoreStimuli.Arrowは画面に矢印を描く。tkMessageBoxはTkinterで定番のダイアログを手軽に表示するためのモジュールだ。 VisionEgg.MoreStimuli.Arrowは、まあだいたいわかるでしょ。tkMessageBoxは詳しくはWeb上で検索してほしいんだけど、一応メソッドの一覧を示しておこう。 いずれも'title'はダイアログのタイトルバーに表示される文字列、'message'はダイアログ本体に表示される文字列。'message'のテキストでは\nで改行することが出来る。 これを使うと実験の最初に保存ファイル名入力とかのダイアログを出した時に、不正な値が入力された場合に再入力を促すように警告ダイアログを出したり入力内容を確認するためのダイアログを出したりすることが簡単にできる。 いつかサンプルプログラムを示したいところだな。
showinfo('title','message') |
情報ダイアログを表示する。ボタンはOKのみ。 |
showwarning('title','message') |
警告ダイアログを表示する。ボタンはOKのみ。 |
showerror('title','message') |
エラーダイアログを表示する。ボタンはOKのみ。 |
askquestion('title','message') |
質問ダイアログを表示する。ボタンは「はい」なら'yes'、「いいえ」なら'no'を戻り値として返す。 |
askokcancel('title','message') |
キャンセルするか否かを問うダイアログを表示する。ボタンは「Ok」ならTrue、「キャンセル」ならFalseを戻り値として返す。 |
askyesno('title','message') |
「はい」か「いいえ」かを問うダイアログを表示する。ボタンは「はい」ならTrue、「いいえ」ならFalseを戻り値として返す。 |
askretrycancel('title','message') |
再試行するか否かを問うダイアログを表示する。ボタンは「再試行」ならTrue、「キャンセル」ならFalseを戻り値として返す。 |
B: へえ。これは便利そう。
A: Tkinterのウィンドウをすでに作成していたら特に問題ないんだが、今回のようにTkinterのアプリケーションじゃないアプリケーションでtkMessageBoxを使うと小さなTkinterのウィンドウが勝手に作成されてしまう。 それを防ぐための工夫が79行目と80行目。79行目でTkinterのウィンドウを作成しておいて、80行目のwithdraw()メソッドでTkinterのウィンドウを非表示にしている。 文章だけじゃよく意味がわからないと思うけど、説明用のスクリーンショットを撮るのが面倒臭いんで、各自で80行目をコメントアウトして実行して試してみて欲しい。
B: 今回は本当に投げやりですな。
A: あと、刺激の方向と押されたキーが一致しているか否かを判定する50行目のif文もちょっと注目してほしいな。 これをいちいちif e.key==K_UP:…とかって場合分けしちゃうと、かなりダラダラとifを書かないといけなくてうっとおしいから。
B: ふむふむ。何気にいろいろなテクニックが使われてますな…。参考になる。
A: んじゃ、次回は3Dの刺激について。といってもパースペクティブに関するごく初歩的なメソッドを取り上げるだけのつもり。ではまた。
行番号なしのソースファイルをダウンロード→ 16-5.py
1# -*- coding:shift-jis -*-
2import VisionEgg
3import VisionEgg.Core
4
5import pygame
6from pygame.locals import KEYDOWN, K_UP, K_DOWN, K_LEFT, K_RIGHT
7
8import VisionEgg.Dots
9import VisionEgg.MoreStimuli
10
11import random
12
13import Tkinter
14import tkMessageBox
15
16screen = VisionEgg.Core.get_default_screen()
17sx,sy = screen.size
18stim = [VisionEgg.Dots.DotArea2D(position=(sx/2,sy/2),
19 dot_lifespan_sec=6000,
20 velocity_pixels_per_sec=30.0),
21 VisionEgg.MoreStimuli.Arrow(position=(sx/2,sy/2))]
22
23viewport = VisionEgg.Core.Viewport(screen=screen,stimuli=stim)
24
25respkeys = [K_UP, K_DOWN, K_LEFT, K_RIGHT]
26directions = [270, 90, 180, 0]
27signal_fraction = 1.0
28
29while True:
30 direction = directions[random.randint(0,3)]
31
32 stim[0].parameters.signal_fraction = signal_fraction
33 stim[0].parameters.signal_direction_deg=direction
34 stim[0].parameters.on = True
35 stim[1].parameters.orientation = direction
36 stim[1].parameters.on = False
37
38 ct = st = VisionEgg.time_func()
39 while ct-st < 1.0:
40 ct = VisionEgg.time_func()
41 e = pygame.event.get()
42 screen.clear()
43 viewport.draw()
44 VisionEgg.Core.swap_buffers()
45
46 waitkey = True
47 while waitkey:
48 for e in pygame.event.get():
49 if e.type == KEYDOWN and e.key in respkeys:
50 if respkeys.index(e.key) == directions.index(direction):
51 stim[1].parameters.color = (1,1,1)
52 responseTF = True
53 else:
54 stim[1].parameters.color = (1,0,0)
55 responseTF = False
56 waitkey = False
57
58 stim[0].parameters.on = False
59 stim[1].parameters.on = True
60 ct = st = VisionEgg.time_func()
61 while ct-st < 1.0:
62 ct = VisionEgg.time_func()
63 e = pygame.event.get()
64 screen.clear()
65 viewport.draw()
66 VisionEgg.Core.swap_buffers()
67
68 if responseTF:
69 signal_fraction -= 0.1
70 if signal_fraction<=0.0:
71 break
72 else:
73 break
74
75
76screen.close()
77
78
79w=Tkinter.Tk()
80w.withdraw()
81
82if responseTF:
83 tkMessageBox.showinfo(u'終了しました',u'signal_fractionが0.0に達しました')
84else:
85 tkMessageBox.showinfo(u'終了しました',u'signal_fraction=%.1fで反応を誤りました' % signal_fraction)