【问题标题】:How to send a code to the parallel port in exact sync with a visual stimulus in Psychopy如何将代码发送到并行端口与 Psychopy 中的视觉刺激完全同步
【发布时间】:2015-03-11 15:44:01
【问题描述】:

我是 python 和 Psychopy 的新手,但是我在编程和设计实验(使用 Matlab 和 EPrime)方面拥有丰富的经验。我正在运行一个 RSVP(快速视觉串行演示)实验,每 X 毫秒显示一个不同的视觉刺激(X 是一个实验变量,可以从 100 毫秒到 1000 毫秒)。由于这是一项生理实验,我需要在刺激开始时通过并行端口发送触发器。我使用示波器和光电传感器测试触发器和视觉开始之间的同步。但是,当我在 win.flip() 之前或之后发送触发器时,即使使用窗口 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 &lt 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()

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

【问题讨论】:

  • 您是否将光电传感器设置在屏幕顶部,您看到什么样的延迟?屏幕中间的光电传感器通常会产生 5-8 毫秒的显示器端延迟。
  • 顺便说一句,由于时间很重要,所以不要使用秒和while来控制时间。使用 win.flip()s 因为视觉时间无论如何都被离散锁定到帧。因此,在 60 Hz 显示器上中断 0.5 秒,在 win.flip() 上循环 0.5*60 次。
  • 嗨 Jonas,我检查了屏幕顶部和中间的光传感器,这会产生类似的延迟。我所说的延迟比屏幕刷新-20-40 毫秒还要大。关于使用翻转的时间,因为我在显示器之间有代码(发送触发器,检查响应),我发现只使用翻转通常会导致一帧额外的帧。如果像我上面那样编程(ttime[t]-ttime[t-1] 是完美的,因为我少等待一帧,然后执行我的代码,然后等待下一个翻转。
  • 这个问题你解决了吗?
  • 不,从来没有...回到使用 EPrime

标签: python-2.7 parallel-port psychopy


【解决方案1】:

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

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

win.flip()
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)
win.flip()

这将在翻转之后调用该函数,在psychopy 进行一些大扫除之前。同样,与其他因素相比,这种时间上的差异是如此微不足道,以至于这并不是表演的问题,而是风格偏好的问题。

【讨论】:

  • 谢谢。我按照您的建议修改了我的代码,但这并没有改变情况 - 延迟仍然相同。看来,flip() 命令首先执行代码,然后等待屏幕垂直空白,而不是在垂直空白上执行。我想知道这是否可以修改。我不知道为什么延迟很长,使用 EPrime 时我没有得到这样的延迟,所以这不是显示器或显卡的结果。
  • PsychoPy 在这方面通常是完美的,所以我仍然怀疑你的代码中的某些东西会导致这种情况。您是否尝试过简单的示例,您只需在黑色背景上呈现白色刺激,然后执行stim.draw(),然后像上面一样发送触发器?没有所有时间的东西。只是为了测试它是否是系统/PsychoPy 中更深层次的东西,或者它是否与您如何将脚本组合在一起。
  • 是的,我使用了一个简单的测试代码,也使用了gist.github.com/jeremygray/9062586中描述的基准代码,修改它以发送代码。当你说psychopy在这方面很完美时,你是指用示波器进行测试的时候吗?因为在代码中测试 (getTime[t]-getTime[t-1]) 是完美的,但屏幕显示不同。
  • 好的。是的,显示器上实际亮度变化的好时机。那么这可能与您的驱动程序/显卡有关。 PsychoPy 使用 OpenGL 进行渲染。不确定 Eprime 做了什么,但这可能是问题的根源。尝试为您的显卡安装最新的驱动程序并再次运行。较旧的驱动程序不能正确支持 OpenGL,但有时较新的驱动程序支持。
  • 显卡是新的,NVidia,所有驱动都是最新的。我使用多台显示器进行了测试。肯定有什么事情发生了,但我不知道如何解决它。
【解决方案2】:

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

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

最好, 苏达

【讨论】:

  • 非常感谢,我会试试这个。但是,这不是Psychopy应该处理的事情吗?使用 EPrime 时没有这样的延迟。
  • 显示器输入延迟仅取决于显示器,如果您使用 EPrime 无法获得它,我认为这里发生了其他事情......
【解决方案3】:

至少有一个例程可用于将触发延迟标准化为屏幕刷新率。我刚刚使用光电传感器对其进行了测试,从触发和刺激显示之间的平均延迟 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)

    [...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-22
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 2017-04-28
    • 2016-04-16
    • 2016-12-31
    相关资源
    最近更新 更多