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?