python matplotlib: plotting in another process

后端 未结 3 779
滥情空心
滥情空心 2020-12-04 02:33

EDIT: The ultimate requirement for such a Python program is: Receive data from UART from a external circuitry (which probably is equipped with some sensors), the program wil

相关标签:
3条回答
  • 2020-12-04 03:14

    You have to rescale, otherwise nothing will appear:

    This works on my computer :

    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)
    
    0 讨论(0)
  • 2020-12-04 03:20

    Till now I'd like to flag my following sample program as the answer to my question. It definitely is not the perfect one, or maybe it's even not the correct way to do that in Python and matplotlib.

    I think the important thing to not cause unresponsiveness on the figure is not to hang the "UI" thread, which when the UI is shown, matplotlib probably is running a event loop on it, so if I put any time.sleep(0.1) or call Queue.get() which block the thread execution, the figure window will just hang.

    So instead of blocking the thread at "Queue.get()", I choose to use "Queue.get_nowait()" as a polling method for incoming new data. The UI thread (ie, matplotlib figure window updating worker) will only block at matplotlib.pyplot.pause(), which will not suspend the event loop I believe.

    If there is another call in matplotlib that can block and wait for a signal, I think that would be better than this polling approach.

    At first I see multiprocessing examples with matplotlib, so I was trying to use multiple processes for concurrency. But it seems that you just need to take care of the synchronization yourself, it is okay to use multithreading instead. And multithreading has the benefit of sharing data within one process. So the following program utilized threading module instead of multiprocessing.

    The following is my test program, I can run it on Windows 7 (64 bit) with Python 2.7, and the Figure Window is responsive at this rate of updating, you can drag it, resize it and so on.

    #!/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"
    
    0 讨论(0)
  • 2020-12-04 03:24

    Made samll modifications to above code. I used Process, Queue, Value, Array in multiprocessing package to reduce the code complexity. Script will plot [0,0], [1,1], [2,2] etc on a graph

    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()
    
    0 讨论(0)
提交回复
热议问题