0

I'm currently developing an application that needs to continuously send a stream of my screen to a window in it. However, I would like a way to be able to stop this and restart it using a stop and start button which I've added too the grid using layout.addWidget(QtWidgets.QPushButton('Start'),0,0), layout.addWidget(QtWidgets.QPushButton('Stop'),0,1). However, I don't know how to properly connect these buttons to an inactive/active thread starting and stopping a thread respectively using those two buttons.

This is my code:

from PyQt5 import QtCore, QtGui, QtWidgets 
import cv2
import numpy as np
from mss import mss


class Thread(QtCore.QThread):
   changePixmap = QtCore.pyqtSignal(QtGui.QImage)
   scaled_size = QtCore.QSize(1500, 1000)
   


   def run(self):
       mon = {'top': 0, 'left': 0, 'width': 1920, 'height': 1080}
       with mss() as sct:

        while True:
           ret = True
           if ret:
               img = sct.grab(mon)
            #    cv2.imshow('test', np.array(img))
               rgbImage = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
               convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
               p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
               self.changePixmap.emit(p)
def scaled(self, scaled_size):
        self.scaled_size = scaled_size


class PlayStreaming(QtWidgets.QWidget):
    def __init__(self):
        super(PlayStreaming,self).__init__()
        self.initUI()

    @QtCore.pyqtSlot(QtGui.QImage)
    def setImage(self, image):
        self.label.setPixmap(QtGui.QPixmap.fromImage(image))

    def initUI(self):
        self.setWindowTitle("Image")
        # create a label
        self.label = QtWidgets.QLabel(self)
        th = Thread(self)
        th.changePixmap.connect(self.setImage)
        th.start()
        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)


class UIWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(UIWidget, self).__init__(parent)
        # Initialize tab screen
        self.tabs = QtWidgets.QTabWidget()
        self.tab1 = QtWidgets.QWidget()   
        self.tab2 = QtWidgets.QWidget()
        self.tab3 = QtWidgets.QWidget()


        # Add tabs
        self.tabs.addTab(self.tab1,"Face")

        # Create first tab
        self.createGridLayout()
        self.tab1.layout = QtWidgets.QVBoxLayout()
        self.display = PlayStreaming()
        self.tab1.layout.addWidget(self.display, stretch=1)
        self.tab1.layout.addWidget(self.horizontalGroupBox)
        self.tab1.setLayout(self.tab1.layout)

        # Add tabs to widget        
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tabs)

    def createGridLayout(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox("Control")
        self.horizontalGroupBox.setStyleSheet("QGroupBox { background-color: red}");
        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QPushButton('Start'),0,0) 
        layout.addWidget(QtWidgets.QPushButton('Stop'),0,1) 
        self.horizontalGroupBox.setLayout(layout)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = UIWidget()
    w.resize(1000, 800)
    w.show()
    sys.exit(app.exec_())

Thanks in advance

To note I used the code at: How to change display size changing with window size in PyQT5? and Screen Capture with OpenCV and Python-2.7 as a template for my code.

Maurice Bekambo
  • 325
  • 6
  • 21

1 Answers1

1

You need a way to break the while True loop in the thread's run method in order to stop the thread. You could instead keep a boolean variable that will be set to False when Thread.stop() is called.

def run(self):
    self._go = True
    
    mon = {'top': 0, 'left': 0, 'width': 1920, 'height': 1080}
    with mss() as sct:

        while self._go:
            ret = True
            if ret:
                img = sct.grab(mon)
                #    cv2.imshow('test', np.array(img))
                rgbImage = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
                convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
                p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
                self.changePixmap.emit(p)
        
def stop(self):
    self._go = False

I would recommend constructing the thread in the UIWidget class and connecting its signals from there.

class UIWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(UIWidget, self).__init__(parent)
        # Initialize tab screen
        self.tabs = QtWidgets.QTabWidget()
        self.tab1 = QtWidgets.QWidget()   
        self.tab2 = QtWidgets.QWidget()
        self.tab3 = QtWidgets.QWidget()

        # Add tabs
        self.tabs.addTab(self.tab1,"Face")

        self.display = PlayStreaming()
        self.th = Thread(self)
        self.th.changePixmap.connect(self.display.setImage)

        # Create first tab
        self.createGridLayout()
        self.tab1.layout = QtWidgets.QVBoxLayout()
        self.tab1.layout.addWidget(self.display, stretch=1)
        self.tab1.layout.addWidget(self.horizontalGroupBox)
        self.tab1.setLayout(self.tab1.layout)

        # Add tabs to widget        
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tabs)

    def createGridLayout(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox("Control")
        self.horizontalGroupBox.setStyleSheet("QGroupBox { background-color: red}");
        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QPushButton('Start', clicked=self.th.start), 0, 0) 
        layout.addWidget(QtWidgets.QPushButton('Stop', clicked=self.th.stop), 0, 1)
        self.horizontalGroupBox.setLayout(layout)

Now when you click the stop button, the current iteration will complete and then it will break out of the loop and stop. If you want the thread to stop immediately no matter where it is in the loop, then include self.th.setTerminationEnabled(True) and connect the stop button signal to self.th.terminate. But note the documentation warns against using QThread.terminate.

alec
  • 5,799
  • 1
  • 7
  • 20
  • Thanks for the response. I tried out the code but the thread keeps running. For some reason, the while doesn't get broken even when the stop function puts the value of self._go = False. – Maurice Bekambo Oct 27 '20 at 22:07
  • @MauriceBekambo Did you remove all the lines to do with QThread in the PlayStreaming class? – alec Oct 29 '20 at 04:43
  • thanks a lot it stops and works now. Do you know how I can make it remove the still frame and just have an empty window when I press stop? – Maurice Bekambo Oct 29 '20 at 07:40