Matplotlib animation too slow ( ~3 fps ) - python

I need to animate data as they come with a 2D histogram2d ( maybe later 3D but as I hear mayavi is better for that ).
Here's the code:
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
import time, matplotlib
plt.ion()
# Generate some test data
x = np.random.randn(50)
y = np.random.randn(50)
heatmap, xedges, yedges = np.histogram2d(x, y, bins=5)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
# start counting for FPS
tstart = time.time()
for i in range(10):
x = np.random.randn(50)
y = np.random.randn(50)
heatmap, xedges, yedges = np.histogram2d(x, y, bins=5)
plt.clf()
plt.imshow(heatmap, extent=extent)
plt.draw()
# calculate and print FPS
print 'FPS:' , 20/(time.time()-tstart)
It returns 3 fps, too slow apparently. Is it the use of the numpy.random in each iteration? Should I use blit? If so how?
The docs have some nice examples but for me I need to understand what everything does.

Thanks to #Chris I took a look at the examples again and also found this incredibly helpful post in here.
As #bmu states in he's answer (see post) using animation.FuncAnimation was the way for me.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def generate_data():
# do calculations and stuff here
return # an array reshaped(cols,rows) you want the color map to be
def update(data):
mat.set_data(data)
return mat
def data_gen():
while True:
yield generate_data()
fig, ax = plt.subplots()
mat = ax.matshow(generate_data())
plt.colorbar(mat)
ani = animation.FuncAnimation(fig, update, data_gen, interval=500,
save_count=50)
plt.show()

I suspect it is the use of np.histogram2d in each loop iteration. or that in each loop iteration of the for loop you are clearing and drawing a new figure. To speed things up you should create a figure once and just update the properties and data of the figure in a loop. Have a look through the matplotlib animation examples for some pointers on how to do this. Typically it involves calling matplotlib.pyploy.plot then, in a loop, calling axes.set_xdata and axes.set_ydata.
In your case however, take a look at the matplotlib animation example dynamic image 2. In this example the generation of data is separated from the animation of the data (may not be a great approach if you have lots of data). By splitting these two parts up you can see which is causing a bottleneck, numpy.histrogram2d or imshow (use time.time() around each part).
P.s. np.random.randn is a psuedo-random number generator. These tend to be simple linear generators which can generate many millions of (psuedo-)random numbers per second, so this is almost certainly not your bottleneck - drawing to screen is almost always a slower process than any number crunching.

Related

Updating plot in real time

I am giving data to a matrix (e.g. with shape 100x100) by the following code:
from random import randint
import matplotlib.pyplot as plt
import numpy as np
import random as rand
tab = np.eye(100, 100)
x = np.arange(0, 100, 1)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(100):
for j in range(100):
tab[i, j] = rand.randint(0, 254)
line1, = ax.plot(x, tab[i, :], 'r-')
line1.set_ydata(tab[i, j])
fig.canvas.draw()
fig.canvas.flush_events()
ax.lines.remove(line1)
I need to update matrix using loops and upgrade plot in the same time.
When loop with j ends, i-loop want to clear plot and start plotting again. Is it possible?
My result:
What I need:
After reading your comment i think i understood what you where trying to do
the reason you got those horizontal lines was that you're setting ydata again after plotting(to a constant so its like plotting a horizontal line)
consider the code below:
from random import randint
import matplotlib.pyplot as plt
import numpy as np
import random as rand
tab = np.eye(100, 100)
x = np.arange(0, 100, 1)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(100):
for j in range(100):
tab[i, j] = ((50-i/2)*(50-i/2)-(50-j)*(50-j))/100
for i in range(100):
line1, = ax.plot(x, tab[i, :], 'r-')
fig.canvas.draw()
fig.canvas.flush_events()
ax.lines.remove(line1)
I used another for to instantiate the tab map (since you're using sensor data I guess that is exactly what you're doing in your code because you need to read all of the data (at least the ones for the current cross section) to be able to plot the type of graph you want. this is equivalent to reading all of the data at the beginning and then starting to plot it)
(I also used simulated values instead of random values for the sake of testing)
if you want to draw the data AS THEY COME FROM THE SENSOR then you must define a function to get the data of the current cross section from the sensor and return an array. Idk the library you're using for the sensor but I'm assuming the scan functions are synchronous so the function will return exactly after the input is over making the whole thing pseudo-real time
from random import randint
import matplotlib.pyplot as plt
import numpy as np
import random as rand
x = np.arange(0, 100, 1)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(100):
data = READ_CURRENT_CROSS_SECTION()
line1, = ax.plot(x, data, 'r-')
fig.canvas.draw()
fig.canvas.flush_events()
ax.lines.remove(line1)
again, if plotting the data as the come from the sensor is your goal here it is going to depend a lot on the library you're using but except for all of that the problem with your code was that it was trying to plot while it was getting the data point by point which gives you insufficient data for plotting a cross section(hence the straight lines) (PS: there actually are some ways to pull it off like this but will be extremely slow!)
So either
write a function to scan the whole 2d area and return the whole map before you start plotting(which will be like my first code and the function i just said will replace lines 11-13). this takes away the real time feature but it will give you a beautiful animated plot in a short time
write a function to scan each cross section and return it as a 100 element array. which makes it kind of real time but i guess is harder to implement. This is like my second code but you have to define READ_CURRENT_CROSS_SECTION yourself

Matplotlib: animate plot_surface using ArtistAnimation [duplicate]

I am looking to create an animation in a surface plot. The animation has fixed x and y data (1 to 64 in each dimension), and reads through an np array for the z information. An outline of the code is like so:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_plot(frame_number, zarray, plot):
#plot.set_3d_properties(zarray[:,:,frame_number])
ax.collections.clear()
plot = ax.plot_surface(x, y, zarray[:,:,frame_number], color='0.75')
fig = plt.figure()
ax = plt.add_subplot(111, projection='3d')
N = 64
x = np.arange(N+1)
y = np.arange(N+1)
x, y = np.meshgrid(x, y)
zarray = np.zeros((N+1, N+1, nmax+1))
for i in range(nmax):
#Generate the data in array z
#store data into zarray
#zarray[:,:,i] = np.copy(z)
plot = ax.plot_surface(x, y, zarray[:,:,0], color='0.75')
animate = animation.FuncAnimation(fig, update_plot, 25, fargs=(zarray, plot))
plt.show()
So the code generates the z data and updates the plot in FuncAnimation. This is very slow however, I suspect it is due to the plot being redrawn every loop.
I tried the function
ax.set_3d_properties(zarray[:,:,frame_number])
but it comes up with an error
AttributeError: 'Axes3DSubplot' object has no attribute 'set_3d_properties'
How can I update the data in only the z direction without redrawing the whole plot? (Or otherwise increase the framerate of the graphing procedure)
There is a lot going on under the surface when calling plot_surface. You would need to replicate all of it when trying to set new data to the Poly3DCollection.
This might actually be possible and there might also be a way to do that slightly more efficient than the matplotlib code does it. The idea would then be to calculate all the vertices from the gridpoints and directly supply them to Poly3DCollection._vec.
However, the speed of the animation is mainly determined by the time it takes to perform the 3D->2D projection and the time to draw the actual plot. Hence the above will not help much, when it comes to drawing speed.
At the end, you might simply stick to the current way of animating the surface, which is to remove the previous plot and plot a new one. Using less points on the surface will significantly increase speed though.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
def update_plot(frame_number, zarray, plot):
plot[0].remove()
plot[0] = ax.plot_surface(x, y, zarray[:,:,frame_number], cmap="magma")
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
N = 14
nmax=20
x = np.linspace(-4,4,N+1)
x, y = np.meshgrid(x, x)
zarray = np.zeros((N+1, N+1, nmax))
f = lambda x,y,sig : 1/np.sqrt(sig)*np.exp(-(x**2+y**2)/sig**2)
for i in range(nmax):
zarray[:,:,i] = f(x,y,1.5+np.sin(i*2*np.pi/nmax))
plot = [ax.plot_surface(x, y, zarray[:,:,0], color='0.75', rstride=1, cstride=1)]
ax.set_zlim(0,1.5)
animate = animation.FuncAnimation(fig, update_plot, nmax, fargs=(zarray, plot))
plt.show()
Note that the speed of the animation itself is determined by the interval argument to FuncAnimation. In the above it is not specified and hence the default of 200 milliseconds. Depending on the data, you can still decrease this value before running into issues of lagging frames, e.g. try 40 milliseconds and adapt it depending on your needs.
animate = animation.FuncAnimation(fig, update_plot, ..., interval=40, ...)
set_3d_properties() is a function of the Poly3DCollection class, not the Axes3DSubplot.
You should run
plot.set_3d_properties(zarray[:,:,frame_number])
as you have it commented in your update function BTW, instead of
ax.set_3d_properties(zarray[:,:,frame_number])
I don't know if that will solve your problem though, but I'm not sure since the function set_3d_properties has no documentation attached. I wonder if you'd be better off trying plot.set_verts() instead.

How to generate a histogram animation with many values

The iteration update very slow, n+=3 for each time only but my data has 10000 elements. Like, It tries to update every single frame n=1,n=2,n=3.. but the hist function is really power consuming. I don't know if there are any way I could skip frames like from n=1 go straight to n=500 and to n=1000.
import matplotlib.animation as animation
import numpy as np
import matplotlib.pyplot as plt
n=10000
def update(curr):
if curr==n:
a.event_source.stop()
first_histogram.cla()
sec_histogram.cla()
thi_histogram.cla()
for_histogram.cla()
first_histogram.hist(x1[:curr], bins=np.arange(-6,2,0.5))
sec_histogram.hist(x2[:curr], bins=np.arange(-1,15,1))
thi_histogram.hist(x3[:curr], bins=np.arange(2,22,1))
for_histogram.hist(x4[:curr], bins=np.arange(13,21,1))
first_histogram.set_title('n={}'.format(curr))
fig=plt.figure()
gspec=gridspec.GridSpec(2,2)
first_histogram=plt.subplot(gspec[0,0])
sec_histogram=plt.subplot(gspec[0,1])
thi_histogram=plt.subplot(gspec[1,0])
for_histogram=plt.subplot(gspec[1,1])
a = animation.FuncAnimation(fig,update,blit=True,interval=1,repeat=False)
How can I make it faster ? Thank you!
There are several things to note here.
blit=True is not useful when clearing the axes in between. It would either not take effect, or you would get wrong tick labels on the axes.
It would only be useful if the axes limits do not change from frame to frame. However in a normal histogram, where more and more data is animated, this would necessarily need to be the case, else your bars either grow out of the axes, or you do not see the low numbers at the start. As an alternative, you could plot a normalized histogram (i.e. a density plot).
Also, interval=1 is not useful. You will not be able to animate 4 subplots with a 1 millisecond frame rate on any normal system. Matplotlib is too slow for that. However, consider that the human brain can usually not resolve framerates above some 25 fps, i.e. 40 ms, anyways. That's probably the frame rate to aim at (although matplotlib may not achieve that)
So a way to set this up is simply via
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x1 = np.random.normal(-2.5, 1, 10000)
def update(curr):
ax.clear()
ax.hist(x1[:curr], bins=np.arange(-6,2,0.5))
ax.set_title('n={}'.format(curr))
fig, ax = plt.subplots()
a = animation.FuncAnimation(fig, update, frames=len(x1), interval=40, repeat=False, blit=False)
plt.show()
If you feel like you want to arrive more quickly at the final number of items in the list, use less frames. E.g. for a 25 times faster animation, show only every 25th state,
a = animation.FuncAnimation(fig, update, frames=np.arange(0, len(x1)+1, 25),
interval=40, repeat=False, blit=False)
This code runs with a framerate of 11 fps (interval of ~85 ms), so it's slower than specified, which in turn means, we could directly set interval=85.
In order to increase the frame rate one may use blitting.
For that, you will need to not update the axes limits at all. To optimize further you may precompute all the histograms to show. Note however that the axes limits should then not change, so we set them at the beginning, which leads to a different plot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x1 = np.random.normal(-2.5, 1, 10000)
bins = np.arange(-6,2,0.5)
hist = np.empty((len(x1), len(bins)-1))
for i in range(len(x1)):
hist[i, :], _ = np.histogram(x1[:i], bins=bins)
def update(i):
for bar, y in zip(bars, hist[i,:]):
bar.set_height(y)
text.set_text('n={}'.format(i))
return list(bars) + [text]
fig, ax = plt.subplots()
ax.set_ylim(0,hist.max()*1.05)
bars = ax.bar(bins[:-1], hist[0,:], width=np.diff(bins), align="edge")
text = ax.text(.99,.99, "", ha="right", va="top", transform=ax.transAxes)
ani = animation.FuncAnimation(fig, update, frames=len(x1), interval=1, repeat=False, blit=True)
plt.show()
Running this code give me a framerate of 215 fps, (4.6 ms per frame), so we could set the interval to 4.6 ms.
Tested in python 3.10 and matplotlib 3.5.1
10000 samples creates a 40MB animation, which exceeds the 2MB limit for posting a gif.
The following animation example uses 500 samples, x1 = np.random.normal(-2.5, 1, 500)

Animating with matplotlib without animation function

Is there a way to animate a graph in matplotlib without resorting to the built in animation functions? I find them extremely awkward to use and feel it would be much simpler to just plot a point, wipe the graph, then plot the next point.
I envision something such as:
def f():
# do stuff here
return x, y, t
where each t would be a different frame.
I mean, I've tried stuff like using plt.clf(), plt.close() etc. but nothing seems to work.
It is sure possible to animate without FuncAnimation. The purpose of "the enivisioned function", however, is not really clear. In an animation, the time is the independent variable, i.e. for each time step you produce some new data to plot or similar. Therefore the function would take t as an input and give some data back.
import matplotlib.pyplot as plt
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for t in range(100):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
fig.canvas.draw()
plt.pause(0.1)
plt.show()
Also, it is not clear why you would want to avoid FuncAnimation. The same animation as above can be produced with FuncAnimation as follows:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
def update(t):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
ani = matplotlib.animation.FuncAnimation(fig, update, frames=100)
plt.show()
There is not much changed, you have the same number of lines, nothing really awkward to see here.
Plus you have all the benefits from FuncAnimation when the animation gets more complex, when you want to repeat the animation, when you want to use blitting, or when you want to export it to a file.
it is not clear why you would want to avoid FuncAnimation.
For very simple tests, where you want to check a situation deep inside a loop, it is not easy to set up an animation function.
For instance, I wanted to visualize what happens with this strange sort algorithm: https://arxiv.org/pdf/2110.01111.pdf. To my opinion, the simplest way to do it is:
import numpy as np
import matplotlib.pyplot as plt
def sort(table):
n = len(table)
for i in range (n):
for j in range (n):
if table[i] < table[j]:
tmp = table[i]
table[i] = table[j]
table[j] = tmp
plt.plot(table, 'ro')
plt.title(f"i {i} j {j}")
plt.pause(0.001)
plt.clf() # clear figure
return table
n = 50
table = np.random.randint(1,101,n)
sort(table)
```python
I agree that FuncAnimation is awkward to use (not pythonic at all). Actually I believe this function doesn't make too much sense. What is the advantage to have it?
Yes, it introduces an implicit loop that you do not have to write yourself. But the reader cannot fully control this loop and -unless he knows the syntax of the function in advance- he cannot even understand it. Personally I avoid FuncAnimation for reasons of clarity and versatility. Here's a minimal pseudocode example to do that:
fig=plt.figure("animation")
M=zeros((sizeX,sizeY)) # initialize the data (your image)
im=plt.imshow(M) # make an initial plot
########### RUN THE "ANIMATION" ###########################
while {some condition}:
M=yourfunction() # updates your image
im.set_array(M) # prepare the new image
fig.canvas.draw() # draw the image
plt.pause(0.1) # slow down the "animation"
Very simple and you can see what is happening in your code.

Matplotlib pyplot in real time

I have a while function that generates two lists of numbers and at the end I plot them using matplotlib.pyplot.
I'm doing
while True:
#....
plt.plot(list1)
plt.plot(list2)
plt.show()
But in order to see the progression I have to close the plot window.
Is there a way to refresh it with the new data every x seconds?
The most robust way to do what you want is to use matplotlib.animation. Here's an example of animating two lines, one representing sine and one representing cosine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
sin_l, = ax.plot(np.sin(0))
cos_l, = ax.plot(np.cos(0))
ax.set_ylim(-1, 1)
ax.set_xlim(0, 5)
dx = 0.1
def update(i):
# i is a counter for each frame.
# We'll increment x by dx each frame.
x = np.arange(0, i) * dx
sin_l.set_data(x, np.sin(x))
cos_l.set_data(x, np.cos(x))
return sin_l, cos_l
ani = animation.FuncAnimation(fig, update, frames=51, interval=50)
plt.show()
For your particular example, you would get rid of the while True and put the logic inside that while loop in the update function. Then, you just have to make sure to do set_data instead of making a whole new plt.plot call.
More details can be found in this nice blog post, the animation API, or the animation examples.
I think what you're looking for is the "animation" feature.
Here is an example
This example is a second one.

Categories