Как отправить код на параллельный порт в точной синхронизации с визуальным стимулом в Psychopy

Я новичок в Python и психопатией, однако у меня огромный опыт программирования и разработки экспериментов (с использованием 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 < 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()

Как я могу синхронизировать мой стимул с триггером? Я не уверен, что это проблема с видеокартой (я использую ЖК-экран ACER со встроенной видеокартой Intel). Большое спасибо,
Шани

2 ответа

win.flip() ждет следующего обновления монитора. Это означает, что следующая строка после win.flip() выполняется почти точно, когда монитор начинает рисовать кадр. Вот где вы хотите отправить свой триггер. Линия как раз перед win.flip() потенциально почти на один кадр раньше, например, 16,7 мс на мониторе с частотой 60 Гц, поэтому ваш триггер прибудет слишком рано.

Есть два практически идентичных способа сделать это. Давайте начнем с самого простого для понимания:

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()

Это вызовет функцию сразу после переворота, до того, как психопия сделает небольшую уборку. Опять же, эта разница во времени настолько незначительна по сравнению с другими факторами, что на самом деле речь идет не о производительности, а о предпочтениях стиля.

Есть скрытая временная переменная, которая обычно игнорируется - задержка на входе монитора, и я думаю, что это причина задержки. Проще говоря, монитору требуется некоторое время для отображения изображения даже после получения данных от видеокарты. Эта задержка не имеет ничего общего с частотой обновления (сколько раз экран переключается в буфер) или временем отклика монитора.

В моем мониторе я нахожу задержку в 23 мс, когда отправляю триггер с помощью callOnFlip(). Как я могу исправить это: этаж (23/16,667) = 1, и 23%16,667 = 6,333. Поэтому я вызываю callOnFlip на втором кадре, жду 6,3 мс и запускаю порт. Это работает. Я не пробовал с WaitBlanking=True, который ожидает начала гашения с графической карты, так как это дает мне еще немного времени для подготовки следующего буфера. Тем не менее, я думаю, что даже с WaitBlanking = True эффект будет там. (Больше после тестирования!)

Бест, Суддха

Существует по крайней мере одна процедура, которую вы можете использовать для нормализации задержки триггера до частоты обновления экрана. Я только что протестировал его с помощью фотодатчика и перешел от средней задержки 13 миллисекунд (sd = 3,5 мс) между триггером и отображением стимула до средней задержки 4,8 миллисекунды (sd = 3,1 мс).

Порядок действий следующий:

  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)

    [...]
Другие вопросы по тегам