How to save dynamic pyplot as a movie file? - python

I've came across this simple example that would help me with my problem:
"""
Pyplot animation example.
The method shown here is only for very simple, low-performance
use. For more demanding applications, look at the animation
module and the examples that use it.
"""
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(6)
y = np.arange(5)
z = x * y[:,np.newaxis]
for i in xrange(5):
if i==0:
p = plt.imshow(z)
fig = plt.gcf()
plt.clim() # clamp the color limits
plt.title("Boring slide show")
else:
z = z + 2
p.set_data(z)
print "step", i
plt.pause(0.5)
This shows animation in pyplot interface, but I'd like to save this animation in some movie format, is there a way?

One way is to save all the steps as images and then make them into a movie with e.g. ffmpeg.

Another way to save a matplotlib animation as a video is explained in this article http://jakevdp.github.com/blog/2012/08/18/matplotlib-animation-tutorial/. It shows a higher level solution where you specify the animation as a draw function that changes with time. Also you don't have to deal with saving each frame.

Related

return a numpy array from a matplotlib plot is too slow, how can i speed it up?

How can I speed up the conversion between matplotlib plots to a numpy arrays?
My program creates millions of plots, and for each plot I want to return its numpy array (I do not care about viewing nor saving the plots! I only care about the conversion to numpy arrrays).
I managed to make the conversion with the following code:
data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
Unfortunately, since I use: fig = plt.figure(num=1) at the beginning, and plt.clf() at the end, the program shows the images one by one on the figure, which slows everything down (about 1 to 2 frames per second).
I'm searching for a faster solution for the conversion from matplotlib plots to numpy arrays.
Update
I made the aggbackend change, but no improvement, where am I wrong?
I'm attaching my code:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import imageio
from Game import Init, Draw, Game_step
images = []
Init()
fig = plt.figure(num=1)
Draw()
fig.canvas.draw()
for stp in range(100):
action_button = np.random.randint(4)
observation = Game_step(action_button)
Draw()
fig.canvas.draw()
data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
images.append(data)
imageio.mimsave("game.gif", images, duration=1 / 35)
when Init: initialize the game, Draw: plot the current game screenshot in matplotlib, Game_step: taking one action in the game environment
The goal is to get the np array of each screen plot
(I used Imagio just for checking, but it redundant)
Try using plt.ioff() to turn off interactive mode. This should prevent your figures from being drawn unless you explicitly call draw() or some other function.
SO Post on the semantics of ioff(): Exact semantics of Matplotlib's "interactive mode" (ion(), ioff())?
As well as a link to the documentation: https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.ioff.html

Animating with matplotlib without animation function

Is there a way to animate a graph in matplotlib without resorting to the built in animation functions? I find them extremely awkward to use and feel it would be much simpler to just plot a point, wipe the graph, then plot the next point.
I envision something such as:
def f():
# do stuff here
return x, y, t
where each t would be a different frame.
I mean, I've tried stuff like using plt.clf(), plt.close() etc. but nothing seems to work.
It is sure possible to animate without FuncAnimation. The purpose of "the enivisioned function", however, is not really clear. In an animation, the time is the independent variable, i.e. for each time step you produce some new data to plot or similar. Therefore the function would take t as an input and give some data back.
import matplotlib.pyplot as plt
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for t in range(100):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
fig.canvas.draw()
plt.pause(0.1)
plt.show()
Also, it is not clear why you would want to avoid FuncAnimation. The same animation as above can be produced with FuncAnimation as follows:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
def update(t):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
ani = matplotlib.animation.FuncAnimation(fig, update, frames=100)
plt.show()
There is not much changed, you have the same number of lines, nothing really awkward to see here.
Plus you have all the benefits from FuncAnimation when the animation gets more complex, when you want to repeat the animation, when you want to use blitting, or when you want to export it to a file.
it is not clear why you would want to avoid FuncAnimation.
For very simple tests, where you want to check a situation deep inside a loop, it is not easy to set up an animation function.
For instance, I wanted to visualize what happens with this strange sort algorithm: https://arxiv.org/pdf/2110.01111.pdf. To my opinion, the simplest way to do it is:
import numpy as np
import matplotlib.pyplot as plt
def sort(table):
n = len(table)
for i in range (n):
for j in range (n):
if table[i] < table[j]:
tmp = table[i]
table[i] = table[j]
table[j] = tmp
plt.plot(table, 'ro')
plt.title(f"i {i} j {j}")
plt.pause(0.001)
plt.clf() # clear figure
return table
n = 50
table = np.random.randint(1,101,n)
sort(table)
```python
I agree that FuncAnimation is awkward to use (not pythonic at all). Actually I believe this function doesn't make too much sense. What is the advantage to have it?
Yes, it introduces an implicit loop that you do not have to write yourself. But the reader cannot fully control this loop and -unless he knows the syntax of the function in advance- he cannot even understand it. Personally I avoid FuncAnimation for reasons of clarity and versatility. Here's a minimal pseudocode example to do that:
fig=plt.figure("animation")
M=zeros((sizeX,sizeY)) # initialize the data (your image)
im=plt.imshow(M) # make an initial plot
########### RUN THE "ANIMATION" ###########################
while {some condition}:
M=yourfunction() # updates your image
im.set_array(M) # prepare the new image
fig.canvas.draw() # draw the image
plt.pause(0.1) # slow down the "animation"
Very simple and you can see what is happening in your code.

How to update 3D arrow animation in matplotlib

I am trying to reproduce the left plot of this animation in python using matplotlib.
I am able to generate the vector arrows using the 3D quiver function, but as I read here, it does not seem possible to set the lengths of the arrows. So, my plot does not look quite right:
So, the question is: how do I generate a number of 3D arrows with different lengths? Importantly, can I generate them in such a way so that I can easily modify for each frame of the animation?
Here's my code so far, with the not-so-promising 3D quiver approach:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d
ax1 = plt.subplot(111,projection='3d')
t = np.linspace(0,10,40)
y = np.sin(t)
z = np.sin(t)
line, = ax1.plot(t,y,z,color='r',lw=2)
ax1.quiver(t,y,z, t*0,y,z)
plt.show()
As Azad suggests, an inelegant, but effective, solution is to simply edit the mpl_toolkits/mplot3d/axes3d.py to remove the normalization. Since I didn't want to mess with my actual matplotlib installation, I simply copied the axes3d.py file to the same directory as my other script and modified the line
norm = math.sqrt(u ** 2 + v ** 2 + w ** 2)
to
norm = 1
(Be sure to change the correct line. There is another use of "norm" a few lines higher.) Also, to get axes3d.py to function correctly when it's outside of the mpl directory, I changed
from . import art3d
from . import proj3d
from . import axis3d
to
from mpl_toolkits.mplot3d import art3d
from mpl_toolkits.mplot3d import proj3d
from mpl_toolkits.mplot3d import axis3d
And here is the nice animation that I was able to generate (not sure what's going wrong with the colors, it looks fine before I uploaded to SO).
And the code to generate the animation:
import numpy as np
import matplotlib.pyplot as plt
import axes3d_hacked
ax1 = plt.subplot(111,projection='3d')
plt.ion()
plt.show()
t = np.linspace(0,10,40)
for index,delay in enumerate(np.linspace(0,1,20)):
y = np.sin(t+delay)
z = np.sin(t+delay)
if delay > 0:
line.remove()
ax1.collections.remove(linecol)
line, = ax1.plot(t,y,z,color='r',lw=2)
linecol = ax1.quiver(t,y,z, t*0,y,z)
plt.savefig('images/Frame%03i.gif'%index)
plt.draw()
plt.ioff()
plt.show()
Now, if I could only get those arrows to look prettier, with nice filled heads. But that's a separate question...
EDIT: In the future, matplotlib will not automatically normalize the arrow lengths in the 3D quiver per this pull request.
Another solution is to call ax.quiever on each arrow, individually - with each call having an own length attribute. This is not very efficient but it will get you going.
And there's no need to change MPL-code

Creating a rotatable 3D earth

I know we can create simple 3-Dimensional spheres using matplotlib, an example of such a sphere is included in the documentation.
Now, we also have a warp method as part of the matplotlib module, an example of it's usage is here .
To warp a cylindrical image to the sphere. Is it possible to combine these methods to create a 3D rotatable earth? Unless my way of thinking about this problem is way off it seems that to be able to do this you would have to take the pixel data of the image and then plot every pixel using the sin and cosine expressions along the surface of the 3D sphere being created in the first example. Some examples of these cylindrical maps can be found here
I know alternative ways to do this are through maya and blender, but I am attempting to stay within matplotlib to do this, as I want to create this plot and then be able to plot geospatial data to the surface using an array of data.
Interesting question. I tried to basically follow the thinking outlined by #Skeletor, and map the image so that it can be shown with plot_surface:
import PIL
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
# load bluemarble with PIL
bm = PIL.Image.open('bluemarble.jpg')
# it's big, so I'll rescale it, convert to array, and divide by 256 to get RGB values that matplotlib accept
bm = np.array(bm.resize([d/5 for d in bm.size]))/256.
# coordinates of the image - don't know if this is entirely accurate, but probably close
lons = np.linspace(-180, 180, bm.shape[1]) * np.pi/180
lats = np.linspace(-90, 90, bm.shape[0])[::-1] * np.pi/180
# repeat code from one of the examples linked to in the question, except for specifying facecolors:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.outer(np.cos(lons), np.cos(lats)).T
y = np.outer(np.sin(lons), np.cos(lats)).T
z = np.outer(np.ones(np.size(lons)), np.sin(lats)).T
ax.plot_surface(x, y, z, rstride=4, cstride=4, facecolors = bm)
plt.show()
Result:
Here what I made some hours ago:
First we import the needed libraries:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import imageio
Secondly, we make the figures and stored them as png in our directory:
Note that I wrote range(0,330,20)
for i in range(0,330,20):
my_map = Basemap(projection='ortho', lat_0=0, lon_0=i, resolution='l', area_thresh=1000.0)
my_map.bluemarble()
my_map.etopo()
name=str(i)
path='/path/to/your/directory/'+name
plt.savefig(path+'.png')
plt.show()
plt.clf()
plt.cla()
plt.close()
And finally we can join all the images in an animated GIF:
images = []
for f in range(0,330,20):
images.append(imageio.imread("/path/to/your/directory/"+str(f)+".png"))
imageio.mimsave('movie.gif', images, duration=0.5)
and then enjoy the result:
I could imagine the following solution:
Using numpy.roll you could shift your array by one column (ore more) with each call. So you could load your image of the earth surface into a numpy array as a template and export the rotated image into a jpg. This you plot as shown in the warp example.

Matplotlib animation too slow ( ~3 fps )

I need to animate data as they come with a 2D histogram2d ( maybe later 3D but as I hear mayavi is better for that ).
Here's the code:
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
import time, matplotlib
plt.ion()
# Generate some test data
x = np.random.randn(50)
y = np.random.randn(50)
heatmap, xedges, yedges = np.histogram2d(x, y, bins=5)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
# start counting for FPS
tstart = time.time()
for i in range(10):
x = np.random.randn(50)
y = np.random.randn(50)
heatmap, xedges, yedges = np.histogram2d(x, y, bins=5)
plt.clf()
plt.imshow(heatmap, extent=extent)
plt.draw()
# calculate and print FPS
print 'FPS:' , 20/(time.time()-tstart)
It returns 3 fps, too slow apparently. Is it the use of the numpy.random in each iteration? Should I use blit? If so how?
The docs have some nice examples but for me I need to understand what everything does.
Thanks to #Chris I took a look at the examples again and also found this incredibly helpful post in here.
As #bmu states in he's answer (see post) using animation.FuncAnimation was the way for me.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def generate_data():
# do calculations and stuff here
return # an array reshaped(cols,rows) you want the color map to be
def update(data):
mat.set_data(data)
return mat
def data_gen():
while True:
yield generate_data()
fig, ax = plt.subplots()
mat = ax.matshow(generate_data())
plt.colorbar(mat)
ani = animation.FuncAnimation(fig, update, data_gen, interval=500,
save_count=50)
plt.show()
I suspect it is the use of np.histogram2d in each loop iteration. or that in each loop iteration of the for loop you are clearing and drawing a new figure. To speed things up you should create a figure once and just update the properties and data of the figure in a loop. Have a look through the matplotlib animation examples for some pointers on how to do this. Typically it involves calling matplotlib.pyploy.plot then, in a loop, calling axes.set_xdata and axes.set_ydata.
In your case however, take a look at the matplotlib animation example dynamic image 2. In this example the generation of data is separated from the animation of the data (may not be a great approach if you have lots of data). By splitting these two parts up you can see which is causing a bottleneck, numpy.histrogram2d or imshow (use time.time() around each part).
P.s. np.random.randn is a psuedo-random number generator. These tend to be simple linear generators which can generate many millions of (psuedo-)random numbers per second, so this is almost certainly not your bottleneck - drawing to screen is almost always a slower process than any number crunching.

Categories