Matplotlib Scatterplot Animation with Time Stamp - python

Hello and thanks in advance for any tips and advice. I'm trying to create an animated scatterplot that changes dot color based on model results that I'm reading into python. The model results would be much easier to interpret if I could display an updating time stamp along with the animation.
I tried to incorporate the answer for to this post:
Matplotlib animating multiple lines and text, but I think the fact that I'm using a scatter dataset instead of lines complicates the way the data needs to be returned, and I'm not sure how to correct the problem.
This code alternately flashes the time step and the scatter animation, which makes for a distracting and useless visual.
# load result
testModel.addResult(testDye,type='dye')
xs = testModel.grid['x']
ys = testModel.grid['y']
zs = testModel.grid['z']
# graphing
fig = plt.figure()
ax = fig.add_subplot(111)
gcf = plt.gcf()
scat = ax.scatter(xs,ys)
timetext = gcf.text(0.2, 0.2, '', size = 14)
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return scat,
def init():
actDye = testModel.getResult(type='dye',layer=1,tIndex=0)
scat.set_array((actDye/1000)*100) #update colors of points
return scat,
ani = animation.FuncAnimation(fig,animate,np.arange(0,200),init_func=init, interval=500, blit=True)
plt.show()
I think that returning timetext along with scat, would fix the problem (like it did for the other poster), but I can't get the syntax right. Switching to this block of code gives me the error that the 'PathCollection' object is not iterable.
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return tuple(scat,) + (timetext,)
What should I be doing differently? Thanks!

Related

Why doesn't blitting work with animation of a scatter plot in matplotlib?

I am trying to create an animation containing a fixed sphere and a trajectory on the surface of the sphere, with a trail of the trajectory containing the last "windowSize" points in the trajectory.
Now, for the purposes of the code I will show here, I won't have an actual such trajectory, but rather just some random points changing each frame.
I am using matplotlib.animation.FuncAnimation. When I use the option blit=False, the animation works as expected. However, I would like to use blit=True to optimize performance.
When I do that, though, what happens is that nothing seems to happen in the animation, except that when I rotate the figure, then it shows an updated version of the figure (some number of frames ahead) and then freezes again.
The code below is based on this similar question.
Let me show the code I am using
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.animation
import pandas as pd
Np = 5000
windowSize = 1000
m = np.random.rand(Np, 3)
df = pd.DataFrame({ "x" : m[0:Np,0], "y" : m[0:Np,1], "z" : m[0:Np,2]})
def init_graph():
u, v = np.mgrid[0:2*np.pi:50j, 0:np.pi:50j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = np.cos(v)
ax.plot_surface(x, y, z, color="bisque", alpha=0.3)
return graph,
def update_graph(num):
if (num<windowSize):
graph._offsets3d = (df.x[0:num], df.y[0:num], df.z[0:num])
else:
graph._offsets3d = (df.x[(num-windowSize):num], df.y[(num-windowSize):num], df.z[(num-windowSize):num])
title.set_text('3D Test, time={}'.format(num))
return graph,
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_box_aspect((1,1,1))
title = ax.set_title('3D Test')
graph = ax.scatter(0, 0, 0)
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames=Np, init_func=init_graph, interval=200, blit=True, repeat=False)
plt.show()
m is an Np by 3 matrix, and each row represents a 3d point (in my real use case, each row is a point in a trajectory on the sphere surface, but for this demo I created m as random numbers).
I create a variable graph that contains a scatter plot, which I believe is an Artist. This is what I return from both the init_func and the updating func which are passed to FuncAnimation (as per the docs).
From what I read, you return an iterable of the Artists which will be updated in the animation. Thus I return a tuple of one element, graph,.
Now, in update_graph, the updating function for the animation, I am updating the scatter plot using graph._offsets3d, which I read in another question here on StackOverflow. I am not totally sure if this is the way to do it and I didn't find much information in the docs about whether to use this or one of the setting methods on the scatter plot.
Why doesn't blitting work with scatter plots?

Blit Behaviour in FuncAnimate -Want to keep previous data

I'm trying to animate a figure using matplotlib->FuncAnimate function. However, I'm having trouble understanding how Blit works. With each frame, I want to draw only the new data point on top of the old one. It says that using Blit it should automatically update only the values that changed. Thus, if I turn it on (blit=True) the previous data points should remain in my figure. But this is not the case. The previous data get deleted and the figure gets redraw from scratch.
In the documentation, it says that I have to return "iterable_of_artists" and the algorithm will know which data has changed. I want to just pass the new data and just plot on top of the old one. By the way, what is an "iterable_of_artists", is that just a list of objects that can be drawn? if someone could point me out to the definition, I would appreciate it.
Anyway, I have worked several base examples that show the odd behavior. In the first example, I'm turning Blit=True and drawing only the new data using the animate function. This in theory should draw on top of the old ones, but is not the case, only the new data is drawn.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
line, = ax.plot([], [], 'ro') #ax.plot will return a tupple
def init():
line.set_data(0, 50)
return line, #Return is not necessary when blit=False
def animate(frame):
x = frame
y = random.randint(0, 100)
line.set_data(x,y)
return line, #Return is not necessary when blit=False
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True, # Turning on Blit
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()
I was able to achieve my goal by tricking the FuncAnimate method. I can use the ax and plot in each frame the new data. If I do that, the old data remains and only the new data is drawn. However, I can do that with Blit=True or Blit=False, it has no effect. So, I'm so confused on how Blit works and what would be the correct way to plot only the new data without having to create a list with all the data to plot. Passing a large list will create a large variable in memory if I have a long set of data points. Here is my workaround but I'm not sure if this is the correct way to do it or if there is a better ways of using Blit=True and just redraw the new data.
import time
import random
import numpy
import matplotlib
import matplotlib.pyplot as pyplot
from matplotlib.animation import FuncAnimation
def livePlot():
fig, ax = pyplot.subplots(1,1)
ax = pyplot.axes(xlim=(0, 2), ylim=(0, 100))
def init():
ax.plot(0, 50, 'ro')
return []
def animate(frame):
x = frame
y = random.randint(0, 100)
ax.plot(x, y, 'ro') # plotting directly on the axis. This keeps the old data
return [] # fooling the blit algorithm with an empty stream
animation = FuncAnimation(
fig, animate,
init_func = init,
frames= [0.5, 1, 1.5, 2.0],
interval=1000,
repeat=False,
blit=True,
cache_frame_data = True)
pyplot.show()
if __name__ == "__main__":
livePlot()

Animating a function where function parameters change with time using FuncAnimation

I am trying to animate a one-dimensional function where the function inputs are same but function parameters are changing with time. The function I am trying to animate is
f(x)=sin(a* pi * x)/(b*x)+ (x-1)^4
Here the data to be plotted is same, but a, b are changing with every update.I am using python and matplotlib library. My initial attempt is as follows:
fig,ax = plt.subplots()
line, = ax.plot([],[])
def animate(i,func_params):
x = np.linspace(-0.5,2.5,num = 200)
a=func_params[i][0]
b=func_params[i][1]
y=np.sin(a*math.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
return line,
ani = animation.FuncAnimation(fig,animate,frames=len(visualize_pop),fargs=(visualize_func,),interval = 100,blit=True)
plt.show()
The above code is not plotting anything.
EDIT: Updated code based on comment.
Your problem is that with plot([],[]) you give matplotlib no data and therefore no way do determine the limits of the axes. Therefore it uses some default values which are way out of the range of the data you actually want to plot. Therefore you have two choices:
1) Set the limits to some values that will contain all your plotted data for all cases,
e.g.
ax.set_xlim([-0.5,2.5])
ax.set_ylim([-2,6])
2) Let ax compute the limits automatically each frame and re-scale the plot see here using these two commands within your animate function (note that this option only works correctly if you turn blitting off):
ax.relim()
ax.autoscale_view()
Here still a completely working version of your code (the commands for solution (1) are commented out and I changed some of the notations):
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig,ax = plt.subplots()
x = np.linspace(-0.5,2.5,num = 200)
line, = ax.plot([],[])
#ax.set_xlim([-0.5,2.5])
#ax.set_ylim([-2,6])
##assuming some parameters, because none were given by the OP:
N = 20
func_args = np.array([np.linspace(1,2,N), np.linspace(2,1,N)])
def animate(i,func_params):
a=func_params[0,i]
b=func_params[1,i]
y=np.sin(a*np.pi*x)/b*x + (x-1)**4
line.set_xdata(x)
line.set_ydata(y)
ax.relim()
ax.autoscale_view()
return line, ax
##blit=True will not update the axes labels correctly
ani = FuncAnimation(
fig,animate,frames=N, fargs=(func_args,),interval = 100 #, blit=True
)
plt.show()

How to add legend/label in python animation

I want to add a legend in a python animation, like the line.set_label() below. It is similar to plt.plot(x,y,label='%d' %*variable*).
However, I find that codes do not work here. The animation only shows lines changing but no label or legend available. How can I fix this problem?
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(0, 100))
N = 3
lines = [plt.plot([], [])[0] for _ in range(N)]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(i):
for j,line in enumerate(lines):
line.set_data([0, 2], [10*j,i])
line.set_label('line %d, stage %d'%(j,i))
return lines
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=20, blit=True)
plt.show()
you must return the legend in your animation function for it to be rendered.
Try this, instead :
legend = plt.legend()
def animate(i):
for j,line in enumerate(lines):
line.set_data([0, 2], [10*j,i])
line.set_label('line %d, stage %d'%(j,i))
legend.remove()
legend = plt.legend()
return lines + [legend]
You should also include the same code in your init function, init is used when resizing the window, otherwise the legend will disappear when resizing
I'm no expert on matplotlib at all, but in the Double Pendulum animation they display texts which changes, and this leads to some variations which can help you.
To get legends with the actual color of the lines, you can either change the initial setting lines to:
lines = [plt.plot([], [], label = 'line {}'.format(i))[0] for i in range(N)]
or add a line.set_label() to the for loop in the init() function. Both these seem to work as expected. At least if you add plt.legend(loc="upper left") right before plt.show().
However the set_label doesn't work within the animate() function, but according to the linked animation you can use specific text fields added to the animation, and that seems to work nicely. Add the following code after initialisation of lines:
texts = [ax.text(0.80, 0.95-i*0.05, '', transform=ax.transAxes) for i in range(N)]
And change animate() to be:
def animate(i):
for j in range(N):
lines[j].set_data([0, 2], [10*j,i]) #, label="hei {}".format(i))
texts[j].set_text('line %d, stage %d'%(j,i))
return lines
This places the text close to the upper right corner, and is updated for each animation step. Since the lines still have their legend displayed, you possibly simplify into one text only displaying the stage. But I leave the fine tuning of messages to your discretion.
Addendum: Extend Line2D
Another alternative could possibly be to extend lines.Line2D and use these lines in your animation, something similar to this article. Not sure if this would work with animation, but if you can't get the above to work, this might be worth a try.
You can try this minimum working example below. The handle of legend hlegend consist of handles of text htext, so we can update htext content in the hf_frame.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
N0 = 20
xdata = np.linspace(0, 2*np.pi, 100)
omega = np.linspace(1, 4, N0)
ydata = np.sin(omega[:,np.newaxis]*xdata)
hline0, = ax.plot(xdata, ydata[0], label=f'omega={omega[0]:.3f}')
hlegend = ax.legend(loc='upper right')
def hf_frame(ind0):
hline0.set_data(xdata, ydata[ind0])
label_i = f'{omega[ind0]:.3f}'
# hline0.set_label(label_i) #doesn't help
htext = hlegend.get_texts()[0]
htext.set_text(label_i)
return hline0,htext
ani = matplotlib.animation.FuncAnimation(fig, hf_frame, frames=N0, interval=200)
plt.show()

Matplotlib Animation for custom artist classes

Goal
Hi,
I am trying to animate a complex figure with several subplots and have started testing with the artist animation and the function animation methods.
For now, my goal is to have the subplot on the left show a moving colored line (not the problem) and the subplot on the right show an updated representation of a brain scan (the problem). Static, this looks something like this.
# Imports
import nilearn as nil
from nilearn import plotting as nlp
from matplotlib import pyplot as plt
window = np.arange(0,200-50)
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
a = ax.axvspan(window[0], window[0]+50, color='blue', alpha=0.5)
ay = fig.add_subplot(122)
b = nlp.plot_stat_map(nil.image.index_img(s_img, 0), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
Problem
As you can see, I am using nilearn for plotting the brain image. For some reason, the nilearn object from plot_stat_map does not have an attribute set_visible unlike the matplotlib object from axvspan.
So when I attempt a simple animation like so:
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
iml = list()
for i in np.arange(50):
a = ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
b = nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay)
iml.append((a,b))
ani = animation.ArtistAniTruemation(fig, iml, interval=50, blit=False,
repeat_delay=1000)
it crashes with the following error:
/home/surchs/Enthought/Canopy_64bit/User/lib/python2.7/site-packages/matplotlib/animation.pyc in _init_draw(self)
974 for f in self.new_frame_seq():
975 for artist in f:
--> 976 artist.set_visible(False)
977 # Assemble a list of unique axes that need flushing
978 if artist.axes not in axes:
AttributeError: 'OrthoSlicer' object has no attribute 'set_visible'
Makes sense, nilearn does maybe not conform to matplotlibs expectations. So I try the function animation method like so:
def show_things(i, window, ax, ay):
ax.axvspan(window[i], window[i]+50, color='blue', alpha=0.5)
nlp.plot_stat_map(nil.image.index_img(s_img, i), axes=ay, colorbar=False, display_mode='x', cut_coords=(0,))
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ax.set_xlim([0, 200])
ay = fig.add_subplot(122)
ani = animation.FuncAnimation(fig, show_things, interval=10, blit=False, fargs=(window, ax, ay))
Although I am not sure if I am using things correctly, this gives me an animated brain plot on the right. However, the plot on the left is now not updated but just drawn over. So instead of a sliding bar, I get an expanding color surface. Something like this:
Question
How do I
get the plot on the left to update (as opposed to overwrite) on each iteration when using the function animation method? I already tried the ax.cla() function in matplotlib but since this also clears all axis attributes (like xlim) this is not a solution for me. Are there altneratives?
get the plot on the right to work with the artist animation method even though the custom plotting class is obviously missing a crucial attribute.
Also, I am not sure if I am doing the whole implementation part right, so any advice on that front is also very appreciated.
I suspect you may need to clear the axvspan axis between plots with ax.cla() to get the correct left plot (N.B. probably should clear the right plot too). To get round the problem of missing attributes, I'd suggest extracting the data from the returned handle from nlp.plot_stat_map and plotting with matplotlib pcolormesh (or imshow). Another possibility is creating a child class and adding this method yourself. It may also be worth submitting a bug/feature request to nilearn if this should be present.
By the way, if you're only after a quick and easy plot, you can do a poor man's version of animation using interactive plots, as a minimal example,
import matplotlib.pyplot as plt
import numpy as np
import time
#Interactive plot
plt.ion()
#Setup figures
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(121)
ay = fig.add_subplot(122)
plt.show()
x = np.linspace(0,2*np.pi)
for i in range(10000):
print(i)
#Clear axes
ax.cla(); ay.cla()
#Update data
yx = np.sin(x+i*0.1)
yy = np.sin(2.*(x+i*0.1))
#Replot
ax.plot(x,yx)
ay.plot(x,yy)
#Pause to allow redraw
plt.draw()
plt.pause(0.01)

Categories

Resources