Matplotlib - stop animation - python

I have made an animation using matplotlib and I'm trying to save it to a file. For this it seems I would need to turn off automatic repeating of the animation. If not, matplotlib will try to render a movie file that never ends.
But how do I keep the animation from looping? I have found that there is a keyword argument for the animation function, repeat, that can be set to False, but this has no apparent effect on my code! So what should I do? I've been googling for way to long with no avail.
The relevant code is as follows (last two lines is where I believe the error must be) (largely based on this):
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
# ax.axis('off')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, n_bodies))
# set up lines and points
lines = sum([ax.plot([], [], [], '-', c=c)
for c in colors], [])
pts = sum([ax.plot([], [], [], 'o', c=c)
for c in colors], [])
# prepare the axes limits
xmin_max = (np.min(r[:,:,0])/2, np.max(r[:,:,0])/2)
ymin_max = (np.min(r[:,:,1])/2, np.max(r[:,:,1])/2)
zmin_max = (np.min(r[:,:,2])/2, np.max(r[:,:,2])/2)
ax.set_xlim(xmin_max)
ax.set_ylim(ymin_max)
ax.set_zlim(zmin_max)
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt in zip(lines, pts):
line.set_data([], [])
line.set_3d_properties([])
pt.set_data([], [])
pt.set_3d_properties([])
return lines + pts
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % r.shape[1]
for line, pt, ri in zip(lines, pts, r):
# x, y, z = ri[:i].T
x, y, z = ri[i-1].T
line.set_data(x, y)
line.set_3d_properties(z)
pt.set_data(x, y)
# pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z)
# pt.set_3d_properties(z[-1:])
ax.legend(['t = %g' % (i/float(n_timesteps))])
#ax.view_init(30, 0.01 *0.3 * i )
fig.canvas.draw()
return lines + pts
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=n_timesteps, interval=10, blit=True,repeat=False)
anim.save('../fig/animation.mp4', writer = 'mencoder', fps=15)
print 'done!'
plt.show()

Have you run this? I have found that saving the animation produces a file with the number of frames set by FuncAnimation( , ,frames=numberofframes).
ani = animation.FuncAnimation(fig, update, frames=numberofframes, interval=1000/fps)
filename = 'doppler_plot'
ani.save(filename+'.mp4',writer='ffmpeg',fps=fps)
ani.save(filename+'.gif',writer='imagemagick',fps=fps)
If the output format is an animated GIF, this will usually repeat when played, but the file will only contain the number of frames specified.

Related

Creating Multi-Scene Animation using consecutive calls to Matplotlib.FuncAnimation creates flickered images

Context: I am trying to create a teaching demo tool to show how the iteration guesses through a set of points to ultimately arrive at the root of an equation
Problem: I want to animate using matplotlib to show the iterations viusally. Specifically, given a curve (see along side) and an initial guess (say 1.5 in this particular case), I want to compose an animation of 3 scenes:
draw a vertical line at x = guess (=1.5), to meet the curve at y= 9 (aka value).
Draw a line through (guess, value) with a slope 'm' (generated by my code). Delete the vertical line at this stage and keep the second line
Delete the second line after a pause
For illustration, here is the image
Additionally here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
xdata2, ydata2 = [], []
ln, = plt.plot([], [])
def init():
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
return [ln]
def update(frame):
xdata.append(frame)
ydata.append(frame ** 3 + 4 * frame ** 2 + frame - 6)
ln.set_data(xdata, ydata)
return [ln]
def update2(frame):
xdata2.append(1.5)
ydata2.append(frame)
ln.set_data(xdata2,ydata2)
return[ln]
ani = FuncAnimation(fig, update, frames=np.linspace(-3, 3, 100),
init_func=init, blit=True)
ani2 = FuncAnimation(fig, update2, frames=np.linspace(0, 3, 100),blit=True)
plt.show()
This is a simplified version of the problem that I am trying to solve and is not part of the code that involves the calculation of the iterations etc. For now I am just trying to draw the curve in Update and post that, draw a vertical line at x=1.5.
Results: At my end, the entire animation is a set of flickering where it is apparent that matplotlib switches "thread context" very rapidly between the two FuncAnimation calls
The desired animation in your next question can be achieved in the form of drawing a curve as a base graph, adding line graphs frame by frame, and deleting that graph object when necessary.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import time
fig, ax = plt.subplots()
x = np.linspace(-3, 3, 100)
y = x ** 3 + 4 * x **2 + x -6
xx = x[74]
yy = y[74]
#print(xx,yy)
xx2 = x[65]
yy2 = y[65]
#print(xx2,yy2)
ln, = ax.plot(x, y)
ln2, = ax.plot([], [])
ln3, = ax.plot([],[])
ax.set_xlim(-3, 3)
ax.set_ylim(-10, 10)
# Move axes center and spines off
ax.spines[['top', 'right']].set_visible(False)
ax.spines[['left', 'bottom']].set_position('center')
# Show ticks axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
def update(i):
ln2.set_data((xx, xx), (0, yy))
ln2.set_color('k')
if i == 2:
ln3.set_data((xx2, xx), (yy2, yy))
ln3.set_color('red')
ln3.set_width=3
if i >=3:
ln2.set_data([],[])
ln3.set_data([],[])
return ln2,ln3
ani = FuncAnimation(fig, update, frames=[0,1,2,3], interval=500, blit=True, repeat=True)
plt.show()

How to stop second plot from showing up in matplotlib?

I am trying to plot an animation of a physics system. I've worked out the equations and it plots, but there's a second plot I don't want that keeps showing up and I can't get it to stop.
import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
# Input constants
m = 10 # mass (kg)
L = 4 # length (m)
g = 9.81 # gravity (m/s^2)
dt = 0.1 # time step size (seconds)
t_max = 40 # max sim time (seconds)
num_steps = 1 + int(t_max/dt)
theta_0 = np.pi/2 # initial angle (radians)
theta_dot_0 = 0 # initial angular velocity (rad/s)
state0 = [theta_0,theta_dot_0]
# Get timesteps
time_index = np.arange(0, t_max + dt, dt)
def derivatives(state, time_index, L=L, m=m, g=g):
theta_dot = state[1]
theta_ddot = -g*np.sin(state[0])/L
return theta_dot,theta_ddot
output = integrate.odeint(derivatives, state0, time_index)
theta = output[:,0]
theta_dot = output[:,1]
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-2*L, 2*L), ylim=(-2*L, 2*L))
ax.set_aspect('equal')
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
x = L*np.sin(theta)
y = -L*np.cos(theta)
def init():
# create empty object to put the points in
line.set_data([], [])
time_text.set_text('')
return line, time_text
def animate(t):
x_t = [0, x[t]]
y_t = [0, y[t]]
# add the point to the line
line.set_data(x_t, y_t)
# add the time text to plot
# time_template will be the text next to thetime
time_text.set_text(time_template % (t*dt))
return line, time_text
# we don't want the range of the steps to start at 0
# start it at one
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
ani
Here's the output:
The plot I want to get rid of is the one I circled with red. This is the entirety of my code, so it should be completely reproducible.
I tried several variations of trimming the plotting code but I wasn't able to debug why it's happening.
How can I get rid of this second plot?
A simple plt.close() before your call to ani will do the job.
Last few lines:
ani = animation.FuncAnimation(fig, animate, range(1, num_steps),
interval=dt*1000, blit=True, init_func=init)
rc('animation', html='jshtml')
#ani.save('pendulum.mp4', fps=15)
plt.close()
ani
Demo:
More info at this link.

python3 class function and matplotlib [duplicate]

I gave an answer on this thread, talking about fading point on matplotlib. And I got curious about ImportanceOfBeingErnest's answer. So I tried to play around with his code.
First, here is my code.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.colors import LinearSegmentedColormap
def get_new_vals():
x = 0
y = 0
while True:
if x >= .9:
x = 0
y = 0
x += .1
y += .1
yield x, y
def update(t, x_vals, y_vals, intensity, scatter, gen):
# Get intermediate points
new_xvals, new_yvals = gen.next()
x_vals.extend([new_xvals])
y_vals.extend([new_yvals])
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals, y_vals])
# Calculate new color values
for index in range(len(intensity)):
if intensity[index] < .1:
intensity[index] = 0
intensity[index] *= .6
intensity.extend(1 for _ in xrange(len([new_xvals])))
intens_dup = np.array(intensity)
"""
intensity = np.concatenate((np.array(intensity) * .6, np.ones(len(new_xvals))))
"""
scatter.set_array(intens_dup)
# Set title
axis.set_title('Time: %0.3f' % t)
def anim_random_points(fig, axis):
x_vals = []
y_vals = []
intensity = []
iterations = 100
colors = [ [0, 0, 1, 0], [0, 0, 1, 0.5], [0, 0.2, 0.4, 1] ]
cmap = LinearSegmentedColormap.from_list("", colors)
scatter = axis.scatter(x_vals, y_vals, c=[], cmap=cmap, vmin=0, vmax=1)
gen_values = get_new_vals()
ani = matplotlib.animation.FuncAnimation(fig, update, frames=iterations,
interval=50, fargs=(x_vals, y_vals, intensity, scatter, gen_values),
repeat=False)
# Position 1 for plt.show()
plt.show()
if __name__ == '__main__':
fig, axis = plt.subplots()
axis.set_xlabel('X Axis', size = 12)
axis.set_ylabel('Y Axis', size = 12)
axis.axis([0,1,0,1])
anim_random_points(fig, axis)
# Position 2 for plt.show()
# plt.show()
I, then, noticed an odd thing. At least for me. Notice the Position 1 and Position 2 (at the end of code). The position 1 is placed just after the animation function, the other one is placed just after code-wise, since the function ends after position 1 and therefore goes to position 2.
Since FuncAnimation requires the figure to run the animation on, I am wondering why the plt.show() works on Position 1, but not on Position 2.
The matplotlib documentation states about FuncAnimation
It is critical to keep a reference to the instance object. The animation is advanced by a timer (typically from the host GUI framework) which the Animation object holds the only reference to. If you do not hold a reference to the Animation object, it (and hence the timers), will be garbage collected which will stop the animation.
If you put plt.show() outside of the anim_random_points function, the variable ani, which holds the reference to the animation, will be garbage collected and there will be no animation to be shown any more.
The solution for that case would be to return the animation from that function
def anim_random_points(fig, axis):
# ...
ani = matplotlib.animation.FuncAnimation(...)
return ani
if __name__ == '__main__':
# ...
ani = anim_random_points(...)
plt.show()
You should really ask two separate questions.
I can answer the first one. The difference between the two positions is due to the fact that ani is a local variable to your function anim_random_points(). It is automatically deleted when the execution reaches the end of the function. Therefore plt.show() in position 2 has nothing to display.
If you want to use plt.show() in position 2, you need to return the ani object from your function, and keep a reference to it in the main part of your code.
def anim_random_points(fig, axis):
(...)
ani = matplotlib.animation.FuncAnimation(...)
return ani
if __name__ == '__main__':
(...)
ani = anim_random_points(fig, axis)
# Position 2 for plt.show()
plt.show()

How to make an animation of a curve from scratch using Matplotlib

Note this is a follow-up question of How to make an animation of a Lissajous curve;
My first idea was to edit my original question and ask for the animation, but I understand and respect SO way of operating. So the best is making another question.
I want to make an animation of the curve (where you incrementally draw it) with parametrization: x(t) = sin(3t) and y(y) = sin(4t) where t[0, 2pi].
For doing so I would add the code:
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'b')
def init():
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
x.append(np.sin(4*frame))
y.append(np.sin(3*frame))
ln.set_data(x, y)
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
The problem is that with this code it doesn't draw the whole curve from scratch. What does is overdrawing it, getting overlapping.
How can I draw it from scratch (i.e. starting with white background)? I've been thinking about an if else but got nothing.
Thanks
EDIT
Let me show you the whole code:
%matplotlib notebook
import matplotlib.pyplot as plt
import math
import numpy as np
from matplotlib.animation import FuncAnimation
# set the minimum potential
rm = math.pow(2, 1 / 6)
t = np.linspace(-10, 10, 1000, endpoint = False)
x = []
y = []
for i in t: #TypeError 'int' object is not iterable
x_i = np.sin( 3 * i )
y_i = np.sin( 4 * i )
x.append(x_i)
y.append(y_i)
# set the title
plt.title('Plot sin(4t) Vs sin(3t)')
# set the labels of the graph
plt.xlabel('sin(3t)')
plt.ylabel('sin(4t)')
fig, ax = plt.subplots()
ln, = plt.plot([], [], 'b')
def init():
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
x.append(np.sin(4*frame))
y.append(np.sin(3*frame))
ln.set_data(x, y)
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
init_func=init, blit=True)
# display the graph
plt.show()
This is the image I get at the beginning (screenshot taken after approximately 1s after started running; that's why you see that funny line): https://imgur.com/a/bNoViDA. As you can see it doesn't start from scratch (i.e not from white background)
This is the plot I get at the end: https://imgur.com/a/WQHHUk9
I am seeking getting that ending point but drawing everything from scratch, without starting with the shown plot.

matplotlib save every n steps for animation

I'm simulating something with many time step, and there are a lot of steps involved. I would like to animate my results, plotting only one step every n.
Right now i have two non working propositions. The first one doesn't seem to loop correctly, and the second still fills the video with steps that are not update in the figure, making the file large and slow.
Can you help me ?
Thanks
X, Y = np.meshgrid(256,256)
fig,ax = plt.subplots()
plot_every = 50
saved_steps = []
def make_step(s, t):
c = ...
if (s%plot_every) == 0:
print("plotting step {:.0f} , t ={:.0f} ".format(s, t*dt))
ax.clear()
ax.contourf(X, Y, c,
np.arange(0, 1.0, 0.01),
extend='both')
saved_steps.append(ax)
for s , t in enumerate(range(t_steps)):
make_step(s,t)
print("will now save anim")
def plot_ani(i):
return saved_steps[i]
anim = animation.FuncAnimation(fig, plot_ani,range(len(saved_steps)) , interval=500, blit=False)
or :
fig,ax = plt.subplots()
saved_steps = []
def make_step(s, t):
if (s%plot_every) == 0:
print("plotting step {:.0f} , t ={:.0f} ".format(s, t*dt))
ax.clear()
ax.contourf(X, Y, c,
np.arange(0, 1.0, 0.01),
extend='both')
return ax
anim = animation.FuncAnimation(fig, make_step,range(len(saved_steps)) , interval=500, blit=False)
and then i do
anim.save('spinodal_decompo_ex.ogv', codec='libtheora')
The problem with your first approach is that you try to store matplotlib axes objects in a list. However, the ax you store in the list is always the same. Once you call ax.clear() even the previously saved ax object will be cleared.
The problem with the second approach is that FuncAnimation will always save the figure for each time step. It does not matter, whether or not you change something in the axes.
Turning towards a solution:
A principle that is always wise to stick to is to keep data generation and calculation separate from data visualization.
The advise would therefore be to
First Calculate the data.
time_steps = 10000
data = []
for t in range(time_steps):
d = calculate_data(t)
data.append(d)
# alternatively save data to file
# if it's to big to be kept in memory.
Only then start visualization
# now start visualizing
plot_every = 50
fig, ax = plt.subplots()
def make_step(step):
ax.clear()
# recall data from list, but take only every 50th entry
c = data[step*plot_every] #alternatively reload data from file
ax.contourf(X, Y, c, ...)
anim = animation.FuncAnimation(fig, make_step,range(time_steps//plot_every), interval=500)
Based on ImportanceofBeingErnest answer, here is what i came up with. The additional thing, is that contourf is not an artist, apparently...
fig = plt.figure()
saved_steps = []
def make_step(i, t):
c = ...
if (i%plot_every) == 0:
print("plotting step {:.0f} , t ={:.0f} ".format(i, t*dt))
im = plt.imshow(c, animated=True)
saved_steps.append([im])
for s , t in enumerate(range(t_steps)):
make_step(s, t)
print("will now save anim")
anim = animation.ArtistAnimation(fig, saved_steps, interval=50, blit=False)
anim.save('spinodal_decompo_ex.ogv', codec='libtheora', extra_args=['-qscale:v', '7'])
thank you for pointing this out.

Categories