dtadres
dtadres

Reputation: 217

matplotlib animation control interval in individual loops

Short

Is there a straightforward way to precisely control the time an animation object is shown while the script is running taking into account the time it takes for each loop of the updatefig function?

Long

I'm writing a script that is supposed to run on a Raspberry Pi. It takes pictures of an arena and tracks moving dots.

One option the user should have, is to see what the program is doing at each frame. The obvious way to do this is to use the animation module of matplotlib as this should be the fastest way to plot. In order to automatically close the plot after the experiment and continue with saving the data etc. I'd like the script to close the plot after a given time (number of frames/framerate)

I found a way for the plot to close itself, by running the script with plt.ion() and then just plt.pause() after calling the animation.

Now, what pause? It seems that plt.pause() does not take the time spent on each loop of the updatefig() function into account. So I'm left with having to define how long each loop will take before calling plt.pause.

To illustrate the problem I've written a short sample code (you might need to change the line of plt.pause, depending on the speed of your machine - 0.55 is the ~mean this loop takes on my computer).

import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
import time
import random

plt.ion()

class example():
    '''
    In this example I want to illustrate that I don't know a good way to control 
    the timing of the animation module.
    I want to have 10 frames with an interval of 10 seconds, so I would think 
    that I only have to call plt.pause(10) after opening the animation.
    Unfortunately it seems that the animation.Funcanimation is essentially 
    blocking the plt.pause function when it's still in the loop.
    '''
    def __init__(self):

        self.image = np.zeros((1000,1000), dtype=np.float32)
        self.total_time = 0

        self.plotting()

    def plotting(self):

        fig = plt.figure(figsize=(7,7))
        ax = fig.add_subplot(111)
        video_object = ax.imshow(self.image, vmin=0, vmax=1000, animated=True)

        def init():
            self.image[:, :] = np.random.rand(1000, 1000)
            video_object = ax.imshow(self.image)
            return(video_object,)

        def updatefig(i):
            start_time = time.time()
            #self.image[:,:] = np.random.rand(1000,1000) # I leave this here as this is how it should be done
            for i in range(self.image.shape[0]): # slow looping 
                for j in range(self.image.shape[1]):
                    self.image[i,j] = random.uniform(0,1000)

            video_object.set_array(self.image)
            self.total_time += time.time() - start_time
            print('time in loop: ' + repr(time.time() - start_time)[0:5])
            return(video_object,)

        ani = animation.FuncAnimation(fig = fig, func = updatefig, init_func = init,
                                      frames = 10, interval=1000, blit=True,
                                      repeat=False)
        fig.show()
        plt.pause(10-(0.55*10))
        plt.close()
        print('Total time loop : ' + repr(self.total_time))

outer_time = time.time()
example()
print('Total time until close fig: ' + repr(time.time() - outer_time))

Upvotes: 2

Views: 1466

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339120

Mixing interactive mode with FuncAnimation might not be a good idea. Instead you can use a timer (fig.canvas.new_timer(interval=interval)) to call plt.close() after interval milliseconds. Here interval would be the number of frames times the repetion time (or interval of the animation), plus a small offset, which might be the time it takes to actually create the window. In the following example this is chosen to be 300 milliseconds, which works fine for me.

import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
import time
import random


class example():

    def __init__(self, nframes = 17, interval = 1000):

        self.image = np.zeros((1000,1000), dtype=np.float32)
        self.total_time = 0
        self.nframes = nframes
        self.interval = interval
        self.plotting()

    def plotting(self):

        fig = plt.figure(figsize=(7,7))
        ax = fig.add_subplot(111)
        video_object = ax.imshow(self.image, vmin=0, vmax=1000, animated=True)
        #creating a timer object and setting an interval of 
        # nframes * interval + time to first draw milliseconds
        timer = fig.canvas.new_timer(interval = self.nframes*self.interval+300) 
        timer.add_callback(plt.close)

        def init():
            self.image[:, :] = np.random.rand(1000, 1000)
            video_object = ax.imshow(self.image)
            # start timer
            timer.start()
            return(video_object,)

        def updatefig(k):
            start_time = time.time()
            #self.image[:,:] = np.random.rand(1000,1000) # I leave this here as this is how it should be done
            for i in range(self.image.shape[0]): # slow looping 
                for j in range(self.image.shape[1]):
                    self.image[i,j] = random.uniform(0,1000)

            video_object.set_array(self.image)
            self.total_time += time.time() - start_time
            print('time in loop: ' + repr(time.time() - start_time)[0:5] + "- " +str(k))
            return(video_object,)

        ani = animation.FuncAnimation(fig = fig, func = updatefig, init_func = init,
                                      frames = self.nframes, repeat=False,
                                      interval=self.interval, blit=True )

        plt.show()

        print('Total time loop : ' + repr(self.total_time))

outer_time = time.time()
example(nframes = 17, interval = 1000)
print('Total time until close fig: ' + repr(time.time() - outer_time))

Upvotes: 2

Related Questions