program does not terminate (tkinter + openCV + threading) - python

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?

You need to join the thread to wait for it to close.
def stop(self):
print("T: Stop method called...")
self.stop_event.set()
self.join()
Also, you may need to close the open connection to the camera.
while (not self.stop_event.is_set()):
...
cam.close() # not sure about exact API here

Related

Tkinter application does not close on root.destroy()

I've been developing a tkinter application for downloading a file from the internet and showing the progress of it on a progress bar (Progressbar).
Since the download process was different from the GUI, (and since tkinter runs the GUI the main thread on an infinite loop), I figured to make a different thread for the download process to carry on, and it would update the progress bar in the background
I would then go on to wait for the background process to end, and call the root.destroy() method to end the application, but the application doesn't close, on closing the application manually - it would return the following error
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
Here is the code for the
class download:
#Initializing the root
def __init__(self):
self.root = Tk()
#creating the progress bar and packing it to the root
def create_progress_bar(self):
self.progress_bar = Progressbar(self.root, orient = HORIZONTAL, length = 200, mode = 'determinate')
self.progress_bar.pack(pady=10)
#function to start the mainloop
def start_application(self):
self.root.mainloop()
#updating the progress bar from the background thread
def update_progress(self, count, blocksize, totalsize):
update = int(count * blocksize * 100 / totalsize)
self.progress_bar['value'] = update
self.root.update_idletasks()
#downloading the file from the background thread
def download_file(self, download_url):
#using the reporthook to get information about download progress
urllib.request.urlretrieve(download_url, 'a.jpg', reporthook=self.update_progress)
#closing the root function
def close(self):
self.root.destroy()
#handling the download
def handle_download(self, download_url):
#creating a different thread
self.process = Thread(target=self.download_file, args=(download_url,))
#creating the progress bar
self.create_progress_bar()
#starting the background thread
self.process.start()
#starting the GUI
self.start_application()
#waiting for the background thread to end
self.process.join()
#closing the application
self.close()
ob = download()
ob.handle_download('link')
For what it's worth, I did try to make things happen on the same thread, but the application could not respond to the updates.
Any insight on the matter would be extremely welcome.
Since your program does self.root.destroy() only after self.root.mainloop() has returned, it does not reach the point of destruction unless you're closing the application manually, and then it's pointless to call .destroy().
You could check from time to time whether the download thread is running, and destroy the window when not.
def watch(self):
if self.process.is_alive(): self.root.after(500, self.watch)
else: self.close()
#handling the download
def handle_download(self, download_url):
…
#starting the background thread
self.process.start()
self.watch()
…
#closing the application
#self.close() is too late here

display single image in fullscreen mode (like powerpoint)

I am trying to open an image in fullscreen to overlap the taskbar and show no menu bar using python.
I got it to work using Tkinter with following code, but since its just a single image that does not need to be updated (ever), the blocking loop of mainloop() is very inconvenient and unnecessary. However, I can't get tkinter to stay open without that update loop. I've tried using root.after() to run the function afterwards but that froze my program (and isn't really what I want to do anyway)
root = tk.Tk()
root.update_idletasks()
root.attributes('-fullscreen', True)
root.overrideredirect(1)
output = self.create_image_from_array(image_array)
canvas = tk.Canvas(root, width=root.winfo_width(), height=root.winfo_height())
canvas.create_image(0, 0, image=output, anchor="nw")
canvas.pack(fill=tk.BOTH, expand=1)
# after freezes my window
# root.after(0,someFunction)
# main loop blocking the function.
root.mainloop()
How can I display a single image on the entire screen using python (in both windows and linux) or how can I "pause" the tkinter loop to prevent the window from closing and updating?
It looks like you will have to handle tkinter's thread yourself rather than relying on mainloop(). Here is one way to do it:
import tkinter as tk
from threading import Thread
import time
class SomeFunc:
def __init__(self):
self.running=True
t = Thread(target=self.func)
t.start()
def func(self):
self.root = tk.Tk()
self.root.update_idletasks()
#root.attributes('-fullscreen', True)
#root.overrideredirect(1)
canvas = tk.Canvas(self.root, width=self.root.winfo_width(), height=self.root.winfo_height())
#canvas.create_image(0, 0, image=output, anchor="nw")
canvas.pack(fill=tk.BOTH, expand=1)
while self.running:
self.root.update()
a = SomeFunc()
x = 10
while x:
x-=1
time.sleep(1)
print(x)

Why my progressbar in toplevel window is not moving?

I am actually creatng a gui which include searching data.I want to show a loading progress bar (indeterminate) to show results are being loaded .I want this progress bar in a new toplevel window but I tried to place it but it didn't showed at first.So after many research ,I make it to appear on screen but there is a problem with it. It opens the window with progress bar and everything freezes and progressbar doesn't work itself.But window is destroyed on time.I want that progressbar moves so that it looks responsive so that user gets idea that something is going on.I start progress bar just before loading results and then starts loading results which include for loops for 2-3 times then all results are gathered then the toplevel window is completely destroys .
Everything is happening accept the new window is freezing and progressbar is not moving.
I have tried everything I could.
class Loading:
def __init__(self,title):
self.title = title
self.window = Toplevel()
self.window.geometry('350x40+100+100')
self.window.title(self.title)
self.window.lift()
self.window.grab_set()
self.window.focus()
def start(self):
self.pbar = Progressbar(self.window, orient="horizontal",length=300,
mode="indeterminate")
self.pbar.place(x=10,y=5,height = 20,width = 300)
print('starting loading')
self.pbar.start()
self.window.update()
def stop(self):
self.window.destroy()
I want that my toplevel window display progressbar moving till all rseults are loaded from different files and it gets destroyed after this
Maybe you can try this snippet of code !
from tkinter import *
from tkinter.ttk import *
root = Tk()
root.geometry('100x100')
def progressbar():
pwin = Toplevel(root)
pwin.geometry('350x40+100+100')
pwin.title('Sample progressbar')
pbar = Progressbar(pwin, orient="horizontal", length=300,
mode="indeterminate")
pbar.place(x=10, y=5, height=20, width=300)
print('starting loading')
pbar.start()
pwin.update()
Button(root, text='PBAR', command=progressbar).pack()
root.mainloop()
And then you can use pwin.destroy() to destroy the progressbar after loading is finished !

Tkinter Show splash screen and hide main screen until __init__ has finished

I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.

Access to Tkinter's Text widget by background thread cause crashes

I created a simple tkinter app, where have used two threads. Their task is to write numbers to widgets such as label and text. One thread is triggered by button (click event) and second is executed as a background thread.
import Tkinter as tk
from ttk import *
from Tkconstants import *
import threading, thread, time
def tl1(text,counter):
while True:
text.insert(END,counter)
counter += 1
time.sleep(2)
def tl2(label,counter):
while True:
label['text'] = counter
counter += 1
time.sleep(1)
class mainWindow():
def __init__(self, master):
self.master = master
self._initLayout()
def _initLayout(self):
#button
self.button = tk.Button(self.master, text="thread1_start", command = self._task1)
self.button.pack()
#label
self.label = tk.Label(self.master)
self.label.pack()
#text
self.text = tk.Text(self.master, width=30)
self.text.pack()
def _task1(self):
t1 = thread.start_new_thread(tl1,(self.text,1))
def _task2(self):
t2 = thread.start_new_thread(tl2,(self.label,1000))
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainWindow = mainWindow(self)
self.mainWindow._task2() #background_thread
app = App()
app.mainloop()
In this manner everything works fine, but if we change the background thread to display results on text widget, the whole app freezes.
Why background thread works fine communicating with label but causes problems with text widget? Is there any way to run it properly?
Tkinter isn't thread safe. You can only access widgets from the thread that created them. Your threads will need to put data on a thread-safe queue, and your GUI thread will need to poll the queue.
In your particular case you don't need threads at all. You can use the tkinter after method to run code periodically.

Categories

Resources