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.
Related
I'm trying to animate a figure using matplotlib->FuncAnimate function. However, I'm having trouble understanding how Blit works. With each frame, I want to draw only the new data point on top of the old one. It says that using Blit it should automatically update only the values that changed. Thus, if I turn it on (blit=True) the previous data points should remain in my figure. But this is not the case. The previous data get deleted and the figure gets redraw from scratch.
In the documentation, it says that I have to return "iterable_of_artists" and the algorithm will know which data has changed. I want to just pass the new data and just plot on top of the old one. By the way, what is an "iterable_of_artists", is that just a list of objects that can be drawn? if someone could point me out to the definition, I would appreciate it.
Anyway, I have worked several base examples that show the odd behavior. In the first example, I'm turning Blit=True and drawing only the new data using the animate function. This in theory should draw on top of the old ones, but is not the case, only the new data is drawn.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
line, = ax.plot([], [], 'ro') #ax.plot will return a tupple
def init():
line.set_data(0, 50)
return line, #Return is not necessary when blit=False
def animate(frame):
x = frame
y = random.randint(0, 100)
line.set_data(x,y)
return line, #Return is not necessary when blit=False
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True, # Turning on Blit
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
I was able to achieve my goal by tricking the FuncAnimate method. I can use the ax and plot in each frame the new data. If I do that, the old data remains and only the new data is drawn. However, I can do that with Blit=True or Blit=False, it has no effect. So, I'm so confused on how Blit works and what would be the correct way to plot only the new data without having to create a list with all the data to plot. Passing a large list will create a large variable in memory if I have a long set of data points. Here is my workaround but I'm not sure if this is the correct way to do it or if there is a better ways of using Blit=True and just redraw the new data.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
def init():
ax.plot(0, 50, 'ro')
return []
def animate(frame):
x = frame
y = random.randint(0, 100)
ax.plot(x, y, 'ro') # plotting directly on the axis. This keeps the old data
return [] # fooling the blit algorithm with an empty stream
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True,
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
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.
I am trying to do the following: Plot points and store a reference in a dictionary. While animating remove points. A minimal example looks as follows:
%matplotlib qt
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.animation as animation
fig = plt.figure()
m = Basemap(projection='aeqd',lat_0=72,lon_0=29, resolution='l',
llcrnrlon=15, llcrnrlat=69,
urcrnrlon=41, urcrnrlat=75.6,area_thresh = 100)
pointDict=dict()
pointDict[1]=m.plot (0, 0,marker='.',label='first')[0]
pointDict[2]=m.plot (0, 0,marker='.',label='second')[0]
def init():
print ("Init")
x,y = m(30, 73)
pointDict[1].set_data(x,y)
x,y = m(31, 73)
pointDict[2].set_data(x,y)
return pointDict.values()
def animate(i):
print ("Frame {0}".format(i))
if i==2:
l=pointDict.pop(1)
print ("Removing {0}".format(l.get_label()))
l.remove()
del l
return pointDict.values()
anim = animation.FuncAnimation(plt.gcf(), animate, init_func=init,
frames=10, interval=1000, blit=True)
plt.show()
Output:
Init
Init
Frame 0
Frame 1
Frame 2
Removing first
Frame 3
Interestingly, if I am plotting just the first point (that is, remove pointDict[2]=m.plot and pointDict[2].set_data in the init function), this works. But if both are plotted, neither removing the first, nor the second point works.
Related questions brought me just as far as I am now:
Matplotlib Basemap animation
How to remove lines in a Matplotlib plot
Matplotlib animating multiple lines and text
Python, Matplotlib, plot multi-lines (array) and animation
I am using Anaconda with Python-2.7 kernel.
I found out what the problem is and want therefore to answer my question by myself:
The problem with this is somewhat unexpected the blit=True.
Obviously, blitting can be only used if the point is set within the animate function. Thus, setting the data in the init routine causes problems.
So there are two options: set blit to False, but this is not very elegant. The other option is to set the points in the first frame.
Then the init and animate functions that work are as follows:
def init():
print ("Init")
pointDict[1].set_data([],[])
pointDict[2].set_data([],[])
return pointDict.values()
def animate(i):
print ("Frame {0}".format(i))
if i==0:
print ("Init")
x,y = m(30, 73)
pointDict[1].set_data(x,y)
x,y = m(31, 73)
pointDict[2].set_data(x,y)
if i==2:
l=pointDict.pop(1)
print ("Removing {0}".format(l.get_label()))
l.remove()
del l
return pointDict.values()
anim = animation.FuncAnimation(plt.gcf(), animate, init_func=init,
frames=10, interval=1000, blit=True)
plt.show()
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)