I have to make an animation of a large number (~90,000) figures. For context, it's a plot of a map for every day from 1700 - 1950, with events of interest marked on relevent days.
I can do this using matplotlib.animation.FuncAnimation, and I have code that does this successfully for a small test period. However, with the complete set of figures this is taking an impractical amount of time to render and will result in a very large movie file.
I have read that apparently moviepy offers both speed and file size advantages. However, I am having trouble getting this to work – I believe my problem is that I have not understood how to correctly set the duration and fps arguments.
A simplified version of my code is :
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10,1)
y = np.random.randn(10,1)
p = plt.plot(x,y,'ko')
time = np.arange(2341973,2342373)
def animate(i):
xn = x+np.sin(2*np.pi*time[i]/10.0)
yn = y+np.cos(2*np.pi*time[i]/8.0)
p[0].set_data(xn,yn)
return mplfig_to_npimage(fig)
fps = 1
duration = len(time)
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)
However, this does not produce the intended result of producing a movie with one frame for each element of time and saving this to an .mp4. I can’t see where I have gone wrong, any help or pointers would be appreciated.
Best wishes,
Luke
Same solution as JuniorCompressor, with just one frame kept in memory to avoid RAM issues. This example runs in 30 seconds on my machine and produces a good quality 400-second clip of 6000 frames, weighing 600k.
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure(facecolor="white") # <- ADDED FACECOLOR FOR WHITE BACKGROUND
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)
last_i = None
last_frame = None
def animate(t):
global last_i, last_frame
i = int(t)
if i == last_i:
return last_frame
xn = x + np.sin(2 * np.pi * time[i] / 10.0)
yn = y + np.cos(2 * np.pi * time[i] / 8.0)
p[0].set_data(xn, yn)
last_i = i
last_frame = mplfig_to_npimage(fig)
return last_frame
duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)
On a sidenote, there is dedicated class of videoclips called DataVideoClip for precisely this purpose, which looks much more like matplotlib's animate. For the moment it's not really speed-efficient (I didn't include that little memoizing trick above). Here is how it works:
from moviepy.video.VideoClip import DataVideoClip
def data_to_frame(time):
xn = x + np.sin(2 * np.pi * time / 10.0)
yn = y + np.cos(2 * np.pi * time / 8.0)
p[0].set_data(xn, yn)
return mplfig_to_npimage(fig)
times = np.arange(2341973, 2342373)
clip = DataVideoClip(times, data_to_frame, fps=1) # one plot per second
#final animation is 15 fps, but still displays 1 plot per second
animation.write_videofile("test2.mp4", fps=15)
Same observations:
In animate a float number will be passed
One frame per second may cause playback problems in many players. It's better to use a bigger frame rate like 15 fps.
Using 15 fps will need many frames. It's better to use caching.
So you can do the following:
import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy
fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)
cache = {}
def animate(t):
i = int(t)
if i in cache:
return cache[i]
xn = x + np.sin(2 * np.pi * time[i] / 10.0)
yn = y + np.cos(2 * np.pi * time[i] / 8.0)
p[0].set_data(xn, yn)
cache.clear()
cache[i] = mplfig_to_npimage(fig)
return cache[i]
duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)
Related
I have a code to make about the 2D schrodinger equation time dependant. I was able to figure how to use the finites differences and I guess I was able to solve the 1D schrodinger equation. But know I have to plot my wave function in spherical coordinate (so in 2D?) and create an animation to observe a wave packet at certain energies (3.48 Hartree). It is suggested that E=k^2/2 So I used this to make my schrodinger equation energy dependant. The potential and deltak are given. I also need to make the wave packet propagatinf from r=15 to r=0, I think that I just need to insert a minus in my exponential in my psi0 which I did in the code below. Any idea or even explanation would be welcome
import numpy as np
from scipy import sparse
import matplotlib.pyplot as plt
import scipy.integrate as integrate
from matplotlib.animation import FuncAnimation
from matplotlib import animation
from IPython import display
dx = 0.02 # spatial separation
a= 15
x = np.arange(0.1, a, dx) # spatial grid points
deltak = 0.2 # center of initial gaussian wave-packet
E=3.48
x0= 2
A=1.0 / (deltak * np.sqrt(np.pi)) # normalization constant
hbar = 1
dt = 0.1 # time interval for snapshots
t0 = 0.0 # initial time
tf = 1.0 # final time
t_eval = np.arange(t0, tf, dt)
k = np.sqrt(2 * E)
Vx= 7.5 * x**2 * np.exp(-x)
def psi_t2(t,psi):
return (-1j * (((-0.5 * D2.dot(psi)) + V * psi)))
# Initial Wavefunction
psi0_ = np.sqrt(A) * np.exp(-(x-x0)**2 / (2.0 * deltak**2)) * np.exp(1j * -k * x)
# Solve the Initial Value Problem
sol_ = integrate.solve_ivp(psi_t2, t_span = [t0, tf], y0 = psi0_, t_eval = t_eval,method="RK23")
for i,t in enumerate (sol_.t):
plt.plot(x, np.abs(sol_.y[:,i])**2)
fig=plt.figure()
ax=plt.subplot(1,1,1)
#fig,ax = plt.subplots(figsize=(8,4))
ax.set_xlim(-5, 16)
ax.set_ylim(0, 5)
title = ax.set_title('')
line1, = ax.plot([], [], "k--")
line2, = ax.plot([], [])
def init():
line1.set_data(x, V)
return line1,
def animate(i):
line2.set_data(x, np.abs(sol_.y[:,i])**2)
#line2.set_data(x, np.real(sol.y[:, i]))
#line2.set_data(x, np.abs(psi))
title.set_text('Time = {0:1.3f}'.format(sol_.t[i])) #permet d'afficher le temps
return line1,
anim = animation.FuncAnimation(fig, animate, init_func=init,frames=len(sol_.t), interval=50, blit=True)
video = anim.to_html5_video()
html = display.HTML(video)
display.display(html)
plt.close()
Now, here is the code I'm working with:
import numpy
from matplotlib import pyplot as plt
import time, sys
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = numpy.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
print(u)
un = numpy.ones(nx)
for n in range(nt):
un = u.copy()
plt.plot(numpy.linspace(0, 2, nx), u)
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
plt.show()
It should animate the solution to the equation ∂u/∂t + c * ∂u/∂x = 0; but I don't know how to animate it - because at the current state, it shows at once the function at all time steps; and if instead I put plt.show() inside the loop (the outer one), it shows the graphs one at a time, and I have to close the graph window to see the next, which is not very convenient.
FuncAnimation can be used to create animations.
The code of the post can be rendered as an animation as follows:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = np.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
x = np.linspace(0, 2, nx)
plot1, = plt.plot(x, u)
def update(t):
un = u.copy()
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
plot1.set_ydata(u)
return plot1,
FuncAnimation(fig, update, frames=nt, blit=True)
plt.show()
PS: Note the comma after plot1 in plot1, = plt.plot(.... This grabs the first element in the list returned by plt.plot.
You could save it as a gif using the following:
import numpy
from matplotlib import pyplot as plt
import time, sys
from celluloid import Camera
from IPython.display import Image
nx = 41
dx = 2 / (nx-1)
nt = 25
dt = 0.025
c = 1
fig = plt.figure()
u = numpy.ones(nx)
u[int(.5 / dx):int(1 / dx + 1)] = 2
print(u)
un = numpy.ones(nx)
fig = plt.figure()
camera = Camera(fig)
for n in range(nt):
un = u.copy()
plt.plot(numpy.linspace(0, 2, nx), u, color= "blue")
for i in range(1, nx):
u[i] = un[i] - c*dt/dx * (un[i] - un[i - 1])
camera.snap()
animation = camera.animate()
animation.save('solution.gif', writer = 'imagemagick')
In essence it recursively takes a camera "snap" for each dt and collates them into a gif saved as "solution.gif" in the current working directory.
The problem I am simulating is a simple pendulum. While I have done it before using PyGame I now decided to use matplotlib's animation tools. It is working but not with the desired effect. Simulating it in real time seems to be working. I have tweeked the interval and amount of frames but the fps is way too low. How do you increase the fps while still playing it in real time. I would greatly appreciate it. Anyway here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
g = 9.80665
L = 2
mu = 0.1
t = 100
theta_0 = np.pi/3
d_theta_0 = 0
def get_d2_theta(theta,d_theta):
return -mu*d_theta-(g/L)*np.sin(theta)
def theta(t):
theta = theta_0
d_theta = d_theta_0
delta_t = 1./60
for time in np.arange(0,t,delta_t):
d2_theta = get_d2_theta(theta,d_theta)
theta += d_theta*delta_t
d_theta += d2_theta*delta_t
return theta
x_data = [0,0]
y_data = [0,0]
fig, ax = plt.subplots()
ax.set_xlim(-2, 2)
ax.set_ylim(-2.5,1)
line, = ax.plot(0, 0)
def animation_frame(i):
x = L*np.sin(theta(i))
y = -L*np.cos(theta(i))
x_data[1] = x
y_data[1] = y
line.set_xdata(x_data)
line.set_ydata(y_data)
return line,
animation = FuncAnimation(fig, func=animation_frame, frames=np.arange(0, 60, (1./60)),interval = 10)
plt.show()
My code has been modified according to many great suggestions from people in this forum. However, I still have some questions about the code.
My code is:
from pylab import *
from numpy import *
N = 100 #lattice points per axis
dt = 1 #time step
dx = 1 #lattice spacing
t = arange(0, 1000000*dt, dt) #time
a = 1 #cofficient
epsilon = 100 #cofficient
M = 1.0 #cofficient
every = 100 #dump an image every
phi_0 = 0.5 #initial mean value of the order parameter
noise = 0.1 #initial amplitude of thermal fluctuations in the order parameter
th = phi_0*ones((N, N)) + noise*(rand(N, N) - 0.5) #initial condition
x, y = meshgrid(fftfreq(int(th.shape[0]), dx), fftfreq(int(th.shape[1]), dx))
k2 = (x*x + y*y) #k is a victor in the Fourier space, k2=x^2+y^2
g = lambda th, a: 4*a*th*(1-th)*(1-2*th) #function g
def update(th, dt, a, k2):
return ifft2((fft2(th)-dt*M*k2*fft2(g(th,a)))/(1+2*epsilon*M*dt*k2**2))
for i in range(size(t)):
print t[i]
if mod(i, every)==0:
imshow(abs(th), vmin=0.0, vmax=1.0)
colorbar()
show()
#savefig('t'+str(i/every).zfill(3)+'.png', dpi=100)
clf()
th=update(th, dt, a, k2)
When I run it, I have to close the figures one by one to see the changes. But I want to demonstrate the changes of the images in one figure. Any good ideas?
Use the "animation" feature of matplotlib, like in
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, data, line):
line.set_data(data[...,:num])
return line,
fig1 = plt.figure()
data = np.random.rand(2, 25)
l, = plt.plot([], [], 'r-')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l),
interval=50, blit=True)
plt.show()
Tutorial at :
http://matplotlib.org/1.3.1/examples/animation/index.html
I am trying to speed up the process of saving my charts to images. Right now I am creating a cString Object where I save the chart to by using savefig; but I would really, really appreciate any help to improve this method of saving the image. I have to do this operation dozens of times, and the savefig command is very very slow; there must be a better way of doing it. I read something about saving it as uncompressed raw image, but I have no clue of how to do it. I don't really care about agg if I can switch to another faster backend too.
ie:
RAM = cStringIO.StringIO()
CHART = plt.figure(....
**code for creating my chart**
CHART.savefig(RAM, format='png')
I have been using matplotlib with FigureCanvasAgg backend.
Thanks!
If you just want a raw buffer, try fig.canvas.print_rgb, fig.canvas.print_raw, etc (the difference between the two is that raw is rgba, whereas rgb is rgb. There's also print_png, print_ps, etc)
This will use fig.dpi instead of the default dpi value for savefig (100 dpi). Still, even comparing fig.canvas.print_raw(f) and fig.savefig(f, format='raw', dpi=fig.dpi) the print_canvas version is marginally faster insignificantly faster, since it doesn't bother resetting the color of the axis patch, etc, that savefig does by default.
Regardless, though, most of the time spent saving a figure in a raw format is just drawing the figure, which there's no way to get around.
At any rate, as a pointless-but-fun example, consider the following:
import matplotlib.pyplot as plt
import numpy as np
import cStringIO
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)
for i in xrange(1000):
xy = np.random.random(2*num).reshape(num,2) - 0.5
offsets = scat.get_offsets() + 0.3 * xy
offsets.clip(0, max_dim, offsets)
scat.set_offsets(offsets)
scat._sizes += 30 * (np.random.random(num) - 0.5)
scat._sizes.clip(1, 300, scat._sizes)
fig.canvas.draw()
If we look at the raw draw time:
import matplotlib.pyplot as plt
import numpy as np
import cStringIO
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)
for i in xrange(1000):
xy = np.random.random(2*num).reshape(num,2) - 0.5
offsets = scat.get_offsets() + 0.3 * xy
offsets.clip(0, max_dim, offsets)
scat.set_offsets(offsets)
scat._sizes += 30 * (np.random.random(num) - 0.5)
scat._sizes.clip(1, 300, scat._sizes)
fig.canvas.draw()
This takes ~25 seconds on my machine.
If we instead dump a raw RGBA buffer to a cStringIO buffer, it's actually marginally faster at ~22 seconds (This is only true because I'm using an interactive backend! Otherwise it would be equivalent.):
import matplotlib.pyplot as plt
import numpy as np
import cStringIO
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)
for i in xrange(1000):
xy = np.random.random(2*num).reshape(num,2) - 0.5
offsets = scat.get_offsets() + 0.3 * xy
offsets.clip(0, max_dim, offsets)
scat.set_offsets(offsets)
scat._sizes += 30 * (np.random.random(num) - 0.5)
scat._sizes.clip(1, 300, scat._sizes)
ram = cStringIO.StringIO()
fig.canvas.print_raw(ram)
ram.close()
If we compare this to using savefig, with a comparably set dpi:
import matplotlib.pyplot as plt
import numpy as np
import cStringIO
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)
for i in xrange(1000):
xy = np.random.random(2*num).reshape(num,2) - 0.5
offsets = scat.get_offsets() + 0.3 * xy
offsets.clip(0, max_dim, offsets)
scat.set_offsets(offsets)
scat._sizes += 30 * (np.random.random(num) - 0.5)
scat._sizes.clip(1, 300, scat._sizes)
ram = cStringIO.StringIO()
fig.savefig(ram, format='raw', dpi=fig.dpi)
ram.close()
This takes ~23.5 seconds. Basically, savefig just sets some default parameters and calls print_raw, in this case, so there's very little difference.
Now, if we compare a raw image format with a compressed image format (png), we see a much more significant difference:
import matplotlib.pyplot as plt
import numpy as np
import cStringIO
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)
for i in xrange(1000):
xy = np.random.random(2*num).reshape(num,2) - 0.5
offsets = scat.get_offsets() + 0.3 * xy
offsets.clip(0, max_dim, offsets)
scat.set_offsets(offsets)
scat._sizes += 30 * (np.random.random(num) - 0.5)
scat._sizes.clip(1, 300, scat._sizes)
ram = cStringIO.StringIO()
fig.canvas.print_png(ram)
ram.close()
This takes ~52 seconds! Obviously, there's a lot of overhead in compressing an image.
At any rate, this is probably a needlessly complex example... I think I just wanted to avoid actual work...
I needed to quickly generate lots of plots as well. I found that multiprocessing improved the plotting speed with the number of cores available. For example, if 100 plots took 10 seconds in one process, it took ~3 seconds when the task was split across 4 cores.