0

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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
sTr8_Struggin
  • 665
  • 2
  • 11
  • 27
  • Threads. If you set up the serial plotting loop in a thread and put some if statement hooks into the while loop you will be able to control it from another thread that does not freeze up when it goes into the while loop. – ebarr Mar 19 '14 at 04:33

1 Answers1

2

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
ebarr
  • 7,704
  • 1
  • 29
  • 40
  • Thanks so much for such a through response! As I have not dealt with threading before I am still trying to understand your code. When I run what you have here I do not get a figure to come up. I guess I will read about threading and try to see if I am missing something obvious. Thanks again! – sTr8_Struggin Mar 19 '14 at 17:55
  • In some versions of matplotlib I think you may need to do a `plt.ion()` and/or a `plt.show()`. When I run this, I get the plot window coming up fine. This question sheds some light on this: http://stackoverflow.com/questions/5541594/matplotlib-draw-showing-nothing – ebarr Mar 19 '14 at 23:05
  • Uhm, this sounds interesting, but can't make it to show anything, even if I turn the interactive mode on. If I include `plt.show()` in the `Plotter` definition, I get a figure, but no data gets displayed. – DrD Mar 05 '15 at 21:38