Tkinter - Possible Bug, Possible Misunderstanding

Hello,
I’m working on a GUI for a virtual hardware camera/filter project, and I wanted the GUI to show the 3D file that will be filtered over the user’s face before the virtual hw cam starts. Thus, I’ve been finding various ways to put an image in a tk.Label object and creating that Label when the visualization button is pressed. I kept finding different chunks of code that would work, and show the image rendering, in certain circumstances, and not work when part of a function in a command from the button.

Now, I’ve found a simple script that depicts some weird behavior in terms of showing the image rendering or not. Here’s the code:

import tkinter as tk
import pyvista as pv
from PIL import Image, ImageTk
import time
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 800
phat_list = []
images_reference_list = []

def find_photos():
    plotter = pv.Plotter(off_screen=True, lighting='none')
    light1 = pv.Light()
    light2 = pv.Light(position=(-5, 13, 2))
    light1.intensity = 1.8
    light2.intensity = 2.
    plotter.add_light(light1)
    plotter.add_light(light2)
    #plotter.import_gltf('./Actual3DPunk_%s.glb' %str(punk_num) )
    plotter.import_gltf('./Actual3DPunk_1755.glb' )
    plotter.camera.zoom(1.3)
    plotter.background_color = (255, 255, 255)
    cpos_ = [(0, 12.5, 75),
             (9.2, 11.3, -8.1),
             (0, 1, 0)]
    plotter.camera_position = cpos_
    punk_img = plotter.screenshot(window_size=(400, 400))

    img = Image.fromarray(punk_img)

    photo_image = ImageTk.PhotoImage(img)
    #tk.Label(window, image=photo_image).pack(side=tk.TOP)
    tk.Label(window, image=photo_image).grid(column=0,row=0,pady=2,padx=2)
    #global new_var
    #new_var = []
    #new_var.append(2)
    #time.sleep(2)
    #photo_image = photo_image
    phat_list.append(photo_image)
    #images_reference_list.append(photo_image)


window = tk.Tk()
#creates the canvas
canvas = tk.Canvas(window, width = WINDOW_WIDTH,
                   height = WINDOW_HEIGHT, bg="green")
#canvas.pack(side=tk.BOTTOM)
canvas.grid(column=0,row=2,pady=20,padx=20)

b1 = tk.Button(canvas, text="Click me to add 5 photos of yourself",
               height = 5, width = 30, command = find_photos)
canvas.create_window(WINDOW_WIDTH//3, WINDOW_HEIGHT//3, window = b1)


window.mainloop()

Btw, I found this from a random forum post with someone helping to create a label after .mainloop() is entered, that’s why it’s called ‘find_photos’ etc. and it’s also why there are variables phat_list and images_references_list. When trying to minimize the code to see where it breaks, I found weird behavior.

As one can see, phat_list doesn’t really do anything and it’s not part of the b1 Button object or anything. However, if the phat_list.append(photo_image) line is commented out, when ran and the button is clicked, the image rendering does NOT show in the GUI. However, if that line is executed before the end of the function, or #images_reference_list.append(photo_image) is run before the end of the function, it WILL show the image in the GUI when the program is ran and the button is clicked. I thought I read somewhere that timing of functions finishing relative to something in another thread might affect what tkinter does, so I tried time.sleep() to get it to wait until after some other thread, but it doesn’t work. I then questioned why appending to a list OUTSIDE the scope of the commanded function does something, and wondered if I created a global variable, if it would render the image properly. Well, when phat_list.append(photo_image) and images_references_list.append(photo_image) are commented out, no out-of-scope variable declaration or extra command seems to let the image render properly. It’s just gray space on the GUI. I left the extra commands in the code to be uncommented to recreate the behavior, and the 3D file can be found here: https://ipfs.io/ipfs/QmPiTf9gXc7JgR6uA7GeYLCQzfUAawoBHbq7iRA6qCpKJg/Actual3DPunk_1755.glb

This is weird to me that adding the image object to a random higher-scope list will let the GUI work, but waiting, creating new variables, reassigning that same image object, etc. all do NOT let the GUI work as it should. Is this a weird bug or am I misunderstanding something, and that’s how it should behave?
Btw, I can create a video of the behavior to illustrate my point more clearly if needed.

Thanks!

Hi !

If I understood your problem correctly, it is that your image won’t show unless a reference to photo_image is stored somewhere before find_photos() returns. Others stumbled over this problem before (see python - Tkinter Label does not show Image - Stack Overflow).

Basically, Tkinter won’t keep a reference to the PhotoImage you pass to a Label. So, if you don’t do it yourself, the PhotoImage will be garbage collected at some point after exiting find_photos()'s scope, and therefore not be accessible after that.
I’m not sure why Tkinter doesn’t store a reference to the PhotoImage, maybe others will kown.

Thanks so much for this feedback. I understand now, and another thing that worked was

a = tk.Label(window, image=photo_image)
a.image = photo_image
a.grid(column=0,row=0,pady=2,padx=2)

as the a.image = photo_image seemed to prevent the garbage collection as well.