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
Related
I'm hoping to find a way to optimise the following situation. I have a large contour plot created with imshow of matplotlib. I then want to use this contour plot to create a large number of png images, where each image is a small section of the contour image by changing the x and y limits and the aspect ratio.
So no plot data is changing in the loop, only the axis limits and the aspect ratio are changing between each png image.
The following MWE creates 70 png images in a "figs" folder demonstrating the simplified idea. About 80% of the runtime is taken up by fig.savefig('figs/'+filename).
I've looked into the following without coming up with an improvement:
An alternative to matplotlib with a focus on speed -- I've struggled to find any examples/documentation of contour/surface plots with similar requirements
Multiprocessing -- Similar questions I've seen here appear to require fig = plt.figure() and ax.imshow to be called within the loop, since fig and ax can't be pickled. In my case this will be more expensive than any speed gains achieved by implementing multiprocessing.
I'd appreciate any insight or suggestions you might have.
import numpy as np
import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
import time, os
def make_plot(x, y, fix, ax):
aspect = np.random.random(1)+y/2.0-x
xrand = np.random.random(2)*x
xlim = [min(xrand), max(xrand)]
yrand = np.random.random(2)*y
ylim = [min(yrand), max(yrand)]
filename = '{:d}_{:d}.png'.format(x,y)
ax.set_aspect(abs(aspect[0]))
ax.set_xlim(xlim)
ax.set_ylim(ylim)
fig.savefig('figs/'+filename)
if not os.path.isdir('figs'):
os.makedirs('figs')
data = np.random.rand(25, 25)
fig = plt.figure()
ax = fig.add_axes([0., 0., 1., 1.])
# in the real case, imshow is an expensive calculation which can't be put inside the loop
ax.imshow(data, interpolation='nearest')
tstart = time.clock()
for i in range(1, 8):
for j in range(3, 13):
make_plot(i, j, fig, ax)
print('took {:.2f} seconds'.format(time.clock()-tstart))
Since the limitation in this case is the call to plt.savefig() it cannot be optimized a lot. Internally the figure is rendered from scratch and that takes a while. Possibly reducing the number of vertices to be drawn might reduce the time a bit.
The time to run your code on my machine (Win 8, i5 with 4 cores 3.5GHz) is 2.5 seconds. This seems not too bad. One can get a little improvement by using Multiprocessing.
A note about Multiprocessing: It may seem surprising that using the state machine of pyplot inside multiprocessing should work at all. But it does.
And in this case here, since every image is based on the same figure and axes object, one does not even have to create new figures and axes.
I modified an answer I gave here a while ago for your case and the total time is roughly halved using multiprocessing and 5 processes on 4 cores. I appended a barplot which shows the effect of multiprocessing.
import numpy as np
#import matplotlib as mpl
#mpl.use('agg') # use of agg seems to slow things down a bit
import matplotlib.pyplot as plt
import multiprocessing
import time, os
def make_plot(d):
start = time.clock()
x,y=d
#using aspect in this way causes a warning for me
#aspect = np.random.random(1)+y/2.0-x
xrand = np.random.random(2)*x
xlim = [min(xrand), max(xrand)]
yrand = np.random.random(2)*y
ylim = [min(yrand), max(yrand)]
filename = '{:d}_{:d}.png'.format(x,y)
ax = plt.gca()
#ax.set_aspect(abs(aspect[0]))
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.savefig('figs/'+filename)
stop = time.clock()
return np.array([x,y, start, stop])
if not os.path.isdir('figs'):
os.makedirs('figs')
data = np.random.rand(25, 25)
fig = plt.figure()
ax = fig.add_axes([0., 0., 1., 1.])
ax.imshow(data, interpolation='nearest')
some_list = []
for i in range(1, 8):
for j in range(3, 13):
some_list.append((i,j))
if __name__ == "__main__":
multiprocessing.freeze_support()
tstart = time.clock()
print tstart
num_proc = 5
p = multiprocessing.Pool(num_proc)
nu = p.map(make_plot, some_list)
tooktime = 'Plotting of {} frames took {:.2f} seconds'
tooktime = tooktime.format(len(some_list), time.clock()-tstart)
print tooktime
nu = np.array(nu)
plt.close("all")
fig, ax = plt.subplots(figsize=(8,5))
plt.suptitle(tooktime)
ax.barh(np.arange(len(some_list)), nu[:,3]-nu[:,2],
height=np.ones(len(some_list)), left=nu[:,2], align="center")
ax.set_xlabel("time [s]")
ax.set_ylabel("image number")
ax.set_ylim([-1,70])
plt.tight_layout()
plt.savefig(__file__+".png")
plt.show()
How do I animate a circle when I have its radius as a function of time (I have them both as arrays) in Python? That is, I want the frames to follow the times given in the time array.
I did the following which works but does not make use of FuncAnimation() (which is a problem as I need to save it as a GIF or mp4).
import matplotlib.pyplot as plt
import numpy as np
time = np.array([0.0,0.1,0.122,0.124,0.4,0.45,0.5,1.2,1.4,1.5])
r = np.array([100.0,99.0,90.0,80.0,78.0,50.0,40.0,30.0,10.0,5.0])
fig, ax = plt.subplots()
ax.set_xlim(-max(r),max(r))
ax.set_ylim(-max(r),max(r))
for j in range(len(time)):
ax.add_artist(plt.Circle((0, 0), r[j], color='r'))
ax.set_title(str(time[j]))
fig.canvas.draw()
if j+1==len(time):
break
plt.pause((time[j+1]-time[j]))
plt.gca().cla()
plt.show()
Is there a way to do it using FuncAnimation() and save it?
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.
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.
So now I can get unique files, hurrah! but it seems the second file is plotting both the first and the second plot, the third is plotting all three, fourth is plotting all four, etc. here is the new code:
for j in range(2):
dhulist=pyfits.open('test.fits')
row=5
colum=j
ax=[]
val=[]
for i in range(1600,3040):
val.append((dhulist[0].data[i,row,colum]))
ax.append(((((dhulist[0].header['CRPIX3'] -i)*(dhulist[0].header['CDELT3']))+5000)/1000))
plt.plot(ax,val)
#plt.show()
plt.savefig("5_{0}.png".format(j))
the plot function of matplotlib updates the current figure, or creates a new figure if there is no current figure. Here is an example that does a good job of explicitly tracking the figure object from creation to closing.
import numpy as np
from matplotlib import pyplot as plt
x = np.linspace(0, 10)
for j in range(3):
y = x ** j
f = plt.figure()
plt.plot(x, y, figure=f)
f.savefig("test_{}.png".format(j))
plt.close(f)
Notice that every operation that involves a figure, opening, plotting, saving, and closing explicitly references the figure object. IMHO, that's a very nice coding style and very helpful if you ever need to work with multiple figures at once. Matplotlib also lets you work with the "current figure" implicitly, which is fine if you're doing something simple. That would look more like this:
import numpy as np
from matplotlib import pyplot as plt
x = np.linspace(0, 10)
for j in range(3):
y = x ** j
plt.figure()
plt.plot(x, y)
plt.savefig("test2_{}.png".format(j))
plt.close()