16

The following short code is meant to create an array with numpy, convert it into an image object with PIL and then insert into a canvas on a tkinter window.

from tkinter import *
from PIL import Image

root = Tk()
array = np.ones((40,40))*150
img = Image.fromarray(array)
canvas = Canvas(root,width=300,height=300)
canvas.pack()
canvas.create_image(20,20,anchor=NW,image=img)

root.mainloop()

This throws the error:

TclError: image "<PIL.Image.Image image mode=F size=40x40 at 0x7F42D3BC3290>" doesn't exist
1
  • 1
    Well you are importing * but using tk.. Should probably fix that first. Commented Nov 14, 2018 at 21:15

2 Answers 2

16

You need to use PhotoImage from ImageTk.

Do this instead:

import tkinter as tk
import numpy as np
from PIL import Image, ImageTk

root = tk.Tk()

array = np.ones((40,40))*150
img =  ImageTk.PhotoImage(image=Image.fromarray(array))

canvas = tk.Canvas(root,width=300,height=300)
canvas.pack()
canvas.create_image(20,20, anchor="nw", image=img)

root.mainloop()
Sign up to request clarification or add additional context in comments.

10 Comments

I get "TclError: image "pyimage1" doesn't exist". Does that code work for you?
OK, the code works after adding the master=root attribute to PhotoImage.
That should not matter here. I have never used master with PhotoImage and this code works fine on my end.
@Mike-SMT master=root helps if you have multiple instances of Tk().
@TheLizzard there should be no reason to have multiple instances of TK.
|
4

tkinter supports only a few image formats directly, but one of them, PPM can be created easily from numpy data. So, here is a solution which converts an array directly to a tk.PhotoImage - no need to take the detour (and overhead!) of ImageTk:

import tkinter as tk

import numpy as np


def _photo_image(image: np.ndarray):
    height, width = image.shape
    data = f'P5 {width} {height} 255 '.encode() + image.astype(np.uint8).tobytes()
    return tk.PhotoImage(width=width, height=height, data=data, format='PPM')


root = tk.Tk()

array = np.ones((40, 40)) * 150
img = _photo_image(array)

canvas = tk.Canvas(root, width=300, height=300)
canvas.pack()
canvas.create_image(20, 20, anchor="nw", image=img)

root.mainloop()

The magic is in the function _photo_image which creates a portable pixmap header and appends the picture data, which must be an array of bytes.

Notes:

  1. the above creates a portable graymap (PGM). With a slight modification, this also works with color images. These have one more dimension. So, use

    height, width = image.shape[:2]
    

    to extract the geometry and P6 for the magic value to pass in the header.

    For example, to convert an openCV image (which is usually encoded as BGR), use:

    import cv2
    
    def _photo_image(image: np.ndarray):
        height, width = image.shape[:2]
        ppm_header = f'P6 {width} {height} 255 '.encode()
        data = ppm_header + cv2.cvtColor(image, cv2.COLOR_BGR2RGB).tobytes()
        return tk.PhotoImage(width=width, height=height, data=data, format='PPM')
    
  2. the above link to the English Wikipedia page on netpbm does not fully explain the header format (you can find it in the examples, though). The German Wikipedia page on portable anymap has more details on the header: Magic Value, space, width, space, height, space, max-pixel-value, space

3 Comments

Thanks Adrian for showing a direct approach from Numpy to TK, as well as the background links on portable images. I've wondered what the byte format should be as I work with Numpy, OpenCV, and TKinter. (I've incorporated the gray-scale approach and will add the color one soon.)
This doesn't seem to work for me, the resultant image in the canvas is mangled (lots of artifacts, lines etc and barely recognisable). The image array was generated using scikiti-mage. Another weird issue is that this only displays when in debug mode in VS Code. When in normal mode, the canvas is blank. I've confirmed that the data is good, because the same numpy array displayed in matplotlib looks fine. Could this be a data formatting issue?
Sorry, I can't help much on this. The technique shown is being heavily used in a project of mine since many years and just works. I'm unsure what you might mean with "data formatting". The dimensions of the array must match the picture format, of course, See the differences between the two examples above. So, if you might have a monochrome image, don't try to convert colors. I have used 8-bit pictures, but it should also be possible to extend the resolution to 16 bit by specifying 65535 instead of 255 for the max. data value. Quite some possibilities of doing something wrong...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.