Stop gif image from repeating in matplotlib - python

I searched for other similar questions but that didn't solve my problem. Below is a simple code that generates an animation in the form of a gif image in matplotlib:
import numpy as np
import matplotlib.pylab as plt
import matplotlib.animation as anm
fig = plt.figure()
def draw(i):
x = np.linspace(0, 5, num = 1000)
y = np.sin(x-i*0.1)
plt.clf()
plt.plot(x, y, 'r-')
anim = anm.FuncAnimation(fig, draw, frames = 10, interval = 500, repeat = False)
anim.save('test.gif', fps = 1, writer = 'imagemagick')
This generates the animation I want but once I open the final image (with eog), it keeps repeating. Since I would be presenting animation in the presentation, I would like it to stop after it is shown once. As you can notice, I have added repeat = False in the FuncAnimation but that doesn't stop repeating the image. What is wrong with my code?
Thanks in advance

My solution to the problem was to change from imagemagick to pillow as a writer.
Try the following:
from matplotlib.animation import FuncAnimation, PillowWriter
anim = FuncAnimation(fig, update, frames=range(1, 51), interval=.001, repeat=False)
anim.save('test.gif', dpi=80, writer=PillowWriter(fps=5))
The quality of the image is a bit worse than with imagemagick from my opinion, but it's a much easier and faster solution.

Using PillowWriter didn't solve the problem for me, so I came up with a workaround: just remove the loop as a post-processing step with PIL. Here is the code:
from PIL import Image
import io
def convert(old_filename, new_filename, duration):
images = []
with Image.open(old_filename) as im:
for i in range(im.n_frames):
im.seek(i)
buf = io.BytesIO()
im.save(buf, format='png')
buf.seek(0)
images.append(Image.open(buf))
images[0].save(new_filename, save_all=True, append_images=images[1:], optimize=False, duration=duration)
convert("myAnimation.gif", "myAnimationNoLoop.gif", 100)

Related

How to draw animation by taking snapshot with matplotlib?

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_)

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()

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

How can I improve the plot of a picture using matplotlib?

Hello I am trying to display a picture from an url using the Matplotlib module but the problem is the following :
When I execute the code the picture has not a good quality.
Here is my code :
import requests
from PIL import Image
from io import BytesIO
fig, ax = plt.subplots()
picture_url = "https://www.google.fr/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
response = requests/get(picture_url)
img = Image.open(BytesIO(response.content))
ax.imshow(img)
plt.show()
Could you help me please ?
Thank you !
While some would say this is the type of answer only a 10x engineer could answer, in this case it only took a 2x!
Try this URL:
https://www.google.fr/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png
The difference is in the "2x" in the URL, which provides a larger png!
The problem is that if the axes is not precisely as large as the image, the image will be interpolated. This results in little artifacts.
You can calculate the figure size and margins such that the image fits exactly into the axes.
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import requests
from PIL import Image
from io import BytesIO
picture_url = "https://www.google.fr/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
response = requests.get(picture_url)
img = np.array(Image.open(BytesIO(response.content)))
dpi = 100
bottom=0.2; left=0.1; right=0.9; top=0.9
width = img.shape[1]/(right-left)/dpi
height= img.shape[0]/(top-bottom)/dpi
fig, ax = plt.subplots(dpi=dpi, figsize=(width, height))
fig.subplots_adjust(bottom=bottom, left=left, right=right, top=top)
ax.imshow(img)
fig.savefig("googlelogo.png")
plt.show()

Simple Python/Matplotlib animation shows: empty graph - why? [duplicate]

I have a python animation script (using matplotlib's funcAnimation), which runs in Spyder but not in Jupyter. I have tried following various suggestions such as adding "%matplotlib inline" and changing the matplotlib backend to "Qt4agg", all without success. I have also tried running several example animations (from Jupyter tutorials), none of which have worked. Sometimes I get an error message and sometimes the plot appears, but does not animate. Incidentally, I have gotten pyplot.plot() to work using "%matplotlib inline".
Does anyone know of a working Jupyter notebook with a simple inline animation example that uses funcAnimation.
[Note: I am on Windows 7]
notebook backend
'Inline' means that the plots are shown as png graphics. Those png images cannot be animated. While in principle one could build an animation by successively replacing the png images, this is probably undesired.
A solution is to use the notebook backend, which is fully compatible with FuncAnimation as it renders the matplotlib figure itself:
%matplotlib notebook
jsanimation
From matplotlib 2.1 on, we can create an animation using JavaScript. This is similar to the ani.to_html5() solution, except that it does not require any video codecs.
from IPython.display import HTML
HTML(ani.to_jshtml())
Some complete example:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
ax.axis([0,2*np.pi,-1,1])
l, = ax.plot([],[])
def animate(i):
l.set_data(t[:i], x[:i])
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t))
from IPython.display import HTML
HTML(ani.to_jshtml())
Alternatively, make the jsanimation the default for showing animations,
plt.rcParams["animation.html"] = "jshtml"
Then at the end simply state ani to obtain the animation.
Also see this answer for a complete overview.
There is a simple example within this tutorial here: http://louistiao.me/posts/notebooks/embedding-matplotlib-animations-in-jupyter-notebooks/
To summarise the tutorial above, you basically need something like this:
from matplotlib import animation
from IPython.display import HTML
# <insert animation setup code here>
anim = animation.FuncAnimation() # With arguments of course!
HTML(anim.to_html5_video())
However...
I had a lot of trouble getting that to work. Essentially, the problem was that the above uses (by default) ffmpeg and the x264 codec in the background but these were not configured correctly on my machine. The solution was to uninstall them and rebuild them from source with the correct configuration. For more details, see the question I asked about it with a working answer from Andrew Heusser: Animations in ipython (jupyter) notebook - ValueError: I/O operation on closed file
So, try the to_html5_video solution above first, and if it doesn't work then also try the uninstall / rebuild of ffmpeg and x264.
Another option:
import matplotlib.animation
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams["animation.html"] = "jshtml"
plt.rcParams['figure.dpi'] = 150
plt.ioff()
fig, ax = plt.subplots()
x= np.linspace(0,10,100)
def animate(t):
plt.cla()
plt.plot(x-t,x)
plt.xlim(0,10)
matplotlib.animation.FuncAnimation(fig, animate, frames=10)
Here is the answer that I put together from multiple sources including the official examples. I tested with the latest versions of Jupyter and Python.
Download FFmpeg ( http://ffmpeg.zeranoe.com/builds/ )
Install FFmpeg making sure that you update the environmental variable ( http://www.wikihow.com/Install-FFmpeg-on-Windows ).
Run this script in Jupyter below. The variable imageList is the only thing that you need to modify. It is an list of images (your input).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
#=========================================
# Create Fake Images using Numpy
# You don't need this in your code as you have your own imageList.
# This is used as an example.
imageList = []
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
imageList.append(np.sin(x) + np.cos(y))
#=========================================
# Animate Fake Images (in Jupyter)
def getImageFromList(x):
return imageList[x]
fig = plt.figure(figsize=(10, 10))
ims = []
for i in range(len(imageList)):
im = plt.imshow(getImageFromList(i), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True, repeat_delay=1000)
plt.close()
# Show the animation
HTML(ani.to_html5_video())
#=========================================
# Save animation as video (if required)
# ani.save('dynamic_images.mp4')
If you have a list of images and want to animate through them, you can use something like this:
from keras.preprocessing.image import load_img, img_to_array
from matplotlib import animation
from IPython.display import HTML
import glob
%matplotlib inline
def plot_images(img_list):
def init():
img.set_data(img_list[0])
return (img,)
def animate(i):
img.set_data(img_list[i])
return (img,)
fig = figure()
ax = fig.gca()
img = ax.imshow(img_list[0])
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=len(img_list), interval=20, blit=True)
return anim
imgs = [img_to_array(load_img(i)) for i in glob.glob('*.jpg')]
HTML(plot_images(imgs).to_html5_video())
Thank to Kolibril. I finally can run animation on Jupyter and Google Colab.
I modify some code which will generate animation of drawing random line instead.
import matplotlib.animation
import matplotlib.pyplot as plt
from itertools import count
import random
plt.rcParams["animation.html"] = "jshtml"
plt.rcParams['figure.dpi'] = 150
fig, ax = plt.subplots()
x_value = []
y_value = []
index = count();
def animate(t):
x_value.append(next(index))
y_value.append(random.randint(0,10))
ax.cla()
ax.plot(x_value,y_value)
ax.set_xlim(0,10)
matplotlib.animation.FuncAnimation(fig, animate, frames=10, interval = 500)
enter image description here

Categories

Resources