Psychopy提供了很多IO交互方式,当然,最根本的还是键盘和鼠标。本节介绍Psychopy鼠标和键盘的编程技巧。

全局按键响应

编写刺激界面免不了要反复调试,要看看字体颜色对不对、图形大小合不合适,一旦发现刺激界面需要改进就得退出程序修改源代码。

如果采用普通的按键检测方式,则需要在一个循环体内检查按键状态,这显然有可能造成不可知的错误(比如在检测按键前进入一个死循环函数,程序永远无法退出啦),这个时候全局按键响应就很有用了。

Psychopy用psychopy.event.globalkeys来设置全局按键,官方文档里没有如何使用全局按键的说明,但在coder的Demo里有演示global_event_keys.py。

global_event.keys.py程序注册了三个按键,按键“b”调用python的setattr函数,设置rect对象的填充颜色为蓝色,按键“ctrl”+“r”调用python的setattr函数,设置rect对象的填充颜色为红色。按键“q”调用core.quit方法终止程序退出。

# -*- coding: utf-8 -*-

from psychopy import core, event, visual, monitors


if __name__=='__main__':
    mon = monitors.Monitor(
        name='my_monitor',
        width=53.704,  # 显示器宽度,单位cm
        distance=45,   # 被试距显示器距离,单位cm
        gamma=None,    # gamma值
        verbose=False) # 是否输出详细信息
    mon.setSizePix((1920, 1080)) # 设置显示器分辨率
    mon.save() # 保存显示器信息

    win = visual.Window(monitor=mon, size=(800, 600), fullscr=False,
        screen=0, winType='pyglet', units='norm', allowGUI=False)
    rect = visual.Rect(win, fillColor='blue', pos=(0, -0.2))
    text = visual.TextStim(
        win,
        pos=(0, 0.5),
        text=('Press\n\n'
              'B for blue rectangle,\n'
              'CTRL + R for red rectangle,\n'
              'Q or ESC to quit.'))

    # Add an event key.
    event.globalKeys.add(key='b', func=setattr,
                         func_args=(rect, 'fillColor', 'blue'),
                         name='blue rect')

    # Add an event key with a "modifier" (CTRL).
    event.globalKeys.add(key='r', modifiers=['ctrl'], func=setattr,
                         func_args=(rect, 'fillColor', 'red'),
                         name='red rect')

    # Add multiple shutdown keys "at once".
    for key in ['q', 'escape']:
        event.globalKeys.add(key, func=core.quit)

    # Print all currently defined global event keys.
    print(event.globalKeys)
    print(repr(event.globalKeys))

    while True:
        text.draw()
        rect.draw()
        win.flip()

以下是event.globalkeys.add()方法的参数介绍 event.globalkeys.add(key, func, func_args=(), func_kwargs=None, modifiers=(), name=None)

parameters type description
key string 按键字符串
func function 按键时执行的函数
func_args iterable 函数的args参数
func_kwargs dict 函数的kwargs参数
modifiers iterable 组合按键字符串列表,例如’shift’,’ctrl’,’alt’,’capslock’,’scrollock’等
name string 按键事件的名称

此外还有event.globalkeys.remove()方法以移除全局按键 event.globalkeys.remove(key, modifiers=())

parameters type description
key string 按键字符串
modifiers iterable 组合按键字符串列表,例如’shift’,’ctrl’,’alt’,’capslock’,’scrollock’等

等待按键和检测按键

除了全局按键响应,Psychopy还提供了等待按键响应和检测按键响应两种方式。

以下为等待按键函数event.waitKeys()的演示程序,按’esc’或五次其他按键退出程序。

# -*- coding: utf-8 -*-

from psychopy import core, event, visual, monitors


if __name__=='__main__':
    mon = monitors.Monitor(
        name='my_monitor',
        width=53.704,  # 显示器宽度,单位cm
        distance=45,   # 被试距显示器距离,单位cm
        gamma=None,    # gamma值
        verbose=False) # 是否输出详细信息
    mon.setSizePix((1920, 1080)) # 设置显示器分辨率
    mon.save() # 保存显示器信息

    win = visual.Window(monitor=mon, size=(800, 600), fullscr=False,
        screen=0, winType='pyglet', units='norm', allowGUI=False)
    msg = visual.TextStim(win, text='press a key\n < esc > to quit')
    msg.draw()
    win.flip()

    k = ['']
    count = 0
    while k[0] not in ['escape', 'esc'] and count < 5:
        k = event.waitKeys()
        print(k)
        count += 1

    win.close()
    core.quit()

event.waitKeys()阻塞函数进程直到被试按键,以下是event.waitKeys()方法的参数介绍 event.waitKeys(maxWait=inf, keyList=None, modifiers=False, timeStamped=False, clearEvents=True)

parameters type description
maxWait numeric value 最大等待时间,默认为inf
keyList iterable 指定函数检测的按键名称,函数仅在按指定键时返回
modifiers bool 如果True,返回(keyname, modifiers)的tuple
timeStamped bool 如果True,返回(keyname, time)
clearEvents bool 如果True,在检测新的按键前清理event buffer
return type description
keys iterable 按键列表;超时返回None

等待按键会阻塞进程,Psychopy还提供了另一种非阻塞检测方式event.getKeys()

以下代码如下不断检测按键并输出,直到按’escape’键退出程序。

# -*- coding: utf-8 -*-

from psychopy import core, event, visual, monitors


if __name__=='__main__':
    mon = monitors.Monitor(
        name='my_monitor',
        width=53.704,  # 显示器宽度,单位cm
        distance=45,   # 被试距显示器距离,单位cm
        gamma=None,    # gamma值
        verbose=False) # 是否输出详细信息
    mon.setSizePix((1920, 1080)) # 设置显示器分辨率
    mon.save() # 保存显示器信息

    win = visual.Window(monitor=mon, size=(800, 600), fullscr=False,
        screen=0, winType='pyglet', units='norm', allowGUI=False)
    msg = visual.TextStim(win, text='press a key\n < esc > to quit')
    msg.draw()
    win.flip()

    count = 0
    while True:
        k = event.getKeys()
        if k:
            if 'escape' in k:
                break
            print(k)

    win.close()
    core.quit()

以下是event.getKeys()方法的参数介绍 event.getKeys(keyList=None, modifiers=False, timeStamped=False)

parameters type description
keyList iterable 指定函数检测的按键名称,函数仅在按指定键时返回
modifiers bool 如果True,返回(keyname, modifiers)的tuple
timeStamped bool 如果True,返回(keyname, time)
return type description
keys iterable 按键列表;超时返回None

鼠标事件

Psychopy提供event.Mouse类来处理鼠标相关的事件,官方文档对此有详细的介绍。以下的代码显示了一个含有矩形的窗,在矩形内部单击左右键可以改变颜色,而按中央滚轮键则退出程序。

# -*- coding: utf-8 -*-

from psychopy import core, event, visual, monitors


if __name__=='__main__':
    mon = monitors.Monitor(
        name='my_monitor',
        width=53.704,  # 显示器宽度,单位cm
        distance=45,   # 被试距显示器距离,单位cm
        gamma=None,    # gamma值
        verbose=False) # 是否输出详细信息
    mon.setSizePix((1920, 1080)) # 设置显示器分辨率
    mon.save() # 保存显示器信息

    win = visual.Window(monitor=mon, size=(800, 600), fullscr=False,
        screen=0, winType='pyglet', units='norm', allowGUI=True)
    rect = visual.Rect(win, fillColor='blue', pos=(0, 0))
    
    # 创建Mouse类
    mouse = event.Mouse(visible=True, newPos=(0, 0), win=win)
    while True:
        # 重置单击事件状态
        mouse.clickReset()

        # 检测左键是否在矩形内单击
        if mouse.isPressedIn(rect, buttons=[0]):
            rect.fillColor = 'red'

        # 检测右键是否在矩形内单击
        if mouse.isPressedIn(rect, buttons=[2]):
            rect.fillColor = 'blue'
        
        # 检测是否单击滚轮键
        # button1: left click
        # button2: middle click
        # button3: right click
        button1, button2, button3 = mouse.getPressed(getTime=False)
        if button2:
            break
        
        rect.draw()
        win.flip()

    core.quit()