【问题标题】:python matplotlib: plotting in another processpython matplotlib:在另一个进程中绘图
【发布时间】:2016-07-10 22:18:39
【问题描述】:

编辑:这样一个 Python 程序的最终要求是:从外部电路(可能配备了一些传感器)从 UART 接收数据,程序将处理这些数据,并在计算机屏幕上绘制一条动态更新的曲线.

所以,我想动态绘制,下面的测试脚本启动一个子进程,在这个过程中,它通过队列接受来自父进程的数据,并相应地绘制数据。

但是当脚本运行时,只显示一个空图,我可以看到控制台打印“put:”和“got:”消息,这意味着父进程和子进程都在运行和通信,但 GUI 中没有任何反应图窗。

此外,GUI 窗口没有响应,如果我单击该窗口,它将崩溃。

系统为 Windows 10,64 位。 Python版本为2.7(32位)

这里有什么问题?谢谢!

import matplotlib.pyplot as plt
import multiprocessing as mp
import random
import numpy
import time

def worker(q):
    plt.ion()
    ln, = plt.plot([], [])
    plt.show()

    while True:
        obj = q.get()
        n = obj + 0
        print "sub : got:", n

        ln.set_xdata(numpy.append(ln.get_xdata(), n))
        ln.set_ydata(numpy.append(ln.get_ydata(), n))
        plt.draw()

if __name__ == '__main__':
    queue = mp.Queue()
    p = mp.Process(target=worker, args=(queue,))
    p.start()

    while True:
        n = random.random() * 5
        print "main: put:", n
        queue.put(n)
        time.sleep(1.0)

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    你必须重新缩放,否则什么都不会出现:

    这适用于我的电脑:

    import matplotlib.pyplot as plt
    import multiprocessing as mp
    import random
    import numpy
    import time
    
    def worker(q):
        #plt.ion()
        fig=plt.figure()
        ax=fig.add_subplot(111)
        ln, = ax.plot([], [])
        fig.canvas.draw()   # draw and show it
        plt.show(block=False)
    
        while True:
            obj = q.get()
            n = obj + 0
            print "sub : got:", n
    
            ln.set_xdata(numpy.append(ln.get_xdata(), n))
            ln.set_ydata(numpy.append(ln.get_ydata(), n))
            ax.relim()
    
            ax.autoscale_view(True,True,True)
            fig.canvas.draw()
    
    if __name__ == '__main__':
        queue = mp.Queue()
        p = mp.Process(target=worker, args=(queue,))
        p.start()
    
        while True:
            n = random.random() * 5
            print "main: put:", n
            queue.put(n)
            time.sleep(1.0)
    

    【讨论】:

    • 这样可以显示曲线。但是我还是一头雾水,为什么matplotlib动态更新曲线这么复杂?那些ax.relimax.autoscale_view(True, True, True) 和`fig.canvas.draw() 调用是如此神秘,甚至有两个不同的对象“ax”和“fig”可以修补......此外,对于这个版本,当我点击图,windows还是变成了“Not Responding”。我想通过创建一个子流程来利用它,应该没问题。 Queue.get() 调用是否使 UI 无响应?等待数据然后在图上绘制的正确方法是什么?
    • 当你用空数据初始化你的绘图时,如果你不重新缩放,你什么也看不到。你可以在这里找到关于 matplotlib 的解释:matplotlib.org/users/artists.html。请注意,“通常” matplotlib 不是交互式的,因此通过这样做,您是在“突破极限”。可能有一种方法可以让您的身材保持响应,但我不知道。发生的情况是您更新了画布,而不是整个窗口,因此您的操作系统将其检测为无响应,即使它不是。
    • 非常感谢您的帮助和您的链接,我会看看。
    【解决方案2】:

    到目前为止,我想将以下示例程序标记为我的问题的答案。它绝对不是完美的,或者它甚至不是在 Python 和 matplotlib 中做到这一点的正确方法。

    我认为不引起图上无响应的重要一点是不要挂起“UI”线程,当显示 UI 时,matplotlib 可能正在其上运行事件循环,所以如果我放任何 @ 987654323@ 或调用Queue.get() 阻塞线程执行,图形窗口将挂起。

    因此,我没有在“Queue.get()”处阻塞线程,而是选择使用“Queue.get_nowait()”作为传入新数据的轮询方法。 UI线程(即matplotlib图形窗口更新工作者)只会阻塞在matplotlib.pyplot.pause(),我相信这不会暂停事件循环。

    如果matplotlib 中有另一个调用可以阻塞并等待信号,我认为这会比这种轮询方式更好。

    起初我看到multiprocessingmatplotlib 的例子,所以我试图使用多个进程来实现并发。但是好像你只需要自己处理同步,用多线程代替就可以了。多线程的好处是在一个进程内共享数据。所以下面的程序使用了threading模块而不是multiprocessing

    以下是我的测试程序,我可以在Windows 7(64位)和Python 2.7上运行,并且图形窗口在这个更新速度下是响应式的,你可以拖动它,调整它的大小等等。

    #!/usr/bin/python
    # vim: set fileencoding=utf-8:
    
    import random
    import time
    import Queue
    import threading
    import numpy as np
    import matplotlib.pyplot as plt
    
    ## Measurement data that are shared among threads
    val1 = []
    val2 = []
    lock = threading.Lock()
    
    def update_data_sync(x, y):
        lock.acquire()
        val1.append(x)
        val2.append(y)
        if len(val1) > 50:
            del val1[0]
        if len(val2) > 50:
            del val2[0]
        lock.release()
    
    def get_data_sync():
        lock.acquire()
        v1 = list(val1)
        v2 = list(val2)
        lock.release()
        return (v1, v2)
    
    def worker(queue):
        plt.ion()
        fig = plt.figure(1)
        ax = fig.add_subplot(111)
        ax.margins(0.05, 0.05)
        #ax.set_autoscale_on(True)
        ax.autoscale(enable=True, axis='both')
        #ax.autoscale(enable=True, axis='y')
        ax.set_ylim(0, 1)
    
        line1, line2 = ax.plot([], [], 'b-', [], [], 'r-')
    
        while True:
            need_draw = False
            is_bye = False
    
            while True:
                ## Try to exhaust all pending messages
                try:
                    msg = queue.get_nowait()
                    if msg is None:
                        print "thread: FATAL, unexpected"
                        sys.exit(1)
                    if msg == 'BYE':
                        print "thread: got BYE"
                        is_bye = True
                        break
                    # Assume default message is just let me draw
                    need_draw = True
                except Queue.Empty as e:
                    # Not 'GO' or 'BYE'
                    break
    
            ## Flow control
            if is_bye:
                break
            if not need_draw:
                plt.pause(0.33)
                continue;
    
            ## Draw it
            (v1, v2) = get_data_sync()
            line1.set_xdata(range(1, len(v1) + 1, 1))
            # Make a clone of the list to avoid competition on the same dataset
            line1.set_ydata(v1)
            line2.set_xdata(line1.get_xdata())
            line2.set_ydata(v2)
    
            ## Adjust view
            #ax.set_xlim(0, len(line1.get_ydata()) + 1)
            #ax.set_ylim(0, 1)
            ## (??) `autoscale' does not work here...
            #ax.autoscale(enable=True, axis='x')
            #ax.autoscale(enable=True, axis='y')
            ax.relim()
            ax.autoscale_view(tight=True, scalex=True, scaley=False)
    
            ## "Redraw"
            ## (??) Maybe pyplot.pause() can ensure visible redraw
            fig.canvas.draw()
            print "thread: DRAW"
            plt.pause(0.05)
        ## Holy lengthy outermost `while' loop ends here
    
        print "thread: wait on GUI"
        plt.show(block=True)
        plt.close('all')
        print "thread: worker exit"
        return
    
    def acquire_data():
        # Fake data for testing
        if not hasattr(acquire_data, 'x0'):
            acquire_data.x0 = 0.5
        x = int(random.random() * 100) / 100.0
        while np.abs(x - acquire_data.x0) > 0.5:
            x = int(random.random() * 100) / 100.0
        acquire_data.x0 = x
    
        y = 0.75 * np.abs(np.cos(i * np.pi / 10)) + 0.15
    
        return (x, y)
    
    if __name__ == "__main__":
        queue = Queue.Queue()
        thr = threading.Thread(target=worker, args=(queue, ))
        thr.start()
    
        for i in range(200):
            (x, y) = acquire_data()
            update_data_sync(x, y)
            #print "main: val1: {}. val2: {}".format(x, y)
            queue.put("GO")
            time.sleep(0.1)
        queue.put('BYE')
    
        print "main: waiting for children"
        thr.join()
        print "main: farewell"
    

    【讨论】:

    • 我遇到了同样的问题,重复显示从套接字接收到的数据。感谢您的回答,我可以毫无问题地显示它们。虽然我在关闭程序或关闭绘图窗口时收到此错误:RuntimeError: main thread is not in main loop. Tcl_AsyncDelete: async handler deleted by the wrong thread。有解决错误的想法吗?
    【解决方案3】:

    对上面的代码做了一些修改。我在多处理包中使用了 Process、Queue、Value、Array 来降低代码复杂度。脚本将在图表上绘制 [0,0]、[1,1]、[2,2] 等

    from multiprocessing import Process, Queue, Value, Array
    import time
    import matplotlib.pyplot as plt
    from queue import Empty
    
    def f(q, num, arr1, arr2):
        plt.ion()
        fig = plt.figure()
        fig = plt.figure(1)
        ax = fig.add_subplot(111)
        ax.margins(0.05, 0.05)
    
        #ax.set_autoscale_on(True)
        ax.autoscale(enable=True, axis='both')
        #ax.autoscale(enable=True, axis='y')
        ax.set_ylim(0, 100)
        line1, line2 = ax.plot([], [], 'b-', [], [], 'r-')
    
    
        while True :
            try:
                #val = q.get_nowait()
                val = q.get(timeout = 0.8) # reducing the timeout value will improve the response time on graph whie mouse is moved on it
                #val = q.get()
                if val == 'Exit':
                    break
                if val == 'Go': 
                    x = num.value
                    #print("num.value = ", x)
                    v1 = arr1[0:num.value]
                    v2 = arr2[0:num.value]
                    #print("v1 :", v1)
                    #print("v2 :", v2)
                    line1.set_xdata(range(1, len(v1) + 1, 1))
                    line1.set_ydata(v1)
                    line2.set_xdata(line1.get_xdata())
                    line2.set_ydata(v2)
    
                    ax.relim()
                    ax.autoscale_view(tight=True, scalex=True, scaley=False)
                    fig.canvas.draw()
                    #print ("thread: DRAW")
                    plt.pause(0.05)
            except Empty as e:
                x = num.value
                line1.set_xdata(range(1, len(v1) + 1, 1))
                line1.set_ydata(v1)
                line2.set_xdata(line1.get_xdata())
                line2.set_ydata(v2)
    
                ax.relim()
                ax.autoscale_view(tight=True, scalex=True, scaley=False)
                fig.canvas.draw()
                plt.pause(0.05)
                continue
    
    
    
    if __name__ == '__main__':
        q = Queue()
        num = Value('i', 0)
        arr1 = Array('d', range(100))
        arr2 = Array('d', range(100))
        p = Process(target=f, args=(q,num, arr1, arr2, ))
        p.start()
    
        for i in range(10):
            arr1[i] = i
            arr2[i] = i 
            num.value = i+1
            q.put("Go")   # prints "[42, None, 'hello']"
            time.sleep(1)
        q.put("Exit")
        p.join()
    

    【讨论】:

      猜你喜欢
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 2013-07-05
      • 1970-01-01
      • 2017-10-08
      • 2018-03-06
      • 1970-01-01
      • 2019-06-30
      相关资源
      最近更新 更多