[Matplotlib-devel] Gauging interest in a AsynchronousAnimation class

toemoss garcia toemossgarcia at gmail.com
Sat Sep 19 21:47:38 CEST 2015


Hi,

Out of my own needs I developed an animation class that is very similar to
FuncAnimation but is drawn asynchronously (not that this couldn't be done
with FuncAnimation, this is just a more direct path). Instead of trying to
explain the differences off the bat I'll give a base example usage

fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))

ani = AsyncAnimation(fig,interval=50)
fig.show() # starts the first 'draw' without entering the mpl mainloop

i = 0
while ani.update(): #update() flushes the animation's queue
    i += 1
    y = np.sin(x+i/10.0)
    ani.append_artist(line,(x,y))

This is the simplest example, where one frame is queued at a time and
`update()` blocks execution until that frame has been rendered (so this is
effectively the same as a FuncAnimation).

Aesthetically, the difference is that the animation code lives inside a
loop instead of a function call. This removes the need to have the
animation code pass variables back to the user or doing any global imports
of variables which IMHO makes the application code cleaner and more
self-contained. Personally, I find this a simpler and more intuitive way to
write interactive animation applications. (Alternatively, writing
application code inside of a class that uses FuncAnimation gives you
similar benefits of cleaner code).

The other more significant difference is that having rendering being done
asynchronously basically decouples the animation code from the application
code, which

   1. Makes it trivial to execute the graphics rendering as a separate
   process
   2. Gives the user more flexibility in their application code (you could
   imagine an application "queueing up" multiple frames at a time, allowing
   them to render in their own time).

Anyway, here is the code as it stands now (most important are the
`append_artist` and `update` functions; the rest of the code is very
similar to the FuncAnimation class):

class AsyncAnimation(Animation):
    """
    Makes an animation by reading from a user filled queue every *interval*
    milliseconds. User adds to the queue by calling *update_artists*.

    Note that data is _not_ copied before being put into the queue; if a
reference
    is passed (such as a numpy ndarray) whatever data the reference is
pointing
    to at draw time will be plotted.

    *init_func* is a function used to draw a clear frame. If not given, the
    results of drawing from the first item in the frames sequence will be
    used. This function will be called once before the first frame.

    *event_source* Default event source to trigger poll of the artist
queue. By
    default this is a timer with an *interval* millisecond timeout.
    """
    def
__init__(self,fig,init_func=None,event_source=None,interval=10,blit=True):
        self._data = deque()
        self._lag = 0
        self._queued = False

        self._fig = fig
        self._interval = interval

        self._init_func = init_func

        if event_source is None:
            event_source = fig.canvas.new_timer(interval=self._interval)

        Animation.__init__(self,fig,event_source=event_source,blit=blit)

    def new_frame_seq(self):
        return itertools.count()

    def _init_draw(self):
        if self._init_func is not None:
            self._drawn_artists = self._init_func()
            for a in self._drawn_artists:
                a.set_animated(self._blit)

    def _draw_next_frame(self, *args):
        # carry on if there's nothing to draw right now
        if self._data:
            Animation._draw_next_frame(self,*args)

    def _draw_frame(self,framedata):
        artdata = self._data.popleft()

        artists = []
        for (a,d) in artdata:
            artists.append(a)
            if d is not None: a.set_data(d)
            a.set_animated(self._blit)
        self._drawn_artists = artists

    def append_artist(self,artist,data=None):
        self._queue_artists((artist,data),)

    def extend_artists(self,artists):
        self._queue_artists(*artists)

    def _queue_artists(self,*artists):
        if len(self._data) and self._queued:
            self._data[-1] += artists
            return

        self._queued = True

        if len(self._data) > self._lag:
            warnings.warn("Artists queue is behind by %d" % len(self._data))
            self._lag = len(self._data)

        self._data.append(artists)

    def update(self,block=True):
        self._queued = False
        self._fig.canvas.flush_events()

        if block:
            while len(self._data) and self.event_source is not None:
                self._fig.canvas.flush_events()

        return self.event_source is not None


So, I've developed this code enough that it fills my needs, but it needs
some work to add in all the expected matplotlib functionality. Is something
that would be useful to people?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-devel/attachments/20150919/eb64a085/attachment.html>


More information about the Matplotlib-devel mailing list