matplotlib animation doesn't animate with imshow - python

I'm trying to animate a few simple subplots with imshow but there's apparently an issue.
This is a small demo of my problem:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
fig,axs=plt.subplots(2,5,figsize=(10,4))
imgs=[]
for row in axs:
for col in row:
col.set_xlim(4.5,-.5)
col.set_ylim(4.5,-.5)
col.set_xticks([])
col.set_yticks([])
#line A
imgs.append(col.imshow([[]],animated=1))
#freezes the animation([[]] is just a placeholder)
#line B
#imgs.append(col.imshow(np.arange(25).reshape((5,5)),animated=1))
#animation does work if the above line is used instead
def func(frm):
for i in range(10):
imgs[i].set_array(np.arange(25).reshape(5,5)*np.log10(frm+1))
return imgs
anim=animation.FuncAnimation(fig,func,10,interval=100)
plt.show()
If I use line A, the animation freezes as if func is not executed (while actually it is), raising no errors. If line B is used instead, the animation works. Am I missing something about imshow animating?

Animation from multiple imshow can be created by ArtistAnimation object using multiple axes generated by subplots command.
from numpy import random
from matplotlib import animation
import matplotlib.pyplot as plt
img_lst_1 = [random.random((368,1232)) for i in range(10)] # Test data
img_lst_2 = [random.random((368,1232)) for i in range(10)] # Test data
fig, (ax1, ax2) = plt.subplots(2,1)
frames = [] # store generated images
for i in range(len(img_lst_1)):
img1 = ax1.imshow(img_lst_1[i], animated=True)
img2 = ax2.imshow(img_lst_2[i], cmap='gray', animated=True)
frames.append([img1, img2])
ani = animation.ArtistAnimation(fig, frames, interval=500, blit=True,
repeat_delay=1000)
ani.save('movie_example.mp4')
Code output:

Related

Animate labels using FuncAnimation in Matplotlib

I am not able to make (animated) labels using FuncAnimation from matplotlib. Please find below a minimal code that I made. ax.annotate has no effect at all - the animation itself works though. What can I change to get animated labels/titles, which are different for each frame?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
fig.clear()
steps = 10
data = np.random.rand(20,20,10)
imagelist = [data[:,:,i] for i in range(steps) ]
im = plt.imshow(imagelist[0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
# does not work
ax.annotate("Frame: %d " % steps,(0.09,0.92),xycoords ='figure fraction')
def updatefig(j):
im.set_array(imagelist[j])
return [im]
ani = animation.FuncAnimation(fig, updatefig, frames=range(steps), interval=200, blit=True)
plt.show()
Two problems overall:
The annotation text never gets updated in updatefig()
The canvas gets cleared+blitted, which wipes out annotations
Five steps to resolve:
Remove fig.clear() to preserve annotations
Save the initial annotation's handle
Update the annotation's text in updatefig()
Include the annotation in the return of updatefig()
Set blit=False to preserve annotations
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
#1 do NOT call fig.clear()
steps = 10
data = np.random.rand(20, 20, steps)
im = plt.imshow(data[:, :, 0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
#2 annotate frame 0 and save handle
annot = ax.annotate('Frame: 0', (0.09, 0.92), xycoords='figure fraction')
def updatefig(j):
im.set_array(data[:, :, j])
#3 update annotation text
annot.set_text(f'Frame: {j}')
#4 include annotation when returning
return im, annot
#5 set blit=False
anim = animation.FuncAnimation(fig, updatefig, frames=steps, blit=False)

How to make a Matplotlib animated violinplot?

I am trying to animate a violinplot, so I have started off with something I think should be very basic, but it is not working. I think the problem is that violinplot doesn't accept set_data, but I don't otherwise know how to pass the changing data to violinplot. For this example I would like a plot where the mean slowly shifts to higher values. If I am barking up the wrong tree, please advise on a code which does work to animate violinplot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
data = np.random.rand(100)
def animate(i):
v.set_data(data+i) # update the data
return v
v = ax.violinplot([])
ax.set_ylim(0,200)
v_ani = animation.FuncAnimation(fig, animate, np.arange(1, 200),
interval=50, blit=True)
Indeed, there is no set_data method for the violinplot. The reason is probably, that there is a lot of calculations going on in the background when creating such a plot and it consists of a lot of different elements, which are hard to update.
The easiest option would be to simply redraw the violin plot and not use blitting.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
data = np.random.normal(loc=25, scale=20, size=100)
def animate(i, data):
ax.clear()
ax.set_xlim(0,2)
ax.set_ylim(0,200)
data[:20] = np.random.normal(loc=25+i, scale=20, size=20)
np.random.shuffle(data)
ax.violinplot(data)
animate(0)
v_ani = animation.FuncAnimation(fig, animate, np.arange(1, 200),
fargs=(data,), interval=50, blit=False)
plt.show()

Plotting 3D trajectory from CSV data using matplotlib

I'm trying to plot a 3D trajectory of a vehicle that comes from a CSV file, plotting is easy, I want to make the animation, actually a "replay" of the movements. I based my code from this example (http://matplotlib.org/examples/animation/simple_3danim.html) and then just modify it to only plot one line and to read the data from a CSV file being read by pandas, the code looks like this:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
import pandas as pd
def update_lines(num, data, line):
# NOTE: there is no .set_data() for 3 dim data...
x = data['x'].values[num]
y = data['y'].values[num]
z = data['z'].values[num]
line[0].set_data(x,y)
line[0].set_3d_properties(z)
print z
return line
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Reading the data from a CSV file using pandas
data = pd.read_csv('data.csv',sep=',',header=0)
# Creating fifty line objects.
# NOTE: Can't pass empty arrays into 3d version of plot()
x = np.array([0])
y = np.array([0])
z = np.array([0])
line = ax.plot(x, y, z)
# Setting the axes properties
ax.set_xlim3d([0.0, 3.0])
ax.set_xlabel('X')
ax.set_ylim3d([0.0, 3.0])
ax.set_ylabel('Y')
ax.set_zlim3d([0.0, 2.0])
ax.set_zlabel('Z')
ax.set_title('3D Test')
# Creating the Animation object
line_ani = animation.FuncAnimation(fig, update_lines, len(data), fargs=(data, line),
interval=10, blit=False)
plt.show()
I print the z just to see if the data is being iterated correctly, but all I get is a white plot like this:
Plot showing absolutely nothing.
at least, there are two issues with your code:
the way of how data is build
length of frames per second
here is the modified working example, please take a look how data variable
was arranged:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
import pandas as pd
from sys import exit
def update_lines(num, data, line):
# NOTE: there is no .set_data() for 3 dim data...
line.set_data(data[0:2, :num])
line.set_3d_properties(data[2, :num])
return line
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Reading the data from a CSV file using pandas
repo = pd.read_csv('data.csv',sep=',',header=0)
data = np.array((repo['x'].values, repo['y'].values, repo['z'].values))
print data.shape[1]
#exit()
# Creating fifty line objects.
# NOTE: Can't pass empty arrays into 3d version of plot()
line = ax.plot(data[0, 0:1], data[1, 0:1], data[2, 0:1])[0]
# Setting the axes properties
ax.set_xlim3d([-2.0, 2.0])
ax.set_xlabel('X')
ax.set_ylim3d([-2.0, 2.0])
ax.set_ylabel('Y')
ax.set_zlim3d([-2.0, 2.0])
ax.set_zlabel('Z')
ax.set_title('3D Test')
# Creating the Animation object
line_ani = animation.FuncAnimation(fig, update_lines, data.shape[1], fargs=(data, line), interval=50, blit=False)
plt.show()
you can watch the beauty of that flight just being tracked

LineCollection animation when updating the frames in a loop

The following code supposed to do the same thing here where lines colours are updated during an animation. However, the colour is not being updated. Which part of this code is wrong?
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines = []
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims = []
for i in range(100):
colors = np.random.random(len(lines))
col.set_array(colors)
ims.append([ax.add_collection(col)])
ani = animation.ArtistAnimation(fig, ims, interval=200, blit=True,repeat_delay=10000)
plt.show()
The output I get from the above code is below
You need to draw the changed artist (your LineCollection) just after the line
col.set_array(colors)
You need to do this because the LineCollection was first added to the axes with the line
ax.add_collection(col)
and then it is changed in the loop. You must then update the figure. The simplest way to do this is by calling plt.draw() just before the line
im=ax.add_collection(col)
This however replots everything, and slows done a lot the animation. The solution is to draw first all the rest with a call to plt.draw() before the loop, and then updating only the changed artist with a call to ax.draw_artist(col). The resulting code looks like this
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines=[]
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims=[]
plt.draw()
for i in range(100):
col = LineCollection(lines,cmap=plt.cm.gray)
colors = np.random.random(len(lines))
col.set_array(colors)
ax.draw_artist(col)
im=ax.add_collection(col)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=25, blit=True,repeat_delay=10000)
plt.show()
It is less tricky to do the same thing using FuncAnimation(), see an example here, but you said you don't want to use it for some reasons.
I found a solution but its so dumb. I have to create the same package of lines again:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines=[]
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims=[]
for i in range(100):
col = LineCollection(lines,cmap=plt.cm.gray)
colors = np.random.random(len(lines))
col.set_array(colors)
im=ax.add_collection(col)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=25, blit=True,repeat_delay=10000)
plt.show()
I would be happy if somebody gives a me a solution without renewing the lines but only updating the colours.
According to the documentation of "LineCollection" we can set the individual color of each segment by using the method "set_color" and providing a sequence of rgba tupels:
c : color or list of colors
Single color (all patches have same color), or a
sequence of rgba tuples; if it is a sequence the patches will
cycle through the sequence.
The following code should work properly. It is using FuncAnimation though.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines = []
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
def update(num, collection):
colors = [(c, c, c) for c in np.random.random(len(lines))]
collection.set_color(colors)
return collection,
ani = animation.FuncAnimation(fig, update, 100, fargs=[col], interval=25, blit=True)
plt.show()

Matplotlib FuncAnimation only draws one frame

I am trying to do an animation using the FuncAnimation module, but my code only produces one frame and then stops. It seems like it doesn't realize what it needs to update. Can you help me what went wrong?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.linspace(0,2*np.pi,100)
def animate(i):
PLOT.set_data(x[i], np.sin(x[i]))
print("test")
return PLOT,
fig = plt.figure()
sub = fig.add_subplot(111, xlim=(x[0], x[-1]), ylim=(-1, 1))
PLOT, = sub.plot([],[])
animation.FuncAnimation(fig, animate, frames=len(x), interval=10, blit=True)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.linspace(0,2*np.pi,100)
fig = plt.figure()
sub = fig.add_subplot(111, xlim=(x[0], x[-1]), ylim=(-1, 1))
PLOT, = sub.plot([],[])
def animate(i):
PLOT.set_data(x[:i], np.sin(x[:i]))
# print("test")
return PLOT,
ani = animation.FuncAnimation(fig, animate, frames=len(x), interval=10, blit=True)
plt.show()
You need to keep a reference to the animation object around, otherwise it gets garbage collected and it's timer goes away.
There is an open issue to attach a hard-ref to the animation to the underlying Figure object.
As written, your code well only plot a single point which won't be visible, I changed it a bit to draw up to current index

Categories