I have N=lots of 256x256 images (grayscales saved as numpy.ndarray with shape=(N, 256, 256)) and want to look at all of them by use of animation. I also want to add some label showing details related to each of the images, such as its index, its maximum value, etc. I'm using matplotlib, which I'm not familiar with.
There are a number of StackOverflow topics concerned with this exact problem (e.g. 1, 2, 4), as well as numerous tutorials (e.g. 3). I pieced together below attempts at solving the problem from these sources.
The two possibilities I have tried are using the matplotlib.animation classes FuncAnimation and ArtistAnimation. I'm not happy with my solutions because:
I have not been able to display and animate text information together with the images. I can display animated text on top of the images using axes.text but don't know how to put text next to the image.
I strongly dislike the FuncAnimation solution for aesthetic reasons (use of global variables, etc.)
I also want an animated colorbar. I think this is possible (somehow) with FuncAnimation but I don't see how it is possible with ArtistAnimation
ArtistAnimation gets slow since a large number of Artists (each picture) are required
# python 3.6
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, ArtistAnimation
images = np.random.rand(1000,256,256)
fig, ax = plt.subplots()
# ####################### Solution using ArtistAnimation ##################################################
# for much larger numbers of pictures this gets very slow
# How do I display information about the current picture as text next to the plot?
ims = []
for i in range(images.shape[0]):
ims.append([plt.imshow(images[i], animated=True)])
ani = ArtistAnimation(fig, ims, interval=250, blit=True, repeat_delay=5000)
plt.show()
# ####################### Solution using FuncAnimation ##################################################
# I don't like to use global variables in principle (but still want to know how to make this work).
# I can't figure out a way to display text while animating.
# Here I try to animate title and return it from update_figure (since it's an Artist and should update?!) but it has no effect.
nof_frames = images.shape[0]
i = 0
im = plt.imshow(images[0], animated=True)
# I do know that variables that aren't changed need not be declared global.
# However, I want to mark them and don't like accessing global variables in the first place.
def update_figure(frame, *frargs):
global i, nof_frames, ax, images, im
if i < nof_frames - 1:
i += 1
else:
i = 0
im.set_array(images[i])
ax.set_title(str(i)) # this has no effect
return im, ax
ani = FuncAnimation(fig, update_figure, interval=300, blit=True)
plt.show()
Related
I have a Kivy application that uses matplotlib to render figures in the application GUI. It means that the application creates a matplotlib Figure and get the Figure's buffer to display it in an Image widget.
For now, each time I want to update the figure, I recreate a Figure and draw everthing, calling refresh_gui_image.
import matplotlib.pyplot as plt
def draw_matplotlib_buffer(image, *elements):
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection()
ax.add_collection(patchCollection)
buffer = fig.canvas.print_to_buffer()
plt.close(fig)
return buffer
# imageWidget is a kivy Widget instance
def refresh_gui_image(imageWidget, image, *elements):
size = image.shape()
imageBuffer = draw_matplotlib_buffer(image, *elements)
imageWidget.texture.blit_buffer(imageBuffer, size=size, colorfmt='rgba', bufferfmt='ubyte')
imageWidget.canvas.ask_update()
In the code above, *elements represent multiple sets of objects. Typically, I have 2 to 4 sets which contains between 10 to 2000 objects. Each objects is represented with a patch, and each set is a PatchCollection on the Figure.
It works very well. With the current code, every patch is redrawn each time refresh_gui_image is called. When the sets becomes bigger (like 2000) objects, the update is too slow (few seconds). I want to make a faster rendering with matplotlib, knowing that some of the sets do not have to be redrawn, and that the image stays in the background, and do not have to be redrawn either.
I know blitting and animated artists can be used, this is what I tried, following this tutorial of the matplotlib documentation:
import matplotlib.pyplot as plt
# fig and ax are now global variable
# bg holds the background that stays identical
fig = None
ax = None
bg = None
def init_matplotlib_data(image, *elements):
global fig, ax, bg
fig = plt.figure(figsize=(5,5), dpi=200)
ax = plt.Axes([0, 0, 1, 1])
ax.set_axis_off()
fig.add_axis(ax)
ax.imshow(image)
fig.canvas.draw() # I don't want a window to open, just want to have a cached renderer
bg = fig.canvas.copy_from_bbox(fig.bbox)
for elem in elements:
# Suppose such a function exists and return a matplotlib.collection.PatchCollection
patchCollection = elem.get_collection(animated=True)
patchCollection.set_animated(True)
ax.add_collection(patchCollection)
def draw_matplotlib_buffer(image, *artists_to_redraw):
global fig, ax, bg
fig.canvas.restore_region(bg)
for artist in artists_to_redraw:
ax.draw_artist(artist)
fig.canvas.blit(fig.bbox)
buffer = fig.canvas.print_to_buffer()
return buffer
I call init_matplotlib_data once, and the refresh_gui_image as many time as I need, with artists I need to update. The point is that I correctly get my image background, but I cannot succeed to get the patches collections on the buffer returned by fig.canvas.print_to_buffer(). I unset the animated flag of the collection and this time they appear correctly. It seems to me, after some tests that ax.draw_artist() and fig.canvas.blit() have no effect. Another behavior I do not understand is that event if I pass animated=True to ax.imshow(image), the image is still drawn.
Why does the ax.draw_artist and fig.canvas.blit functions does not update the buffer returned by fig.canvas.print_to_buffer as expected ?
Apparently, blitting is a particular feature meant for GUI. Even thought the Agg backend support blitting, it does not mean that blitting can be used solely with it.
I came up with a solution where I store every artist I want to draw, and change their data whenever I need. I then use fig.canvas.print_to_buffer(), I am not sure what it does exactly, but I think the figure is fully redrawn. It is probably not as fast as what blitting can do, but it has the advantage to not reallocate and recreate every artists for each update. One can also remove artists from the canvas by calling the remove() method of an artist, and put it again with ax.add_artist(..).
I think this solution answer my question, since it is the fastest solution to have dynamic plotting with matplotlib while dumping the canvas into a buffer.
I'm trying to create an animation which shows multiple particles moving around.
If I have one particle with one array giving the positions of that particle in each step of the animation, I get it to work (mostly thanks to extensive help from other answers I found here on stackoverflow).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
positions = np.array([[2,2],[3,3],[4,4]])
def init():
scatterplot.set_offsets([[], []])
return [scatterplot]
def update(i, scatterplot, positions):
scatterplot.set_offsets(positions[i])
return [scatterplot]
fig = plt.figure()
scatterplot = plt.scatter([], [], s=100)
plt.xlim(0,5)
plt.ylim(0,5)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(scatterplot, positions), interval=1000, frames=3,
blit=True, repeat=True)
plt.show()
But I cannot figure out how to add more particles to the same animation.
Let's say I want to add a second particle with positions
positions2 = np.array([[2,1][3,2][4,3]])
and have it move around in the same scatter plot, how do I manage that?
I'm a matplotlib newbie, and have been googling furiously to no avail, will be very grateful for any help :)
EDIT:
I figured it out eventually, just a matter of formatting the data correctly.
positions = np.array([[[2,2],[2,1]],[[3,3],[3,2]],[[4,4],[4,3]]])
Where the array contains all the positions in step one, then all the positions in step two etc. works.
I'd prefer to get one color pr moving point, to keep track of them, but at least it works now.
I figured it out eventually, just a matter of formatting the data correctly.
positions = np.array([[[2,2],[2,1]],[[3,3],[3,2]],[[4,4],[4,3]]])
Where the array contains all the positions in step one, then all the positions in step two etc. works.
I'd prefer to get one color pr moving point, to keep track of them, but at least it works now.
I am trying to follow the basic animation tutorial located here and adapting it to display an already computed dataset instead of evaluating a function every frame, but am getting stuck. My dataset involves XY coordinates over time, contained in the lists satxpos and satypos I am trying to create an animation such that it traces a line starting at the beginning of the dataset through the end, displaying say 1 new point every 0.1 seconds. Any help with where I'm going wrong?
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
Code here creates satxpos and satypos as lists
fig = plt.figure()
ax = plt.axes(xlim=(-1e7,1e7), ylim = (-1e7,1e7))
line, = ax.plot([], [], lw=2)
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(satxpos[i], satypos[i])
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames = len(satxpos), interval = 1, blit=True)
Edit: The code runs without errors, but generates a blank plot window with no points/lines displayed and nothing animates. The dataset is generated correctly and views fine in a static plot.
In order to "trace a line starting at the beginning of the dataset through the end" you would index your arrays to contain one more element per timestep:
line.set_data(satxpos[:i], satypos[:i])
(Note the :!)
Everything else in the code looks fine, such that with the above manipulation you should get and extending line plot. You might then want to set interval to something greater than 1, as that would mean 1 millesecond timesteps (which might be a bit too fast). I guess using interval = 40 might be a good start.
Your code looks correct! So long as satxpos and satypos are both configured and initialized properly, I believe everything else is valid!
One part of the code you do not show in your question is the invocation of the anim.save() and plt.show() functions, which are both necessary for your code to work (as per the tutorial you shared!)
You would therefore need to add something like:
anim.save('basic_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()
to the end of your code to create the animation (and show it, I presume)!
Hope it helps!
Source - Matplotlib Animation Tutorial
I saw you mentioned "the parts that generate satxpos and satypos do create valid datasets. I can view those as a static plot just fine". But my guess is still the problem originated from your satxpos and satypos.
One way you can trouble shoot is to replace your two functions and animation code with line.set_data(satxpos[i], satypos[i]). Set i as 0, 1, ... and see if you can see the plot. If not, your satxpos and satypos are not as valid as you claimed.
As an example, a valid satxpos and satypos can be like this:
x = np.array([np.linspace(-1e7, 1e7, 1000)])
i = 200
satxpos = x.repeat(i, axis=0)
satypos = np.sin(2 * np.pi * (satxpos - 0.01 * np.arange(i).reshape(-1, 1).repeat(satxpos.shape[1], axis=1)))
satypos *= 1e7 / 2
This works with the code you provided thus indicating the code you've shown us is fine.
Edit in response to comments:
If your satxpos and satypos are just np.linespace, the animation loop will get just one point with (satxpos[i], satypos[i]) and you won't see the point on the plot without a setting like marker='o'. Therefore, you see nothing in your animation.
I wrote the following code based on the matplotlib site example.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
nFreqs = 1024
nFFTWindows = 512
viewport = np.ones((nFreqs, nFFTWindows))
im = plt.imshow(viewport, animated=True)
def updatefig(*args):
global viewport
print viewport
viewport = np.roll(viewport, -1, axis=1)
viewport[:, -1] = 0
im.set_array(viewport)
return im,
ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True)
plt.show()
Before changing the animation works, but now it doesn't. I expected it to start with a purple plot, which slowly turns yellow from the right edge to the left. The viewport variable does update correctly (checked it with print in my function).
I get the static image (all ones, like it was initially):
Where did I go wrong here?
The problem is you are defining a plot initially with a single colour (1.0) so the colour range is set to this. When you update the figure, the range of colours is 1.0 +- some small value so you don't see the change. You need to set the colour range to between one and zero with vmin/vmax arguments as follows:
im = plt.imshow(viewport, animated=True, vmin=0., vmax=1.)
The rest of the code stays the same and this should work as expected. Another alternative is to add the call,
im.autoscale()
after im.set_array(viewpoint) to force the colour range to be updated each time.
The imshow plot is initialized with one single value (1 in this case), so any value normalized to the range between 1 and 1 becomes the same color.
In order to change this, you may
initiate the imshowplot with limits for the color (vmin=0, vmax=1).
initiate the imshow plot with a normalization instance
norm = matplotlib.colors.Normalize(vmin=0, vmax=1)
im = plt.imshow(arr, norm=norm)
Set the limits afterwards using im.set_clim(0,1).
Preferences > IPython Console > Graphics > Backend and change it from "Inline" to "Automatic"
Do not forget to restart you IDE (Spyder, PyCharm, etc.) after applying above change.
Cheers
:)
Im trying to save a GIF with the evolucion of some waves in 2d using pcolormesh (using surface or wireframe would also be ok).
This has been my aproach so far:
set the quadmesh to plot in polar coordinates:
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation as FuncAnimation
pi=np.pi
rmax=6.
r=2*np.linspace(0,np.sqrt(rmax*.5),100)**2
phi=np.linspace(0,2*pi,80)
R, P = np.meshgrid(r, phi)
X, Y = R*np.cos(P), R*np.sin(P)
set the figure and functions for the animation:
count is the amount of frames i have.
Z is a count*2D-array with the values i want to plot.
(it has the sum of some fourier like series)
fig, ax = pyplot.subplots()
def anim_I(count,r,phi):
anim=np.zeros((count,len(phi), len(r)))
for i in range(count):
anim[i,:,:]=coef_transf(final_coefs[i,:,:,:,0],r,phi)**2
return anim
Z=anim_I(count,r,phi)
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
ax.pcolormesh(X, Y,Z[0,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
pyplot.colorbar()
anim = FuncAnimation(fig, animate, frames = range(0,count,7), blit = False)
i don't really need to see it live, so i just save a gif.
anim.save('%d_%d_%d-%d.%d.%d-2dgif.gif' %(localtime()[0:6]), writer='imagemagick')
pyplot.close()
While this works, it can take to an hour to make the gif of a even a hundred frames.
I wan't to know what would be the correct way to do this so it could be usable.
I have seen the other post in this regard, but i couldn't get the code working, or it would be just as inneficient.
You could try to write
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=plot.set_array(Z[i,:,:].ravel())
return plot,
instead of
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
This does not create a new object every time you call the animate funtion. Instead it changes the image of object that was already created.
However, the set_array method seems to need a flattened array, hence the .ravel().
This only produces the right image if you set the shading option of the pcolormap function to shading='gouraud'.
I don't know why, unfortunatelly, it seems to have to do with the sorting of the array.
I hoped, that helped a little.
I suggest inserting a
pyplot.clf()
at the beginning of your animate(i) function. This will start each frame with a blank figure. Otherwise, I suspect the plot will not be cleared, and the long time is due to actually plotting all previous frame below the new one.