Управление покадровым рисунком в Shady - PullRequest
2 голосов
/ 28 мая 2019

Я изо всех сил пытаюсь понять, как я могу контролировать поток того, что рисует на экране Шейди.Я привык к Psychtoolbox, где вы продолжаете добавлять кадры, рисуя их в буферном буфере, а затем явно выталкиваете их на экран с вызовом flip ().С Shady все происходит автоматически, поэтому, когда вы добавляете объект Stimulus в World, он рисуется как можно скорее.Но если мне нужно добавить несколько стимулов, как я могу гарантировать, что на экране ничего не будет обновлено, пока все они не будут нарисованы?

Предположим, например, что я хочу нарисовать пустой экран и маленький квадратсверху это в левом верхнем углу.Я мог бы сделать что-то вроде:

w = Shady.World()
s1 = w.Stimulus(None, 'blank', envelopeSize=[1920, 1200], backgroundColor=[0, 0, 0], z=0)
s2 = w.Stimulus(None, 'square', envelopeSize=[20, 20], x=-950, y=590, backgroundColor=[1, 1, 1], z=1)

Но как я могу гарантировать, что у меня не получится, что s1 будет вытягиваться из кадра n и далее, s2 из кадра n + 1 и далее?Возможно, они могут быть объединены в один объект Stimulus, но я бы хотел их разделить (в моей реальной проблеме маленький квадрат фактически используется для запуска фотоэлемента, и поэтому мне нужно иметь возможность прошить его для одногокадр, в разное время во время выполнения задания).

1 Ответ

1 голос
/ 29 мая 2019

Во-первых, было бы неплохо прочитать документацию Shady по Concurrency .Первые два примера (под заголовком «Запуск однопоточного») не вызывают проблем, о которых вы беспокоитесь, поскольку экземпляры Stimulus создаются до того, как на самом деле отображается один кадр.Вот простой пример:

import Shady
w = Shady.World(threaded=False)
# s1 = ...    
# s2 = ...
# ...    
w.Run()

Помимо этого, вы правы в том, что одно из внутренних изменений парадигмы Шейди заключается в том, что вы никогда не называете flip() эквивалентом себя.И поначалу это может показаться незнакомым, но будьте уверены: в приложениях, которые мы создали на Shady, мы не пропускаем дни вызова flip() сами.Обновления параметров стимула выполняются в обратных вызовах, таких как:

  • так называемый «анимационный обратный вызов», который вы можете дополнительно установить в каждый экземпляр World или Stimulus;

  • динамические (вызываемые) значения, которые можно присвоить любому свойству World или Stimulus (они оцениваются сразу после любых обратных вызовов анимации);

  • обратные вызовы событий, которые реагируют на нажатия клавиш и т. Д.

Изменения, которые вы выполняете в рамках одного и того же обратного вызова, гарантированно будут отображаться в одном кадре, иэто ответ на то, как мы строго контролируем время, даже когда не работает однопоточно.Это правда, что если вы запустите многопоточность и выполните один w.Stimulus() вызов за другим, они не обязательно появятся в одном кадре (фактически, они гарантированно появятся в различных кадрах, потому что.Stimulus() вызов в потоке без рисования фактически откладывает реальную работу по созданию стимула в поток рисования, а затем ждет , пока это не будет завершено, прежде чем вернуться).Возможные противоядия: (1) запуск однопоточный;(2) выполнить все вызовы создания w.Stimulus() в выделенном методе Prepare(), как во втором примере документации;или (3) убедиться, что стимулы имеют visible=False при создании, и сделать их видимыми только позже.Во всех этих случаях мы осторожно отделяем создание стимулов (которые могут быть медленными) от манипуляций их свойств.

Первые два типа обратного вызова:наиболее актуально для того, что вы описываете.Они описаны в документации Шейди по «Создание свойств динамического» * ​​1049 *.В рамках, которые они предоставляют, есть (как всегда в Python и в Shady) множество различных способов достижения цели, которую вы описываете.Лично мне нравится использовать экземпляр StateMachine в качестве обратного вызова анимации.Вот как я мог бы создать простой повторяющийся стимул, начало которого предвещает сенсорный патч, мигающий для одного кадра:

import random
import Shady

w = Shady.World(canvas=True, gamma=2.2)


# STIMULI

Shady.Stimulus.SetDefault(visible=False)
# let all stimuli be invisible by default when created

gabor = w.Sine(pp=0)
sensorPatch = w.Stimulus(
    size = 100,  # small,
    color = 1,   # bright,
    anchor = Shady.UPPER_LEFT, # with its top-left corner stuck...
    position = w.Place(Shady.UPPER_LEFT),  # to the top-left corner of the screen
)


# STATE MACHINE

sm = Shady.StateMachine()

@sm.AddState
class InterTrialInterval(sm.State):
    # state names should be descriptive but can be anything you want

    def duration(self):
        return random.uniform(1.0, 3.0)

    next = 'PresentGabor'

@sm.AddState
class PresentGabor(sm.State):

    def onset(self):
        gabor.visible = True
        sensorPatch.visible = Shady.Impulse() # a dynamic object: returns 1.0 the first time it is evaluated, then 0.0 thereafter

    duration = 2.0

    def offset(self):
        gabor.visible = False

    next = 'InterTrialInterval'


w.SetAnimationCallback( sm )
# now sm(t) will be called at every new time `t`, i.e. on every frame,
# and this will in turn call `onset()` and `offset()` whenever appropriate
...