例題19-6:放しても~♪

B: えー。ごほん。Aさんは本当に体調が悪かったようで今日はお休みだそうです。なので今日はぼくが成長した姿を存分にお見せしたいと思います。

Pyhtonで心理実験 [第二部] B君成長編

B: ふっ。さて、今回のお題はAさんが先日ちらっと言っていた「ioHubではキーリリースが計測できる」という話。Aさんが帰った後に気になって自分なりに調べてみたのです。で、ぼくの力にかかればあっというまに解決してしまったので皆さんにサンプルプログラムをお示しする次第ですよ。へへへ。まずは19-6a.py、キーボードのキーを押したら押したキーが緑色で表示され、放したら放したキーの名前が赤色で表示されます。

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

 1import psychopy.visual
 2
 3from psychopy.iohub import launchHubServer,EventConstants
 4io=launchHubServer(psychopy_monitor_name='default')
 5display = io.devices.display
 6win = psychopy.visual.Window(display.getPixelResolution(),
 7                             monitor=display.getPsychopyMonitorName(), 
 8                             units=display.getCoordinateType(),
 9                             screen=display.getIndex(),fullscr=True)
10
11keyboard = io.devices.keyboard
12
13msg = psychopy.visual.TextStim(win, text='Please press any button (press ESC to exit)')
14waitESCAPE = True
15
16io.clearEvents('all') 
17
18while waitESCAPE:
19    kb_events = keyboard.getEvents()
20    
21    for kb_event in kb_events:
22        if kb_event.type == EventConstants.KEYBOARD_RELEASE:
23            msg.setText('"%s" key is released!' % (kb_event.key))
24            msg.setColor((1,0,0)) #red
25        
26        elif kb_event.type == EventConstants.KEYBOARD_PRESS:
27            if kb_event.key == 'ESCAPE':
28                waitESCAPE = False
29            else:
30                msg.setText('"%s" key is pressed!' % (kb_event.key))
31                msg.setColor((0,1,0)) #green
32    
33    msg.draw()
34    win.flip()
35
36io.quit()

B: ioHubの初期化や終了は前回の例題19-5から丸写しです。ポイントは22行目と26行目のif文です。キーボードのキーが押されるとpsychopy.iohub.EventConstants.KEYBOARD_PRESS、放されるとpsychopy.iohub.EventConstants.KEYBOARD_RELEASEというIDのイベントが生じます。このIDはイベントインスタンスのtypeというデータ属性に保持されているので、typeがこれらのIDと一致するかを調べれば、キー押しだったのか放したのかがわかるのです。えっへん! ここでは3行目でfrom psychopy.iohub import EventConstantsとしてimportしてあるので、いちいちpyshopy.iohubと書かなくてもEventConstants.KEYBOARD_PRESSと書けば良いのですね。わからないキミは 例題1-4 を復習したまえ!(ばばーん)

B: ま、他は特筆すべきことはないですな。22行目と26行目のif文にヒットしたらそれぞれ文字を赤色、緑色にして押されたキー名を更新しています。あ、キー名はkeyというデータ属性に入っていますが、これは例題19-5にも出てきてますな。覚えていますか?

B: (ずずーっとお茶を飲んで)では、次のサンプルに行きましょうか。19-6a.pyをベースにして、マウスのボタンにも対応させています。

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

 1import psychopy.visual
 2
 3from psychopy.iohub import launchHubServer,EventConstants
 4io=launchHubServer(psychopy_monitor_name='default')
 5display = io.devices.display
 6win = psychopy.visual.Window(display.getPixelResolution(),
 7                             monitor=display.getPsychopyMonitorName(), 
 8                             units=display.getCoordinateType(),
 9                             screen=display.getIndex(),fullscr=True)
10
11keyboard = io.devices.keyboard
12mouse = io.devices.mouse
13
14msg = psychopy.visual.TextStim(win, text='Please press any button (press ESC to exit)')
15waitESCAPE = True
16
17io.clearEvents('all') 
18
19while waitESCAPE:
20    kb_events = keyboard.getEvents()
21    mouse_events = mouse.getEvents()
22    
23    for mouse_event in mouse_events:
24        if mouse_event.type == EventConstants.MOUSE_BUTTON_RELEASE:
25            msg.setText('button %s button is released! (%d,%d))' % (mouse_event.button_id, mouse_event.x_position, mouse_event.y_position))
26            msg.setColor((0,0,1)) #blue
27        
28        elif mouse_event.type == EventConstants.MOUSE_BUTTON_PRESS:
29                msg.setText('button %s is pressed! (%d,%d)' % (mouse_event.button_id, mouse_event.x_position, mouse_event.y_position))
30                msg.setColor((1,1,0)) #yellow
31    
32    for kb_event in kb_events:
33        if kb_event.type == EventConstants.KEYBOARD_RELEASE:
34            msg.setText('"%s" key is released!' % (kb_event.key))
35            msg.setColor((1,0,0)) #red
36        
37        elif kb_event.type == EventConstants.KEYBOARD_PRESS:
38            if kb_event.key == 'ESCAPE':
39                waitESCAPE = False
40            else:
41                msg.setText('"%s" key is pressed!' % (kb_event.key))
42                msg.setColor((0,1,0)) #green
43    
44    msg.draw()
45    win.flip()
46
47io.quit()

B: ふふん。まっ、こんなのぼくにかかれば簡単すぎますかな。12行目でマウスへのインターフェースを変数mouseに代入。21行目のmouse.getEvents()でマウスのイベントを取得。後は23行目からのfor文でキーボードと同様に処理。キーボードとの違いはまずイベントIDがMOUSE_BUTTON_RELEASEとMOUSE_BUTTON_PRESSになっていること。それからボタンはbutton_idというデータ属性に格納されていること。x_positionとy_positionというデータ属性に現在のマウスカーソル座標が格納されているのでそれもついでに表示してみましたよ、と。さて、次のサンプルは…

?: ぐぉ~~~~ら゛~~~~~~~な゛に゛をや゛っどる゛が~~~~~~~

B: うわっ!びっくりした!! え、Aさんじゃないですか。病院じゃなかったんですか?

A: ざっぎじん゛ざづがお゛わ゛っでよ゛っでみ゛だん゛じゃ~(訳:さっき診察が終わって寄ってみたんじゃ~~:以下聞き苦しいので翻訳でお届けします)

B: そ、そうですか。すごくつらそうな声ですし、早く家に帰って休まれた方が…

A: おめおめと休んでられるか! 人が書いたプログラムを勝手に使ってなにをしとるんじゃ!

B: あ、だめだめ!それ言っちゃダメ!

A: まったく油断も隙もありゃしない。これ、私が実験室のPCに置いといたプログラムだろ?

B: あー、そのですね、えーっと。共有PCのデスクトップに置いてある方が悪いんですよっ!共有PCは皆のものだから勝手にファイルを消されたりしても文句を言うな、自分で適切に管理しろってAさんがいったんじゃないですか!!はあはあ。

A: む。確かにそれは私が言ったとおりである。

B: でしょ、でしょ!

A: …で? 何か言うことは?

B: あー、えー。…ごめんなさい。二度としません。

A: ん。まあよろしい。で、今出してこようとしたファイルは? それも私が書いたものかな?

B: ちっ、違いますよ!これは正真正銘ぼくが書いたものです。19-6a.pyをベースにして、キーが放された時に押していた時間をミリ秒単位で表示する機能を追加してあります。

注釈

  • 2015/02/15 追記: PsychoPy 1.81(もしかすると1.80?)ではキー名やキーリリースに関する仕様が変わりました。キー押し時間の計測にはKEYBOARD_RELEASEイベントを使用します。詳しくは追記を参照してください。

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

 1import psychopy.visual
 2
 3from psychopy.iohub import launchHubServer,EventConstants
 4io=launchHubServer(psychopy_monitor_name='default')
 5display = io.devices.display
 6win = psychopy.visual.Window(display.getPixelResolution(),
 7                             monitor=display.getPsychopyMonitorName(), 
 8                             units=display.getCoordinateType(),
 9                             screen=display.getIndex(),fullscr=True)
10
11keyboard = io.devices.keyboard
12
13msg = psychopy.visual.TextStim(win, text='Please press any button (press ESC to exit)')
14waitESCAPE = True
15
16io.clearEvents('all') 
17
18while waitESCAPE:
19    kb_events = keyboard.getEvents()
20    
21    for kb_event in kb_events:
22        if kb_event.type == EventConstants.KEYBOARD_CHAR:
23            msg.setText('"%s" key is released! (duration: %.1f msec)' % (kb_event.key, 1000*kb_event.duration))
24            msg.setColor((1,0,0)) #red
25        
26        elif kb_event.type == EventConstants.KEYBOARD_PRESS:
27            if kb_event.key == 'ESCAPE':
28                waitESCAPE = False
29            else:
30                msg.setText('"%s" key is pressed!' % (kb_event.key))
31                msg.setColor((0,1,0)) #green
32    
33    msg.draw()
34    win.flip()
35
36io.quit()

A: ふむ。解説してもらおうか。

B: え、ええとですね。その。キーが放された時にはKEYBOARD_RELEASEの他にもKEYBOARD_CHARというIDのイベントが発生してですね。こちらにはdurationというデータ属性にキーが押されていた時間が記録されています。19-6c.pyはこのデータ属性を参照するように変更しただけです。23行目ですね。

A: なるほど。ところでKEYBOARD_CHARにはどんなデータ属性がある?

B: データ属性ですか。ええと、auto_repeated、confidence_interval、count、chocolate、…チョコレート?!

A: ははは。私のメモを丸写ししただろう。そんなこともあろうかと私のメモにはダミーワードが仕込んであるのだ。もちろんchocolateなどというデータ属性は無い。

B: くっ、こんな機密文書でもなんでもないものにそんな仕掛けをするとは。なんて 暇なおっさん なんだ!

A: ま、19-6c.pyは私が書いたものじゃないし、私のメモを見たとはいえB君が書いたんならまあ大したものだ。いや、別に大した改造はしていないが自分で改造して試してみようという心意気に免じて今回は無罪放免にしてやろうじゃないの。

B: ありがとうごぜぇますだ。うう。

A: 読者の皆様へ補足事項。いずれのプログラムも複数キーの同時押しに対応していますので、(1)xを押す、(2)xを押したままzを押す、(3)zを放す、(4)xを放す、とかしたりしても、ちゃんと(4)でxを放した時には(1)から(4)までの時間が表示されます。便利ですね。 それから最終行のio.quit()は非常に大事です 。これを忘れると、プログラムは正常に終了したように見えますが、裏でpythonのプロセスが走り続けてしまいます。io.quit()をし忘れたプログラムを何回か起動すると pythonのプロセスがたまり続けてコンピュータの動作が激遅になります のでご注意あれ。そうなってしまった時にはタスクマネージャーを使うとかkillするとか再起動するとかしてください。

B: してください。

A: あー。また具合が悪くなってきたからそろそろ帰るわ。この「Pyhtonで心理実験 [第二部] B君成長編」とかいう看板はゴミ集積所に出しとくから。戸締りはよろしく。んじゃねー。げふげふ、ごふっ。

B: くーっ、くやしい!

追記(2015/02/25)

最近キーリリース計測に関してご質問があって例題19-6のサンプルプログラムを実行したところ、19-6c.pyでキーリリースが検出できなくなっていることに気づきました。 公式のiohubのサンプルを確認すると、KEYBOARD_CHARではなくKEYBOARD_RELEASEを使うように仕様変更されたようです。また、キー名も変更されました。 まだ該当部のソースを読んでいませんが、恐らくpsychopy.event.getKeys()等の従来の関数で得られる名前と一致させるようにしたのでしょう。 以上の変更への対策として、19-6c.pyに以下の修正を加える必要があります。

  • 22行目: EventConstants. KEYBOARD_CHAR をEventConstants. KEYBOARD_RELEASE に変更

  • 27行目: キー名を 'ESCAPE' から 'escape' に変更

修正を加えたものを19-6d.pyとしてダウンロードできるようにしておきますのでご利用ください。

なお、 公式のiohubのサンプルではキーリリースの判定をKEYBOARD_RELEASEの受信ではなく、KEYBOARD_PRESS以外の受信で行っている ので、それに従って22行はkb_event.type != EventConstants.KEYBOARD_PRESSと修正すべきかもしれません(そうすると26行目はelseだけでよくなりますね)。 個人的にはKEYBOARD_PRESS以外のイベントが全てキーリリース時にしか発生しないという確信が無い限りこういうコードは書きたくない ので19-6d.pyではKEYBOARD_RELEASEで判定しています。