fast way to display video from arrays in jupyter-lab - python

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.

Related

How to save a matplotlib.pause animation to video?

I have an animation created with plt.pause as below...
import matplotlib.pyplot as plt
for i in range(10):
plt.scatter(0, i)
plt.pause(0.01)
plt.show()
How can I save this as a video file, e.g. a .mp4 file?
First, I have to redefine the plotting as a function with the plotted point given by a frame i. plt.pause() can be replaced by matplotlib.animate. Finally, I used a package suggested here to get the conversion to .mp4 format. You will need to install MoviePy:
pip install MoviePy
and then I just animate the function with matplotlib.animate into a .gif format before sending it to MoviePy. You'll need to figure out what directory you want this all to happen in. I'm just using the current working directory.
Here is the code:
import matplotlib.pyplot as plt
import matplotlib.animation as ani
import os
import moviepy.editor as mp
frames=20
fig = plt.figure()
ax = plt.gca()
def scatter_ani(i=int):
plt.scatter(0, i)
name = r"\test.gif"
path = str(os.getcwd())
anim = ani.FuncAnimation(fig, scatter_ani, frames = frames, interval=50)
anim.save(path+name)
clip = mp.VideoFileClip(path+name)
clip.write_videofile(path+r"\test.mp4")
The gif is then
To tweak the length of the animation, check out the docs for matplotlib.animate. For example, if you want to clear all of the previous data-points and show only the dot moving up, then you need to clear the axes in the function, and bound the y-axis so you see the motion ie
def scatter_ani(i=int):
ax.clear()
ax.set_ylim(0, frames+1)
plt.scatter(0, i)
to get:

Suppress display of final frame in matplotlib animation in jupyter

I am working on a project that involves generating a matplotlib animation using pyplot.imshow for the frames. I am doing this in a jupyter notebook. I have managed to get it working, but there is one annoying bug (or feature?) left. After the animation is created, Jupyter shows the last frame of the animation in the output cell. I would like the output to include the animation, captured as html, but not this final frame. Here is a simple example:
import numpy as np
from matplotlib import animation
from IPython.display import HTML
grid = np.zeros((10,10),dtype=int)
fig1 = plt.figure(figsize=(8,8))
ax1 = fig1.add_subplot(1,1,1)
def animate(i):
grid[i,i]=1
ax1.imshow(grid)
return
ani = animation.FuncAnimation(fig1, animate,frames=10);
html = HTML(ani.to_jshtml())
display(html)
I can use the capture magic, but that suppresses everything. This would be OK, but my final goal is to make this public, via binder, and make it as simple as possible for students to use.
I have seen matplotlib animations on the web that don't seem to have this problems, but those used plot, rather than imshow, which might be an issue.
Any suggestions would be greatly appreciated.
Thanks,
David
That's the answer I got from the same thing I was looking for in 'jupyter lab'. Just add plt.close().
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML
grid = np.zeros((10,10),dtype=int)
fig1 = plt.figure(figsize=(8,8))
ax1 = fig1.add_subplot(1,1,1)
def animate(i):
grid[i,i]=1
ax1.imshow(grid)
return
ani = animation.FuncAnimation(fig1, animate,frames=10);
html = HTML(ani.to_jshtml())
display(html)
plt.close() # update

Display animated sequence of images without blocking the interactive terminal

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.

animating image stack with vispy

I'm trying to migrate from MATLAB to Python and one of the things I frequently rely on during development in Matlab is the ability to rapidly visualize slices of a datacube by looping through layers and calling drawnow, e.g.
tst = randn(1000,1000,100);
for n = 1:size(tst, 3)
imagesc(tst(:,:,n));
drawnow;
end
When I tic/toc this in MATLAB it shows that the figure is updating at about 28fps. In contrast, when I try to do this using matplotlib's imshow() command this runs at a snails pace in comparison, even using set_data().
import matplotlib as mp
import matplotlib.pyplot as plt
import numpy as np
tmp = np.random.random((1000,1000,100))
myfig = plt.imshow(tmp[:,:,i], aspect='auto')
for i in np.arange(0,tmp.shape[2]):
myfig.set_data(tmp[:,:,i])
mp.pyplot.title(str(i))
mp.pyplot.pause(0.001)
On my computer this runs at about 16fps with the default (very small) scale, and if I resize it to be larger and the same size as the matlab figure it slows down to about 5 fps. From some older threads I saw a suggestion to use glumpy and I installed this along with all of the appropriate packages and libraries (glfw, etc.), and the package itself works fine but it no longer supports the easy image visualization that was suggested in a previous thread.
I then downloaded vispy, and I can make an image with it using code from this thread as a template:
import sys
from vispy import scene
from vispy import app
import numpy as np
canvas = scene.SceneCanvas(keys='interactive')
canvas.size = 800, 600
canvas.show()
# Set up a viewbox to display the image with interactive pan/zoom
view = canvas.central_widget.add_view()
# Create the image
img_data = np.random.random((800,800, 3))
image = scene.visuals.Image(img_data, parent=view.scene)
view.camera.set_range()
# unsuccessfully tacked on the end to see if I can modify the figure.
# Does nothing.
img_data_new = np.zeros((800,800, 3))
image = scene.visuals.Image(img_data_new, parent=view.scene)
view.camera.set_range()
Vispy seems very fast and this looks like it will get me there, but how do you update the canvas with new data? Thank you,
See ImageVisual.set_data method
# Create the image
img_data = np.random.random((800,800, 3))
image = scene.visuals.Image(img_data, parent=view.scene)
view.camera.set_range()
# Generate new data :
img_data_new = np.zeros((800,800, 3))
img_data_new[400:, 400:, 0] = 1. # red square
image.set_data(img_data_new)

Animating a Quadmesh from pcolormesh with matplotlib

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!

Categories