Recursive animation matplotlib - python

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.

Related

Matplotlib FuncAnimation Step-by-Step Animation Function

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)

Blit Behaviour in FuncAnimate -Want to keep previous data

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

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.

about the args of matplotlib.animation.FuncAnimation

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.

Python - Matplotlib FuncAnimation - Mac - Not returning points

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)

Categories

Resources