I have been trying to animate a series of surface plots that I created for a 2D heat flow problem using finite element method. At each time step, I saved a plot instead of the whole matrix, in order to be more efficient.
I had trouble with the FuncAnimation
in the matplotlib.animation library, so I decided to render a surface plot at each time, save the surface plot as a .png file, and then read that image using pyplot.imread
. From there, I want to store each image into a list so that I can use the ArtistAnimation ( example). However it is not making the animation, instead I get two separate blank plots and then my surface plot .pngs when I print imgplot
to the screen.
Additionally, when I try to save the animation, I get the following error message:
AttributeError: 'module' object has no attribute 'save'.
Any help with reading in a set of .pngs from the current directory, saving them in a list, and then using ArtistAnimation to "animate" those .pngs would be greatly appreciated. I do not need anything fancy.
(Note - I have to make the code automated, so unfortunately I cannot use an outside source to animate my images like iMovie or ffmpeg.)
Below is my code:
from numpy import *
from pylab import *
import matplotlib.pyplot as plt
import matplotlib.image as mgimg
from matplotlib import animation
## Read in graphs
p = 0
myimages = []
for k in range(1, len(params.t)):
fname = "heatflow%03d.png" %p
# read in pictures
img = mgimg.imread(fname)
imgplot = plt.imshow(img)
myimages.append([imgplot])
p += 1
## Make animation
fig = plt.figure()
animation.ArtistAnimation(fig, myimages, interval=20, blit=True, repeat_delay=1000)
animation.save("animation.mp4", fps = 30)
plt.show()
Issue 1: Images are not displayed
You need to store your animation object in a variable:
my_anim = animation.ArtistAnimation(fig, myimages, interval=100)
This requirement is specific for animation
and is not consistent with other plotting function in matplotlib
, where you can usually use my_plot=plt.plot()
or plt.plot()
indifferently.
This question is further discussed here.
Issue 2: Save does not work
Without any animation
instance, it will not be possible to save a figure either. This is because the save
method
belongs to the ArtistAnimation
class. What you did was calling save
from the animation
module, this is what raised the error.
Issue 3: Two windows
The last issue is that you get two figures popping up. The reason is that when you call plt.imshow()
, it displays an image on the current figure, but since no figure has been created yet, pyplot
implicitly creates one for you.
When python later interprets the fig = plt.figure()
statement, it creates a new figure (another window) and labels it "Figure 2".
Moving this statement to the beginning of your code, solves that problem.
Here is the modified code:
import matplotlib.pyplot as plt
import matplotlib.image as mgimg
from matplotlib import animation
fig = plt.figure()
# initiate an empty list of "plotted" images
myimages = []
#loops through available png:s
for p in range(1, 4):
## Read in picture
fname = "heatflow%03d.png" %p
img = mgimg.imread(fname)
imgplot = plt.imshow(img)
# append AxesImage object to the list
myimages.append([imgplot])
## create an instance of animation
my_anim = animation.ArtistAnimation(fig, myimages, interval=1000, blit=True, repeat_delay=1000)
## NB: The 'save' method here belongs to the object you created above
#my_anim.save("animation.mp4")
## Showtime!
plt.show()
(To run the code above, just add 3 images into your working folder with name "heatflow001.png" through "heatflow003.png".)
Alternative approach using FuncAnimation
You were probably right when you first tried to use FuncAnimation
, since gathering images in a list is costly in terms of memory. I tested the code below against the one above, by comparing memory usage on the
system monitor. It appears that the FuncAnimation
approach is more efficient. I believe the difference will grow even bigger as you use more images.
Here is the second code:
from matplotlib import pyplot as plt
from matplotlib import animation
import matplotlib.image as mgimg
import numpy as np
#set up the figure
fig = plt.figure()
ax = plt.gca()
#initialization of animation, plot array of zeros
def init():
imobj.set_data(np.zeros((100, 100)))
return imobj,
def animate(i):
## Read in picture
fname = "heatflow%03d.png" % i
## here I use [-1::-1], to invert the array
# IOtherwise it plots up-side down
img = mgimg.imread(fname)[-1::-1]
imobj.set_data(img)
return imobj,
## create an AxesImage object
imobj = ax.imshow( np.zeros((100, 100)), origin='lower', alpha=1.0, zorder=1, aspect=1 )
anim = animation.FuncAnimation(fig, animate, init_func=init, repeat = True,
frames=range(1,4), interval=200, blit=True, repeat_delay=1000)
plt.show()