例題6-1:面倒ならあらかじめ描いておけば?¶
A: そろそろマンネリ化してきた気がするが、B君なに悩んでんの?
B: なんですか、その声のかけ方は。
A: いやさ、毎回こういう出だしもどうかと思うんだけど考えるのも面倒くさくて。で、何を難しそうな顔してたの。
B: …まあいいです。いや、この図の背景をぐるぐる回すと黄色い点が消えてしまうように見えるっていうんですけど、 どんな感じなのか見てみたいと思って。
A: Motion Induced Blindnessだね。Webで検索すればでデモが見つかると思うけど、 自分で作ってみようってのはいいことだ。で、何が困ったの。
B: 背景の青い十字なんですけど、これVisionEgg.MoreStimuli.Target2Dを二つ組み合わせて十字にして、 それを縦横に並べて描いてみたんですが、さらにそれを回転させようとすると、頭が混乱してきて…。
A: 冷静に考えれば全然難しい計算じゃないはずだがな。まあ面倒くさいのは確かだし、十字ひとつに2つの長方形、 十字を7×7に並べると49×2=98個の長方形を描画しないといけない。今時のPCのスペックなら全然問題ない個数だが、もっと数が多くなったらいずれ限界が来る。 それにばらばらに動くわけでも色が変化する長方形を描画するわけでもないのに独立したオブジェクトを使うのは節約の精神に反する。
B: 節約の精神って、そんなのあるんですか?
A: いや、適当に言ってみた。とにかく、背景の十字はひとつのまとまりとして動くんだから、 ひとつにまとめてしまった方がいい。
B: まとめるって、具体的にどうやってまとめるんですか?
A: 十字を並べた絵を先に描いておいて、それを回せばいいのさ。具体的にはVisionEgg.Texturesを使う。
B: テクスチャって授業で聞いたような…、表面のザラザラとか、そんなのでしたっけ。
A: ん。なんかわかってんのかわかってないのか怪しい表現だがまあいい。B君は覚えているかどうかわからないが、 VisionEggをインストールしたときにpyopenglというのをインストールしただろ? これはpythonからOpenGLという3次元グラフィックスのAPIをpythonから使うための ラッパーで、実はVisionEggの刺激描画にはOpenGLが使われているんだ。それで、3Dグラフィックスでは少ないポリゴン数で表現力を高めるために、ポリゴンに 画像ファイルをさまざまな方法で「貼る」ことができる。これをテクスチャマッピングと言う。VisionEggからテクスチャマッピングを簡単に利用する インターフェースがVisionEgg.Texturesだ。
B: …わからない用語がいっぱい出てきたような気がしますが、要するにどういうことですか?
A: VisionEggでJPEGとかBMPとかの画像ファイルを表示させることができるんだよ。
B: 最初からそう言ってくれればいいのに。
A: それじゃなんでTexturesっていう名前なのかわからんだろ。まあとにかくサンプルを作ってみるか。 画像ファイルを用意しないといけないからちょっと待ってて。
A: そらよっと。こんなもんかな。
1#!/usr/bin/env python
2# -*- coding: shift-jis -*-
3
4from VisionEgg import *
5from VisionEgg.Core import *
6from pygame import *
7
8import VisionEgg.Textures
9
10
11from math import sin, cos
12
13screen = get_default_screen()
14screen.parameters.bgcolor = (0.0,0.0,0.0)
15SX,SY= screen.size
16
17tex = VisionEgg.Textures.Texture('06-1a.png')
18stimBlueCrosses = VisionEgg.Textures.TextureStimulus(
19 texture = tex, size = tex.size,
20 anchor='center', position=(SX/2,SY/2))
21
22viewport = Viewport(screen=screen, stimuli=[stimBlueCrosses])
23
24startTime = VisionEgg.time_func()
25t = VisionEgg.time_func()
26while t-startTime < 5.0:
27 t = VisionEgg.time_func()
28 event.get()
29 screen.clear()
30 viewport.draw()
31 swap_buffers()
B: 案外短いプログラムですね。
A: 単に画像を表示するだけだからな。それだけで何百行も書かないといけないと大変だ。
B: あ、そりゃそうですね。
A: 解説すべきことはあまりない。まず8行目でVisionEgg.Texturesをimportしておく。 17行目から20行目が画像ファイルを読み込んでVisionEggの刺激とする部分だが、ここは2つの段階に分解できる。まず第1段階が17行目のVisionEgg.Textures.Texture。 これは画像ファイルを読み込んでVisionEggで使えるように準備する関数だ。
B: TexturesにTexture。前はsがあって後はsがないんですね。ややこしい。
A: 問題はVisionEgg.Textures.Textureの引数だが、これをきちんと説明するのはちょっと厄介だ。 この関数はかなり柔軟性が高くて、いろいろな型のデータを突っ込んで画像化することが出来る。ここではとりあえず画像ファイル名を指定して ファイルを読み込む例を紹介しておく。今回のサンプルで読み込んでいる06-1a.pngはソースファイルと一緒にダウンロード出来るように 準備してあるが、自作の刺激の場合は別なソフトウェアを使って画像ファイルを作成しておくこと。 そうそう、パスは指定してないのでプログラムの実行ディレクトリに画像ファイルを置いておく必要があることに注意。
B: pngってあまり聞かない拡張子ですね。JPEGとかは使えないんですか?
A: 使える。自動的に判別して読み込んでくれるから、単に'ほげほげ.jpg'とファイル名を指定すればいい。 個人的にはJPEGは非可逆圧縮なのでこういう刺激画像の保存には使いたくないが。
B: 非可逆?
A: 完全にオリジナルの画像を再現出来ない場合があるってことだよ。 説明するといろいろと面白いんだけど、時間がないんで詳しくは自分で調べてくれ。
B: むー。
A: さて、準備したテクスチャを実際に刺激として使うには、VisionEgg.Textures.TextureStimulusという関数を使う。 18行目から20行目だね。textureという引数に先程VisionEgg.Textures.Textureで準備したテクスチャを指定する以外は、 今まで使ってきたVisionEgg.MoreStimuli.Target2Dとかと同じようにanchorとかpositionといった引数を必要に応じて指定すればいい。
B: size = tex.sizeって何ですか?
A: VisionEgg.Textures.Textureで準備したテクスチャは、sizeというデータ属性を持っている。 これはテクスチャ画像の縦横のサイズを示したタプルが収められているので、そのままVisionEgg.Textures.TextureStimulusの引数sizeに渡す値として使えるんだ。
B: ふむふむ。
A: ちなみにこの引数sizeの値を変更すると、それに合わせてテクスチャが自動的に拡大縮小される。 拡大縮小の方法なんかも指定出来るんだが、そのためにはOpenGLをちょっと勉強してもらないといけないのでここではパス。 まあ 画像ファイルを読み込む方法を使うときは、読み込んだらそのまま使えるところまで画像編集ソフトで編集しておくべき だと思う。 PIL (python imaging library)とかを使いこなせるようになったらまた話は別だが。
B: PIL?
A: いずれ詳しく説明するよ。 自然画像などの複雑な画像刺激を使う実験をするのであれば知っておいた方がいいパッケージだからね。
B: またそんな空約束して。大丈夫なんですか?
A: あまり大丈夫じゃない。けどぼちぼち進めていくから期待せずに待ってくれ。 PILはweb上にたくさん解説があるから自分で調べてくれてもいいんだぞ?
B: …待ちます。
A: んじゃいずれ。あと目新しいところは14行目かな? screen.parameters.bgcolorに0.0から1.0の値を3つまたは4つ並べた タプルを指定すると、背景の色を変更できる。ここでは3つがすべて0.0、すなわち黒を指定している。(注:例題4-1ですでに解説なしで使っていました…)
B: 4つ? 3つなら赤、緑、青の三色ですよね。4つめは何ですか?
A: α値というのを指定する。背景色の場合はあまり意味がない。これも大事なので 回を改めてきちんと説明するよ。
B: そんなに宿題を山積みして大丈夫なんですかー?
A: よけいなお世話だ。22行目でViewportの設定をして、24行目からは懐かしの例題1-1「5秒待つ」だね。 実行すると画像を5秒間表示して自動的に終了する。そら。
B: そっけないですね。
A: 画像を表示するだけだからな。さて、じゃあ注視点と刺激を配置するか。 円を表示するにはVisionEgg.MoreStimuli.FilledCircleを使うといい。ちょいちょいと書き足して…っと。
1#!/usr/bin/env python
2# -*- coding: shift-jis -*-
3
4from VisionEgg import *
5from VisionEgg.Core import *
6from pygame import *
7from VisionEgg.MoreStimuli import FilledCircle
8
9import VisionEgg.Textures
10
11
12from math import sin, cos, pi
13
14screen = get_default_screen()
15screen.parameters.bgcolor = (0.0,0.0,0.0)
16SX,SY= screen.size
17
18stimRadius = 3
19imgRadius = 100
20
21tex = VisionEgg.Textures.Texture('06-1a.png')
22stimBlueCrosses = VisionEgg.Textures.TextureStimulus(
23 texture = tex, size = tex.size,
24 anchor='center', position=(SX/2,SY/2))
25
26stimFixation = VisionEgg.MoreStimuli.FilledCircle(
27 radius = stimRadius,
28 color = (0.0, 1.0, 0.0),
29 anchor = 'center',
30 position = (SX/2,SY/2))
31
32stimTargets = [VisionEgg.MoreStimuli.FilledCircle(
33 radius = stimRadius,
34 color = (1.0, 1.0, 0.0),
35 anchor = 'center',
36 position = (SX/2+imgRadius*cos((30+120*i)*pi/180.0),
37 SY/2+imgRadius*sin((30+120*i)*pi/180.0)))
38 for i in range(3)]
39
40stimList = [stimBlueCrosses, stimFixation]
41stimList.extend(stimTargets)
42
43viewport = Viewport(screen=screen, stimuli=stimList)
44
45startTime = VisionEgg.time_func()
46t = VisionEgg.time_func()
47while t-startTime < 5.0:
48 t = VisionEgg.time_func()
49 stimBlueCrosses.parameters.angle = t-startTime
50 event.get()
51 screen.clear()
52 viewport.draw()
53 swap_buffers()
A: stimRadiusというのが注視点や刺激の半径。imgRadiusというのは3つの刺激を 配置する仮想円の半径だ。緑色の注視点を背景画像の中心に合わせ、その周りに120度間隔で3個の黄色い刺激を配置している。 あとはstimListという変数に刺激のリストを格納しているが、このリストに刺激を並べる順番に注意してほしい。 例題1-5でちょっと解説したように、VisionEggではこのリストに並んでいる順番に刺激を描画するので、刺激を重ね合わせる 時に下になるものから順にリストに並べなければいけない。
B: あー。そういえば重ね順がどうとか聞きましたね。
A: このサンプルプログラムではstimBlueCrosses、つまり青の十字画像が一番 最初に描画されるようになっているわけだが、40から41行目を書き換えて他の刺激をstimBlueCrossesの前にしてみたらその辺りが よくわかると思う。 あと変更点は49行。毎フレームの描画前にstimBlueCross.parameters.angleに刺激表示開始からの時刻を回転角として代入している。 これで時間が経つにつれて刺激が回転することになる。
B: ふむふむ。じゃあ実行、と。
B: …あのー。なんだか回転がすっっっごく遅いんですが。
A: ああ。angleに指定する角度の単位はdegだからな。このサンプルプログラムの 書き方だと経過した秒数がそのまま回転角になっているから、5秒の表示時間でやっと5度回転するだけだ。 次は刺激の運動速度の調節について説明しよう。