Python Forum
[PyQt] Best option to implement parallel computing for hardware control and GUI?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] Best option to implement parallel computing for hardware control and GUI?
#1
I have a python script that reads frames from an industrial camera, processes the images, and after that sends control signals to actuators inside the technical system. The setup is a proof-of-concept prototype, which is why I prefer to use Python for rapid prototyping.

Overall the script works fine, but a graphical user interface for monitoring and control of the process would be very helpful. Therefore, I would like to have a graphical interface that shows the most recent camera image (update rate of approx. 2 images per second), and also it should allow some basic interaction with mouse clicks and key presses, so that I can influence the control loop via the GUI.

Now I am thinking of two possible options:
1) Run the graphical user interface and the already developed python script as separate processes and communicate via TCP sockets.
2) Use Multithreading (especially QRunnable and QThreadPool from PyQt5) to setup parallel processing.

At the moment I'm leaning towards Option (1) because it seems that QRunnable and QThreadPool is not suited for bidirectional communication via signals [1].

Do you have a alternative ideas how to approach this problem? Or do you have arguments why option (1) or (2) might be better suited?

[1]: https://stackoverflow.com/a/61625408/7053480

PS: I already asked the question on StackOverflow [2], but it was closed because the question is too much based on opinions. However, I think it is difficult to rephrase the question without asking for opinions because in principle the problem can be solved in many different ways.

[2]: https://stackoverflow.com/questions/7936...ia-sockets
Reply
#2
So I did some research on the web and assembled the following Proof-Of-Principle-Code. On the first glance it seems to work fine. Do you think there could be any issue with it?

from PyQt5 import QtCore, QtWidgets, QtTest
import sys


# Static variables that are written/read by both threads
class ExchangeData(object):
    count_value = 0


# Worker class that contains code to run in second thread
class MyWorker(QtCore.QObject):
    # Class attributes (static)
    finished_signal = QtCore.pyqtSignal()

    def __init__(self, mutex):
        super(MyWorker, self).__init__()
        # Class attributes (different for each instance)
        self._mutex = mutex
        self._is_stop_requested = False

    @QtCore.pyqtSlot()
    def request_stop(self):
        self._is_stop_requested = True

    @QtCore.pyqtSlot()
    def run(self):
        for i in range(1, 100):
            print("i = " + str(i))
            self._mutex.lock()
            ExchangeData.count_value = i
            self._mutex.unlock()
            QtTest.QTest.qWait(500)
            if self._is_stop_requested:
                break
        self.finished_signal.emit()


# Class that contains code that is run in main thread
class mainWindow(QtWidgets.QMainWindow):
    request_stop_signal = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setWindowTitle("Aberration Correction GUI")

        # GUI setup: Layout
        frame = QtWidgets.QFrame()
        main_layout = QtWidgets.QVBoxLayout()
        frame.setLayout(main_layout)
        self.setCentralWidget(frame)

        # GUI setup: Add widgets to layout
        self.button1 = QtWidgets.QPushButton("Request stop!")
        main_layout.addWidget(self.button1)
        self.button1.clicked.connect(self.button_clicked_callback)
        self.label1 = QtWidgets.QLabel("Worker is running!")
        main_layout.addWidget(self.label1)
        self.button2 = QtWidgets.QPushButton("Get data")
        main_layout.addWidget(self.button2)
        self.button2.clicked.connect(self.button_clicked_exchange_data)
        self.label2 = QtWidgets.QLabel("{count_value}")
        main_layout.addWidget(self.label2)

        # Prepare worker and thread
        self.mymutex = QtCore.QMutex()
        self.myworker = MyWorker(self.mymutex)
        self.thread1 = QtCore.QThread()
        # This must be done before attaching signals
        self.myworker.moveToThread(self.thread1)
        # Signals for worker
        self.myworker.finished_signal.connect(self.worker_finished)
        self.myworker.finished_signal.connect(self.thread1.quit)
        self.myworker.finished_signal.connect(self.thread1.deleteLater)
        self.myworker.finished_signal.connect(self.myworker.deleteLater)
        self.thread1.started.connect(self.myworker.run)
        self.request_stop_signal.connect(self.myworker.request_stop)

        self.thread1.start()

    @QtCore.pyqtSlot()
    def button_clicked_callback(self):
        print("Emit signal to request stop!")
        # Call request_stop-function via qtSlots
        self.request_stop_signal.emit()

    @QtCore.pyqtSlot()
    def button_clicked_exchange_data(self):
        print("Exchanging data...")
        self.mymutex.lock()
        self.label2.setText(str(ExchangeData.count_value))
        self.mymutex.unlock()

    @QtCore.pyqtSlot()
    def worker_finished(self):
        self.label1.setText("Worker finished!")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = mainWindow()
    window.show()
    app.exec()
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020