"_tkinter.TclError: too many nested evaluations" in Classification GUI

"_tkinter.TclError: too many nested evaluations" in Classification GUI

请帮帮我,我快疯了...

目标: 一个显示图像的 GUI,三个用于将显示的图像保存在“好”、“坏”和“不确定”文件夹中的按钮,以及一个用于退出应用程序的附加按钮。

详情: 显示的图片被不应应用于已保存图像的功能更改(这很好用!)。按下按钮时,显示的图片应保存到特定文件夹,然后显示下一张图像。图片名称来自pandas df的列表,其中它们的根文件夹路径必须在252、504和756张图片(“ROOT_PICTURE_PATH_X”)之后更改。

问题: 当我 运行 代码一切正常时,直到我到达图像 249 然后程序停止并显示以下错误消息并且 tkinter window 停止工作。

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?)

我的假设: 我没有正确设置 tkinter window 并且 root.title 重叠或类似的东西。不过,这是我第一次使用 tkinter。

附加问题: 我是否在程序末尾正确编程了根文件夹的更改,还是必须在主循环中插入“ROOT_PICTURE_PATHS”?

代码:

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. ***')

所以你的问题的根本原因是你 re-entrantly 调用 mainloop,这是一个 no-no.

总而言之,您的代码确实需要大量清理才能正常工作;因为我没有你的本地数据集,也不能完全推断出它在磁盘上的真实结构,这可能不是你想要的,但它应该能让你更接近。

我们的想法是,您构建基础 UI 一次,然后在浏览图像时重新配置它。

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