'How to use Threading correctly with a GUI and Threads in my Case

I use a 2 Thread Webcam Stream Class to show a Live Stream on my GUI. (As an Overlay, I didn't find an other Way with high FPS) And I use a GUI Function for my GUI. (No Class yet, because i am not good in classes)

I need to join() the 2 Stream Threads, because they depend on an Event. So I could speed up the Stream, I think.

How can I join() them without blocking my "Mainthread" with the GUI ?

Using the GUI as Daemon didn't work.

May be I can split the Threads in to different Pools in some Way, to be able to use join()?

I would be happy if Someone could help me to understand what to to.

Here is my minimalistic Code:

import sys
sys.path.append('C:\\Users\\User\\Python\\pyproj\\project2')
sys.path.append('C:\\Users\\User\\Python\\pyproj\\project1\\Lib\\site-packages\\paho_mqtt-1.6.1-py3.10.egg')
sys.path.append('C:\\Users\\User\\Python\\modules\\')
sys.path.append('C:\\Users\\User\\Python\\pyproj\\project1\\Lib\\site-packages\\pyparsing-3.0.7-py3.10.egg')
sys.path.append('C:\\Users\\User\\Python\\pyproj\\project1\\Lib\\site-packages\\packaging-21.3-py3.10.egg')
sys.path.append('C:\\Users\\User\\Python\\Medien\\Bilder')
from imutils.video import FPS
import threading
from threading import Thread
from threading import Event

import numpy as np
import argparse
import imutils
from videoclasses import FPS
from videoclasses import WebcamVideoStream 
import cv2
import tkinter as tk
import time

if sys.version_info >= (3, 0):
    from queue import Queue
else:
    from  Queue import Queue

import PIL.Image as Image
import PIL.ImageTk as ImageTk
import logging
                    
cam1fullurl="http://User:password@IP/axis-cgi/mjpg/video.cgi"
#cam1fullurl='C:/Users/User/Python/Medien/Videos/example.mp4'
streamcap = cv2.VideoCapture(cam1fullurl)

def start():
    print('start pressed')
    global streamactivekey
    streamactivekey = True
    #global waspaused
    #paused = True
    #T_ImVideo = threading.Thread(target=ImVideo)
    #T_ImVideo.start()
    ImVideo()

def start2():
    print('start pressed')
    global streamactivekey
    streamactivekey = True
    mts=multi_thread_stream(ready)
    #mts.t1.join()
    #mts.t2.join()

def stop():
    print('stop pressed')
    global streamactivekey
    streamactivekey = False
    global waspaused
    waspaused = True
    ImVideo()

window = tk.Tk()
window.title('Test')
#main thread id
print('Main Thread ID = ', threading.get_ident())

ready = threading.Event()
ready = ready
global videoframe
global videolabel1
global win_name

def framework():
    print('framework Thread ID = ', threading.get_ident())
    window.geometry("800x600+10+20")
    frame1 = tk.Frame(window,bg='green')
    frame1.place(relheight=1,relwidth=1)
    global videoframe
    videoframe = tk.Frame(window,bg="red",highlightbackground="black", highlightthickness=1)
    videoframe.place(relx = 0.25, rely=0.25, relheight = 0.5, relwidth = 0.5)
    global videolabel1
    videolabel1 = tk.Label(videoframe,fg='#000000', bg = 'yellow')
    videolabel1.place(relx = 0.5, rely = 0.5, anchor = "center")
    videolabel1.config(font=("Courier bold", 20))
    #basic setup
    if streamcap.isOpened():
        width  = streamcap.get(cv2.CAP_PROP_FRAME_WIDTH)   # float `width`
        height = streamcap.get(cv2.CAP_PROP_FRAME_HEIGHT)  # float `height`
        size = "Breite: "+str(int(width))+"  Höhe :"+str(int(height))
        print('Videosize :',size)
    global win_name
    win_name = 'Stream'
    streamactivekey = True
    global waspaused
    waspaused = False
    
    button1 = tk.Button(frame1,text='Start',command=start2)
    button1.place(rely=0.9,relx=0,relheight=0.1,relwidth=0.1)
    button2 = tk.Button(frame1,text='Stop',command=stop)
    button2.place(rely=0.9,relx=0.9,relheight=0.1,relwidth=0.1)

class multi_thread_stream:
    #global streamactivekey
    def __init__(self, ready=None):
        self.ready = ready
        self.cap = cv2.VideoCapture(cam1fullurl)
        self.frame = {}
        #Create the Threads
        #self.t1 = threading.Thread(target=self.capture_stream,daemon=True)
        #self.t2 = threading.Thread(target=self.display_image,daemon=True)
        self.t1 = threading.Thread(target=self.capture_stream)
        self.t2 = threading.Thread(target=self.display_image)
        self.t1.name = 'capture_thread'
        self.t2.name = 'display_thread'
        self.t1.start()
        self.t2.start()

    def capture_stream(self):
        while True:
            # Capture frame-by-frame
            self.ret, self.frame = self.cap.read()
            self.ready.set()
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

    def display_image(self):
        while True:
        #global waspaused
        #global streamactivekey
        #if streamactivekey:
            # Display the resulting frame
            self.ready.wait()
            fwidth = videoframe.winfo_width()
            fheight = videoframe.winfo_height() 
            frootx = videoframe.winfo_rootx()
            frooty = videoframe.winfo_rooty()
            global picnostreamsize
            picnostreamsize = (frootx, frooty)
            cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
            cv2.resizeWindow(win_name,fwidth, fheight)
            cv2.moveWindow(win_name, frootx, frooty)
            cv2.imshow(win_name,self.frame)
            #cv2.imshow('frame_2nd_trhead', self.frame)
            cv2.setWindowProperty('Stream', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
            self.ready.clear()
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break;
            #window.after(1,ImVideo)

    def __del__(self):
        # When everything done, release the capture
        self.cap.release()
        cv2.destroyAllWindows()


def ImVideo():
    global waspaused
    if streamactivekey:
        ret, frameiv = streamcap.read()
        #frameiv = threading.Thread(target=capread).start()
        fwidth = videoframe.winfo_width()
        fheight = videoframe.winfo_height() 
        frootx = videoframe.winfo_rootx()
        frooty = videoframe.winfo_rooty()
        fx = videoframe.winfo_x()
        fy = videoframe.winfo_y()
        global picnostreamsize
        picnostreamsize = (frootx, frooty)
        #print(f'frootx:{frootx} frooty:{frooty}')
        #print(f'fx:{fx} fy:{fy}')
        cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(win_name,fwidth, fheight)
        cv2.moveWindow(win_name, frootx, frooty)
        cv2.imshow(win_name,frameiv)
        if waspaused:
            print('waspaused')
            cv2.setWindowProperty('Stream', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
            cv2.setWindowProperty('Stream', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL)
            waspaused = False
        cv2.setWindowProperty('Stream', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
        window.after(1,ImVideo)
        
def setpicnostream():
    global picnostream
    global videoframe
    global videolabel1
    img = Image.open(r'C:\Users\User\Python\Medien\Bilder\streamoff.png')
    x = videoframe.winfo_width()
    y = videoframe.winfo_height()
    print('x :',x)
    print('y :',y)
    img = img.resize((x,y), Image.ANTIALIAS)
    picnostream = ImageTk.PhotoImage(img)
    videolabel1.config(font=("Courier bold", 20),image = picnostream)


#framework()
T_framework = threading.Thread(target=framework,daemon=True)
T_framework.start()

window.after(50,setpicnostream)



window.mainloop()

As far as I understand the .read() Operation is an IO Blocker. So the .read() and the imshow() should be in the same "Threadpool" depending on the Event .ready().

Later I want to use up to 3 Webcams, so I would need 4 "Threadpools" that dont block each other. GUI, Cam1, Cam2, Cam3.



Solution 1:[1]

there is no code here, so it is very hard to say. You need some way to send a message into the code running in the threads so that its terminated from "inside" (for example, returning from the entry function). This can be something as sophisticated as a queue, or you can just use a dictionary (or other variable) at global level: you set this var in the control thread, and test and act upon its value in the target thread.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jsbueno