如何在 Psychopy 中与视觉刺激完全同步地将代码发送到并行端口



我是python和psychopy的新手,但是我在编程和设计实验(使用Matlab和EPrime)方面拥有丰富的经验。我正在运行一个RSVP(快速视觉连续演示)实验,每X毫秒显示一次不同的视觉刺激(X是一个实验变量,可以从100毫秒到1000毫秒)。由于这是一个生理实验,我需要在刺激开始时通过并行端口发送触发器。我使用示波器和光电传感器测试触发器和视觉开始之间的同步。但是,当我在 win.flip() 之前或之后发送触发器时,即使使用 window waitBlanking=False 参数,我仍然会在刺激的开始和代码的开始之间得到差异。

附上我的代码:


    im=[]
    for pic in picnames:               
        im.append(visual.ImageStim(myWin,image=pic,pos=[0,0],autoLog=True))
    myWin.flip() # to get to the next vertical blank
    while tm < and t < len(codes):                
        im[tm].draw()                                             
        parallel.setData(codes[t]) # before
        myWin.flip()                
        #parallel.setData(codes[t]) # after
        ttime.append(myClock.getTime())
        core.wait(0.01)
        parallel.setData(0)                
        dur=(myClock.getTime()-ttime[t])*1000                
        while dur < stimDur-frameDurAvg+1:
           dur=(myClock.getTime()-ttime[t])*1000
        t=t+1
        tm=tm+1            
        myWin.flip()

如何将刺激开始与触发同步?我不确定这是否是显卡问题(我使用的是带有板载英特尔显卡的 LCD ACER 屏幕)。非常感谢,
沙尼

win.flip()等待下一次显示器更新。这意味着win.flip()之后的下一行几乎完全在监视器开始绘制帧时执行。这是您要发送触发器的位置。win.flip()之前的线路可能早了几乎一帧,例如在 60Hz 显示器上为 16.7 毫秒,因此您的触发器会过早到达。

有两种几乎相同的方法可以做到这一点。让我们从最明确的开始:

for i in range(10):
    win.flip()
    # On the first flip
    if i == 0:
        parallel.setData(255)
        core.wait(0.01)
        parallel.setData(0)

。因此,信号是在图像被推送到显示器后发送的。

稍微更精确的计时方法将为您节省 0.01 毫秒(加上减去一个数量级)。在脚本早期的某个地方定义

def sendTrigger(code): 
    parallel.setData(code)
    core.wait(0.01)
    parallel.setData(0)

然后做

win.callOnFlip(sendTrigger, code=255)
for i in range(10):
    win.flip()

这将在第一次翻转之后调用该函数,在精神病做一些清理之前。因此,该函数本可以调用win.callOnNextFlip,因为它仅在随后的第一次翻转时执行。

同样,与其他因素相比,这种时间差异是如此之小,以至于这不是一个性能问题,而是风格偏好的问题。

有一个隐藏的时序变量通常被忽略 - 监视器输入滞后,我认为这就是延迟的原因。简而言之,即使在从显卡获得输入后,显示器也需要一些时间来显示图像。此延迟与刷新率(屏幕切换缓冲区的次数)或监视器的响应时间无关。

在我的显示器中,当我使用 callOnFlip() 发送触发器时,我发现延迟为 23 毫秒。我如何更正它是:floor(23/16.667) = 1,23%16.667 = 6.333。所以我在第二帧上调用 callOnFlip,等待 6.3 毫秒并触发端口。这行得通。我还没有尝试过 WaitBlanking=True,它从显卡等待消隐开始,因为这给了我更多的时间来准备下一个缓冲区。但是,我认为即使使用WaitBlanking=True,效果也会存在。(测试后更多!

最好苏达

至少有一个例程可用于将触发延迟规范化为屏幕刷新率。我刚刚用光电传感器单元对其进行了测试,从触发和刺激显示之间的平均延迟 13 毫秒(sd = 3.5 ms)变为 4.8 毫秒的平均延迟(sd = 3.1 ms)。

程序如下:

  1. 计算两个显示器之间的平均持续时间。假设您的屏幕刷新率为 85.05(这是我的情况)。这意味着两次刷新之间的平均持续时间为 1000/85.05 = 11.76 毫秒。
  2. 在你调用 win.flip() 之后,在发送触发器之前等待这个平均延迟:core.wait(0.01176)。

这并不能确保所有延迟现在都等于零,因为您无法掌握 win.flip() 命令与屏幕当前状态之间的同步,但它会将延迟集中在零附近。至少,它对我有用。

因此,代码可以更新如下:

    refr_rate = 85.05
    mean_delay_ms = (1000 / refr_rate)
    mean_delay_sec = mean_delay_ms / 1000  # Psychopy needs timing values in seconds
    def send_trigger(port, value):
        core.wait(mean_delay_sec)
        parallel.setData(value)
        core.wait(0.001)
        parallel.setData(0)
    [...]
    stimulus.draw()
    win.flip()
    send_trigger(port, value)
    [...]

最新更新