Python - Matplotlib FuncAnimation - Mac - Not returning points - python

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)

Related

Recursive animation matplotlib

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.

Matplotlib's FuncAnimation calls init_func more than once

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.

Removing points stored in a dictionary in a matplotlib basemap animation

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()

How to save an animation without showing the previous frames in the video?

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.

Matplotlib.animation: display points after plotting?

I was reading Jake Vanderplas's excellent piece of code for plotting a point which moves along a sine curve:
"""
This short code snippet utilizes the new animation package in
matplotlib 1.1.0; it's the shortest snippet that I know of that can
produce an animated plot in python. I'm still hoping that the
animate package's syntax can be simplified further.
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def simData():
# this function is called as the argument for
# the simPoints function. This function contains
# (or defines) and iterator---a device that computes
# a value, passes it back to the main program, and then
# returns to exactly where it left off in the function upon the
# next call. I believe that one has to use this method to animate
# a function using the matplotlib animation package.
#
t_max = 10.0
dt = 0.05
x = 0.0
t = 0.0
while t < t_max:
x = np.sin(np.pi*t)
t = t + dt
yield x, t
def simPoints(simData):
x, t = simData[0], simData[1]
time_text.set_text(time_template%(t))
line.set_data(t, x)
return line, time_text
##
## set up figure for plotting:
##
fig = plt.figure()
ax = fig.add_subplot(111)
# I'm still unfamiliar with the following line of code:
line, = ax.plot([], [], 'bo', ms=10)
ax.set_ylim(-1, 1)
ax.set_xlim(0, 10)
##
time_template = 'Time = %.1f s' # prints running simulation time
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
## Now call the animation package: (simData is the user function
## serving as the argument for simPoints):
ani = animation.FuncAnimation(fig, simPoints, simData, blit=False,\
interval=10, repeat=True)
plt.show()
How would you modify this code so that each point (i.e. calculated value of x, y) would remain on the figure after being plotted? Thus, toward the end of the animation, you would see all the previous dots forming the sine curve.
Check out this link for explanation of:
# I'm still unfamiliar with the following line of code:
line, = ax.plot([], [], 'bo', ms=10)
Python code. Is it comma operator?
Also, this might not help with your graphing problem, but it appears that you are definiting time_template but then not using that definition, unless it is being called globally after that point in your script.

Categories

Resources