pythonmatplotlibarduinoserial-portinteractive-mode

Plotting Serial data in Python how to pause matplotlib figure?


Basically I want to pause the live plot that I have setup here:

import serial
import numpy as np
from matplotlib import pyplot as plt
ser = serial.Serial('/dev/tty.usbmodemfa131', 9600)


#Setting up the animated plot
plt.ion() # set plot to animated
fig = plt.figure()

ydata = [0] * 100
ax1=plt.axes()  

# make plot
line, = plt.plot(ydata)
plt.ylim([24,29])

#Starting data collection
while True:
    rawData = ser.readline().rstrip()
    x = len(rawData)
    #print(floatData)
    if len(rawData) <= 6:
        try:
            data = float(rawData)
            if data <= 100:
                ydata.append(data)
                del ydata[0]
                line.set_xdata(np.arange(len(ydata)))
                line.set_ydata(ydata)  # update the data
                plt.draw() # update the plot
        except ValueError:
            print "Not a float"

I am wanting to pause the plot in the figure but I can't figure out how to work with the figure at all as the data is streaming.


Solution

  • So a good solution to your problem would be to use a thread to run the plotting window and have control performed through the main thread. This could be implemented like so:

    import serial
    import numpy as np
    from matplotlib import pyplot as plt
    from threading import Thread,Event
    import time
    
    #A simple ring buffer to keep the plotting data in
    class RingBuffer(object):
        def __init__(self,size):
            self.size = size
            self._size = 2*size
            self._buffer = np.zeros(self._size)
            self._idx = 0
    
        def insert(self,val):
            idx = self._idx%self.size
            self._buffer[idx] = val
            self._buffer[idx+self.size] = val
            self._idx+=1
            if self._idx > self.size:
                self._idx-=self.size
    
        def get(self):
            start_idx = (self._idx)%self.size
            end_idx = start_idx+self.size
            return self._buffer[start_idx:end_idx]
    
    #A thread to handle plotting and reading data from the serial port
    class Plotter(Thread):
        def __init__(self,stream,stop,pause):
            Thread.__init__(self)
            self.stream = stream
            self.fig = plt.figure()
            self.ax = plt.axes()
            self.size = 100
            self.xdata = np.arange(self.size)
            self.ydata = RingBuffer(self.size)
            self.line, = self.ax.plot(self.ydata.get())
            plt.ylim([24,29])                                                  
            self.stop = stop
        self.pause = pause
    
        #The main loop of the thread, invoked with .start()
        def run(self):
            while not self.stop.is_set():
                if self.pause.is_set():
                    continue
                raw_data = self.stream.readline().rstrip()                     
                if len(raw_data) <= 6:
                    try:
                        data = float(raw_data)
                    except ValueError:
                        print "Not a float"
                    else:
                        if data <= 100:
                            self.ydata.insert(data)
                            self.line.set_xdata(self.xdata)
                            self.line.set_ydata(self.ydata.get())
                            plt.draw()
    
                #sleep statement to reduce processor load 
            time.sleep(1) 
    
    #this is implemented to facilitate use through something like Ipython easily
    class Controller(object):
        def __init__(self):
    
            #These events control execution of the thread main loop
            self.stop = Event()
            self.pause = Event()
            self.stream = serial.Serial('/dev/tty.usbmodemfa131', 9600)
            self.plotter_thread = Plotter(self.stream,self.stop,self.pause)
            self.plotter_thread.start()
    
    def main():
        con = Controller()
    
        #Loop over terminal input
        #possible commands are stop, pause and unpause
        while True:
            command = raw_input("cmd >>>")
            if command == "stop":
                con.stop.set()
                con.plotter_thread.join()
                break
            elif command == "pause":
                con.pause.set()
            elif command == "unpause":
                con.pause.clear()
            else:
                print "unrecognised command"
    
    if __name__ == "__main__":
        main()
    

    This should give you a command line interface like:

    cmd >>>
    

    Where any of the following statements should control the plotter

    cmd >>>pause
    cmd >>>unpause
    cmd >>>stop