Image doesn't exist error when I try to use it to create a canvas image in tkinter but I have confirmed that it is not being garbage collected

I am trying to create an image for my tkinter canvas. The image path is fine, the PhotoImage object gets created without issue but when I try to create the canvas image using the PhotoImage object, an exception is raised.

If I run this test code, everything works fine and the canvas image is created:

import tkinter as tk
from tkinter import PhotoImage

root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600)
canvas.pack()

image_path = "images/card_front2.png"
try:
    pic = PhotoImage(file=image_path)
    canvas.create_image(415, 265, image=pic)
except Exception as e:
    print(f"Error loading image: {e}")

root.mainloop()

But now I am creating tkinter class objects within my own classes to create a flashcard app. Here is the relevant portion of my class to create the canvas setup that will be rooted to the reference_window. The focus is on the final 7 lines.

class CanvasSetup:
    dutch_word = ""
    eng_word = ""
    counter = 1
    pic_list = []

    def __init__(self, window_ref, data, lang, colour_light, colour_dark, colour_neutral):
        ##### Define inputs #####
        self.window_ref = window_ref
        self.data = data
        self.my_lang = lang
        self.colour_light = colour_light
        self.colour_dark = colour_dark
        self.colour_neutral = colour_neutral

        ##### Get image for English canvas #####
        self.eng_pic = PhotoImage(file="card_back2.png")
        CanvasSetup.pic_list.append(self.eng_pic)
        self.non_eng_pic = None  # Initially set to None


        ##### Get number list for length of data inputted #####
        self.number_list = [num for num in range(len(self.data["ENG"].keys()))]

        ##### Create canvas #####
        # Get image for other lang canvas ##
        if self.my_lang == "Dutch":
            self.non_eng_pic = PhotoImage(file="card_front2.png")
            CanvasSetup.pic_list.append(self.non_eng_pic)
        else:
            self.non_eng_pic = PhotoImage(file="card_front2.png")
            CanvasSetup.pic_list.append(self.non_eng_pic)

        self.canvas = Canvas(self.window_ref, width=800, height=500, highlightthickness=0, bg=self.colour_light)
        self.canvas.grid(column=0, row=0, columnspan=3)
        print(f"non_eng_pic: {CanvasSetup.pic_list[1]}")
        try:
            self.canvas_img = self.canvas.create_image(415, 265, image=CanvasSetup.pic_list[1])

        except Exception as e:
            print(e)
        print(f"non_eng_pic###########: {CanvasSetup.pic_list[1]}")

The first print statement prints:
“non_eng_pic: pyimage3”

Then the ‘try:’ block errors out so the ‘except:’ block gets printed:
“image “pyimage3” doesn’t exist”

But then the final print statement prints:
“non_eng_pic###########: pyimage3”

So, the image is not getting garbage collected. What am I doing wrong? I have tried everything I can think of and searched for answers but nothing gets rid of this error. Please help.

Hello,

notice that in your "working" script, you define the path of the image by including the "images" folder as part of the path.

However, in your "non-working" class-based script, you only include the image filename.

Unless you will always be working from the same working directory as the image, it will help if you include the PATH of the image. Here is an example assuming there are additional folders in the PATH relative to the C: drive.

self.eng_pic = r'C:\folder1\folder2\images\card_front2.png'

Hi,

Thank you for your response. I actually have the image in both the “images” folder inside the working directory and directly inside the working directory to see if it made a difference if I call it directly or use the path, but it doesn’t.

The image is created successfully, so that isn’t the problem. It then errors out at the canvas.create_image() step, saying that the image doesn’t exist. Then the very next line of code shows that the image does still exist.

Could you include a complete running program for the case where it doesn’t work, so that we can reproduce the error? It works fine for me when I run your second example like this:

from tkinter import Tk, PhotoImage, Canvas

# The code from your second example goes here.
# ...

def main():
    root = Tk()
    CanvasSetup(root, {"ENG": {}}, None, None, None, None)
    root.mainloop()

if __name__ == "__main__":
    main()

Does that work for you as well?

It is a little curious that I get “pyimage2” when I run it whereas you apparently get “pyimage3”. It’s only creating two images, as far as I can tell, so how is it getting to number 3? Since you’re storing the images in a class attribute (as opposed to an instance attribute) perhaps it’s getting the wrong image from the wrong window instance somehow? You could try replacing CanvasSetup.pic_list[1] with self.non_eng_pic in the self.canvas.create_image call and see if that helps.

Also, it might be easier to debug if you don’t catch the exceptions (i.e. if you remove the try/except). The traceback you get by default often contains useful information.

Hello,

inspecting your script more carefully, you have the same statements (image: "card_front2.png") for two conditions. If the images need to be unique for the two conditions, you need to provide different images. Otherwise, there is no need for the conditional statements:

I would advise on using the complete path for ALL the images so that there are no issues relative to the working directory.

Update
I have a question regarding this line:

  1. The attribute self.number_list is not being referenced anywhere else in the script that you provided. Is it really necessary?
  2. The way that it is written, obtaining the length of (self.data["ENG"].keys()) only makes sense if “ENG” references another dictionary (nested dictionary). The following test scripts highlight the two potential use cases to obtain a list of numbers:

Example if referencing a nested dictionary:

cars = {'Audi': 'European', 'Chevy': 'American', 'Volkswagen': 'German'}
sample = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'cars': cars}

number_list = [num for num in range(len(sample["cars"].keys()))]
print(number_list)

Example if not referencing a nested dictionary:

cars = {'Audi': 'European', 'Chevy': 'American', 'Volkswagen': 'German'}

sample = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'cars': cars}
number_list = [num for num in range(len(sample))]
print(number_list)

What is the nature of self.data?

Hi all,

So, I have figured out what the problem is and it actually isn’t in this block of code.

In my app, I am creating 2 tkinter windows but both of them I created using Tk(), instead of creating the second widow (that uses this CanvasSetup class) using Top_Level().

Once I fixed this mistake, my code now works perfectly.

Thank you for everyone’s feedback, I really appreciate it.

For others with a similar problem, the tkinter photo doc mentions a need to keep a python reference to the PhotoImage.