Skip to content
Advertisement

How to multiprocess multiple plots in a single PyQt GUI instance

I have a plot object called CrosshairPlotWidget. Each plot object spawns a thread which updates its data but these threads are still within the same main GUI process. Here’s what I currently have and an illustration:

enter image description here

1 main GUI process with 2 threads

I want to run the two plots each in a separate process, but both within the same GUI instance (same window). Essentially I’m trying to put each plot into its own separate child process to achieve true concurrency since I’m CPU bottlenecked. By having each update thread in a separate process, it will bypass Python’s global interpreter lock. Here’s an illustration of the desired goal:

1 main GUI process with 2 child processes each with its own thread

I’ve looked at

but none really help in my situation.

I also found this that does multiprocessing but not in the same GUI window.

from PyQt4 import QtCore, QtGui
import multiprocessing as mp
from threading import Thread
import pyqtgraph as pg
import numpy as np
import sys
import random
import time

class CrosshairPlotWidget(QtGui.QWidget):
    """Scrolling plot with crosshair"""

    def __init__(self, parent=None):
        super(CrosshairPlotWidget, self).__init__(parent)

        # Use for time.sleep (s)
        self.FREQUENCY = .025
        # Use for timer.timer (ms)
        self.TIMER_FREQUENCY = self.FREQUENCY * 1000

        self.LEFT_X = -10
        self.RIGHT_X = 0
        self.x_axis = np.arange(self.LEFT_X, self.RIGHT_X, self.FREQUENCY)
        self.buffer = int((abs(self.LEFT_X) + abs(self.RIGHT_X))/self.FREQUENCY)
        self.data = []

        self.crosshair_plot_widget = pg.PlotWidget()
        self.crosshair_plot_widget.setXRange(self.LEFT_X, self.RIGHT_X)
        self.crosshair_plot_widget.setLabel('left', 'Value')
        self.crosshair_plot_widget.setLabel('bottom', 'Time (s)')
        self.crosshair_color = (101,255,183)

        self.crosshair_plot = self.crosshair_plot_widget.plot()

        self.layout = QtGui.QGridLayout()
        self.layout.addWidget(self.crosshair_plot_widget)

        self.crosshair_plot_widget.plotItem.setAutoVisible(y=True)
        self.vertical_line = pg.InfiniteLine(angle=90)
        self.horizontal_line = pg.InfiniteLine(angle=0, movable=False)
        self.vertical_line.setPen(self.crosshair_color)
        self.horizontal_line.setPen(self.crosshair_color)
        self.crosshair_plot_widget.setAutoVisible(y=True)
        self.crosshair_plot_widget.addItem(self.vertical_line, ignoreBounds=True)
        self.crosshair_plot_widget.addItem(self.horizontal_line, ignoreBounds=True)

        self.crosshair_update = pg.SignalProxy(self.crosshair_plot_widget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)

        self.update_data_thread = Thread(target=self.plot_updater, args=())
        self.update_data_thread.daemon = True
        self.update_data_thread.start()

    def plot_updater(self):
        """Updates data buffer with data value"""

        while True:
            self.data_point = random.randint(1,101)
            if len(self.data) >= self.buffer:
                del self.data[:1]
            self.data.append(float(self.data_point))
            self.crosshair_plot.setData(self.x_axis[len(self.x_axis) - len(self.data):], self.data)
            time.sleep(self.FREQUENCY)

    def update_crosshair(self, event):
        """Paint crosshair on mouse"""

        coordinates = event[0]  
        if self.crosshair_plot_widget.sceneBoundingRect().contains(coordinates):
            mouse_point = self.crosshair_plot_widget.plotItem.vb.mapSceneToView(coordinates)
            index = mouse_point.x()
            if index > self.LEFT_X and index <= self.RIGHT_X:
                self.crosshair_plot_widget.setTitle("<span style='font-size: 12pt'>x=%0.1f,   <span style='color: red'>y=%0.1f</span>" % (mouse_point.x(), mouse_point.y()))
            self.vertical_line.setPos(mouse_point.x())
            self.horizontal_line.setPos(mouse_point.y())

    def get_crosshair_plot_layout(self):
        return self.layout

if __name__ == '__main__':
    # Create main application window
    app = QtGui.QApplication([])
    app.setStyleSheet("""
        QWidget {
            background-color: #19232D;
            border: 0px solid #32414B;
            padding: 0px;
            color: #F0F0F0;
            selection-background-color: #1464A0;
            selection-color: #F0F0F0;
        }""")
    app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
    mw = QtGui.QMainWindow()
    mw.setWindowTitle('Crosshair Plot')

    # Create and set widget layout
    # Main widget container
    cw = QtGui.QWidget()
    ml = QtGui.QGridLayout()
    cw.setLayout(ml)
    mw.setCentralWidget(cw)

    # Create crosshair plot
    crosshair_plot1 = CrosshairPlotWidget()
    crosshair_plot2 = CrosshairPlotWidget()

    ml.addLayout(crosshair_plot1.get_crosshair_plot_layout(),0,0,1,1)
    ml.addLayout(crosshair_plot2.get_crosshair_plot_layout(),0,1,1,1)
    mw.show()

    ## Start Qt event loop unless running in interactive mode or using pyside.
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

Advertisement

Answer

The GUI can only live in the main thread that belongs to the main process, so what you require is not possible. As you have seen in the other examples, the closest thing to what you want is that the code that produces the live data in another process

         Child Process 1               Child Process 2
        ┌----------------┐           ┌----------------┐  
        | ┌-----------┐  |           | ┌-----------┐  |
        | | Producer1 |  |           | | Producer2 |  |
        | └-----------┘  |           | └-----------┘  |
        └-------┬--------┘           └-------┬--------┘   
                |                            |
                └---------------┬------------┘
                                |
                       ┌--------┴--------┐ 
                       |  Main Thread    |
                       |  ┌-----------┐  | 
                       |  |    GUI    |  |
                       |  └-----------┘  | 
                       └-----------------┘
                          Main Process
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement