How to draw animation by taking snapshot with matplotlib? - python

In my project, I have many polygons to draw for each time step.
At each step, the number of polygons varies, thus it is difficult to keep Axes.patchs and translate them to make the animation.
I want to create animation with final figures (show after calling matplotlib.pyplot.show()), how to do this?
We take the sin curve as example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ims = []
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
z = np.cos(x)
for i in range(1,100):
tmpx = x[:i]
tmpy = y[:i]
tmpz = z[:i]
plt.plot(tmpx, tmpz)
im = plt.plot(tmpx, tmpy)
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=200)
ani.save('/home/test.gif', writer='imagemagick')
plt.show()
There are two curves: animated-sin-curve and static-cos-curve.
the sin-curve is kept as Line2D objects for each step
the cos-curve stay static for each step.
In this way, we show different Artist object for each step.
But I want to keep the rasterized Line2D figure for each step.
I find classes of AxesImage/FigureImage, but I don't know how to save the rasterized figure and make them work.
I tried to convert figure.canvas to AxesImage with following code :
def fig2AxesImage(fig):
import PIL.Image as Image
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
buf = numpy.fromstring(fig.canvas.tostring_argb(), dtype=numpy.uint8)
buf.shape = (w, h, 4)
# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = numpy.roll(buf, 3, axis=2)
image = Image.frombytes("RGBA", (w, h), buf.tostring())
image = numpy.asarray(image)
return plt.imshow(image, animated=True)
but with this way, I have to clear canvas at start of next frame, which make the final animation a blank video. (but the .jpg figures I output for each step get the right content)
Does anyone have done this before that save rasterized canvas-figures of matplotlib.pyplot.figure() as a animation Vedio?

celluloid for python 2.7
''' copy from celluloid'''
# from typing import Dict, List # not supported by python 2.7. So comment it
from collections import defaultdict
from matplotlib.figure import Figure
from matplotlib.artist import Artist
from matplotlib.animation import ArtistAnimation
__version__ = '0.2.0'
class Camera:
def __init__(self, figure):
self.figure_ = figure
self.offsets_ = { k:defaultdict(int) \
for k in ['collections', 'patches', 'lines', 'texts', 'artists', 'images']
}
self.photos_ = []
def snap(self):
frame_artists = []
for i, axis in enumerate(self.figure_.axes):
if axis.legend_ is not None:
axis.add_artist(axis.legend_)
for name in self.offsets_:
new_artists = getattr(axis, name)[self.offsets_[name][i]:]
frame_artists += new_artists
self.offsets_[name][i] += len(new_artists)
self.photos_.append(frame_artists)
def animate(self):
return ArtistAnimation(self.figure_, self.photos_)

Related

How do I convert N displayed arrays (each array visually similar to a PNG) into an mp4 in python? [duplicate]

I have a couple of images that show how something changes in time. I visualize them as many images on the same plot with the following code:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
img = [] # some array of images
fig = plt.figure()
for i in xrange(6):
fig.add_subplot(2, 3, i + 1)
plt.imshow(img[i], cmap=cm.Greys_r)
plt.show()
and get something like:
Which is ok, but I would rather animate them to get something like this video. How can I achieve this with python and preferably (not necessarily) with matplotlib
For a future myself, here is what I ended up with:
def generate_video(img):
for i in xrange(len(img)):
plt.imshow(img[i], cmap=cm.Greys_r)
plt.savefig(folder + "/file%02d.png" % i)
os.chdir("your_folder")
subprocess.call([
'ffmpeg', '-framerate', '8', '-i', 'file%02d.png', '-r', '30', '-pix_fmt', 'yuv420p',
'video_name.mp4'
])
for file_name in glob.glob("*.png"):
os.remove(file_name)
Another solution is to use AnimationArtist from matplotlib.animation as described in the animated image demo. Adapting for your example would be
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.animation as animation
img = [] # some array of images
frames = [] # for storing the generated images
fig = plt.figure()
for i in xrange(6):
frames.append([plt.imshow(img[i], cmap=cm.Greys_r,animated=True)])
ani = animation.ArtistAnimation(fig, frames, interval=50, blit=True,
repeat_delay=1000)
# ani.save('movie.mp4')
plt.show()
You could export images from matplotlib using Agg interface.
See those examples:
Agg Buffer to Array
CanvasAgg demo
Here is your full code:
# imports
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import cv2
# Use Agg backend for canvas
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
# create OpenCV video writer
video = cv2.VideoWriter('video.mp4', cv2.VideoWriter_fourcc('A','V','C','1'), 1, (mat.shape[0],mat.shape[1]))
# loop over your images
for i in xrange(len(img)):
fig = plt.figure()
plt.imshow(img[i], cmap=cm.Greys_r)
# put pixel buffer in numpy array
canvas = FigureCanvas(fig)
canvas.draw()
mat = np.array(canvas.renderer._renderer)
mat = cv2.cvtColor(mat, cv2.COLOR_RGB2BGR)
# write frame to video
video.write(mat)
# close video writer
cv2.destroyAllWindows()
video.release()
You can try drawing the images (frames) sequentially with a delay. If you have many frames, it might make sense to reduce the wait time between frames in the plt.pause() function.
# need this line if you're using jupyter notebooks
%matplotlib notebook
x = [] # Some array of images
fig = plt.figure()
viewer = fig.add_subplot(111)
plt.ion() # Turns interactive mode on (probably unnecessary)
fig.show() # Initially shows the figure
for i in range(len(x)):
viewer.clear() # Clears the previous image
viewer.imshow(x[i]) # Loads the new image
plt.pause(.1) # Delay in seconds
fig.canvas.draw() # Draws the image to the screen
You could for example export the images to png using plt.savefig("file%d.png" % i), then use ffmpeg to generate the video.
Here you find help to generate video from images
I implemented a handy script that just suits you and new comers. Try it out here.
For your example:
imagelist = YOUR-IMAGE-LIST
def redraw_fn(f, axes):
img = imagelist[f]
if not redraw_fn.initialized:
redraw_fn.im = axes.imshow(img, animated=True)
redraw_fn.initialized = True
else:
redraw_fn.im.set_array(img)
redraw_fn.initialized = False
videofig(len(imagelist), redraw_fn, play_fps=30)
Here's a copy-pastable function, handy for if you're dealing with long videos and are using a streaming iterator (from here)
from typing import Iterator, Optional, Tuple
from pathlib import Path
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
def write_animation(
itr: Iterator[np.array],
out_file: Path,
dpi: int = 50,
fps: int = 30,
title: str = "Animation",
comment: Optional[str] = None,
writer: str = "ffmpeg",
) -> None:
"""Function that writes an animation from a stream of input tensors.
Args:
itr: The image iterator, yielding images with shape (H, W, C).
out_file: The path to the output file.
dpi: Dots per inch for output image.
fps: Frames per second for the video.
title: Title for the video metadata.
comment: Comment for the video metadata.
writer: The Matplotlib animation writer to use (if you use the
default one, make sure you have `ffmpeg` installed on your
system).
"""
first_img = next(itr)
height, width, _ = first_img.shape
fig, ax = plt.subplots(figsize=(width / dpi, height / dpi))
# Ensures that there's no extra space around the image.
fig.subplots_adjust(
left=0,
bottom=0,
right=1,
top=1,
wspace=None,
hspace=None,
)
# Creates the writer with the given metadata.
Writer = mpl.animation.writers[writer]
metadata = {
"title": title,
"artist": __name__,
"comment": comment,
}
mpl_writer = Writer(
fps=fps,
metadata={k: v for k, v in metadata.items() if v is not None},
)
with mpl_writer.saving(fig, out_file, dpi=dpi):
im = ax.imshow(first_img, interpolation="nearest")
mpl_writer.grab_frame()
for img in itr:
im.set_data(img)
mpl_writer.grab_frame()

Cannot save animated gif with transparent background (matplotlib.animation)

I am trying to save an animation with a completely transparent background. Setting:
fig1 = (...,facecolor=(1,1,1,0))
Does not seem to work. Also, just as a side note, if you do that and view the plot then you get these weird transparency effects and lagging animation. Curious why that happens too, but mostly I just want the background to save as transparent.
If I then try:
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"facecolor": (1,1,1,0)})
Then I get an output which does not have a transparent background and makes the lines thick. Same curiosity as above as why making the figure alpha to 0 would give this effect.
Another attempt:
fig1 = (...,facecolor=(1,1,1,0))
line_ani.save(...,savefig_kwargs={"transparent": None})
Also doesn't produce a transparent background.
If I just include the facecolor in the dictionary, then it gives the undesired line thickening bug.
line_ani.save(...,savefig_kwargs={"transparent": None,"facecolor":(1,1,1,0)})
The code is below.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from matplotlib.pyplot import figure
def update_line(num, data, line):
line.set_data(data[..., :num])
return line,
def plots():
plt.xlim(-1, 1)
plt.ylim(-1, 1)
plt.xticks([])
plt.yticks([])
plt.box()
# Since I'm calling things twice, it's convenient to define these
fs = (3,3)
inter = 100
frames = 219
lw = 0.25
alph = 0
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth =lw)
# Generate placeholder for data and set initial conditions
DAT =np.zeros((2,300))
DAT[0][0] = 0
DAT[1][0] = 1
theta=2*np.pi*(1/np.e +0.01)
# 2D Rotation Matrix
def R(x):
return [[np.cos(x),-np.sin(x)],[np.sin(x),np.cos(x)]]
# Generate the data
for i in range(len(DAT[0])):
if i < len(DAT[0])-1:
DAT[0][i+1]=DAT[0][i]*R(theta)[0][0] + DAT[1][i]*R(theta)[0][1]
DAT[1][i+1]=DAT[0][i]*R(theta)[1][0] + DAT[1][i]*R(theta)[1][1]
# Animate the data
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
plt.show()
# Save the animation
matplotlib.use("Agg")
fig1 = plt.figure(figsize=fs)
l, = plt.plot([], [],'r',linewidth = lw)
plots()
line_ani = animation.FuncAnimation(fig1, update_line, frames, fargs=(DAT, l),
interval=inter, blit=True,repeat_delay = 2000)
print("Saving animation...")
now=time.time()
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": None})
later = time.time()
print("Saved in time: ", int(later-now),"seconds")
If you run the code it should show you the animation and then save it. It also will calculate the runtime.
Setting transparent = True does the trick...
line_ani.save('lines1.gif', writer='imagemagick',savefig_kwargs={"transparent": True})

matplotlib ArtistAnimation shows only one frame

I am trying to make an animation with matplotlib.animation, using the ArtistAnimation function. I have a large matrix and I want to show a different part of this matrix in a colormap using plt.imshow() in each frame. However, only one frame is shown in the animation and I don't know why. I tried to mimic the code in An animated image using a list of images. I have looked at this question and this question, but I could not find a solution there.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
def make_animation(pwv_matrix, length_side):
pwv_shape = pwv_matrix.shape
fig = plt.figure()
num_frames = min(pwv_shape[0], pwv_shape[1])-length_side
y_min = int(np.round(pwv_shape[0]/2) - np.round(length_side/2))
y_max = int(np.round(pwv_shape[0]/2) + np.round(length_side/2))
x_min = 0
x_max = length_side
ims = []
for i in range(0, num_frames):
pwv_frame = pwv_matrix[x_min:x_max, y_min:y_max]
im = plt.imshow(pwv_frame, animated=True, cmap = 'viridis')
ims.append([im])
x_min += 1
x_max += 1
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
plt.show()
x = np.linspace(0, 1, 200)
y = np.linspace(0, 1, 200)
pwv_matrix, yv = np.meshgrid(x, y)
length_side = 20 #m
make_animation(pwv_matrix, length_side)
where self.pwv_matrix is a large matrix obtained from a .dat file. Does anyone see the problem?
Thanks very much in advance!
Try adding a very small delay after the show() function with
matplotlib.pyplot.pause(interval)[source]
function or:
from time import sleep
sleep(0.05)
The actual drawing of the image occurs one you give it some processing time (in a very simplified way of explaining it)
If it does not work there are some other things you might try, let me know if it doesn't work

Problem animating matplotlib pcolor plot with polar projection using object

I am trying to create a plotting object that produces an animated matplotlib pcolor plot with a polar projection. Currently the object can either create a set of polar plots or try to create an animation of those plots.
When creating the set of polar plots (but not the animation) the object works as planned.
The animation portion of the object is based on this example, which works on my system. Unfortunately the animation as implemented in my object is not working. There is a figure and an MP4 file produced for the animation but both the figure and the too-short animation both show just some mis-shaped axes.
Does anyone have a suggestion of how to capture this figure series in an animation when embedded in an object?
I am using python 3.7, matplotlib 3.03 on a windows 10 machine
The code for the object and the code to run its instantiation are given below.
class Polar_smudge(object):
# object for creating polar contour plots
def __init__(self, azimuth_grid, range_grid):
import numpy as np
self.azimuth_grid = np.deg2rad(azimuth_grid)
self.range_grid = range_grid
self.fig = None
self.ax = None
self.images = []
#------------------------------------------------------------------
def add_data(self, value_grid):
import numpy as np
self.value_grid = value_grid
self.value_grid[self.value_grid<=0] = np.nan
#------------------------------------------------------------------
def add_figure(self, value_grid):
import matplotlib.pyplot as plt
# make and set-up figure
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_rlim([0,10])
# make plot
cax = ax.pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r)
ax.grid()
plt.show()
#------------------------------------------------------------------
def start_figure(self):
import matplotlib.pyplot as plt
# make and set-up figure
if self.fig is None :
self.fig, self.ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
self.ax[0].set_theta_zero_location("N")
self.ax[0].set_theta_direction(-1)
def update_figure(self, value_grid):
import matplotlib.pyplot as plt
# make figure and add to image list
self.images.append((self.ax[0].pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r),))
def end_figure(self):
import matplotlib.animation as animation
# animate the figure list
im_ani = animation.ArtistAnimation(self.fig, self.images, interval=50, repeat_delay=3000,blit=True)
im_ani.save('smudge.mp4')
#============This runs the object ====================================
import numpy as np
azimuth_bins = np.linspace(0, 360, 360)
range_bins = np.linspace(0, 10, 30)
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
# this works but isnt what I want
good_smudge = Polar_smudge(azimuth_grid,range_grid)
for ix in range(3):
val_grid = np.random.randn(360,30)
good_smudge.add_figure(val_grid)
# this doesnt work
bad_smudge = Polar_smudge(azimuth_grid,range_grid)
bad_smudge.start_figure()
for ix in range(3):
val_grid = np.random.randn(360,30)
bad_smudge.update_figure(val_grid)
bad_smudge.end_figure()
In response to the comment from Earnest, I did some further refinement and it appears that the problem is not linked to being embedded in an object, and also that increasing the number of frames (to eg. 30) does not solve the problem. The code snippet below provides a more concise demonstration of the problem (but lacks the correctly produced figure output option).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
azimuth_bins = np.linspace(0, 360, 60)
range_bins = np.linspace(0, 10, 30)
images = []
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
fig,ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
for ix in range(30):
val_grid = np.random.randn(60,30)
images.append((ax[0].pcolor(azimuth_grid, range_grid, val_grid, cmap=plt.cm.viridis_r),))
# animate the figure list
im_ani = animation.ArtistAnimation(fig, images, interval=50, repeat_delay=3000,blit=False)
im_ani.save('smudge2.mp4')

Python: Animated 3D Scatterplot gets slow

My program plots the positions of particles in my file for every time step. Unfortunately it gets slower and slower although I used matplotlib.animation. Where is the bottleneck?
My data file for two particles looks like the following:
# x y z
# t1 1 2 4
# 4 1 3
# t2 4 0 4
# 3 2 9
# t3 ...
My script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
with open('//home//data.dat', 'r') as fp:
particleData = []
for line in fp:
line = line.split()
particleData.append(line)
x = [float(item[0]) for item in particleData]
y = [float(item[1]) for item in particleData]
z = [float(item[2]) for item in particleData]
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
def animate(i):
global x, y, z, numP
#ax.clear()
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
idx0 = i*numP
idx1 = numP*(i+1)
ax.scatter(x[idx0:idx1],y[idx0:idx1],z[idx0:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()
I would suggest to use pyqtgraph in this case. Citation from the docs:
Its primary goals are 1) to provide fast, interactive graphics for
displaying data (plots, video, etc.) and 2) to provide tools to aid in
rapid application development (for example, property trees such as
used in Qt Designer).
You can check out some examples after the installation:
import pyqtgraph.examples
pyqtgraph.examples.run()
This small code snippet generates 1000 random points and displays them in a 3D scatter plot by constantly updating the opacity, similar to the 3D scatter plot example in pyqtgraph.examples:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#generate random points from -10 to 10, z-axis positive
pos = np.random.randint(-10,10,size=(1000,3))
pos[:,2] = np.abs(pos[:,2])
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
#generate a color opacity gradient
color = np.zeros((pos.shape[0],4), dtype=np.float32)
color[:,0] = 1
color[:,1] = 0
color[:,2] = 0.5
color[0:100,3] = np.arange(0,100)/100.
def update():
## update volume colors
global color
color = np.roll(color,1, axis=0)
sp2.setData(color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Small gif to give you an idea of the performance:
EDIT:
Displaying multiple points at every single time step is a little bit tricky since the gl.GLScatterPlotItem takes only (N,3)-arrays as point locations, see here. You could try to make a dictionary of ScatterPlotItems where each of them includes all time steps for a specific point. Then one would need to adapt the update function accordingly. You can find an example below where pos is an (100,10,3)-array representing 100 time steps for each point. I reduced the update time to 1000 ms for a slower animation.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
pos = np.random.randint(-10,10,size=(100,10,3))
pos[:,:,2] = np.abs(pos[:,:,2])
ScatterPlotItems = {}
for point in np.arange(10):
ScatterPlotItems[point] = gl.GLScatterPlotItem(pos=pos[:,point,:])
w.addItem(ScatterPlotItems[point])
color = np.zeros((pos.shape[0],10,4), dtype=np.float32)
color[:,:,0] = 1
color[:,:,1] = 0
color[:,:,2] = 0.5
color[0:5,:,3] = np.tile(np.arange(1,6)/5., (10,1)).T
def update():
## update volume colors
global color
for point in np.arange(10):
ScatterPlotItems[point].setData(color=color[:,point,:])
color = np.roll(color,1, axis=0)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(1000)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Keep in mind that in this examples, all points are shown in the scatter plot, however, the color opacity (4th dimension in the color array) is updated in every time step to get an animation. You could also try to update the points instead of the color to get better performance...
I would guess your bottleneck is calling ax.scatter and ax.set_xlim3d and similar in every frame in the animation.
Ideally, you should make a call to scatter once, then use the object returned by scatter and its set_... properties in the animate function (more details here).
I can't figure out how to do it with scatter, but if you use ax.plot(x, y, z, 'o') instead, you can then follow the demo method here.
Using some random data for x, y, z. It would work like this
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
from numpy.random import random
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
x, y, z = random(timesteps), random(timesteps), random(timesteps)
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
line = ax.plot(x[:1], y[:1], z[:1], 'o')[0]
def animate(i):
global x, y, z, numP
idx1 = numP*(i+1)
# join x and y into single 2 x N array
xy_data = np.c_[x[:idx1], y[:idx1]].T
line.set_data(xy_data)
line.set_3d_properties(z[:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()

Categories

Resources