例題16-1:とっても大切な縞模様

A: さて、毎度唐突だが例題16ではVisionEggの刺激の中でまだ解説していないものをいくつか取り上げる。まずは知覚心理学者のこころの故郷、グレーティングから。

B: グレーティング。講義で聞いたことは覚えていますが、なんでしたっけ。

A: 今回のサンプルプログラムが走ってる画面を見れば一目瞭然だな。こんなのだよ。

../_images/16-1-01.png

B: ああ、この縞々! 先生がやたらと気合を入れて説明してくださったんですが、何が大切なのかさっぱりわからなかったっけなあ。

A: ふむ。確かに最初はなんでこんな縞々の見え方を研究するのかわからないもんだよなあ。で、もちろん今はB君もなぜグレーティングの知覚が大事なのかわかっているだろう?

B: …。

A: …。

B: …。

A: …まあ、なんだ。聞かなかったことにして先に進もうか。

B: (泣)

  • 行番号なしのソースファイルをダウンロード→ 16-1.py

 1# -*- coding:shift-jis -*-
 2import VisionEgg
 3import VisionEgg.Core
 4
 5import pygame
 6import pygame.locals
 7import VisionEgg.Gratings
 8import numpy
 9
10screen = VisionEgg.Core.get_default_screen()
11sx,sy = screen.size
12stim = [VisionEgg.Gratings.SinGrating2D(position=(sx/2-150,sy/2),
13                                        size=(200,200),
14                                        ignore_time=False,
15                                        spatial_freq=0.01,
16                                        temporal_freq_hz=1.0/5,
17                                        contrast = 0.2,
18                                        pedestal = 0.75,
19                                        phase_at_t0 = numpy.pi),
20        VisionEgg.Gratings.SinGrating2D(position=(sx/2+150,sy/2),
21                                        size=(200,200),
22                                        color1 = (0.7,0.5,0.5),
23                                        color2 = (0.5,0.7,0.5),
24                                        orientation = 45,
25                                        ignore_time=False,
26                                        spatial_freq=0.01,
27                                        temporal_freq_hz=1.0/5)]
28
29viewport = VisionEgg.Core.Viewport(screen=screen,stimuli=stim)
30
31ct = st = VisionEgg.time_func()
32stim[0].parameters.t0_time_sec_absolute = st
33stim[1].parameters.t0_time_sec_absolute = st
34while ct-st < 5:
35    ct = VisionEgg.time_func()
36    e = pygame.event.get()
37    screen.clear()
38    viewport.draw()
39    VisionEgg.Core.swap_buffers()
40
41screen.close()

A: 単に二つのグレーティングを表示するだけのサンプルだ。ポイントはただひとつ、VisionEgg.Gratings.SinGrating2Dクラスだ。 ちょっと読みにくい書き方で恐縮だが、12行目からVisionEgg.Gratings.SinGrating2Dのインスタンスを二つ作ってstimというリストに格納している。後はいつもどおりこれをVisionEgg.Core.Viewportに渡して表示しているだけだな。

B: へ、もうこれで終わり?

A: いやいや、さすがに私でもこれだけで終わりにゃせんよ。まず、VisionEgg.Gratings.SinGrating2Dクラスのオブジェクトは、VisionEggが内部に持っているタイマーを使って勝手にアニメーションするようになっている。 ignore_timeという引数がTrueであれば、タイマーを無視して静止したグレーティングを描く。この引数のデフォルト値はFalseなんで、このサンプルのようにわざわざignore_time=Falseと書かなくてもいいんだが、一応説明のために書いてある。

B: 勝手に動くってのは気持ち悪いなあ。

A: 便利と言え。…と言いたいところだが、正直私も最初ちょと戸惑った。んで、動く速さはtemporal_freq_hzで指定する。temporal_freq_hzが1ならば、1秒間で1周期移動する。0.1ならば10秒で1周期だな。

B: ふむふむ。

A: で、グレーティングの周期なんだが、こちらはspatial_freqという引数で指定する。単位はヘルプにはcycles/eye_coord_unitって書いてあるんだが、通常のViewportの設定なら1eye_coord_unitってのは要するに1ピクセルなので、1ピクセルあたり何周期かを指定すればいい。

B: ちょ、1ピクセルあたりの周期数って、1ピクセルに何周期もあっても描画できないじゃないですか。

A: うん。だからサンプルプログラムを見てもらったらわかるとおり、この引数の値は1よりずっと小さな値になる。サンプルだと0.01、100ピクセルで1周期だな。

B: 1周期の長さにすればわかりやすいのに。

A: んー。それはどうかな。グレーティングを実験に使うような研究をしてる人ならcpd(cycles per degree)という単位に慣れているだろうから、こっちの方が直観的かも知れん。まあ、どうせ簡単に換算できるのでどっちでもいい。 後、注意すべき点があるとすれば、グレーティングの色やコントラストの指定方法だな。モノクロのグレーティングであれば、12から19行目の1個めのグレーティングの例のように、contrastとpedestalという引数で指定することが出来る。contrastはグレーティングを刺激に使うような人なら意味わかるよな。解説はパス。

B: 相変わらずひどい解説者だなあ。

A: pedestalってのは、グレーティングをg×sin(w t)+pって式で表すとしたら、pの値がpedestalだ。要するに平均輝度だが、明るさは0.0~1.0の値しか取れないので、サチってしまうようなパラメータを設定してしまうと平均輝度には一致しない。

B: ??? サチる?

A: 一言で言うと範囲を超えてしまうってことだな。saturationから来た言葉だ。

B: saturationっていうのは…

A: 辞書を引きたまえ。

B: うへぇ。

A: 話を戻すぞ。このサンプルの2個目のグレーティングは、赤と緑のグレーティングだ。まあこのサンプルは適当に色を決めてあるんだが、赤と緑が等輝度になるように調整されたグレーティングは知覚の研究でよく利用される。

B: 調整してないんですか。

A: おいおい、プログラム上で指定した数値に対して実際に画面上でどんな色が表示されるかは個々のモニターで違うんだから、どんな環境で実行されるかわからないサンプルプログラムでちゃんと調整できるわけないだろ。

B: サンプルを読む人はそういう調整方法を知りたいのでは。

A: やけに食い下がるな。まあ散々山積みになっているネタの在庫が捌けて、気が向いたらな。とにかく、こういう具合に色を指定しないといけない場合は、contrastとかpedestalでは指定できないので、color1とcolor2というパラメータを指定する。正弦波の上限と下限にあたる色を指定するわけだな。あとはここまでの例題を見てきた人なら大体わかるでしょ。

B: サンプルプログラムの32行目と33行目はいったい…。

A: ああ、忘れてた。勝手に動いてくれるってのは便利なんだが、実験によっては「いま、この瞬間に一番明るい部分がこの位置に来てほしい!」ということもあるだろう。そんなときには、32行目や33行目のように「この時刻をt=0にしてほしい!」という時刻をt0_time_sec_absoluteにセットするんだ。 そうするとその時刻を基準にグレーティングが描かれる。

B: なるほど。

A: それに関してもう一つ。そのt0_time_sec_absoluteの時刻にグレーティングの位相がずれていて欲しい場合は、19行目にあるようにphase_at_t0という引数に位相を指定する。

B: …位相。位相ってなんでしたっけ。

A: 高校の数学をやり直したまえ。

B: うぅっ、今回は厳しいなあ。

A: なんか作者があまり余裕ないらしいぞ。まあそう言いつつ余裕があるからこそ書いてるはずなんだがな。ホントに余裕がなかったら書けるはずがない。

B: そりゃそうですね。それはそうと、AさんAさん。

A: ん? なんだ?

B: プログラムを実行したら、右側のグレーティングが斜めになっているんですが、これは何とかならないんですか?

A: 斜めになってるんですがって、24行目にあるようにorientation=45って指定しているんだから、斜めになっているのが正しい。

B: いや、そうじゃなくて。縞々は斜めで、刺激は斜めじゃないように出来ないんですか。

A: よくわからんが、左側の刺激と同じように刺激の4辺は垂直、水平になっていて欲しいという事か。

B: そうそう、左側と同じに出来ないのかってことです。

A: ふむ。勿論できるが、ちょっと面倒くさいぞ。書き直すから待っていてくれ。

B: へ、そんなに時間かかるんですか。

A: Viewportをいじる必要があるんだ。ちょっと待てよ。

B: はあい。

A: …と。よっしゃ、出来た。

B: へ? 今待ち始めたばかりですが?

A: いや、昔書いたプログラムがないかなあと思ったら、プラッド刺激のデモ用に書いたプログラムを見つけた。これをちょいちょいといじって…、と。

  • 行番号なしのソースファイルをダウンロード→ 16-2.py

 1# -*- coding:shift-jis -*-
 2import VisionEgg
 3import VisionEgg.Core
 4
 5import pygame
 6import pygame.locals
 7import VisionEgg.Gratings
 8import numpy
 9
10screen = VisionEgg.Core.get_default_screen()
11sx,sy = screen.size
12
13stimulus1 = VisionEgg.Gratings.SinGrating2D(position = (150, 150),
14                                            anchor = 'center',
15                                            size = ( 450 , 450 ),
16                                            spatial_freq = 0.04,
17                                            temporal_freq_hz = 1.0,
18                                            orientation = 45 )
19
20stimulus2 = VisionEgg.Gratings.SinGrating2D(position = (150, 150),
21                                            anchor = 'center',
22                                            size = (450, 450),
23                                            spatial_freq = 0.01,
24                                            temporal_freq_hz = 1.0,
25                                            orientation = 135,
26                                            max_alpha = 0.5 )
27
28stimulus3 = VisionEgg.Gratings.SinGrating2D(position = (sx/2-225,sy-225),
29                                            size = (300, 300),
30                                            ignore_time=True,
31                                            orientation = 0,
32                                            spatial_freq=0.01)
33
34plaidview = VisionEgg.Core.Viewport(screen=screen,
35                                    stimuli=[stimulus1,stimulus2],
36                                    size=(300,300),
37                                    anchor = 'center',
38                                    position=(sx/2, sy/2) )
39
40viewport = VisionEgg.Core.Viewport(screen=screen,stimuli=[stimulus3])
41
42
43
44ct = st = VisionEgg.time_func()
45while ct-st < 5:
46    ct = VisionEgg.time_func()
47    e = pygame.event.get()
48    screen.clear()
49    viewport.draw()
50    plaidview.draw()
51    VisionEgg.Core.swap_buffers()
52
53screen.close()

B: プラッド刺激ってなんでしたっけ。

A: これも実物を見た方が早いからな。まずサンプルプログラムの解説をするぞ。このプログラムのカギはVisionEgg.Core.Viewportの使い方だ。 まずstimulus1、stimulus2で向きと空間周波数が異なる二つのグレーティングを作る。2つ目のグレーティングはmax_alpha=0.5を指定することで半透明になっている(26行目)。max_alphaについて詳しくは 例題8-2 参照ね。 両者のpositionは同じ(150,150)で、結果的にこれらの二つのグレーティングが重ね合わせた刺激が描かれる。こういう刺激をプラッドと言う。

B: ふんふん。

A: 今回は、Viewportの働きについての理解を深めるために、これに加えてもう一つstimulus3というグレーティングを描いている。こいつには30行目にあるようにignore_timeにTrueを指定してあって、動かないグレーティングの例にもなっている。

B: さっきは書き足したのはこの部分ですね。

A: さて、ここからがポイントだぞ。stimulus1とstimulus2は34行目でplaidviewというVisionEgg.Core.Viewportのインスタンスに関連付けられているが、このViewportには今までのサンプルプログラムにはなかったsize、position、anchorのパラメータが指定されている。 実は、 Viewportにこれらのパラメータを指定することによって、スクリーンの大きさと異なるViewportを使うことが出来る のだ。

B: はあ、わざわざ強調してくださっているんですが、それが何の意味があるのかいまいちわからないんですが…。

A: 34から38行目で定義しているViewportの大きさは300×300。上に戻ってstimulus1、stimulus2の定義を見ると、大きさは450×450。Viewportの対角線の長さは300×sqrt(2)=426.26...、刺激の中心はViewportの中心に一致しているのだから、刺激はViewportを完全に覆い尽くす。

../_images/16-1-02.png

B: むむむ。難しいけど絵に描いてもらったらわかりました。

A: Viewportからはみ出る刺激は描画されないので、これで斜め向きのグレーティングで4辺が水平、垂直になっている刺激を切り抜くことが出来た。こんな具合に画像の一部分だけを表示することを クリッピング という。 ま、とにかく、後は50行目のようにこのpladviewをdrawしてやればいい。この刺激の位置はpladviewのpositionによって決まる点が要注意だな。あと、他の刺激も描画するときは、他の刺激を登録したViewportを続けてdrawすればいい。 刺激が重なっている場合、先にdrawしたViewportに含まれる刺激が下になり、後からdrawしたViewportの刺激が上になる。

B: はあ。何とか理解しましたが、面倒くさいなあ。何個も斜めのグレーティングを描画したい場合、その個数だけViewportを用意しなきゃいけないんですか?

A: ま、このやり方だとそうだな。実はこのViewportを使うやり方はVisionEgg.Gratings.Grating2Dのhelpに書いてある方法なんだ。 この他にも、 例題8-1例題8-2 で紹介した穴が空いたテクスチャで不要な部分を隠すといった事も出来るな。それぞれの方法に長所、短所があるので、臨機応変に使い分けるといい。

B: どんな短所があるんですか?

A: Viewportを複数使う方法は面倒くさいだろ。穴あきテクスチャで覆い隠す方法はいくつもグレーティングを並べるときに、テクスチャの大きさをよく考えないと隣の刺激まで覆い隠してしまいかねない。 まあ穴はフレーム毎にあけなおすことが出来るのでかなり柔軟に対応できるが、空けなおすくらいならViewportを複数使う方が面倒くさくない気がする。ま、実際にそういう刺激が必要になった時に悩みなさい。

B: はあい。じゃあ実行してみますね…おおお、結構きれいだ。

../_images/16-1-03.png

A: 次はこれまた知覚心理学者のこころの故郷のGaborパッチを描いてみるぞ。ではまた。