Please help me, I'm becoming insane...
The Goal: A GUI which shows an image, three buttons to save the displayed image in a "good", "bad" and "unsure" folder and one additional button to quit the application.
Details: The displayed picture is altered by a function which should not be applied to a saved image (This works fine!). When a button is pressed the shown picture should be saved to a specific folder and the next image is to be shown. The Picture names come from a list of a pandas df, where their root folder path has to change after 252, 504 and 756 pictures ("ROOT_PICTURE_PATH_X").
PROBLEM: When I run the code everything works fine, until I reach image 249 then the program stops with the following error message and the tkinter window stops working.
Exception in Tkinter callback
Traceback (most recent call last):
File "/home/sizimmermann/anaconda3/envs/testlab_1/lib/python3.9/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
File "/tmp/ipykernel_20993/382054037.py", line 128, in <lambda>
command=lambda: good(local_img_path)
File "/tmp/ipykernel_20993/382054037.py", line 110, in good
ROOT.title(
File "/home/sizimmermann/anaconda3/envs/testlab_1/lib/python3.9/tkinter/__init__.py", line 2226, in wm_title
return self.tk.call('wm', 'title', self._w, string)
_tkinter.TclError: too many nested evaluations (infinite loop?)
My assumption: I did not set up the tkinter window correctly and the root.title is overlapping or something like this. It is my first time with tkinter though.
Additional question: Did I program the change of the root folder correctly at the end of the program or do I have to insert the "ROOT_PICTURE_PATHS" in the mainloop?
CODE:
import cv2 as cv
import tkinter as tk
import os
import pandas as pd
from PIL import Image as Pimage
from PIL import ImageTk
# change the 'init_path' to the location where you unpacked this project
INIT_PATH = '/media/sf_shared_folder/04_coding/99_pipeline'
ROOT_IMG_PATH = os.path.join(INIT_PATH, '03_sorted_data')
CLASSIFIED_IMG_PATH = os.path.join(ROOT_IMG_PATH, 'classified_images')
GOOD_IMG_PATH = os.path.join(CLASSIFIED_IMG_PATH,'good')
BAD_IMG_PATH = os.path.join(CLASSIFIED_IMG_PATH,'bad')
UNSURE_IMG_PATH =os.path.join(CLASSIFIED_IMG_PATH,'unsure')
ROOT_PICTURE_PATH_1 = os.path.join(
ROOT_IMG_PATH,'20220405/133107_MEASUREMENT_complete/ROIs'
)
ROOT_PICTURE_PATH_2 = os.path.join(
ROOT_IMG_PATH,'20220405/152317_MEASUREMENT_complete/ROIs'
)
ROOT_PICTURE_PATH_3 = os.path.join(
ROOT_IMG_PATH,'20220408/093417_MEASUREMENT_complete/ROIs'
)
ROOT_PICTURE_PATH_4 = os.path.join(
ROOT_IMG_PATH,'20220411/082727_MEASUREMENT_complete/ROIs'
)
data = pd.read_csv(os.path.join(ROOT_IMG_PATH, 'main_raw_data.csv'))
# extract a list with 'PostProcessPicturePath'
PPP = data[['PostProcessPicturePath']]
# PPP.head(1)
counter = 0
classification_list = []
def img_manipulation(local_image_to_manipulate):
img = cv.imread(local_image_to_manipulate)
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(
thresh,
cv.RETR_TREE,
cv.CHAIN_APPROX_SIMPLE
)
cv.drawContours(img, [contours[0]], 0, (0,255,0), 1)
img = cv.resize(img, (650,650))
img = Pimage.fromarray(img)
local_imgtk = ImageTk.PhotoImage(image=img)
# local_imgtk = ImageTk.PhotoImage(
# Pimage.open(local_image_to_manipulate)
# ) # image=img
return(local_imgtk)
def init_ui(local_img_path):
global BTN_P_BAD
global BTN_P_GOOD
global BTN_P_UNSURE
global BTN_P_QUIT
global classification_list
global counter
global img_label
global PPP
global ROOT
img_name = PPP.iat[counter,0]
ROOT = tk.Tk()
str_counter = str(counter)
ROOT.title(
'Image Classification for: ' img_name ' Counter: ' str_counter 'of 1008'
)
first_img_path=os.path.join(local_img_path, img_name)
start_image = img_manipulation(first_img_path)
img_label = tk.Label(image=start_image)
img_label.grid(row=0, column=0, columnspan=4)
def good(local2_img_path):
global BTN_P_BAD
global BTN_P_GOOD
global BTN_P_UNSURE
global counter
global classification_list
global GOOD_IMG_PATH
global img_label
global img_name
global PPP
global ROOT
# print('\n counter by button press: ', counter)
img_name = PPP.iat[counter,0]
if counter == 0:
img_2_save = Pimage.open(first_img_path)
image_save_string = os.path.join(
GOOD_IMG_PATH,
str('1_' first_img_path[-19:])
)
else:
current_img_path = os.path.join(local2_img_path, img_name)
img_2_save = Pimage.open(current_img_path)
image_save_string = os.path.join(
GOOD_IMG_PATH,
str('1_' current_img_path[-19:])
)
image_save_string = image_save_string
img_2_save.save(image_save_string)
counter =1
# img_label.grid_forget()
img_name = PPP.iat[counter,0]
str_counter = str(counter)
ROOT.title(
'Image Classification for: ' img_name ' Counter: ' str_counter 'of 1008'
)
next_img_path=os.path.join(local2_img_path, img_name)
next_imgtk = img_manipulation(next_img_path)
img_label = tk.Label(image = next_imgtk)
img_label.grid(row=0, column=0, columnspan=4)
classification_list.append(1)
print('\n counter after if: ', counter)
# print(len(classification_list))
BTN_P_GOOD = tk.Button(
ROOT,
text="good or '->'",
fg='green',
font='bold',
justify="center",
activebackground='green',
command=lambda: good(local_img_path)
)
BTN_P_GOOD.grid(row=1,column=3)
BTN_P_BAD = tk.Button(
ROOT,
text="bad or '<-'",
fg='red',
font ='bold',
justify="center",
activebackground='red',
command=lambda: bad(local_img_path)
)
BTN_P_BAD.grid(row=1,column=1)
BTN_P_UNSURE = tk.Button(
ROOT,
text="unsure",
fg='gray',
font ='bold',
justify="center",
activebackground='orange',
command=lambda: unsure(local_img_path)
)
BTN_P_UNSURE.grid(row=1,column=2)
ROOT.mainloop()
return
def bad(local2_img_path):
global BAD_IMG_PATH
global BTN_P_BAD
global BTN_P_GOOD
global BTN_P_UNSURE
global counter
global classification_list
global img_label
global img_name
global PPP
global ROOT
img_name = PPP.iat[counter,0]
if counter == 0:
img_2_save = Pimage.open(first_img_path)
image_save_string = os.path.join(
BAD_IMG_PATH,
str('0_' first_img_path[-19:])
)
else:
current_img_path = os.path.join(local2_img_path, img_name)
img_2_save = Pimage.open(current_img_path)
image_save_string = os.path.join(
BAD_IMG_PATH,
str('0_' current_img_path[-19:])
)
img_2_save.save(image_save_string)
counter =1
img_label.grid_forget()
img_name = PPP.iat[counter,0]
str_counter = str(counter)
ROOT.title(
'Image Classification for: ' img_name ' Counter: ' str_counter 'of 1008'
)
next_img_path=os.path.join(local2_img_path, img_name)
next_imgtk = img_manipulation(next_img_path)
img_label = tk.Label(image = next_imgtk)
img_label.grid(row=0, column=0, columnspan=4)
classification_list.append(0)
BTN_P_GOOD = tk.Button(
ROOT,
text="good or '->'",
fg='green',
font='bold',
justify="center",
activebackground='green',
command=lambda: good(local_img_path)
)
BTN_P_GOOD.grid(row=1,column=3)
BTN_P_BAD = tk.Button(
ROOT,
text="bad or '<-'",
fg='red',
font ='bold',
justify="center",
activebackground='red',
command=lambda: bad(local_img_path)
)
BTN_P_BAD.grid(row=1,column=1)
BTN_P_UNSURE = tk.Button(
ROOT,
text="unsure",
fg='gray',
font ='bold',
justify="center",
activebackground='orange',
command=lambda: unsure(local_img_path)
)
BTN_P_UNSURE.grid(row=1,column=2)
ROOT.mainloop()
return
def unsure(local2_img_path):
global BTN_P_BAD
global BTN_P_GOOD
global BTN_P_UNSURE
global counter
global classification_list
global img_label
global img_name
global PPP
global ROOT
global UNSURE_IMG_PATH
img_name = PPP.iat[counter,0]
if counter == 0:
img_2_save = Pimage.open(first_img_path)
image_save_string = os.path.join(
UNSURE_IMG_PATH,
str('2_' first_img_path[-19:])
)
else:
# ROOT.title('Image Classification for: ' img_name)
current_img_path = os.path.join(local2_img_path, img_name)
img_2_save = Pimage.open(current_img_path)
image_save_string = os.path.join(
UNSURE_IMG_PATH,
str('2_' current_img_path[-19:])
)
img_2_save.save(image_save_string)
counter =1
img_label.grid_forget()
img_name = PPP.iat[counter,0]
str_counter = str(counter)
ROOT.title(
'Image Classification for: ' img_name ' Counter: ' str_counter 'of 1008'
)
next_img_path=os.path.join(local2_img_path, img_name)
next_imgtk = img_manipulation(next_img_path)
img_label = tk.Label(image = next_imgtk)
img_label.grid(row=0, column=0, columnspan=4)
classification_list.append(2)
BTN_P_GOOD = tk.Button(
ROOT,
text="good or '->'",
fg='green',
font='bold',
justify="center",
activebackground='green',
command=lambda: good(local_img_path)
)
BTN_P_GOOD.grid(row=1,column=3)
BTN_P_BAD = tk.Button(
ROOT,
text="bad or '<-'",
fg='red',
font ='bold',
justify="center",
activebackground='red',
command=lambda: bad(local_img_path)
)
BTN_P_BAD.grid(row=1,column=1)
BTN_P_UNSURE = tk.Button(
ROOT,
text="unsure",
fg='gray',
font ='bold',
justify="center",
activebackground='orange',
command=lambda: unsure(local_img_path)
)
BTN_P_UNSURE.grid(row=1,column=2)
ROOT.mainloop()
return
BTN_P_GOOD = tk.Button(
ROOT,
text="good or '->'",
fg='green',
font='bold',
justify="center",
activebackground='green',
command=lambda: good(local_img_path)
)
BTN_P_BAD = tk.Button(
ROOT,
text="bad or '<-'",
fg='red',
font ='bold',
justify="center",
activebackground='red',
command=lambda: bad(local_img_path)
)
BTN_P_UNSURE = tk.Button(
ROOT,
text="unsure",
fg='gray',
font ='bold',
justify="center",
activebackground='orange',
command=lambda: unsure(local_img_path)
)
BTN_P_QUIT = tk.Button(
ROOT,
text="QUIT",
fg='red',
font ='bold',
justify="center",
activebackground='red',
command = lambda: ROOT.destroy()
)
BTN_P_BAD.grid(row=1,column=1)
BTN_P_GOOD.grid(row=1,column=3)
BTN_P_UNSURE.grid(row=1,column=2)
BTN_P_QUIT.grid(row=1,column=4)
print('\n *** Mainloop start *** \n')
# ROOT.after(2000, lambda: ROOT.destroy())
ROOT.mainloop()
print(
'************************************************* \n'
'*** Please classify the Pictures in the GUI. ***'
'\n *************************************************')
try:
os.mkdir(CLASSIFIED_IMG_PATH)
os.mkdir(GOOD_IMG_PATH)
os.mkdir(BAD_IMG_PATH)
os.mkdir(UNSURE_IMG_PATH)
print(
'*** Folder classified images created in:\n',
CLASSIFIED_IMG_PATH,
' ***'
)
except OSError:
pass
if counter == 0:
init_ui(ROOT_PICTURE_PATH_1)
print('First batch samples classified.')
elif counter == 252:
init_ui(ROOT_PICTURE_PATH_1)
print('Second batch samples classified.')
elif counter == 504:
init_ui(ROOT_PICTURE_PATH_1)
print('Third batch samples classified.')
elif counter == 756:
init_ui(ROOT_PICTURE_PATH_1)
print('\n *** Classification completed. ***')
CodePudding user response:
So the root cause of your problem is you're re-entrantly calling mainloop
, which is a no-no.
All in all, your code really needs a bunch of cleaning to make it work; since I don't have your local dataset and can't quite infer the real structure of it on disk, this might not be exactly what you want but it should get you closer.
The idea is that you build the base UI once and just reconfigure it when you flip through the images.
import shutil
import sys
import cv2 as cv
import tkinter as tk
import os
import pandas as pd
from PIL import Image, ImageTk
# change the 'init_path' to the location where you unpacked this project
INIT_PATH = "/media/sf_shared_folder/04_coding/99_pipeline"
ROOT_IMG_PATH = os.path.join(INIT_PATH, "03_sorted_data")
CLASSIFIED_IMG_PATH = os.path.join(ROOT_IMG_PATH, "classified_images")
def img_manipulation(local_image_to_manipulate):
img = cv.imread(local_image_to_manipulate)
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, [contours[0]], 0, (0, 255, 0), 1)
img = cv.resize(img, (650, 650))
img = Image.fromarray(img)
return ImageTk.PhotoImage(image=img)
class ImageClassificationApp:
def __init__(self, root, image_paths):
self.root = root
self.image_paths = list(image_paths)
self.current_image_index = 0
self.build_ui()
self.update_image()
def build_ui(self):
good_button = tk.Button(
self.root,
text="good or '->'",
fg="green",
font="bold",
justify="center",
activebackground="green",
command=lambda: self.mark_current("good"),
)
good_button.grid(row=1, column=3)
bad_button = tk.Button(
self.root,
text="bad or '<-'",
fg="red",
font="bold",
justify="center",
activebackground="red",
command=lambda: self.mark_current("bad"),
)
bad_button.grid(row=1, column=1)
unsure_button = tk.Button(
self.root,
text="unsure",
fg="gray",
font="bold",
justify="center",
activebackground="orange",
command=lambda: self.mark_current("unsure"),
)
unsure_button.grid(row=1, column=2)
quit_button = tk.Button(
self.root,
text="QUIT",
fg="red",
font="bold",
justify="center",
activebackground="red",
command=lambda: sys.exit(),
)
quit_button.grid(row=1, column=4)
img_label = tk.Label()
img_label.grid(row=0, column=0, columnspan=4)
self.img_label = img_label
@property
def current_image(self):
if self.current_image_index < len(self.image_paths):
return self.image_paths[self.current_image_index]
return None
def update_image(self):
img_name = self.current_image
if not img_name:
return # TODO: handle running out of images more gracefully
self.root.title(
f"Image Classification for: {img_name} Counter: {self.current_image_index} of {len(self.image_paths)} "
)
img_path = os.path.join(ROOT_IMG_PATH, img_name)
image = img_manipulation(img_path)
self.img_label.configure(image=image)
def mark_current(self, mark):
img_name = self.current_image
if not img_name:
return # TODO: handle running out of images more gracefully
dest_dir = os.path.join(CLASSIFIED_IMG_PATH, mark)
os.makedirs(dest_dir, exist_ok=True)
dest_path = os.path.join(dest_dir, img_name)
shutil.copy(os.path.join(ROOT_IMG_PATH, img_name), dest_path)
self.go_to_next_image()
def go_to_next_image(self):
self.current_image_index = 1
self.update_image()
def main():
data = pd.read_csv(os.path.join(ROOT_IMG_PATH, "main_raw_data.csv"))
paths = list(data["PostProcessPicturePath"].values)
root = tk.Tk()
app = ImageClassificationApp(root, paths)
root.mainloop()
if __name__ == "__main__":
main()