How to have smooth animations in Jupyter Notebook and Matplotlib
Published:
Jupyter Notebook is a godsend. Compared to other methods to share a slightly incoherent mix of code and ideas it is certainly the best way to do so. It is what I currently use to share my progress with my supervisors because it is just so convenient. Just send them a link and they can read your thoughts and how that translates to code, with the results of that intermingled. It may even be Second Coming of Literate Programming.
But there was one issue I ran into. I currently have cine MRI data (2D slices in time) which I wanted to view sequentially through time. Should be easy, I thought, just imshow
every image in a loop. But this does not work. It either just does not show anything at all or it you get annoying blanks in between draws, distracting you from what actually happens. And StackOverflow is riddled with questions and YMMV. And for me, it varied. As in, all options did not work. Either they had the problems as I described before, or they opend so many figures it crashed my browser.
But in the end I found a solution that works, but for the life of me I cannot find anymore. I hope someone can inform me where I can find it again so I can give some proper credit. In lieu of that, I will share my solution here. Partly for preservation of knowledge for myself in case I run into this problem again in the future, or so someone won’t have the same problem.
Note that this was successfully tested with the following packages in Python 3.7.1:
jupyter 1.0.0
jupyter-client 5.2.3
jupyter-console 5.2.0
jupyter-contrib-core 0.3.3
jupyter-contrib-nbextensions 0.5.0
jupyter-core 4.4.0
jupyter-highlight-selected-word 0.2.0
jupyter-latex-envs 1.4.4
jupyter-nbextensions-configurator 0.4.0
jupyterlab 0.34.9
jupyterlab-launcher 0.13.1
matplotlib 2.2.3
notebook 5.6.0
Pillow 5.2.0
Inspect the data
So let’s take a look at the data first (Image probably from the CuteLittleAnimalGIFs Tumblr, at least that is an source from 2012 I could find).
%matplotlib notebook
import matplotlib.pyplot as plt
from glob import glob
from PIL import Image
images = sorted(glob('images/sloth-frame-*.jpg'))
nrows, ncols = 2, 4
fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(9, 5))
for i in range(nrows):
for j in range(ncols):
with Image.open(images[ncols * i + j]) as img:
ax[i,j].imshow(img)
ax[i,j].set_xticks([]); ax[i,j].set_yticks([])
plt.tight_layout()
plt.show()
Look at that cute coconut! Most important for our purposes here is the %matplotlib notebook
magic. It is essential for having smooth animations. So let’s animate him.
# Enable interactive mode
plt.ion()
# Create a plot
fig, ax = plt.subplots(1,1)
while True:
for i in range(len(images)):
with Image.open(images[i]) as img:
# Clear the current plot
ax.clear()
ax.set_xticks([])
ax.set_yticks([])
# Display new data
ax.imshow(img)
# Possibly do other stuff here for other subplots
# ...
# Draw the figure
fig.canvas.draw()
# Leave interactive mode after you are done
plt.ioff()
Now he is scratching happily! And without hiccups, blanks or other drawing artifacts. And you can do this for multiple subplots by just repeating all your operations for every subplot. They will draw at the same time because interactive mode is enabled and won’t draw until you give the command with fig.canvas.draw()
.
This recipe will work for any type of plot you do with matplotlib. So not just imshow
but also plot
, quiver
, even 3D plots.