Feed data to graph from another thread

4,989 views
Skip to first unread message

sm...@whoop.com

unread,
Feb 12, 2014, 1:55:10 PM2/12/14
to pyqt...@googlegroups.com
I'm working on a app that will display real time data sent via a serial port and pyserial.  I've found pyscope.py that Luke posted to a thread a while ago which implements a scrolling plot of data pulled from alsaaudio. 

This is very close to what I want but its driven by a timer rather than by an event from the serial code.  Having the serial stuff all run in a separate thread and send an event when the data is ready seem like a better solution than polling the port.

Anyone have an example of suing a separate thread to feed data to pyqtgraph?

Luke Campagnola

unread,
Feb 12, 2014, 2:11:30 PM2/12/14
to pyqt...@googlegroups.com
Warning: It's easy to do, and also very easy to do wrong (and a threaded application gone wrong can be very difficult to debug).
The rules are:
   (1) Any object that might be accessed simultaneously by two threads must be protected with a mutex, semaphore, etc.
   (2) Only the main GUI thread can do anything related to the GUI. Never try to plot, update labels, etc. from a secondary thread.
   (3) Use signals to pass messages from one thread to another; Qt is pretty good about automatically handling the details.

Here's a simple example using QThread:   (the equivalent using python's threading looks very similar)

import pyqtgraph as pg
import time

plt = pg.plot()

def update(data):
    plt.plot(data, clear=True)

class Thread(pg.QtCore.QThread):
    newData = pg.QtCore.Signal(object)
    def run(self):
        while True:
            data = pg.np.random.normal(size=100)
            # do NOT plot data from here!
            self.newData.emit(data)
            time.sleep(0.05)

thread = Thread()
thread.newData.connect(update)
thread.start()


 

sm...@whoop.com

unread,
Feb 12, 2014, 2:27:50 PM2/12/14
to pyqt...@googlegroups.com

Here's a simple example using QThread:   (the equivalent using python's threading looks very similar)

import pyqtgraph as pg
import time

plt = pg.plot()

def update(data):
    plt.plot(data, clear=True)

class Thread(pg.QtCore.QThread):
    newData = pg.QtCore.Signal(object)
    def run(self):
        while True:
            data = pg.np.random.normal(size=100)
            # do NOT plot data from here!
            self.newData.emit(data)
            time.sleep(0.05)

thread = Thread()
thread.newData.connect(update)
thread.start()


Thanks.  That indeed does seem pretty simple.

Next question:  While this works for the simple display of the data later I want to allow the user to change a few settings that will get sent back to the device via the serial port.  I was going to connect those up to various pyqtgraph gui controls.  Will it be equally simple to send those events back to the serial thread or will that be a lot more complex?


Luke Campagnola

unread,
Feb 12, 2014, 2:51:33 PM2/12/14
to pyqt...@googlegroups.com
On Wed, Feb 12, 2014 at 2:27 PM, <sm...@whoop.com> wrote:
Here's a simple example using QThread:   (the equivalent using python's threading looks very similar)

[snip]
 
Thanks.  That indeed does seem pretty simple.

Next question:  While this works for the simple display of the data later I want to allow the user to change a few settings that will get sent back to the device via the serial port.  I was going to connect those up to various pyqtgraph gui controls.  Will it be equally simple to send those events back to the serial thread or will that be a lot more complex?

That is slightly more complex. Sending signals to the main GUI only works because that thread is running an event loop that handles the signal for you. To do the reverse, you would need to start a new event loop in the secondary thread. This can be done (Qt docs have more information), but might be overkill. 

The popular alternative is to use a mutex to allow the main thread to block the secondary thread while it modifies the thread's objects in some way. Below, I have extended the previous example to include a button to stop the updates. Things of note:

   (1) Thread._stop is accessed by both threads, so it must be protected with a mutex every time it is accessed
   (2) I have used threading.Lock here, but QMutex works just as well (although it lacks __enter__ and __exit__)
   (3) Thread.stop() is defined as a method of a QThread class, but don't let this confuse you--only the main GUI thread will ever execute this method. When the stop button emits its signal, the main thread receives the signal and carries out the callback immediately.
 

import pyqtgraph as pg
import threading
import time

plt = pg.plot()

def update(data):
    plt.plot(data, clear=True)

class Thread(pg.QtCore.QThread):
    newData = pg.QtCore.Signal(object)
    def __init__(self):
        super(Thread, self).__init__()
        self.stopMutex = threading.Lock()
        self._stop = False

    def run(self):
        while True:
            # Must protect self._stop with a mutex because the main thread 
            # might try to access it at the same time.
            with self.stopMutex:
                if self._stop:
                    # causes run() to exit, which kills the thread.
                    break
            data = pg.np.random.normal(size=100)
            self.newData.emit(data)
            time.sleep(0.05)

    def stop(self):
        # Must protect self._stop with a mutex because the secondary thread 
        # might try to access it at the same time.
        with self.stopMutex:
            self._stop = True

thread = Thread()
thread.newData.connect(update)
thread.start()

stopBtn = pg.QtGui.QPushButton("Stop")
stopBtn.setParent(plt)
stopBtn.show()
stopBtn.clicked.connect(thread.stop)



sm...@whoop.com

unread,
Feb 12, 2014, 3:52:21 PM2/12/14
to pyqt...@googlegroups.com

Below, I have extended the previous example to include a button to stop the updates. Things of note:

Thanks for the help and the examples.
 
Reply all
Reply to author
Forward
0 new messages