Controlling dynamic properties in Shady according to video frames not time

这一生的挚爱 提交于 2020-02-24 12:08:58

问题


I am trying to use Shady to present a sequence of image frames. I'm controlling the flow from another machine, so that I first instruct the machine running Shady to present the first frame, and later on to run the rest of the frames. I create a World instance, and attach to it an animation callback function. Within this callback I listen for communications from the other machine (using UDP). First I receive a command to load a given sequence (stored as a numpy array), and I do

def loadSequence(self, fname):
    yy = np.load(fname)
    pages = []
    sz = yy.shape[0]
    for j in range(yy.shape[1]/yy.shape[0]):
         pages.append(yy[:, j*sz:(j+1)*sz])
    deltax, deltay = (self.screen_px[0] - sz) / 2, (self.screen_px[1] - sz) / 2
    if (self.sequence is None):
        self.sequence = self.wind.Stimulus(pages, 'sequence', multipage=True, anchor=Shady.LOCATION.UPPER_LEFT, position=[deltax, deltay], visible=False)
    else:
        self.sequence.LoadPages(pages, visible=False)

When I receive the command to show the first frame, I then do:

def showFirstFrame(self, pars):
    self.sequence.page = 0 if (pars[0] == 0) else (len(self.sequence.pages) - 1)
    self.sequence.visible = True

But what do I do now to get the other frames to be be displayed? In the examples I see, s.page is set as a function of time, but I need to show all frames, regardless of time. So I was thinking of doing something along these lines:

def showOtherFrames(self, pars, ackClient):
    direction, ack = pars[0], pars[2]
    self.sequence.page = range(1, len(self.sequence.pages)) if (direction == 0) else range(len(self.sequence.pages)-2, -1, -1)

But this won't work. Alternatively I thought of defining a function that takes t as argument, but ignores it and uses instead a counter kept in a global variable, but I'd like to understand what is the proper way of doing this.


回答1:


Your global-variable idea is one perfectly valid way to do this. Or, since it looks like you're defining things as methods of an instance of your own custom class, you could use instance methods as your animation callbacks and/or dynamic property values—then, instead of truly global variables, it makes sense to use attributes of self:

import Shady

class Foo(object):

    def __init__(self, stimSources):
        self.wind = Shady.World()
        self.stim = self.wind.Stimulus(stimSources, multipage=True)
        self.stim.page = self.determinePage  # dynamic property assignment

    def determinePage(self, t):
        # Your logic here.
        # Ignore `t` if you think that's appropriate.
        # Use `self.wind.framesCompleted` if it's helpful.
        # And/or use custom attributes of `self` if that's
        # helpful (or, similarly, global variables if you must).
        # But since this is called once per frame (whenever the
        # frame happens to be) it could be as simple as:
        return self.stim.page + 1
        # ...which is indefinitely sustainable since page lookup
        # will wrap around to the number of available pages.

# Let's demo this idea:
foo = Foo(Shady.PackagePath('examples/media/alien1/*.png'))
Shady.AutoFinish(foo.wind)

Equivalent to that simple example, you could have the statement self.stim.page += 1 (and whatever other logic) inside a more-general animation callback.

Another useful tool for frame-by-frame animation is support for python's generator functions, i.e. functions that include a yield statement. Worked examples are included in python -m Shady demo precision and python -m Shady demo dithering.

It can also be done in a StateMachine which is always my preferred answer to such things:

import Shady

class Foo(object):
    def __init__(self, stimSources):
        self.wind = Shady.World()
        self.stim = self.wind.Stimulus(stimSources, multipage=True)


foo = Foo(Shady.PackagePath('examples/media/alien1/*.png'))         

sm = Shady.StateMachine()
@sm.AddState
class PresentTenFrames(sm.State):
    def ongoing(self): # called on every frame while the state is active
        foo.stim.page += 1
        if foo.stim.page > 9:
            self.ChangeState()

@sm.AddState
class SelfDestruct(sm.State):
    onset = foo.wind.Close

foo.wind.SetAnimationCallback(sm)

Shady.AutoFinish(foo.wind)



回答2:


When you make s.page a dynamic property, the function assigned to it must take one argument (t), but you can still just use any variables in the space when defining that function, and not even use the time argument at all.

So, for example, you could do something as simple as:

w = Shady.World(...)
s = w.Stimulus(...)
s.page = lambda t: w.framesCompleted

which will set the page property to the current frame count. That sounds like it could be useful for your problem.



来源:https://stackoverflow.com/questions/56382543/controlling-dynamic-properties-in-shady-according-to-video-frames-not-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!