Reputation: 217
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
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