Saving and displaying movies and dynamic figures
It is insanely useful to create movies to illustrate a talk, blog post or just to include in a notebook:
from IPython.display import HTML
HTML('<center><video controls autoplay loop src="../files/2016-11-15_noise.mp4" width=61.8%/></center>')
For years I have used a custom made solution made around saving single frames and then calling ffmpeg
to save that files to a movie file. That function (called anim_save
had to be maintained accross different libraries to reflect new needs (going to WEBM and MP4 formats for instance). That made the code longer than necessary and had not its place in a scientific library.
Here, I show how to use the animation
library from matplotlib to replace that
There quite a few pages on the web describing a possible alternative:
import numpy as np
image = np.random.rand(64, 16, 128)
from IPython.display import display, clear_output, HTML, Image
import sys
import matplotlib.pyplot as plt
from matplotlib import animation, rc
animation.rcParams['animation.writer'] = 'ffmpeg'
# First set up the figure, the axis, and the plot element we want to animate
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 2))
plt.close()
ax.xlim = (0, image.shape[1])
ax.ylim = (0, image.shape[2])
ax.set_xticks([])
ax.set_yticks([])
img = ax.imshow(image[:, :, 0].T, cmap='gray')
img.set_interpolation('nearest')
def animate(i):
#img = ax.imshow(image[:, :, i].T, cmap='gray')
img.set_data(image[:, :, i].T)
#ax.xaxis.set_visible(False)
#ax.yaxis.set_visible(False)
clear_output(wait=True)
print ('It: %i'%i)
sys.stdout.flush()
return (img,)
# call the animator. blit=True means only re-draw the parts that have changed.
# *interval* draws a new frame every *interval* milliseconds.
anim = animation.FuncAnimation(fig, animate, frames=image.shape[-1], interval=50, blit=True)
HTML(anim.to_html5_video())
The same anim
object can be used to save the movie as a file:
help(anim.save)
anim.save('../files/2016-11-15_noise.mp4', writer='ffmpeg', fps=10, dpi=100, metadata={'title':'test'})
!ffprobe ../files/2016-11-15_noise.mp4
using aPNG¶
Write numpy array(s) to a PNG or animated PNG file using https://pypi.python.org/pypi/numpngw
!pip3 install -U numpngw
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from numpngw import AnimatedPNGWriter
def update_line(num, data, line):
line.set_data(data[:, :num+1])
return line,
fig = plt.figure(figsize=(5.75, 5.6))
ax = fig.add_subplot(111, xlim=(-1, 1), ylim=(-1, 1),
autoscale_on=False, aspect='equal',
title="Matplotlib Animation")
num_frames = 20
theta = np.linspace(0, 24*np.pi, num_frames)
data = np.exp(1j*theta).view(np.float64).reshape(-1, 2).T
lineplot, = ax.plot([], [], 'c-', linewidth=3)
ani = animation.FuncAnimation(fig, update_line, frames=num_frames,
init_func=lambda : None,
fargs=(data, lineplot))
writer = AnimatedPNGWriter(fps=2)
ani.save('numpngw.png', dpi=50, writer=writer)
Image('numpngw.png')
Alien life¶
Update: let's implement the code @ https://github.com/rougier/alien-life/blob/master/alien-life.py
!python3 -m pip install noise
!python3 -m pip install tqdm
import numpy as np
import matplotlib.pyplot as plt
from noise._simplex import noise4
import matplotlib.animation as animation
import tqdm
n = 40000
radius = 200
width,height = 500, 500
length = 50
scale = 0.005
time = 0
T = np.random.uniform(0, 2*np.pi, n)
R = np.sqrt(np.random.uniform(0, 1, n))
P = np.zeros((n,2))
X,Y = P[:,0], P[:,1]
X[...] = R*np.cos(T)
Y[...] = R*np.sin(T)
intensity = np.power(1.001-np.sqrt(X**2 + Y**2), 0.75)
X[...] = X*radius + width//2
Y[...] = Y*radius + height//2
def update(*args):
global P, time, pbar
time += 2*0.002
P_ = np.zeros((n,2))
cos_t = 1.5*np.cos(2*np.pi*time)
sin_t = 1.5*np.sin(2*np.pi*time)
for i in range(n):
x, y = P[i]
dx = noise4(scale*x, scale*y, cos_t, sin_t, 2)
dx *= intensity[i]*length
dy = noise4(100+scale*x, 200+scale*y, cos_t, sin_t, 2)
dy *= intensity[i]*length
P_[i] = x + dx, y +dy
pbar.update(1)
scatter.set_offsets(P_)
fig = plt.figure(figsize=(5,5))
ax = plt.subplot(1,1,1, aspect=1, frameon=False)
scatter = plt.scatter(X, Y, s=1.5, edgecolor="none", facecolor="black", alpha=.25)
T = np.linspace(0, 2*np.pi, 200)
X = width/2 + radius*np.cos(T)
Y = height/2 + radius*np.sin(T)
plt.plot(X, Y, color="k", linewidth=1.5)
ax.set_xticks([])
ax.set_yticks([])
font = "Source Sans Pro"
font = "DejaVu Sans"
ax.text(0.55, 1.11, "A L I E N", size=36,
name=font, weight=100,
ha="right", va="top", transform=ax.transAxes)
ax.text(0.55, 1.11 - 0.0025, " L I F E", size=36,
name=font, weight=600,
ha="left", va="top", transform=ax.transAxes)
ax.text(0.5, -0.05, "Made with matplotlib.org", size=16,
name=font, weight=100,
ha="center", va="bottom", transform=ax.transAxes)
ax.text(0.5, -0.10, "Original idea by Necessary Disorder", size=12,
name=font, weight=100,
ha="center", va="bottom", transform=ax.transAxes)
anim = animation.FuncAnimation(fig, update, frames=250, interval=20)
pbar = tqdm.tqdm(total=250)
anim.save('alien-life.mp4', writer='ffmpeg', fps=60)
#anim.save('alien-life.gif', writer='imagemagick', fps=30)
pbar.close()
plt.show()