"Celluloid, Easy Matplotlib Animations"
I really enjoy working with Data Visualization and I always wonder what’s the best way to provide more direct and intuitive visual interactions when I have to explain some result or complex model.
Lately, I’ve been growing to use GIFs and quick videos. Even if this makes the coding part harder and more complex, the result generally is much more efficient in communicating my findings and process.
But in Python, there’s always an easier and simpler way and to simplify the animating process, Celluloid was born.
Using only 50 lines of code to deal with Matplotlib Artists and ArtistAnimations Celluloid creates an animation from the series of images you want to plot into the Camera abstraction.
Let’s start by installing the library with
$ pip install celluloid
Now let’s get to it!
Four usage examples
- The simplest one
- Evolution plot
- Working with images
- Using dynamic labels and titles
The simplest one
Let’s create a simple plot just to demonstrate the basic usage of how to run the code in a Jupyter Notebook, but we could also use the method save(‘filename.gif_or_mp4’)
from celluloid import Camera # getting the camera
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML # to show the animation in Jupyter
fig, ax = plt.subplots() # creating my fig
camera = Camera(fig)# the camera gets the fig we'll plot
for i in range(10):
ax.plot([i] * 5, c='black') # 5 element array from 0 to 9
camera.snap() # the camera takes a snapshot of the plot
animation = camera.animate() # animation ready
HTML(animation.to_html5_video()) # displaying the animation


Evolution Plot
This is a fun kind of plot, but not a very useful one. The idea is to plot the dataset evolving little by little.
from celluloid import Camera # getting the camera
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML
fig, ax = plt.subplots(figsize=(10, 6)) # let's make it bigger
camera = Camera(fig)# the camera gets our figure
x = np.linspace(0, np.pi*2, 60) # a sine circle
y = np.sin(x) # sine
for i in range(len(x)):
x_t = x[:i] # current dataset
y_t = y[:i] # current sine
ax.plot(x_t, y_t, c='blue')
camera.snap()
animation = camera.animate()
HTML(animation.to_html5_video())


A fun little trick that makes the plot look like hand drawn
x_t = x[:i] + 1.3*np.random.random(size=i)


Working with Images
Images may help a lot to present ideas, and algorithms that use images are mainly well explained by videos
In this example, I animated a Neural Style Transfer between an image of my cat and Munch’s The Scream. I trained the model for 200 epochs and saved the result each 10 epochs (I’ll explain more about the algorithm in another article)
In my case, I saved the 20 photos during training to make the GIF using the pattern "./NST/epochs/epoch_k.jpg"
from celluloid import Camera # getting the camera
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML
import os
fig, ax = plt.subplots() # make it bigger
camera = Camera(fig)# the camera gets our figure
for img in os.listdir("NST/epochs"):
img_obj = plt.imread(os.path.join("NST/epochs"), img) # reading
ax.imshow(img_obj) # plotting
camera.snap()
animation = camera.animate()
HTML(animation.to_html5_video())

Using dynamic labels and titles
This is one of the most interesting aspects of celluloid, here we have the capacity to make the plot very dynamic.
In this example I plotted two normal distributions with distinct means but the same standard deviation and then I changed this standard deviation to evaluate the impact it has on each curve.
I put the current standard deviation on the title and the real mean as the label of each curve. I had to pass the calculated label as a tuple because I used two curves, if it was only one I could’ve used just a like plt.legend([calculated_label]), which is simple
from celluloid import Camera # getting the camera
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from IPython.display import HTML
std = 3 # start std
Am = 15. # mean of first normal
Bm = 12.5 # mean of second normal
fig, ax = plt.subplots(figsize=(9,6)) # empty fig
camera = Camera(fig)
for j in range(10):
plt.ylim((0, 0.2)) # setting up the limits (or else it will auto ajust
plt.xlim((-50, 50))
A = np.random.normal(Am, std, size=(1000)) # creating the 1000-sized normals
B = np.random.normal(Bm, std, size=(1000))
A_plot = sns.distplot(A, color='red')
B_plot = sns.distplot(B, color='blue')
plt.legend((
'Real Mean A: {:.2f}'.format(np.mean(A)),
'Real Mean B: {:.2f}'.format(np.mean(B))
))
ax.text(0.5, 1.01, "Standard Deviation = "+str(std), transform=ax.transAxes) # making the dynamic title
camera.snap() # camera snapshot
std += 1 # incrementing the std
anim = camera.animate() # animating the plots
HTML(anim.to_html5_video())

All done?
This were just my basic ideas of how to use celluloid, but truth be told the possibilities are infinite and I’ve used in several applications, even to demonstrate how some Machine Learning Algorithms learn check it out.
Decision Tree
Support Vector Machine Classifier
If you liked this Tutorial, used and it worked (or if it didn’t) please contact me using the links in my profile