Search code examples
pythonmatplotlibtkintertkinter-canvas

Tkinter image doesn't display without matplotlib


My code generates an image as a numpy array, and then displays it in a canvas object. I use matplotlib for debugging and when when display the matplotlib window, it seems to work correctly.

However, when I close the matplotlib window, the image object is cleared and becomes fully white.

You can see with both windows open the canvas is correctly displayed. However when closing the matplotlib window, it becomes fully white.

Note: I've tried completely removing the matplotlib calls, and now the image is always white and never displays anything.

enter image description here

enter image description here

from skimage.draw import polygon
from PIL import Image, ImageTk

import matplotlib.pyplot as plt

import tkinter as tk
import numpy as np

def generate():
    
    # clear canvas
    canvas.delete("all")

    # make clean array
    img = np.ones((width, height, 3), dtype=np.float32) * 0.75

    # randomly generate positions
    x_generated = np.random.uniform(0, width, 5)
    y_generated = np.random.uniform(0, height, 5)
    
    for x, y in zip(x_generated, y_generated):
        base_height = 70
        base_width = 70

        # generate the corners
        x0 = x - base_width/2
        x1 = x + base_width/2
        y0 = y - base_height/2
        y1 = y + base_height/2

        # create rectangle
        poly = np.array((
            (x0, y0),
            (x1, y0),
            (x1, y1),
            (x0, y1),
        ))

        rr, cc = polygon(poly[:, 0], poly[:, 1], img.shape)
        img[rr, cc, :] = 0.5

        # # canvas method. I don't want to use this!
        # canvas.create_rectangle(x0, y0, x1, y1, fill="red", outline="")
    
    # display generated image in tkinter canvas
    scaled_to_8bit = img * 255
    newarr = scaled_to_8bit.astype(np.uint8)
    imgarray = ImageTk.PhotoImage(image=Image.fromarray(newarr, 'RGB'))

    # create the canvas image object from the numpy array
    canvas.create_image(20, 20, anchor="nw", image=imgarray)
    
    # display pixel data for debugging
    plt.imshow(img)
    plt.show()



if __name__ == "__main__":
    width = 1000
    height = 1000

    # Create a root window
    root = tk.Tk()
    root.configure(background="green")

    # Create a canvas widget
    canvas = tk.Canvas(root, width=width, height=height)
    canvas.pack()

    # Create a button widget
    button = tk.Button(root, text="Generate", command=generate)
    button.pack()

    # start main tk loop
    root.mainloop()

Solution

  • The issue was that when the matplotlib window is closed, the canvas loses the reference to the image, and so the canvas is updated and its blank.

    I used a class to organise the code and allow for the reference to the image to be stored nicely

    from skimage.draw import polygon
    from PIL import Image, ImageTk
    
    import matplotlib.pyplot as plt
    
    import tkinter as tk
    import numpy as np
    
    class Application:
        
        def __init__(self):
            print("init")
            self.width = 1000
            self.height = 1000
    
            # Create a root window
            root = tk.Tk()
            root.configure(background="green")
    
            # Create a canvas widget
            self.canvas = tk.Canvas(root, width=self.width, height=self.height)
            self.canvas.pack()
    
            # Create a button widget
            button = tk.Button(root, text="Generate", command=self.generate)
            button.pack()
    
            # start main tk loop
            root.mainloop()
    
    
        def generate(self):
            
            # clear canvas
            self.canvas.delete("all")
    
            # make clean array
            self.img = np.ones((self.width, self.height, 3), dtype=np.float32) * 0.75
    
            # randomly generate positions
            x_generated = np.random.uniform(0, self.width, 5)
            y_generated = np.random.uniform(0, self.height, 5)
            
            for x, y in zip(x_generated, y_generated):
                base_height = 70
                base_width = 70
    
                # generate the corners
                x0 = x - base_width/2
                x1 = x + base_width/2
                y0 = y - base_height/2
                y1 = y + base_height/2
    
                # create rectangle
                poly = np.array((
                    (x0, y0),
                    (x1, y0),
                    (x1, y1),
                    (x0, y1),
                ))
    
                rr, cc = polygon(poly[:, 0], poly[:, 1], self.img.shape)
                self.img[rr, cc, :] = 0.5
    
                # # canvas method. I don't want to use this!
                # canvas.create_rectangle(x0, y0, x1, y1, fill="red", outline="")
            
            # display generated image in tkinter canvas
            scaled_to_8bit = self.img * 255
            self.img_8bit = scaled_to_8bit.astype(np.uint8)
            self.photoimage = ImageTk.PhotoImage(image=Image.fromarray(self.img_8bit, 'RGB'))
    
            # create the canvas image object from the numpy array
            self.canvas.create_image(0, 0, anchor="nw", image=self.photoimage)
            
            # display pixel data for debugging
            plt.imshow(self.img_8bit)
            plt.show()
    
    
    
    if __name__ == "__main__":
        print("start")
        app = Application()