Quitting matplotlib.pyplot animation gracefully - python

I have a script that plots data of some photometry apertures, and I want to plot them in an xy plot. I am using matplotlib.pyplot with python 2.5.
The input data is stored in around 500 files and read. I am aware that this is not the most efficient way of inputting the data but that's another issue...
Example code:
import matplotlib.pyplot as plt
xcoords = []
ycoords = []
# lists are populated with data from first file
pltline, = plt.plot(xcoords, ycoords, 'rx')
# then loop populating the data from each file
for file in filelist:
xcoords = [...]
ycoords = [...]
pltline.set_xdata(xcoords)
pltline.set_ydata(ycoords)
plt.draw()
As there are over 500 files, I will occasionally want to close the animation window in the middle of the plotting. My code to plot works but it doesn't exit very gracefully. The plot window does not respond to clicking the close button and I have to Ctrl+C out of it.
Can anyone help me find a way to close the animation window while the script is running whilst looking graceful (well more graceful than a series of python traceback errors)?

If you update the data and do the draw in a loop, you should be able to interrupt it. Here's an example (that draws a stationary circle and then moves a line around the perimeter):
from pylab import *
import time
data = [] # make the data
for i in range(1000):
a = .01*pi*i+.0007
m = -1./tan(a)
x = arange(-3, 3, .1)
y = m*x
data.append((clip(x+cos(a), -3, 3),clip(y+sin(a), -3, 3)))
for x, y in data: # make a dynamic plot from the data
try:
plotdata.set_data(x, y)
except NameError:
ion()
fig = figure()
plot(cos(arange(0, 2.21*pi, .2)), sin(arange(0, 2.21*pi, .2)))
plotdata = plot(x, y)[0]
xlim(-2, 2)
ylim(-2, 2)
draw()
time.sleep(.01)
I put in the time.sleep(.01) command to be extra sure that I could break the run, but in my tests (running Linux) it wasn't necessary.

Related

Way to wait until user deletes Matplotlib figure before adding more?

So I have a function that scatter-plots some data and does so by creating new figures. The maximum amount of figures allowed at a time is 20 to avoid memory overload. If the user wants to plot a data-set with 6 variables to be exact, then there would be 30 different figures. Is there a way to wait until the user deletes the necessary amount of figures before adding more?
This is what I've though of:
import matplolib.pyplot as plt
... # some code
# this below is inside a loop structure
f = plt.figure
# add some stuff to the figure
plt.show(block=False)
Halt() # checks to see if there are too many figures
Where Halt() is defined as such:
def halt():
first = True
while plt.gcf().number > 20: # are there more than 20 figures
if first:
# show message
first = False
# time.sleep(100)
The only problem with this is that it "freezes" the program, not allowing the user to exit out of any of the figures, as it is "not responding". I've also tried the time.sleep() but that does not seem work either.
Does anyone know of a good way to loop until a condition is met?
https://matplotlib.org/api/_as_gen/matplotlib.pyplot.show.html says:
If False ensure that all windows are displayed and return immediately. In this case, you are responsible for ensuring that the event loop is running to have responsive figures.
How to do this, you ask? Well, the documentation is at https://matplotlib.org/users/interactive_guide.html#explicitly-spinning-the-event-loop .
After some fiddling around, I made the following which plots 20 figures with maximum 5 at the same time:
import matplotlib.pyplot as plt
import numpy as np
from time import sleep
def plot_stuff(exponent, titlenum):
x = np.linspace(0.0, 1.0)
f = plt.figure()
ax = f.add_subplot(1, 1, 1)
ax.set_title('{} - {}'.format(titlenum, exponent))
ax.plot(x, x**exponent)
def get_fighandles():
fignumbers = plt.get_fignums()
return [plt.figure(fign) for fign in fignumbers]
N_figs_eventually_plotted = 20
N_figs_max_simultaneous = 5
N=0
while N < N_figs_eventually_plotted:
if len(get_fighandles()) < N_figs_max_simultaneous:
N += 1
# put here whichever update is needed when you can add new figures
plot_stuff(np.random.random(), N)
plt.show(block=False)
print('hi')
for fig in get_fighandles():
print(fig.canvas)
fig.canvas.flush_events()
fig.canvas.draw_idle() # might not be needed, but here it's fast
sleep(0.1)
# note: solution terminates when the last figure is plotted, you might want something to prevent this (for instance a plt.show(block=True) when the last figure is plotted)
There might be some subtle concurrency bugs (for instance, if you close a figure after the loop reads the figure handles but before it flushes the events), but I do not see how you can avoid that for your use case.

Why is matplotlib.animation slower with large windows that with small windows?

So, I wrote up a Langton's Ant program.
#!/bin/env python
import matplotlib.animation as ani
import matplotlib.pyplot as plt
import numpy as np
w,h = 100,100
d = np.array([0,1])
p = np.array([w//2,h//2])
grid = np.ones((w,h),dtype=int)
#MAIN
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(1,1,1)
img = ax.imshow(grid,vmin=-1,vmax=1)
tle = ax.text(0.5,0.95,"",bbox={'facecolor':'w','alpha':0.5,'pad':5},transform=ax.transAxes,ha="center")
ax.tick_params(axis='x',which='both',bottom=False,top=False,labelbottom=False,labeltop=False)
ax.tick_params(axis='y',which='both',right=False,left=False,labelright=False,labelleft=False)
def langtons_ant_animator(frame):
global w,h,d,p,grid
v = grid[p[0],p[1]]
grid[p[0],p[1]] = -v
d = [[0,v],[-v,0]] # d
p = (p+d) % (w,h)
img.set_data(grid)
tle.set_text(f'{frame: 9} Steps; Point ({p[0]: 3},{p[1]: 3}), Direction ({d[0]: 2},{d[1]: 2})')
return [tle,img]
animation = ani.FuncAnimation(
fig = fig,
func = langtons_ant_animator,
interval = 1,
blit = True,
)
plt.show(block=True)
While this program is running I can dynamically change the window size. If I make the size really small (1in by 1in) the Ant moves at rapid speeds, nearing 1000 steps a second. But if I leave the window at (10in by 10in) it updates at around 100 or so steps a second. You would think that the number of pixels being updated is what effects the speed, but this isn't the case; change the window to a 1000,1000 and the animation runs just as fast at 10in by 10in.
It seems that window size is the most important factor in animation speed. Why?
Why doesn't img take into consideration the underlying numpy array size and know exactly the pixels that need to be updated together?
Could this be done smarter to get faster speeds?
Why are pixels so slow to update in matplotlib in the first place?

How to plot real time graph using python for the signals received from RTL SDR?

Using the below python(ubuntu) code and rtlsdr, I am able to plot a graph. Can anyone please tell me how to modify this code to plot the graph continuously in real time?
from pylab import *
from rtlsdr import *
sdr = RtlSdr()
sdr.sample_rate = 2.4e6
sdr.center_freq = 93.5e6
sdr.gain = 50
samples = sdr.read_samples(256*1024)
sdr.close()
psd(samples.real, NFFT=1024, Fs=sdr.sample_rate/1e6, Fc=sdr.center_freq/1e6)
xlabel('Frequency (MHz)')
ylabel('Relative power (dB)')
show()
Normally you can update a pyplot-generated plot by calling plot.set_xdata(), plot.set_ydata(), and plot.draw() (Dynamically updating plot in matplotlib), without having to recreate the entire plot. However, this only works for plots that directly draw a data series. The plot instance cannot automatically recalculate the spectral density calculated by psd().
You'll therefore need to call psd() again when you want to update the plot - depending on how long it takes to draw, you could do this in regular intervals of a second or less.
This might work:
from pylab import *
from rtlsdr import *
from time import sleep
sdr = RtlSdr()
sdr.sample_rate = 2.4e6
sdr.center_freq = 93.5e6
sdr.gain = 50
try:
while True: # run until interrupted
samples = sdr.read_samples(256*1024)
clf()
psd(samples.real, NFFT=1024, Fs=sdr.sample_rate/1e6, Fc=sdr.center_freq/1e6)
xlabel('Frequency (MHz)')
ylabel('Relative power (dB)')
show()
sleep(1) # sleep for 1s
except:
pass
sdr.close()
Edit: Of course, I'm not sure how read_samples runs; my example here assumed that it returns almost immediately. If it blocks for a long time while waiting for data, you may want to read less data at a time, and discard old data as you do so:
from collections import deque
max_size = 256*1024
chunk_size = 1024
samples = deque([], max_size)
while True:
samples.extend(sdr.read_samples(chunk_size))
# draw plot

Update mayavi plot in loop

What I want to do is to update a mayavi plot in a loop. I want the updating of the plot to be done at a time specified by me (unlike, e.g., the animation decorator).
So an example piece of code I would like to get running is:
import time
import numpy as np
from mayavi import mlab
V = np.random.randn(20, 20, 20)
s = mlab.contour3d(V, contours=[0])
for i in range(5):
time.sleep(1) # Here I'll be computing a new V
V = np.random.randn(20, 20, 20)
# Update the plot with the new information
s.mlab_source.set(scalars=V)
However, this doesn't display a figure. If I include mlab.show() in the loop, then this steals the focus and doesn't allow the code to continue.
I feel what I should be using is a traits figure (e.g. this). I can follow the example traits application to run a figure which live-updates as I update the sliders. However, I can't get it to update when my code asks it to update; the focus now is 'stolen' by visualization.configure_traits().
Any pointers, or a link to appropriate documentation, would be appreciated.
EDIT
David Winchester's answer gets a step closer to the solution.
However, as I point out in the comments, I am not able to manipulate the figure with the mouse during the time.sleep() step. It is during this step that, in the full program, the computer will be busy computing the new value of V. During this time I would like to be able to manipulate the figure, rotating it with the mouse etc.
I thin Mayavi uses generators to animate data. This is working for me:
import time
import numpy as np
from mayavi import mlab
f = mlab.figure()
V = np.random.randn(20, 20, 20)
s = mlab.contour3d(V, contours=[0])
#mlab.animate(delay=10)
def anim():
i = 0
while i < 5:
time.sleep(1)
s.mlab_source.set(scalars=np.random.randn(20, 20, 20))
i += 1
yield
anim()
I used this post as reference ( Animating a mayavi points3d plot )
If you use the wx backend, you can call wx.Yield() periodically if you want to interact with your data during some long-running function. In the following example, wx.Yield() is called for every iteration of some "long running" function, animate_sleep. In this case, you could start the program with $ ipython --gui=wx <program_name.py>
import time
import numpy as np
from mayavi import mlab
import wx
V = np.random.randn(20, 20, 20)
f = mlab.figure()
s = mlab.contour3d(V, contours=[0])
def animate_sleep(x):
n_steps = int(x / 0.01)
for i in range(n_steps):
time.sleep(0.01)
wx.Yield()
for i in range(5):
animate_sleep(1)
V = np.random.randn(20, 20, 20)
# Update the plot with the new information
s.mlab_source.set(scalars=V)

Slow tkinter GUI

I've written a simple GUI in python using pylabs and tkinter based on an example found here:
http://hardsoftlucid.wordpress.com/various-stuff/realtime-plotting/
used for sine wave generation.
Except I tweaked it to pull data through suds from a server on the internet. It's not working as I exactly anticipated as the GUI is somewhat slow. I think its due to the timer. I just started learning how to use matplotlib functions yesterday so I'm not aware of how every function works.
How can I speed it up? Right now the data comes in at 2-3 seconds which is fine, but I just want to increase the GUI responsiveness.
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
plt.ion() # set plot to animated
url = "http://10.217.247.36/WSDL/v4.0/iLON100.WSDL"
client = Client(url, username='ilon', password='ilon', location = 'http://10.217.247.36/WSDL/iLON100.WSDL')
read = client.factory.create('ns0:E_xSelect')
read['xSelect'] = """//Item[starts-with(UCPTname, "Net/MB485/MAIN POWER/Fb/PowerSum")]"""
ydata = [0] * 50
ax1=plt.axes()
# make plot
line, = plt.plot(ydata)
plt.ylim([10,40])
# start data collection
while True:
x = client.service.Read(read).Item[0].UCPTvalue[0].value #data stream
x = float(x)
ymin = float(min(ydata))-10
ymax = float(max(ydata))+10
plt.ylim([ymin,ymax])
ydata.append(x)
del ydata[0]
line.set_xdata(np.arange(len(ydata)))
line.set_ydata(ydata) # update the data
plt.draw() # update the plot

Categories