0

how to pass data(variable) from QMainWindow to Qthread?

I am trying to create a save function that takes user's input but the function itself is in QMainWindow class and savepath in Qthread class. How do I emit a signal from QMainWindow to Qthread to replace a default savepath? I've tried to do something similar in topic 33300818 How to pass data between threads in PyQt5? but I didn't get results

Here's a part of the code

from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QGridLayout, QAction, QMainWindow, QMessageBox, QStatusBar, QDesktopWidget, QFileDialog, QToolBar, QTextEdit
from PyQt5.QtGui import QPixmap, QIcon, QFont, QTextCursor
from PyQt5.QtCore import pyqtSignal, Qt, QThread
import sys
import os
import cv2
import time
import numpy as np
from datetime import datetime


VERSION = "v1.00"

IMG_SIZE        = 640, 480          # 640,480 or 1280,720 or 1920,1080
CAP_API         = cv2.CAP_DSHOW
EXPOSURE        = 0
TEXT_FONT       = QFont("Courier", 10)
camera_num      = 1
timestr         = time.strftime("%d-%b-%Y-%H_%M_%S")
save_vpath       = ''
save_seq        = 0
full_save_vpath = os.path.join(save_vpath, "%04d-%s.mkv" % (
    save_seq,
    timestr))
CurrUser        = os.environ.get('USERNAME')



class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)


    def __init__(self, *args, **kwargs):
        super().__init__()
        self._run_flag = True

    def run(self):
        total_frames = 0

        self.ThreadActive = True
        self.cap = cv2.VideoCapture(camera_num-1 + CAP_API)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, IMG_SIZE[0])
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, IMG_SIZE[1])
        if EXPOSURE:
            self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)
            self.cap.set(cv2.CAP_PROP_EXPOSURE, EXPOSURE)
        else:
            self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1)

        prev_frame_time = 0
        new_frame_time = 0

        if full_save_vpath:
            width = IMG_SIZE[0]
            height = IMG_SIZE[1]
            self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
            fps = 30
            self.out = cv2.VideoWriter(full_save_vpath, self.fourcc, fps, (width, height))

        while self.ThreadActive:
            ret, frame = self.cap.read()
            if not ret:
                break
            if ret:
                height, width, _ = frame.shape
                rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

                new_frame_time = time.time()
                fps_count = 1 / (new_frame_time - prev_frame_time)
                prev_frame_time = new_frame_time
                fps_count = int(fps_count)
                fps_count = str(fps_count)


                self.change_pixmap_signal.emit(frame)

            if full_save_vpath:
                self.out.write(frame)

            total_frames += 1

        self.cap.release()


    def stop(self):
        self._run_flag = False
        self.wait()
        if full_save_vpath:
            self.out.release()

class MyWindow(QMainWindow):
    text_update = pyqtSignal(str)

    def __init__(self):
        super(MyWindow, self).__init__()


        self.vthread = VideoThread(self)

        self.setWindowTitle(VERSION)
        self.setWindowIcon(QIcon('Icon/count.png'))
        cent = QDesktopWidget().availableGeometry().center()  # Finds the center of the screen
        self.frameGeometry().moveCenter(cent)
        self.widget = QWidget()
        self.layout = QGridLayout(self.widget)
        self.display_width = 1280
        self.display_height = 720
        self.image_label = QLabel(self)
        self.image_label.resize(self.display_width, self.display_height)
        self.image_label.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.image_label)
        self.setCentralWidget(self.widget)

        #create statusbar
        self.status = QStatusBar()
        self.status.setStyleSheet("background : lightgray;")
        self.setStatusBar(self.status)  # Adding status bar to the main window

        #create mainmenu
        self.statusBar()
        menu_Bar = self.menuBar()
        file_menu = menu_Bar.addMenu('&File')
        play_menu = menu_Bar.addMenu('&Play')

        #create exit action
        exitAction = QAction(QIcon('Icon/exit.png'), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)
        file_menu.addAction(exitAction)

        #create save action
        saveAction = QAction('Save as', self)
        saveAction.setStatusTip("Change folder where v/o will be saved.")
        saveAction.triggered.connect(self.change_folder)
        file_menu.addAction(saveAction)

        #create start action
        self.startAction = QAction(QIcon('Icon/rec_start.png'), 'Start Feed', self)
        self.startAction.setStatusTip("Start camera feed.")
        self.startAction.triggered.connect(self.start_feed)
        self.startAction.triggered.connect(self.start_inactive)
        self.startAction.setEnabled(True)
        play_menu.addAction(self.startAction)

        #create stop action
        self.stopAction = QAction(QIcon('Icon/rec_stop.png'), 'Stop Feed', self)
        self.stopAction.setStatusTip("Stop camera feed.")
        self.stopAction.triggered.connect(self.stop_feed)
        self.stopAction.triggered.connect(self.hide_frame)
        self.stopAction.triggered.connect(self.start_active)
        self.stopAction.setEnabled(False)
        play_menu.addAction(self.stopAction)


        #create toolbar
        self.toolbar = QToolBar('Toolbar')
        self.addToolBar(self.toolbar)
        self.toolbar.setMovable(False)
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(self.startAction)
        self.toolbar.addAction(self.stopAction)


    def update_image(self, img_label):
        qt_img = self.convert_cv_qt(img_label)
        self.image_label.setPixmap(qt_img)

    def convert_cv_qt(self, frame):
        rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
        return QPixmap.fromImage(convert_to_Qt_format)

    def write(self, text):
        self.text_update.emit(str(text))

    def flush(self):
        pass

    # Append to text display
    def append_text(self, text):
        cur = self.textbox.textCursor()     # Move cursor to end of text
        cur.movePosition(QTextCursor.End)
        s = str(text)
        while s:
            head,sep,s = s.partition("\n")  # Split line at LF
            cur.insertText(head)            # Insert text at cursor
            if sep:                         # New line if LF
                cur.insertBlock()
        self.textbox.setTextCursor(cur)     # Update visible cursor

    def start_feed(self):
        self.stopAction.setEnabled(True)
        self.thread = VideoThread()
        self.image_label.setAlignment(Qt.AlignCenter)
        #self.layout.addWidget(self.image_label)
        self.thread.change_pixmap_signal.connect(self.update_image)
        self.thread.start()
        self.textbox = QTextEdit(self.widget)
        self.textbox.setFont(TEXT_FONT)
        self.textbox.setMaximumSize(640, 100)
        self.text_update.connect(self.append_text)
        sys.stdout = self
        print(datetime.now())
        self.layout.addWidget(self.textbox, 1, 0)

    def start_inactive(self):
        self.startAction.setEnabled(False)

    def start_active(self):
        self.startAction.setEnabled(True)

    def stop_feed(self):
        if self.thread.ThreadActive:
            self.thread.change_pixmap_signal.disconnect()
            self.text_update.disconnect()
            self.thread.ThreadActive = False
        else:
            self.thread.terminate()

    def hide_frame(self):
        self.widget = QWidget()
        self.layout = QGridLayout(self.widget)
        self.display_width = 1280
        self.display_height = 720
        self.image_label = QLabel(self)
        self.image_label.resize(self.display_width, self.display_height)
        self.image_label.move(0, 0)
        self.layout.addWidget(self.image_label)
        self.setCentralWidget(self.widget)


    def change_folder(self):
        path = QFileDialog.getExistingDirectory(self, "video location", "")
        if path:
            self.chosen_path = os.path.join(path, "%04d-%s.mkv" % (save_seq, timestr)).replace(os.sep, '/')
        self.save_seq = 0
        print(self.chosen_path)

if __name__ == "__main__":

    app = QApplication(sys.argv)
    win = MyWindow()
    win.show()
    sys.exit(app.exec_())```
D121
  • 1
  • Make `full_save_vpath` an instance attribute instead of a global variable, then just do `self.thread.full_save_vpath = "/some/other/path"`. Note: as the [documentation clearly warns](https://doc.qt.io/qt-5/qthread.html#terminate), you should **not** use `terminate()`. – musicamante Jun 04 '23 at 12:47
  • thanks, I was able to make parametric savepath via instance attributes of a class. Guess I shouldn't use global variables. I'm aware of terminate() usage warnings, but it's not causing any bugs at this point. I'll remove it sometime later. – D121 Jun 09 '23 at 07:58

1 Answers1

0

As suggested by musicamante, it's better to use instance attributes of a class than global variables. Here's partial code with working save sequence and date/time

from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QGridLayout, QAction, QMainWindow, QMessageBox, QStatusBar, QDesktopWidget, QFileDialog, QToolBar, QTextEdit
from PyQt5.QtGui import QPixmap, QIcon, QFont, QTextCursor
from PyQt5.QtCore import pyqtSignal, Qt, QThread
import sys
import os
import cv2
import time
import numpy as np
from datetime import datetime


VERSION = "v1.00"

IMG_SIZE        = 640, 480          # 640,480 or 1280,720 or 1920,1080
CAP_API         = cv2.CAP_DSHOW
EXPOSURE        = 0
TEXT_FONT       = QFont("Courier", 10)
camera_num      = 1
CurrUser        = os.environ.get('USERNAME')



class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)


    def __init__(self, *args, **kwargs):
        super().__init__()
        self._run_flag = True
        self.timestr = time.strftime("%d-%b-%Y-%H_%M_%S")
        self.save_vpath = ''
        self.save_seq = 0
        self.full_save_vpath = os.path.join(self.save_vpath, "%04d-%s.mkv" % (self.save_seq, self.timestr))
        print(self.full_save_vpath)

    def run(self):
        total_frames = 0

        self.ThreadActive = True
        self.cap = cv2.VideoCapture(camera_num-1 + CAP_API)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, IMG_SIZE[0])
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, IMG_SIZE[1])
        if EXPOSURE:
            self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)
            self.cap.set(cv2.CAP_PROP_EXPOSURE, EXPOSURE)
        else:
            self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1)

        if self.full_save_vpath:
            width = IMG_SIZE[0]
            height = IMG_SIZE[1]
            self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
            fps = 30
            self.out = cv2.VideoWriter(self.full_save_vpath, self.fourcc, fps, (width, height))

        while self.ThreadActive:
            ret, frame = self.cap.read()
            if not ret:
                break
            if ret:
                height, width, _ = frame.shape
                rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

                self.change_pixmap_signal.emit(frame)

            if self.full_save_vpath:
                self.out.write(frame)

            total_frames += 1

        self.cap.release()


    def stop(self):
        self._run_flag = False
        self.wait()
        if self.full_save_vpath:
            self.out.release()

class MyWindow(QMainWindow):
    text_update = pyqtSignal(str)

    def __init__(self):
        super(MyWindow, self).__init__()

        self.thread = VideoThread()

        self.setWindowTitle(VERSION)
        self.setWindowIcon(QIcon('Icon/count.png'))
        cent = QDesktopWidget().availableGeometry().center()  # Finds the center of the screen
        self.frameGeometry().moveCenter(cent)
        self.widget = QWidget()
        self.layout = QGridLayout(self.widget)
        self.display_width = 1280
        self.display_height = 720
        self.image_label = QLabel(self)
        self.image_label.resize(self.display_width, self.display_height)
        self.image_label.setAlignment(Qt.AlignCenter)
        self.layout.addWidget(self.image_label)
        self.setCentralWidget(self.widget)

        #create statusbar
        self.status = QStatusBar()
        self.status.setStyleSheet("background : lightgray;")  # Setting style sheet to the status bar
        self.setStatusBar(self.status)  # Adding status bar to the main window

        #create mainmenu
        self.statusBar()
        menu_Bar = self.menuBar()
        file_menu = menu_Bar.addMenu('&File')
        play_menu = menu_Bar.addMenu('&Play')

        #create exit action
        exitAction = QAction(QIcon('Icon/exit.png'), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)
        file_menu.addAction(exitAction)

        #create save as action
        saveAction = QAction('Save as', self)
        saveAction.setStatusTip("Change folder where v/o will be saved.")
        saveAction.triggered.connect(self.change_folder)
        file_menu.addAction(saveAction)

        #create start action
        self.startAction = QAction(QIcon('Icon/rec_start.png'), 'Start Feed', self)
        self.startAction.setStatusTip("Start camera feed.")
        self.startAction.triggered.connect(self.start_feed)
        self.startAction.triggered.connect(self.start_inactive)
        self.startAction.setEnabled(True)
        play_menu.addAction(self.startAction)

        #create stop action
        self.stopAction = QAction(QIcon('Icon/rec_stop.png'), 'Stop Feed', self)
        self.stopAction.setStatusTip("Stop camera feed.")
        self.stopAction.triggered.connect(self.stop_feed)
        self.stopAction.triggered.connect(self.hide_frame)
        self.stopAction.triggered.connect(self.start_active)
        self.stopAction.setEnabled(False)
        play_menu.addAction(self.stopAction)


        #create toolbar
        self.toolbar = QToolBar('Toolbar')
        self.addToolBar(self.toolbar)
        self.toolbar.setMovable(False)
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(self.startAction)
        self.toolbar.addAction(self.stopAction)


    def update_image(self, img_label):
        qt_img = self.convert_cv_qt(img_label)
        self.image_label.setPixmap(qt_img)

    def convert_cv_qt(self, frame):
        rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb_image.shape
        bytes_per_line = ch * w
        convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
        return QPixmap.fromImage(convert_to_Qt_format)

    def write(self, text):
        self.text_update.emit(str(text))

    def flush(self):
        pass

    # Append to text display
    def append_text(self, text):
        cur = self.textbox.textCursor()     # Move cursor to end of text
        cur.movePosition(QTextCursor.End)
        s = str(text)
        while s:
            head,sep,s = s.partition("\n")  # Split line at LF
            cur.insertText(head)            # Insert text at cursor
            if sep:                         # New line if LF
                cur.insertBlock()
        self.textbox.setTextCursor(cur)     # Update visible cursor

    def start_feed(self):
        self.stopAction.setEnabled(True)
        self.image_label.setAlignment(Qt.AlignCenter)
        #self.layout.addWidget(self.image_label)
        self.thread.change_pixmap_signal.connect(self.update_image)
        self.thread.start()
        self.textbox = QTextEdit(self.widget)
        self.textbox.setFont(TEXT_FONT)
        self.textbox.setMaximumSize(640, 100)
        self.text_update.connect(self.append_text)
        sys.stdout = self
        print(datetime.now())
        self.layout.addWidget(self.textbox, 1, 0)
        self.thread.timestr = time.strftime("%d-%b-%Y-%H_%M_%S")
        self.thread.full_save_vpath = os.path.join(self.thread.save_vpath, "%04d-%s.mkv" % (self.thread.save_seq, self.thread.timestr)).replace(os.sep, '/')

    def start_inactive(self):
        self.startAction.setEnabled(False)

    def start_active(self):
        self.startAction.setEnabled(True)

    def stop_feed(self):
        if self.thread.ThreadActive:
            self.thread.change_pixmap_signal.disconnect()
            self.text_update.disconnect()
            self.thread.ThreadActive = False
            self.thread.save_seq += 1
        else:
            self.thread.terminate()

    def hide_frame(self):
        self.widget = QWidget()
        self.layout = QGridLayout(self.widget)
        self.image_label = QLabel(self)
        self.image_label.move(0, 0)
        self.layout.addWidget(self.image_label)
        self.setCentralWidget(self.widget)


    def change_folder(self):
        self.thread.save_vpath = QFileDialog.getExistingDirectory(self, "video location", "")
        if self.thread.save_vpath:
            self.thread.full_save_vpath = os.path.join(self.thread.save_vpath, "%04d-%s.mkv" % (self.thread.save_seq, self.thread.timestr)).replace(os.sep, '/')
        print(self.thread.full_save_vpath)
        self.thread.save_seq = 0

if __name__ == "__main__":

    app = QApplication(sys.argv)
    win = MyWindow()
    win.show()
    sys.exit(app.exec_())
D121
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 10 '23 at 01:52