python - 在 Shady 中控制逐帧绘制
问题描述
我正在努力理解如何控制 Shady 在屏幕上绘制的内容的流动。我已经习惯了 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)
但是我怎么能保证我不会从第 n 帧开始绘制 s1,从第 n+1 帧开始绘制 s2?也许它们可以组合成一个 Stimulus 对象,但我想将它们分开(在我的实际问题中,小方块实际上是用来触发光电管的,所以我需要能够将其闪烁一次帧,在任务期间的不同时间)。
解决方案
首先,最好阅读有关Concurrency的 Shady 文档。前两个示例(在“运行单线程”标题下)没有您担心的问题,因为Stimulus
实例是在实际渲染单个帧之前创建的。这是一个简单的例子:
import Shady
w = Shady.World(threaded=False)
# s1 = ...
# s2 = ...
# ...
w.Run()
除此之外,你是对的,Shady 固有的范式转变之一是你flip()
自己永远不会调用任何等价物。起初这可能会让人感到陌生,但请放心:在我们基于 Shady 构建的应用程序中,我们不会错过打电话给flip()
自己的日子。对刺激参数的更新在回调中完成,例如:
World
您可以选择安装到每个或Stimulus
实例中的所谓“动画回调” ;您可以分配给任何
World
或Stimulus
属性的动态(可调用)值(在任何动画回调之后立即评估这些值);响应按键等的事件回调。
您在同一个回调中所做的更改保证会在同一帧上呈现——这就是我们如何保持严格控制时间的答案,即使在 不运行单线程时也是如此。确实,如果您运行多线程并一个w.Stimulus()
接一个地发出调用,则不能保证它们出现在同一帧上(实际上,它们可以保证出现在不同的帧上,因为.Stimulus()
非绘图线程中的调用实际上会延迟刺激创建的实际工作进入绘图线程,然后等待直到完成后再返回)。可能的解毒剂是(1)运行单线程;(2) 以专用的方式执行所有w.Stimulus()
创建调用Prepare()
方法,如文档的第二个示例中所示;或 (3) 确保刺激物visible=False
在创建时具有,并且仅在以后使它们可见。在所有这些情况下,我们小心地将刺激的创建(可能很慢)与对其属性的操纵分开。
前两种回调类型与您所描述的内容最相关。它们包含在 Shady 关于“使属性动态化”的文档中。在它们提供的框架内,有许多不同的方法来实现您描述的目标(与 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
推荐阅读
- installshield - InstallShield 2016 Pro,服务不会安装多个功能
- machine-learning - 我应该在最后一个卷积层中使用 conv 3x3 进行语义分割吗?
- javascript - 文本字段使用 react-select 自动建议用户名
- node.js - 在 Nodejs 中找不到模块
- google-apps-marketplace - 限制服务帐号的 G Suite 域范围委派
- javascript - JavaScript 使用递归添加两个链表值
- jquery - 我正在尝试使用 jQuery 更改背景图像,它可以在我的本地机器上运行,但不能用于在线服务
- c++ - 分隔整个部分和小数部分的代码
- performance - 为给定文件中的每一行生成 shasum 时的性能问题
- javascript - 通过选择选项过滤表格行