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