简体   繁体   中英

Can't display PyQt5 GUI with threading FlaskAPI

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM