import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(0, 2*np.pi, 0.01) # x-array
i=1
line, = ax.plot(x, np.sin(x))
def animate():
i= i+2
x=x[1:] + [i]
line.set_ydata(np.sin(x)) # update the data
return line,
#Init only required for blitting to give a clean slate.
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, init_func=init,
interval=25, blit=True)
plt.show()
I get error like this:animate() takes no arguments (1 given)..so confused. i don't even give an arg to the callback func. Was there something that i missed?
thanks.
It looks like the documentation is off, or at least unclear here: the function has an intrinsic first argument, the frame number. So you can simply define it as def animate(*args) or def animate(framenumber, *args), or even def animate(framenumber, *args, **kwargs).
See also this example.
Note that you'll run into other problems after that:
i and x within animate should be declared global. Or better, pass them as arguments through the fargs keyword in FuncAnimation.
x = x[1:] + [i] doesn't work the way you think it does. Numpy arrays work differently than lists: it would add [i] to every element of x[1:] and assign that to x, thus making x one element shorter. One possible correct way would be x[:-1] = x[1:]; x[-1] = i.
Related
I am trying to use matplotlib's FuncAnimation to make an animated video. Each frame is just a boolean n x n array visualised as white/black squares. I can do this successfully by defining all the arrays in advance and then going through them one by one. This uses code similar to matplotlib's example.
My items are rather large and I want to run the simulation for a long time. I thus don't want to create the entire list of arrays then go through them one by one. Instead, I want to define the animate function to do each step. Let me explain with a minimal non-working example. My actual example includes far larger arrays!
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n,n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000 / 30,
blit = True
)
return anim
anim = create_video(10)
This initialises some random 10 x 10 set of 0/1s then just 'rolls' it at each step. I get an error.
RuntimeError: The animation function must return a sequence of Artist objects.
If I remove the return anim, replacing it with pass, and replacing anim = create_video(10) with create_video(10), then I get a warning.
UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation.
Clearly, I don't understand well enough FuncAnimation. What I want to happen is for the function animate to update the array X, by 'rolling' it one step, as well as doing im.set_array(X).
As explained in this answer:
As the error suggests, and as can be seen e.g. in the
simple_animation example, but also from the FuncAnimation
documentation, the init_func as well as the updating func are
supposed to return an iterable of artists to animate.
The documentation does not say that this is actually only needed when
using blit=True, but since you are using blitting here, it is
definitely needed.
So you have two ways:
add
return im,
to animate function
set blit = False in FuncAnimation
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n, n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
return im,
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000/30,
blit = True
)
plt.show()
return anim
anim = create_video(10)
I want to animate some time propagation of a wavefunction. But I don't want to calculate all time steps every time because it takes a huge amount of time but take the previous value of the wavefunction as initial value. I don't know how to realize this with animation.FuncAnimation.
That's what I thought of:
import numpy as np
from matplotlib import animation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
wavefunction_0 = some array
def next_wavefunction(wavefunction_init):
wavefunction = time_propagation(Psi = wavefunction_init)
return wavefunction
def animate(framenumber, wavefunction, surf):
if framenumber == 0:
wavefunction = wavefunction_0
else:
wavefunction = next_wavefunction(wavefunction)
ax.clear()
surf = ax.plot_surface(X, Y, np.reshape(np.abs(wavefunction), (space_shape)), rstride=1, cstride=1, linewidth=0, antialiased=False, cmap='jet', edgecolor='none')
return surf, wavefunction
anim = animation.FuncAnimation(fig, animate, fargs=(wavefunction, surf),
interval=200, blit=False)
At the moment it is not working since fargs = wavefunction but wavefunction is a return value of animate(...). Is it possible to take the return value of animate and pass it as fargs?
Matplotlib expects that the animate function passed to matplotlib.animation.FuncAnimation returns a list of artists, as such it is not possible (at least to my understanding) to return non-artists like
return surf, wavefunction
So even if you pass wavefunction into animate, you would not be able to return the mutated array. Unless you can refactor the code into a manner such that the array for the current frame can be calculated without information from the previous frame you cannot use this approach.
There are two ways you could get around this, one is to use a global variable to store the wavefunction array and mutate it as needed, that way changes made in the function persist beyond the end of the function. For demonstration, here is an example of this implementation which is slightly simpler than a changing wavefunction in 3 dimensions,
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
n = 100
wf = np.zeros((n,2))
def next_wf():
global wf
offset = wf[0,0] + 0.1
wf[:,0] = np.linspace(offset, np.pi*4+offset, wf.shape[0])
wf[:,1] = np.sin(wf[:,0])
def animate(frame):
next_wf()
plt.cla()
plot, = plt.plot(wf[:,0], wf[:,1])
return plot,
next_wf()
fig, ax = plt.subplots(1)
anim = animation.FuncAnimation(fig, animate, interval=25)
This will create an animation like the following
However, it should be noted that using global variables is explicitly advised against by the Variables and Scope page of the documentation,
Note that it is usually very bad practice to access global variables from inside functions, and even worse practice to modify them. This makes it difficult to arrange our program into logically encapsulated parts which do not affect each other in unexpected ways. If a function needs to access some external value, we should pass the value into the function as a parameter. [...]
In a simple, self contained script it is unlikely to cause harm but in more complicated code it can be detrimental. The more 'proper' way to do this is to wrap the entire thing in a class, i.e.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
class waveanim:
def __init__(self):
n = 100
self.wf = np.zeros((n,2))
self.next_wf()
fig, ax = plt.subplots(1)
anim = animation.FuncAnimation(fig, self.animate, interval=25, blit=True)
anim.save('./animation.gif', writer='imagemagick')
def next_wf(self):
offset = self.wf[0,0] + 0.1
self.wf[:,0] = np.linspace(offset, np.pi*4+offset, self.wf.shape[0])
self.wf[:,1] = np.sin(self.wf[:,0])
def animate(self, frame):
self.next_wf()
plt.cla()
plot, = plt.plot(self.wf[:,0], self.wf[:,1])
return plot,
waveanim()
Which gives the same result as above.
The documentation for matplotlib.animation.FuncAnimation says:
init_func : [...] This function will be called once before the first frame.
But whenever I use FuncAnimation, the init_func is called multiple times.
You can see this by adding a print statement to the basic example from Matplotlib's website:
"""
A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
def animate(i):
line.set_ydata(np.sin(x + i/10.0)) # update the data
return line,
# Init only required for blitting to give a clean slate.
def init():
# ---> Adding a print statement here <---
print('Initializing')
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
interval=25, blit=True)
plt.show()
Apart from producing a lovely plot, it immediately gives the standard output:
Initializing
Initializing
If I keep the animation running, the init_func is in fact called over and over again!
Is this a bug or a feature? What can I do about it?
A little background: I use the init_func to initialize multiple plots as described here, only within a class:
class MyAnimation:
def __init__(self, n):
self.fig, self.ax = plt.subplots()
self.n = n
self.lines = []
def run_animation(self):
def init():
for i in range(self.n):
line = ax.plot([], [])[0]
self.lines.append(line)
return self.lines
def animate(i):
... # Update lines
return self.lines
animation = FuncAnimation(self.fig, animate, init_func=init, blit=True)
plt.show()
Of course, this makes no sense if init() is called more than once, because additional lines will be appended to self.lines.
Is my approach not good practice?
Should I set self.lines = [] inside the init() function?
First of all it should be mentionned that "This function will be called once before the first frame." means that it is called once before the first frame per animation. So because the FuncAnimation's repeat argument is True by default, each time the animation is repeated, init_func will be called again.
Next you are right that this function is actually called twice. The first call is to set the animation up. This step is apparently needed internally. One therefore should take this into account and make sure the init function does not produce anything irreversible in the plot.
Also note that if you did not provide any init_func, the first frame would be used for the internal setup procedure.
Therefore I think you are completely right to reset the lines attribute in the init_func. Possibly you might even want to clear the axes (self.ax.clear()) to get rid of previously drawn elements.
I would like to save an animation using Python but I get the frames superposed! I want to get the frames displayed individually.
Please here what I used:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import pi, cos, sin
fig = plt.figure()
plt.axis([-1.5, 1.5,-1.5, 1.5])
ax = plt.gca()
ax.set_aspect(1)
N=100
xp = [None] * N
yp = [None] * N
def init():
# initialize an empty list of cirlces
return []
def animate(i):
xp[i]=sin(i*pi/10)
yp[i]=cos(i*pi/10)
patches = []
patches.append(ax.add_patch( plt.Circle((xp[i],yp[i]),0.02,color='b') ))
return patches
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=N-1, interval=20, blit=True)
anim.save("example.avi")
plt.show()
There are some things I'm not sure about and it really seems to be that the axis.plot() behavior and FuncAnimate() behavior are different. However, the code below works for both.
Use only one patch (in your case)
The key point from your code is that you are adding a new circle in addition to the old circles every iteration:
patches = []
patches.append(ax.add_patch( plt.Circle((xp[i],yp[i]),0.02,color='b') ))
Even though you clear the patches list, they are still stored in the axis.
Instead, just create one circle and change its position.
Clear first frame with init()
Also, init() needs to clear the patch from the base frame.
Standalone Example
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import pi, cos, sin
fig = plt.figure()
plt.axis([-1.5, 1.5, -1.5, 1.5])
ax = plt.gca()
ax.set_aspect(1)
N = 100
xp = []
yp = []
# add one patch at the beginning and then change the position
patch = plt.Circle((0, 0), 0.02, color='b')
ax.add_patch(patch)
def init():
patch.set_visible(False)
# return what you want to be cleared when axes are reset
# this actually clears even if patch not returned it so I'm not sure
# what it really does
return tuple()
def animate(i):
patch.set_visible(True) # there is probably a more efficient way to do this
# just change the position of the patch
x, y = sin(i*pi/10), cos(i*pi/10)
patch.center = x, y
# I left this. I guess you need a history of positions.
xp.append(x)
yp.append(y)
# again return what you want to be cleared after each frame
# this actually clears even if patch not returned it so I'm not sure
# what it really does
return tuple()
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=N-1, interval=20, blit=True)
# for anyone else, if you get strange errors, make sure you have ffmpeg
# on your system and its bin folder in your path or use whatever
# writer you have as: writer=animation.MencoderWriter etc...
# and then passing it to save(.., writer=writer)
anim.save('example.mp4')
plt.show()
Return values???
Regarding the return values of init() and animate(), It doesn't seem to matter what is returned. The single patch still gets moved around and drawn correctly without clearing previous ones.
Using the following code I am attempting to have points iteratively added to a graph. Since I am on OSX, I am not using blit=True. I can get a single point at the original to plot and can see output from my update function (fed from a generator). I can also see that my coordinates are being appended to the array of coordinates to be plotted. What am I missing in getting my generated / updated points visualized?
the stromboli function, called by data_gen() returns a pair of coordinates. It could be a random x,y for all intents and purposes.
#Visualization Imports
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update(coord):
print coord[0], coord[1]
pt.set_xdata(numpy.append(pt.get_xdata(),coord[0]))
pt.set_ydata(numpy.append(pt.get_ydata(),coord[1]))
print pt.get_xdata()
return pt,
def data_gen():
while True:
yield stromboli(args.velocity)
#Visualization
fig = plt.figure()
ax = plt.axes()
pt, = ax.plot([], [],'ro')
ani = animation.FuncAnimation(fig, update, data_gen, interval=100)
plt.plot(0,0,'b*')
plt.show()
I suspect you might be getting something funny with effective closures (are you running this in an interactive environment?) in that pt will be defined from a previous code execution, which is what gets grabbed when def update runs. You then make a new version of pt and but that is not what is getting updated by update
Either move pt, = ax.plot([],[],'ro') above the definition of update or try passing in pt as an argument.
ex:
def update(coord,pt):
print coord[0], coord[1]
pt.set_xdata(numpy.append(pt.get_xdata(),coord[0]))
pt.set_ydata(numpy.append(pt.get_ydata(),coord[1]))
print pt.get_xdata()
return pt,
....
ani = animation.FuncAnimation(fig, update, data_gen, fargs = (pt,),interval=100)