Search code examples
pyqt5qthread

QTreading implementation for two different functions - PyQT5


I've been trying to implement multithreading on PyQT5 using QThreads. I want to display a video and update a set of labels at the same time(I used a counter as an example). From my research I found different ways to implement QThreads, it was recommended to use the following method instead of instantiating QTread and modifying run method, so I followed that advice. Here is my reduced code:

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPoint, QRect, QSize, Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import sys

import os

import cv2

import numpy as np
import time

############################# Telemtry widgets update ##########################
class DISPLAY(QObject):
    acc1_val = pyqtSignal(int)
    finished = pyqtSignal()

    def run(self):
        global cntr
        cntr = 0
        while 1:
            cntr = cntr +1
            time.sleep(1)
            self.acc1_val.emit(cntr)
            if(cntr >50):
                cntr = 0 

        self.finished.emit()



######################################################################################

class VideoThread(QObject):
    ImageUpdate = pyqtSignal(QImage)
    frame_cv = pyqtSignal(np.ndarray)
    started = pyqtSignal()
    finished = pyqtSignal()
    frame=[]
    def run(self):
        self.display_width= 1280
        self.display_height = 720
        self.ThreadActive = True
        Capture = cv2.VideoCapture('sample.mp4')
        while Capture.isOpened():
            ret, self.frame = Capture.read()
            if ret:
                Image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                ConvertToQtFormat = QImage(Image.data, Image.shape[1], Image.shape[0], QImage.Format_RGB888)
                Pic = ConvertToQtFormat.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
                self.ImageUpdate.emit(Pic)
                time.sleep(30/1000)  #use it just when playing from a file to memic FPS delay
        Capture.release()

        self.finished.emit()
    def sel(self):
        self.frame_cv.emit(self.frame)


############################ GUI #######################################


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1920, 1080)
        MainWindow.setFocusPolicy(QtCore.Qt.NoFocus)

        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")

        self.vid = QtWidgets.QLabel(self.centralwidget)

        self.vid.setGeometry(QtCore.QRect(550, 70, 1280, 720))
        self.vid.setFrameShape(QtWidgets.QFrame.Box)
        self.vid.setText("")
        self.vid.setObjectName("vid")
        self.vid.setFocusPolicy(Qt.StrongFocus) #important to be able to listen to a keypress

        self.Title = QtWidgets.QLabel(self.centralwidget)
        self.Title.setGeometry(QtCore.QRect(30, 10, 421, 71))
        font = QtGui.QFont()
        font.setPointSize(24)
        font.setBold(True)
        font.setWeight(75)
        self.Title.setFont(font)
        self.Title.setObjectName("Title")
        
        self.ACC1_X = QtWidgets.QLineEdit(self.centralwidget)
        self.ACC1_X.setGeometry(QtCore.QRect(220, 420, 41, 31))
        self.ACC1_X.setObjectName("ACC1_X")

        
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1920, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)


        ###################### Display thread ######################
    
        self.thread1 = QThread()       
        self.worker1 = DISPLAY()
        self.worker1.moveToThread(self.thread1)
        self.thread1.started.connect(self.worker1.run)
        self.worker1.acc1_val.connect(self.Label_update)

        self.worker1.finished.connect(self.thread1.quit)
        self.worker1.finished.connect(self.worker1.deleteLater)          

        self.thread1.finished.connect(self.thread1.deleteLater)

        self.thread1.start()




    def Label_update(self,acc_val):

        self.ACC1_X.setText(str(acc_val))
        print(str(acc_val))

        ############### video thread ############################3
        self.thread2 = QThread()
        self.worker2 = VideoThread()
        self.worker2.moveToThread(self.thread2)
        self.thread2.started.connect(self.worker2.run)
        self.worker2.finished.connect(self.thread2.quit)
        self.worker2.finished.connect(self.worker2.deleteLater)          

        self.thread2.finished.connect(self.thread2.deleteLater)

        
        # connect its signal to the update_image slot
        self.worker2.ImageUpdate.connect(self.ImageUpdateSlot)


        # start the thread
        self.thread2.start()



    def ImageUpdateSlot(self, Image):
        self.vid.setPixmap(QPixmap.fromImage(Image))



    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.Title.setText(_translate("MainWindow", "TEST"))

    

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

When I run it I get the following error:

1
2
QThread: Destroyed while thread is still running
Aborted (core dumped)

WHat's the best way to implement it, and can normal threads be used instead of QThreads in this case?

Thank you


Solution

  • I figured it out, for some reason I defined worker2 inside of Label_update by mistake, I just moved Label_update method down and followed the comment of musicanante and now its working. here is the full working code if anyone is interested.

    ############################# Telemtry widgets update

    ##########################
    class DISPLAY(QObject):
        acc1_val = pyqtSignal(int)
        finished = pyqtSignal()
    
        def run(self):
            global cntr
            cntr = 0
            while 1:
                cntr = cntr +1
                time.sleep(1)
                self.acc1_val.emit(cntr)
                if(cntr >50):
                    cntr = 0 
    
            self.finished.emit()
    
    
    
    ######################################################################################
    
    class VideoThread(QObject):
        ImageUpdate = pyqtSignal(QImage)
        frame_cv = pyqtSignal(np.ndarray)
        started = pyqtSignal()
        finished = pyqtSignal()
        frame=[]
        def run(self):
            self.display_width= 1280
            self.display_height = 720
            self.ThreadActive = True
            Capture = cv2.VideoCapture('sample.mp4')
            while Capture.isOpened():
                ret, self.frame = Capture.read()
                if ret:
                    Image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                    ConvertToQtFormat = QImage(Image.data, Image.shape[1], Image.shape[0], QImage.Format_RGB888)
                    Pic = ConvertToQtFormat.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
                    self.ImageUpdate.emit(Pic)
                    time.sleep(30/1000)  #use it just when playing from a file to memic FPS delay
            Capture.release()
    
            self.finished.emit()
        def sel(self):
            self.frame_cv.emit(self.frame)
    
    
    ############################ GUI #######################################
    
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(1920, 1080)
            MainWindow.setFocusPolicy(QtCore.Qt.NoFocus)
    
            self.centralwidget = QtWidgets.QWidget(MainWindow)
            self.centralwidget.setObjectName("centralwidget")
    
            self.vid = QtWidgets.QLabel(self.centralwidget)
    
            self.vid.setGeometry(QtCore.QRect(550, 70, 1280, 720))
            self.vid.setFrameShape(QtWidgets.QFrame.Box)
            self.vid.setText("")
            self.vid.setObjectName("vid")
            self.vid.setFocusPolicy(Qt.StrongFocus) #important to be able to listen to a keypress
    
            self.Title = QtWidgets.QLabel(self.centralwidget)
            self.Title.setGeometry(QtCore.QRect(30, 10, 421, 71))
            font = QtGui.QFont()
            font.setPointSize(24)
            font.setBold(True)
            font.setWeight(75)
            self.Title.setFont(font)
            self.Title.setObjectName("Title")
            
            self.ACC1_X = QtWidgets.QLineEdit(self.centralwidget)
            self.ACC1_X.setGeometry(QtCore.QRect(220, 420, 41, 31))
            self.ACC1_X.setObjectName("ACC1_X")
    
            
            MainWindow.setCentralWidget(self.centralwidget)
            self.menubar = QtWidgets.QMenuBar(MainWindow)
            self.menubar.setGeometry(QtCore.QRect(0, 0, 1920, 22))
            self.menubar.setObjectName("menubar")
            MainWindow.setMenuBar(self.menubar)
    
            self.retranslateUi(MainWindow)
            QtCore.QMetaObject.connectSlotsByName(MainWindow)
    
    
            ###################### Display thread ######################
        
            self.thread1 = QThread()       
            self.worker1 = DISPLAY()
            self.worker1.moveToThread(self.thread1)
            self.thread1.started.connect(self.worker1.run)
            self.worker1.acc1_val.connect(self.Label_update)
    
            #self.worker1.finished.connect(self.thread1.quit)
            #self.worker1.finished.connect(self.worker1.deleteLater)             
    
    
            self.thread1.finished.connect(self.worker1.deleteLater)
    
            self.thread1.start()
    
    
    
    
    
            ############### video thread ############################3
            self.thread2 = QThread()
            self.worker2 = VideoThread()
            self.worker2.moveToThread(self.thread2)
            self.thread2.started.connect(self.worker2.run)
    
            #self.worker2.finished.connect(self.thread2.quit)
    
            #self.worker2.finished.connect(self.worker2.deleteLater)             
    
    
            self.thread2.finished.connect(self.worker2.deleteLater)
            
            # connect its signal to the update_image slot
            self.worker2.ImageUpdate.connect(self.ImageUpdateSlot)
    
    
            # start the thread
            self.thread2.start()
    
    
        def Label_update(self,acc_val):
    
            self.ACC1_X.setText(str(acc_val))
            print(str(acc_val))
    
    
        def ImageUpdateSlot(self, Image):
            self.vid.setPixmap(QPixmap.fromImage(Image))
    
    
    
        def retranslateUi(self, MainWindow):
            _translate = QtCore.QCoreApplication.translate
            MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
            self.Title.setText(_translate("MainWindow", "TEST"))
    
        
    
    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
        MainWindow = QtWidgets.QMainWindow()
        ui = Ui_MainWindow()
        ui.setupUi(MainWindow)
        MainWindow.show()
        sys.exit(app.exec_())