I want to be able to display an animated sequence of images in Python without blocking the Python REPL. (The "sequence of images" is an array of static images). I've found a kludgy way of doing this, which is to launch a separate process which displays the animation using Tkinter. Launching a whole process in this way is quite inefficient in terms of memory.
Another way of putting this is, I want something like Pillow's Image.show, but which can display animations. Notice that Image.show launches a new window, and doesn't block the REPL.
Is there a non-kludgy way to do this?
I used the following code to display a sequence of images as an animation using Matplotlib:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def display(images, title):
"""Display an animated sequence of images in a new window."""
fig = plt.figure(figsize=(images[0].width/100,images[1].height/100))
ims = []
for i in range(len(images)):
im = plt.imshow(images[i], animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=10, blit=True,
repeat_delay=0)
plt.axis('off')
plt.axis("tight")
plt.axis("image")
fig.tight_layout(pad=0)
fig.canvas.set_window_title(title)
plt.show()
This doesn't hog the terminal. It's still quite slow though.
Related
How do I make an animation made with ImageMagick play on my python Jupyter Notebook?
I'm new to animations on Python, so I'm studying how to make one. So far, I copied the code from DevelopPaper but when I run the cells, it only shows a static image of it. I also tried it for other animation examples there but it's the same for all of them -- animations from Imagemagick won't play on Jupyter Notebook but when I open the saved file manually it works just fine. How do I get the animations to play on Jupyter Notebook itself? Thanks!
enter image description here
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
plt.style.use('seaborn-pastel')
fig = plt.figure()
ax = plt.axes(xlim=(0, 4), ylim=(-2, 2))
line, = ax.plot([], [], lw=3)
def init():
line.set_data([], [])
return line,
def animate(i):
x = np.linspace(0, 4, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
return line,
anim = FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
anim.save('sine_wave.gif', writer='imagemagick')
There's lots of options. (And you probably aren't finding the solutions easily because you aren't using the proper terms as your post title reflects. It seems you want to play a .gif in Jupyter or an animated plot made of frames.)
First, addressing showing the gif inside Jupyter:
The easiest is to use JupyterLab to open a new notebook run your code after clicking here. (For using your code there, change that last line to anim.save('sine_wave.gif') because I didn't bother installing imagemagick there, yet. Pillow is able to make the gif, and matplotlib will handle selecting Pillow for the task if you just don't specify writer.)
When the gif file gets made, you can just double click on it in the file browser on the left side and it will play. You can arrange the window it plays in as you want relative the notebook by clicking on the tab above it and dragging and then releasing it as you want.
If you prefer the more traditional notebook for playing the gif in, you can simply put the following code in a cell after the gif file is generated:
from IPython.display import Image
Image("sine_wave.gif")
(Note that here it says for a local .gif file you need to read the bytes and display it, like Image("sine_wave.gif","rb").read()). And so that may be something to keep in mind if not as simple as I illustrate in the Unix system.)
Executing that notebook cell will play the gif as output for the cell if the gif image file is in the same folder with the notebook file.
Second, display alternatives instead of making a gif:
For how to properly get the plotting animation to play, not necessarily as a gif, I'd suggest adding controls following examples and links towards the bottom of the notebook that you can open in active form by clicking 'launch binder' here. You are looking for examples and links that use or involve FuncAnimation() towards the bottom there. The ones I suggest involving frames allow you to control and play them using a widget or make HTML that can play and be controlled. Importantly, that widget approach will work even when the notebook is 'static' as you can see here. I put static in quotes because some active features will work in nbviewer, though usually note in Github preview, which trips up a lot of novices now that Github attempts a preview rendering for notebooks even of substantial length. (And if you need a portable HTML5-compatible video file to be generated, that is covered in that same section.) Here's a couple of variations on your code using related approaches to add the widget/frame generation that will work in active sessions here:
one option
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML, display
plt.style.use('seaborn-pastel')
def generate_animation():
fig = plt.figure()
ax = plt.axes(xlim=(0, 4), ylim=(-2, 2))
lineplot, = ax.plot([], [], lw=3)
def init():
lineplot.set_data([], [])
return lineplot, #return [lineplot] also works like in https://nbviewer.org/github/raphaelquast/jupyter_notebook_intro/blob/master/jupyter_nb_introduction.ipynb#pre-render-animations-and-export-to-HTML
def animate(i):
x = np.linspace(0, 4, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
lineplot.set_data([x], [y])
return [lineplot]
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
display(HTML(anim.to_jshtml()))
plt.close(fig)
generate_animation()
another variant
# RUN THIS TWICE. SECOND TIME WILL BE NICE SINGLE PLOT DISPLAYED.
# January 2023 I was finding first time I ran this or code like at https://nbviewer.org/gist/fomightez/d862333d8eefb94a74a79022840680b1 that it output a non-interactive frame of plot, too. Just re-ran and then it is just the interactive one with widget.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML, display
plt.rcParams["animation.html"] = "jshtml"
plt.ioff() #needed so the second time you run it you get only single plot
plt.style.use('seaborn-pastel')
fig = plt.figure()
ax = plt.axes(xlim=(0, 4), ylim=(-2, 2))
lineplot, = ax.plot([], [], lw=3)
def init():
lineplot.set_data([], [])
return lineplot, #return [lineplot] also works like in https://nbviewer.org/github/raphaelquast/jupyter_notebook_intro/blob/master/jupyter_nb_introduction.ipynb#pre-render-animations-and-export-to-HTML
def animate(i):
x = np.linspace(0, 4, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
lineplot.set_data([x], [y])
return [lineplot]
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
anim
A Jupyter notebook with those to options run and more fully explained is here; you'll note the widgets work there.
Click here to launch that notebook in an active, temporary Jupyter session served via MyBinder.org with the environment already having Python and everything needed to make the animation with a widget work in the notebook installed and working.
I'm trying to display a video from some arrays in an notebook in jupyter-lab. The arrays are produced at runtime. What method to display the images can deliver a (relatively) high framerate? Using matplotlib and imshow is a bit slow. The pictures are around 1.8 megapixel large.
Above some very small example to visualize what I want to achieve.
while(True): #should run at least 30 times per second
array=get_image() #returns RGBA numpy array
show_frame(array) #function I search for
The fastest way (to be used for debug purpose for instance) is to use matplotlib inline and the matplotlib animation package. Something like this worked for me
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import animation
from IPython.display import HTML
# np array with shape (frames, height, width, channels)
video = np.array([...])
fig = plt.figure()
im = plt.imshow(video[0,:,:,:])
plt.close() # this is required to not display the generated image
def init():
im.set_data(video[0,:,:,:])
def animate(i):
im.set_data(video[i,:,:,:])
return im
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=video.shape[0],
interval=50)
HTML(anim.to_html5_video())
The video will be reproduced in a loop with a specified framerate (in the code above I set the interval to 50 ms, i.e., 20 fps).
Please note that this is a quick workaround and that IPython.display has a Video package (you can find the documentation here) that allows you to reproduce a video from file or from an URL (e.g., from YouTube).
So you might also consider storing your data locally and leverage the built-in Jupyter video player.
I'm trying to create an animated histogram for work, using matplotlib.animation, but animation.FuncAnimation is not functioning properly : when using this code, that i found on the official documentation,
"""
A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
def animate(i):
print(i)
line.set_ydata(np.sin(x + i/10.0)) # update the data
return line,
# Init only required for blitting to give a clean slate.
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200),init_func=init,interval=25, blit=True)
plt.show()
I get as final result the graph created by init() function (an empty graph also), but no animate iterations. Furthermore, I tested other codes, which practically gave me the same result : i get the initialization, or the first frame, but not more. matplotlib and matplotlib.animation are installed, everything seems to be ok, except it doesn't work. Have someone an idea how to fix it ? (Thank you in advance :) !)
I had the same issue working with Jupyter notebook and I solved it by inserting the line
%matplotlib notebook
in the code.
It may be that IPython inside your Spyder is configured to automatically use the inline backend. This would show your plots inside the console as png images. Of course png images cannot be animated.
I would suggest not to use IPython but execute the script in a dedicated Python console. In Spyder, go to Run/Configure.. and set the option to new dedicated Python console.
I'm implementing an image viewer using matplotlib. The idea is that changes being made to the image (such as filter application) will update automatically.
I create a Figure to show the inital image and have added a button using pyQt to update the data. The data does change, I have checked, but the Figure does not. However, if after I've pressed the filter application button, I move the image using matplotlib's standard tool bar, the image is then updated.
I assume I'm doing something wrong when updating the image, but since the fact of moving it actually forces the update, it then shows the data change. I would like for this to happen when I press the button, though.
Below is some of the code. This is the initial figure initialization, which shows the original image:
self.observableFig = Figure((4.0, 4.0), dpi=100)
self.canvas = FigureCanvas(self.observableFig)
self.canvas.setParent(self.observableWindow)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.canvas.mpl_connect('button_press_event', self.on_click)
# Showing initial data on Window
self.observableFig.clear()
self.observableAxes = self.observableFig.add_subplot(1, 1, 1)
min, max = self.min, self.max
self.observableAxes.imshow(
self.data,
vmin=min,
vmax=max,
origin='lower'
)
And this is the event for when the button that changes the data is pressed:
self.observableAxes.imshow(self.data/2, origin='lower')
# plt.clf()
# plt.draw()
# plt.show()
I have tried draw(), show(), basically anything I've found on pyplot about this. I have also tried both with and without plt.ion() at the beginning, but it hasn't made a difference in this.
Thanks in advance.
The reason that nothing is updating is that you're trying to use pyplot methods for a figure that's not a part of the pyplot state machine. plt.draw() won't draw this figure, as plt doesn't know the figure exists.
Use fig.canvas.draw() instead.
Regardless, it's better to use fig.canvas.draw() that plt.draw(), as it's clear which figure you're drawing (the former draws one, the latter draws all, but only if they're tracked by pyplot).
Try something along these lines:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10,10))
# To make a standalone example, I'm skipping initializing the
# `Figure` and `FigureCanvas` and using `plt.figure()` instead...
# `plt.draw()` would work for this figure, but the rest is identical.
fig, ax = plt.subplots()
ax.set(title='Click to update the data')
im = ax.imshow(data)
def update(event):
im.set_data(np.random.random((10,10)))
fig.canvas.draw()
fig.canvas.mpl_connect('button_press_event', update)
plt.show()
As a result of a full day of trial and error, I'm posting my findings as a help to anyone else who may come across this problem.
For the last couple days, I've been trying to simulate a real-time plot of some radar data from a netCDF file to work with a GUI I'm building for a school project. The first thing I tried was a simple redrawing of the data using the 'interactive mode' of matplotlib, as follows:
import matplotlib.pylab as plt
fig = plt.figure()
plt.ion() #Interactive mode on
for i in range(2,155): #Set to the number of rows in your quadmesh, start at 2 for overlap
plt.hold(True)
print i
#Please note: To use this example you must compute X, Y, and C previously.
#Here I take a slice of the data I'm plotting - if this were a real-time
#plot, you would insert the new data to be plotted here.
temp = plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
plt.draw()
plt.pause(.001) #You must use plt.pause or the figure will freeze
plt.hold(False)
plt.ioff() #Interactive mode off
While this technically works, it also disables the zoom functions, as well as pan, and well, everything!
For a radar display plot, this was unacceptable. See my solution to this below.
So I started looking into the matplotlib animation API, hoping to find a solution. The animation did turn out to be exactly what I was looking for, although its use with a QuadMesh object in slices was not exactly documented. This is what I eventually came up with:
import matplotlib.pylab as plt
from matplotlib import animation
fig = plt.figure()
plt.hold(True)
#We need to prime the pump, so to speak and create a quadmesh for plt to work with
plt.pcolormesh(X[0:1], Y[0:1], C[0:1])
anim = animation.FuncAnimation(fig, animate, frames = range(2,155), blit = False)
plt.show()
plt.hold(False)
def animate( self, i):
plt.title('Ray: %.2f'%i)
#This is where new data is inserted into the plot.
plt.pcolormesh(X[i-2:i], Y[i-2:i], C[i-2:i])
Note that blit must be False! Otherwise it will yell at you about a QuadMesh object not being 'iterable'.
I don't have access to the radar yet, so I haven't been able to test this against live data streams, but for a static file, it has worked great thus far. While the data is being plotted, I can zoom and pan with the animation.
Good luck with your own animation/plotting ambitions!