Matplotlib Freezes When input() used in Spyder

对着背影说爱祢 提交于 2020-01-03 16:46:10

问题


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.


回答1:


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()



回答2:


Someone who knows better than me, please post an answer if possible. I know very little about Python/Scipy/Spyder

This is a kludgy module I wrote which prevents the Matplotlib window from freezing while input() is pending) under Spyder.

You must call prompt_hack.start() beforehand and prompt_hack.finish() after, and replace input() with prompt_hack.input()

prompt_hack.py

import matplotlib.pyplot
import time
import threading

# Super Hacky Way of Getting input() to work in Spyder with Matplotlib open
# No efforts made towards thread saftey!

prompt = False
promptText = ""
done = False
waiting = False
response = ""

regular_input = input

def threadfunc():
    global prompt
    global done
    global waiting
    global response

    while not done:   
        if prompt:   
            prompt = False
            response = regular_input(promptText)
            waiting = True
        time.sleep(0.1)

def input(text):
    global waiting
    global prompt
    global promptText

    promptText = text
    prompt = True

    while not waiting:
        matplotlib.pyplot.pause(0.01)
    waiting = False

    return response

def start():
    thread = threading.Thread(target = threadfunc)
    thread.start()

def finish():
    global done
    done = True


来源:https://stackoverflow.com/questions/34938593/matplotlib-freezes-when-input-used-in-spyder

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!