在阴影中控制逐帧绘制

2024-06-01 02:50:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在努力理解如何控制Shady在屏幕上绘制的内容流。我习惯了Psychtoolbox,在这里,通过在backbuffer上绘制来不断添加到帧中,然后通过调用flip()显式地将其推到屏幕上。有了阴影,事情似乎会自动发生,所以当你向一个世界添加一个刺激对象时,它会很快被绘制出来。但是,如果我必须添加几个刺激,我怎么能保证屏幕上没有更新,直到他们都被画出来

例如,假设我想画一个空白屏幕,在它的左上角画一个小正方形。我可以这样做:

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)

但是我怎么能保证我不会从n帧开始画s1,从n+1帧开始画s2呢?也许它们可以组合成一个刺激对象,但我想把它们分开(在我的实际问题中,小正方形实际上是用来触发一个光电管的,所以我需要能够在任务中的不同时间闪烁一帧)


Tags: 对象none内容屏幕绘制shady习惯s2
1条回答
网友
1楼 · 发布于 2024-06-01 02:50:59

首先,阅读Concurrency上的可疑文档是个好主意。前两个示例(在标题“Running single threaded”下)没有您担心的问题,因为Stimulus实例是在实际渲染单个帧之前创建的。下面是一个简单的例子:

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

除此之外,Shady固有的范式转变之一就是你自己从不称任何flip()等价物。一开始可能会觉得不熟悉,但请放心:在我们基于Shady构建的应用程序中,我们不会错过自己调用flip()的日子。在回调中更新刺激参数,例如:

  • 所谓的“动画回调”,您可以选择安装到每个WorldStimulus实例中

  • 动态(可调用)值,可以分配给任何WorldStimulus属性(在任何动画回调之后立即计算这些值)

  • 响应按键等的事件回调。

在同一回调中所做的更改保证在同一帧上呈现,这就是我们如何严格控制计时的答案,即使运行单线程。确实,如果运行多线程并发出一个接一个的w.Stimulus()调用,它们就不能保证出现在同一帧上(事实上,它们一定会出现在不同的帧上,因为非绘制线程中的.Stimulus()调用实际上会将实际的刺激创建工作延迟到绘制线程中,然后等待直到完成后才返回)。可能的解毒剂是(1)运行单线程(2) 在专用的Prepare()方法中执行所有w.Stimulus()创建调用,如文档的第二个示例所示;或者(3)确保刺激物在创建时有visible=False,并且只在以后可见。在所有这些情况下,我们都会小心地将刺激物的产生(可能很慢)与对刺激物性质的操纵分开

前两种回调类型与您所描述的内容最相关。Shady在"Making properties dynamic"上的文档中介绍了它们。在它们提供的框架中,有许多不同的方法(在Python和Shady中都是这样)来实现您描述的目标。我个人喜欢使用^{}实例作为动画回调。下面是我如何创建一个简单的重复呈现的刺激,它的开始是由一个传感器补丁在一帧中闪烁所预示的:

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

相关问题 更多 >