Reputation: 325
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.
Upvotes: 0
Views: 1301
Reputation: 6112
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
.
Upvotes: 1