Matplotlib, button to close a loop, python - python

I have a plot that allows user to click on a data point. Then it generates a continuous loop that was supposed to be closed by clicking on the close button. The example below will trigger a print message of "Still Looping" every 1second. Then I created a button hoping to close the loop by changing loopBool to True. But it doesn't work because once the loop starts I am not able to interact with the figure window. How can I solve this? Many thanks
import numpy as np
from matplotlib import pyplot as plt
import time
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
# it is not always column 0 and 1
sctPlot = ax.scatter([-0.3,0,0.3], [0.3,0.3,0.3], c="blue", picker = 2, s=[50]*3)
fig.subplots_adjust(bottom=0.3, left=0.1)
plt.grid(False)
plt.axis([-0.6, 0.6, -0.6, 0.6])
loopBool = True
def closeLooping(event):
global loopBool
loopBool = False
def looping(event):
global loopBool
while (loopBool == True):
print "Still Looping!"
time.sleep(1)
print "Stop!!"
axCloseButton = plt.axes([0.1, 0.15, 0.2, 0.06])
bClose = Button(axCloseButton, "Close", color = axcolor, hovercolor = '0.975')
bClose.on_clicked(closeLooping)
fig.canvas.mpl_connect('pick_event', looping)

It seems to me that a process stuck in an infinite loop is in a sense incompatible with a GUI. GUIs themselves perform a loop, checking for and responding to events happening. The best solution would probably be to remove your infinite loop, by transforming that part of code to an event-based one.
However, I also found a solution to your actual problem. I'm not very familiar with the programming constructs involved, so I can't tell you how efficient or elegant this solution is. The point is to run your infinite loop in a separate thread, thereby preventing your main python process from being stuck in the loop. This will keep the GUI responsive. However, this can lead to problems if you want to interrupt your program during its run.
The code:
import time
import threading #this is new
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
# it is not always column 0 and 1
sctPlot = ax.scatter([-0.3,0,0.3], [0.3,0.3,0.3], c="blue", picker = 2, s=[50]*3)
fig.subplots_adjust(bottom=0.3, left=0.1)
plt.grid(False)
plt.axis([-0.6, 0.6, -0.6, 0.6])
loopBool = True
def closeLooping(event):
global loopBool
loopBool = False
def looping(event):
global loopBool
while (loopBool == True):
print("Still Looping!")
time.sleep(1)
print("Stop!!")
def looping_pre(event): #this is new
thread = threading.Thread(target=looping, args=(event,))
#thread.daemon = True #might or might not be needed
thread.start()
axCloseButton = plt.axes([0.1, 0.15, 0.2, 0.06])
bClose = widgets.Button(axCloseButton, "Close", hovercolor = '0.975')
bClose.on_clicked(closeLooping)
plt.show() #this is new
fig.canvas.mpl_connect('pick_event', looping_pre) #this is changed
Note that I changed a few things because your exact code didn't seem to run properly for me. I removed color=axcolor from the Button call; and I added a plt.show() before the event connect, otherwise a figure window didn't appear for me (neither through ipython, nor with python).
The relevant addition is the threading module and the looping_pre front-end, which calls the looping function as a separate Thread. For this reason the 'pick_event' doesn't call looping, but rather looping_pre.
This code will (when running in ipython) show the figure window, start looping on a click to the data, then stop looping on button click. However, when I push ctrl+c, the loop keeps on going, since it is a separate thread. I only managed to kill it by using a reset, thereby removing the value of the global loopBool. The commented line specifying whether the Thread should be deamonized should affect this behaviour (by which I mean that it would seem logical to me), but I didn't see any effect.

Related

matplotlib funcAnimation won't repeat

I am making a animated bar plot for basic bubble sort . It runs pretty good. But doesn't repeat itself (loop). I am trying it in jupyter notebook , I added %matplotlib qt,
Why won't my animFunc repeat although I have set the repeat to True .
x=["1","2","3","4","5","6","7","8","9","10"]
y=[7,8,5,3,1,9,4,2,10,6]
temp=0
def animator_X():
for a in range(len(y)-1):
for b in range(len(y)-a-1):
if y[b]>y[b+1]:
temp = y[b]
y[b]=y[b+1]
y[b+1]=temp
yield y
fig,ax = plt.subplots(figsize=(7,5))
def init():
ax.clear()
y=[7,8,5,3,1,9,4,2,10,6]
plt.bar(x,y,color=['blue'])
def animX(i):
ax.clear()
plt.bar(x,y,color=['blue'])
return plt
animx = FuncAnimation(fig,animX,frames=animator_X,interval=1000,init_func=init,repeat=True)
plt.show()
You aren't resetting the main y variable when it repeats the init function after a run.
Try:
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x=["1","2","3","4","5","6","7","8","9","10"]
y=[7,8,5,3,1,9,4,2,10,6]
temp=0
def animator_X():
for a in range(len(y)-1):
for b in range(len(y)-a-1):
if y[b]>y[b+1]:
temp = y[b]
y[b]=y[b+1]
y[b+1]=temp
print(y)
yield y
fig,ax = plt.subplots(figsize=(7,5))
def init():
global y
ax.clear()
y=[7,8,5,3,1,9,4,2,10,6]
plt.bar(x,y,color=['blue'])
def animX(i):
ax.clear()
plt.bar(x,y,color=['blue'])
return plt
anim = animation.FuncAnimation(fig,animX,frames=animator_X,interval=100,init_func=init)
plt.show()
That code will run in sessions launched from here. Go there and press launch binder. When it comes up, you can paste in the code.
I suspect in OP's code the addition of the global y line in the init() function will fix the OP's version.
Further explanation
It does keep repeating with the code posted in the OP because the kernel keeps running on that cell after the first pass.
A y object that is local solely to the init function is getting reset within the scope of the init() function when it repeats after the first pass. I don't know enough about how FuncAnimation() decides to update/and what it displays then to tell you why OP code without updating y in the main scope results in it showing the init() state and doesn't instead flash to the init state and then back to the sorted state. The kernel is still running and so maybe it is flashing between those two yet the init() dominates for some reason? That's speculation because the FuncAnimation() is so specialized that it doesn't display what is put in print statements inside the init or main function that gets animated, and so probing what's going on separate from the plot, in a simplistic manner, is not easy.

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.

Bokeh checkbox only updates when its checked

Im using bokeh server to plot a line graph, where theres a checkbox button that will flip the line if its checked. If its unchecked I want to see the original version of the line (unflipped). Following the flip/unflip, a second function is called to perform some other calculations:
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox, layout
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import CheckboxGroup
from bokeh.plotting import figure
def flip_signal(signal, flip):
if flip:
signal = -signal
else:
signal = signal
return signal
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_height=500, plot_width=850, title="test",
tools="crosshair,pan,reset,save,wheel_zoom")
line_orig = plot.line('x', 'y', source=source, line_width=1, line_alpha=1)
flip_signal_btn = CheckboxGroup(labels=["Flip signal"])
def update_flip(attrname, old, new):
if 0 in flip_signal_btn.active:
flip = True
else:
flip = False
# Update plot
source.data = dict(x=source.data['x'], y=flip_signal(source.data['y'], flip))
def update_peaks(attrname, old, new):
# do something else
pass
for w in [flip_signal_btn]:
w.on_change('active', update_flip)
w.on_change('active', update_peaks)
options = widgetbox(flip_signal_btn)
doc_layout = layout(row([options], height=200))
curdoc().add_root(row(plot, doc_layout, width=800))
curdoc().title = "checkbox"
The checkbox only seems to call update_flip when its checked, so in order to flip (or unflip) the signal I need to click it twice. For example, when I uncheck the box nothing happens, but I'm expecting it to unflip the signal. Rather it only unflips the signal if I uncheck and then check the box again
The callback is being invoked with the correct values on every button click, as can be verified with some print statements. The error is in your logic. Since you are operating on the current signal, rather than some original signal, you presumably always want to flip every time, unconditionally. Currently, you are only flipping every other button push, because this flip_signal(..., False) just returns the signal passed in, as-is. Changing the update_flip callback to always flip yields the behavior you want:
def update_flip(attrname, old, new):
# flip the *current* data on *every* button toggle
source.data = dict(x=source.data['x'], y=flip_signal(source.data['y'], True))
For your logic to work you would need a different function than your current flip_signal. You would need a function that always returns the original unflipped signal on False, and always returns the flipped signal on True. Contrast this with the current flip_signal, if you call it with False it gives you back whatever you passed in, regardless of whether it is the flipped or unflipped signal.

How does one close a figure or replace a figure without having to manually close each figure in Python/pylab?

I have searched numerous sites, used plots, subplots, some basic animation, and other roundabout ways, but the figure will not close despite using close(), clf(), etc.
I have something like this:
import numpy
from pylab import *
import time
fig = Figure()
counter1 = 0
counter2 = 0
while counter1<5:
counter1 = counter1+1
while counter2<10:
scatter(x_list[counter2], y_list[counter2], hold = 'on') ### x_list and y_list are just lists of random numbers
counter2 = counter2 + 1
show()
sleep(0.5)
close()
I am looking for any solution, as seen above. Plots, subplots, animation...
Two side issues to start: first, are you sure that this is the code you're actually running? sleep isn't a function in my version of pylab, so your import time doesn't seem to match your call, it should be time.sleep(0.5).. Second, I don't understand your loops at all. It looks like you're plotting the same thing 5 times, because counter1 has no effect and you add each point to the scatterplot before you pause. Are you trying to plot x_list/y_list point by point?
If you use draw() instead of show() I think it should work; the show() is what's holding the close(). Is the following something like what you want?
import time
from pylab import *
ion()
# test data
x = arange(0, 10, 0.5)
y = 10*x+exp(x)*abs(cos(x))
for j in range(len(x)):
if j > 0: scatter(x[:j], y[:j])
# assuming we don't want the limits to change
xlim(0, 10)
ylim(0, 1000)
draw()
time.sleep(2)
#close()
Note that I've commented out the close() because this way it produces a nice animation. If you leave it in, it'll keep closing and reopening the window, which could be what you want, but doesn't look very useful to my eyes. YMMV, of course.

Quitting matplotlib.pyplot animation gracefully

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.

Categories