Home > Software design >  "_tkinter.TclError: too many nested evaluations" in Classification GUI
"_tkinter.TclError: too many nested evaluations" in Classification GUI

Time:05-05

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()
  • Related