Issue
Windows 7. If I open a plain ipython terminal at the command line I can type:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4, 5])
plt.show(block=False)
input("Hello ")
But if I do the same thing in Spyder, as soon as I ask for user input, the Matplotlib window freezes, so I can't interact with it. I need to interact with the plot while the prompt is showing.
In both Spyder and the plain console, matplotlib.get_backend() return 'Qt4Agg'
Edit: To clarify, I have matplotlib set up where it shows up in its own window, not embedded as a PNG. (I had to set Backend: Automatic originally to get this behavior)
As an aside, in Spyder, the plot opens instantly after plt.plot(). In the regular console, it only opens after plt.show(). Also, if I press Ctrl-C after typing input() in Spyder, the entire console hangs unexpectedly. Vs. in IPython, it just raises KeyboardInterrupt and returns control to the console.
Edit: Even more complete example: Works in IPython console, not in Spyder (freezes). Want to move the plot around, according to user input.
import matplotlib.pyplot as pl
def anomaly_selection(indexes, fig, ax):
selected = []
for i in range(0, len(indexes)):
index = indexes[i]
ax.set_xlim(index-100, index+100)
ax.autoscale_view()
fig.canvas.draw()
print("[%d/%d] Index %d " % (i, len(indexes), index), end="")
while True:
response = input("Particle? ")
if response == "y":
selected.append(index)
break
elif response == "x":
return selected
elif response == "n":
break
fig, ax = pl.subplots(2, sharex=True)
ax[0].plot([1, 2, 3, 4, 5]) # just pretend data
pl.show(block=False)
sel = anomaly_selection([100, 1000, 53000, 4300], fig, ax[0])
Lots of Edit: I believe this is an issue with input() blocking Qt. My workaround if this question doesn't get traction, is to build a Qt window with the Matplotlib plot embedded in it, and get keyboard input through the window instead.
Solution
After a lot more digging I came to the conclusion that you simply should be making a GUI. I would suggest you use PySide or PyQt. In order for matplotlib to have a graphical window it runs an event loop. Any click or mouse movement fires an event which triggers the graphical part to do something. The problem with scripting is that every bit of code is top level; it suggests the code is running sequentially.
When you manually input the code into the ipython console it works! This is because ipython has already started a GUI event loop. Every command that you call is handled within the event loop allowing other events to happen as well.
You should be creating a GUI and declare that GUI backend as the same matplotlib backend. If you have a button click trigger the anomaly_selection function then that function is running in a separate thread and should allow you to still interact within the GUI.
With lots of fiddling and moving around the way you call fucntions you could get the thread_input function to work.
Fortunately, PySide and PyQt allow you to manually make a call to process GUI events. I added a method that asks for input in a separate thread and loops through waiting for a result. While it is waiting it tells the GUI to process events. The return_input
method will hopefully work if you have PySide (or PyQt) installed and are using it as matplotlib's backend.
import threading
def _get_input(msg, func):
"""Get input and run the function."""
value = input(msg)
if func is not None:
func(value)
return value
# end _get_input
def thread_input(msg="", func=None):
"""Collect input from the user without blocking. Call the given function when the input has been received.
Args:
msg (str): Message to tell the user.
func (function): Callback function that will be called when the user gives input.
"""
th = threading.Thread(target=_get_input, args=(msg, func))
th.daemon = True
th.start()
# end thread_input
def return_input(msg=""):
"""Run the input method in a separate thread, and return the input."""
results = []
th = threading.Thread(target=_store_input, args=(msg, results))
th.daemon = True
th.start()
while len(results) == 0:
QtGui.qApp.processEvents()
time.sleep(0.1)
return results[0]
# end return_input
if __name__ == "__main__":
stop = [False]
def stop_print(value):
print(repr(value))
if value == "q":
stop[0] = True
return
thread_input("Enter value:", stop_print)
thread_input("Enter value:", stop_print)
add = 0
while True:
add += 1
if stop[0]:
break
print("Total value:", add)
This code seems to work for me. Although it did give me some issues with ipython kernel.
from matplotlib import pyplot as pl
import threading
def anomaly_selection(selected, indexes, fig, ax):
for i in range(0, len(indexes)):
index = indexes[i]
ax.set_xlim(index-100, index+100)
ax.autoscale_view()
#fig.canvas.draw_idle() # Do not need because of pause
print("[%d/%d] Index %d " % (i, len(indexes), index), end="")
while True:
response = input("Particle? ")
if response == "y":
selected.append(index)
break
elif response == "x":
selected[0] = True
return selected
elif response == "n":
break
selected[0] = True
return selected
fig, ax = pl.subplots(2, sharex=True)
ax[0].plot([1, 2, 3, 4, 5]) # just pretend data
pl.show(block=False)
sel = [False]
th = threading.Thread(target=anomaly_selection, args=(sel, [100, 1000, 53000, 4300], fig, ax[0]))
th.start()
#sel = anomaly_selection([100, 1000, 53000, 4300], fig, ax[0])
while not sel[0]:
pl.pause(1)
th.join()
Answered By - justengel
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.