-2

I have a PyQt5 app. The app is bigger than the example which I am sharing now. I am sharing the following code for convenience.

Example application consisting of a GUI class, a FlaskAPI class, a class that provides a video stream. The GUI class uses the class that inherits QLabel class and called ImageLabel and this part is not related to my question. While displaying the video stream on the GUI, I want to enable a C# application to interact with my Python application. So I decided to use Flask to provide a REST interface for the C# application. However, I have a problem now. Because of the collision of the PyQt5 main thread and the FlaskAPI, I am trying to apply multithreading but I faced with a problem.

My problem is that I can't display GUI if I start FlaskAPI threading. How can I run the Flask API with GUI and communicate a method in GUI class?

main.py

import sys

from PyQt5.QtCore import Qt, pyqtSlot, QThread
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QHBoxLayout

from FlaskAPI import FlaskAPI
from ImageLabel import ImageLabel
from StreamThread import StreamThread


class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.init_UI()


    def init_UI(self):
        self.setFixedSize(690, 530)
        self.image_lbl = ImageLabel()

        th = StreamThread()
        th.changePixmap.connect(self.setImage, Qt.QueuedConnection)
        th.start()


        btn_cnt = QPushButton("Continue")
        btn_pa = QPushButton("Pause")

        hbox = QHBoxLayout()
        hbox.addWidget(btn_cnt)
        hbox.addWidget(btn_pa)

        vbox = QVBoxLayout()
        vbox.addWidget(self.image_lbl)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

        btn_pa.clicked.connect(lambda: self.btn_pa_clicked(th))

        self.show()
        self.start_api()

    def start_api(self):
        """
        Start Flask API
        """
        self.thread = QThread()
        self.api = FlaskAPI()
        self.api.moveToThread(self.thread)

        self.thread.start()

    @pyqtSlot(QImage)
    def setImage(self, image):
       """
       Set frame on the ImageLabel instance (inherits QLabel to display video stream)
       """
       self.image_lbl.pixmap = QPixmap.fromImage(image).scaled(720, 540)
       self.image_lbl.setPixmap(QPixmap.fromImage(image).scaled(720, 540))

    def btn_pa_clicked(self, th):
        th.terminate()                                                    # terminates the video stream

        image = QImage("img/default.jpg")  # self.image_lbl.pixmap
        image = image.convertToFormat(QImage.Format_RGB888)
        # image = image.toImage()
        if image is not None:
            #(h, w, c) = image.shape
            #qimage = QImage(image.data, h, w, 3 * h, QImage.Format_RGB888)
            self.image_lbl.pixmap = QPixmap.fromImage(image)
            self.image_lbl.repaint()


def main():
    app = QApplication(sys.argv)
    main_form = MainWindow()
    sys.exit(app.exec_())



if __name__ == '__main__':
    main()

FlaskAPI.py

from PyQt5.QtCore import QObject

from flask import Flask

class FlaskAPI(QObject):
    def __init__(self):
        """
        Run Flask API and set the example route.
        """
        self.app = Flask(__name__)
        self.app.add_url_rule('/get_value', 'get_value', self.get_value)

        self.app.run(debug=True)

    def get_value(self):
        """
        Return example data.
        """
        data = {"id": "value"}

        return data

StreamThread.py

import cv2
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QImage

#https://stackoverflow.com/a/44404713/13080899
class StreamThread(QThread):
    changePixmap = pyqtSignal(QImage)

    def __init__(self):
        super(StreamThread, self).__init__()


    def run(self):
        """
        Start video stream on thread and emit the captured frame
        """
        self.capt = cv2.VideoCapture(0, cv2.CAP_DSHOW)

        while(True):
            ret, frame = self.capt.read()

            if ret:
                rbgImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rbgImage.shape
                bytesPerLine = ch*w
                convertToQtFormat = QImage(rbgImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(p)

ImageLabel.py

from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QObject, QPoint, pyqtSlot
from PyQt5.QtWidgets import QLabel

class Communicate(QObject):
    cor_update = pyqtSignal()
    cor_curr = pyqtSignal()

class ImageLabel(QLabel):
    def __init__(self, width=720, height=540):
        super(ImageLabel, self).__init__()
        self.setMouseTracking(True)
        self.pixmap = QtGui.QPixmap("img/im.jpg")

        #### initialize coordinates ####
        self.x1 = 0
        self.y1 = 0

        self.x_curr = 0
        self.y_curr = 0

        self.x2 = 0
        self.y2 = 0

        ##### enable state to allow user for drawing
        self.enable_labelling = False

        ##### enable state to track coordinates for drawing
        self.enable_cor = False

        ################################
        self.source = Communicate()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.drawPixmap(self.rect(), self.pixmap)
        if self.enable_labelling == True:
            self.paintRect(event, qp)
            qp.end()

    def paintRect(self, event, qp):
        br = QtGui.QBrush(QtGui.QColor(50, 255, 255, 40))
        qp.setBrush(br)

        if self.enable_cor == True:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x_curr - self.x1, self.y_curr - self.y1)))
        else:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x2 - self.x1, self.y2 - self.y1)))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x1 = event.x()
            self.y1 = event.y()

            self.enable_cor = True

            self.source.cor_update.emit()

    def mouseMoveEvent(self, event):
        self.x_curr = event.x()
        self.y_curr = event.y()

        self.source.cor_curr.emit()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x2 = event.x()
            self.y2 = event.y()

            self.source.cor_update.emit()

            self.enable_cor = False
Ugurcan
  • 366
  • 1
  • 4
  • 17
  • 1
    Your code is really too extensive, since the issue is only on the Flask thread you should at least remove everything else. That said, you do *not* seem to e running the Flask in a separate thread, as you're starting it in the `__init__`, and you move the object to the other thread *after* that. – musicamante Jan 06 '21 at 14:43
  • I removed the `self.app.run(debug=True)` and added `super(FlaskAPI, self).__init__()` in FlaskAPI. However, it didn't work. – Ugurcan Jan 06 '21 at 14:59
  • How should that change anything? Read and understand what I wrote in the previous comment: you're moving the object to the other thread **after** the init, whatever you do before `moveToThread` will *not* be executed in the new thread. – musicamante Jan 06 '21 at 15:12
  • Thank you. I solved the problem by removing `self.app.run()` in `__init__`. I run the api after `moveToThread` and it is working now. – Ugurcan Jan 06 '21 at 19:42
  • If your approach is working, consider adding your own answer. – musicamante Jan 06 '21 at 19:45
  • One more question. Does it make sense that using Flask on another thread? Or should I run Flask independently and use it as a controller between C# app and Python app? – Ugurcan Jan 06 '21 at 20:30

1 Answers1

0

According to @musicamante feedback I removed the self.app.run() in __init__ in FlaskAPI.py. I created a new method then I start it when thread started.

Here is how I start the FlaskAPI in main.py on a new thread. main.py

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.init_UI()
        self.start_api()

    def start_api(self):
        """
        Start Flask API
        """
        self.thread = QThread()
        self.api = FlaskAPI()
        self.api.moveToThread(self.thread)
        self.thread.started.connect(self.api.start)
        self.thread.start()

The updated FlaskAPI module.

FlaskAPI.py

from PyQt5.QtCore import QObject

from flask import Flask

class FlaskAPI(QObject):
    def __init__(self):
        """
        Run Flask API and set the example route.
        """
        super(FlaskAPI, self).__init__()
        self.app = Flask(__name__)
        self.app.add_url_rule('/get_value/', 'get_value', self.get_value)


    def start(self):
        self.app.run()

    def get_value(self):
        """
        Return example data.
        """
        data = {"id": "value"}

        return data

Ugurcan
  • 366
  • 1
  • 4
  • 17