I try to build a GUI application to grab frames from a camera and display them in a Tkinter GUI. The Tkinter mainloop is executed in the main thread, while the frame grabbing and updating of the gui takes place in a separate thread.
The code below works as a video stream is grabbed and displayed correctly in my gui window. However when I invoke the on_close() method by clicking the "x" to close the gui, the gui will close, but the program won't terminate fully. Last CLI output will be "Mainloop stopped!", but the program does not terminate as I would expect. So, I suspect the additional thread keeps on running, even though I quit the while loop in the threads run() method via the stop event.
This is my code:
import threading    
import cv2
import tkinter
from tkinter import ttk
from PIL import Image
from PIL import ImageTk
import camera
class mainThread(threading.Thread):
    def __init__(self, gui):
        threading.Thread.__init__(self)
        self.stop_event = threading.Event()
        self.gui = gui
    def stop(self):
        print("T: Stop method called...")
        self.stop_event.set()
    def run(self):
        print("Connecting to camera...")
        cam = camera.Camera(resolution=(1280, 960), exposure=-3, bit_depth=12)
        cam.connect(device_id=0)
        while (not self.stop_event.is_set()):
            print("running..., stop event = " + str(self.stop_event.is_set()))
            # retrieve frame and frame time
            frame, _, _ = cam.get_frame()
            # display frame
            self.gui.updater(frame)
            # wait for displaying image
            cv2.waitKey(1)
class gui(object):
    def __init__(self, root):
        self.root = root
        # create and start thread running the main loop
        self.main_thread = mainThread(self)
        self.main_thread.start()
        # bind on close callback that executes on close of the main window
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        # change window title
        self.root.title("MCT Laser Welding Quality Control")
        # set min size of root window and make window non resizable
        self.root.minsize(600, 400)
        self.root.resizable(False, False)
        # configure grid layout
        self.root.rowconfigure(0, weight=1)
        self.root.rowconfigure(1, weight=1)
        self.root.columnconfigure(0, weight=1)
        # create image panel
        self.image_panel = None
    def on_close(self):
        self.main_thread.stop()
        self.root.destroy()
    def updater(self, image):
        # TODO: resize frame first
        # convert image to tkinter image format
        image = Image.fromarray(image)
        image = ImageTk.PhotoImage(image)
        # if the panel does not exist, create and pack it
        if self.image_panel is None:
            # show the image in the panel
            self.image_panel = ttk.Label(self.root, image=image)
            self.image_panel.image = image  # keep reference
            # pack object into grid
            self.image_panel.grid(row=0, column=0)
        # just update the image on the panel
        else:
            self.image_panel.configure(image=image)
            self.image_panel.image = image  # keep reference
    def run(self):
        self.root.mainloop()
if __name__ == "__main__":
    # create a main window
    root = tkinter.Tk()
    # set style
    style = ttk.Style()
    style.theme_use("vista")
    # create gui instance
    user_interface = gui(root)
    # run the user interface
    root.mainloop()
    print("Mainloop stopped!")
EDIT: I found out, that the line
image = ImageTk.PhotoImage(image)
is preventing the thread from stopping as described above. When I remove this line and simply update the label text with an incrementing number, everything works as excepted and the thread terminates, when I close the gui window. Any ideas, why ImageTk.PhotoImage() causes the thread to not terminate properly?
 
    