Tuesday, 25 September 2018

Convert a C or numpy array to a Tkinter PhotoImage with a minimum number of copies

I know a recipe for displaying an MxNx3 numpy array as an RGB image via Tkinter, but my recipe makes several copies of the array in the process:

a = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Original
ppm_header = b'P6\n%i %i\n255\n'%(a.shape[0], a.shape[1])
a_bytes = a.tobytes() # First copy
ppm_bytes = ppm_header + a_bytes # Second copy https://en.wikipedia.org/wiki/Netpbm_format
root = tk.Tk()
img = tk.PhotoImage(data=ppm_bytes) # Third and fourth copies?
canvas = tk.Canvas(root, width=a.shape[0], height=a.shape[1])
canvas.pack()
canvas.create_image(0, 0, anchor=tk.NW, image=img) # Fifth copy?
root.mainloop()

How can I achieve an equivalent result with a minimum number of copies?

Ideally, I would create a numpy array which was a view of the same bytes that the Tkinter PhotoImage object was using, effectively giving me a PhotoImage with mutable pixel values, and making it cheap and fast to update the Tkinter display. I don't know how to extract this pointer from Tkinter.

Perhaps there's a way via ctypes, as hinted at here?

The PhotoImage.put() method seems very slow, but maybe I'm wrong, and that's a path forward?

I tried making a bytearray() containing the ppm header and the image pixel values, and then using numpy.frombuffer() to view the image pixel values as a numpy array, but I think the PhotoImage constructor wants a bytes() object, not a bytearray() object, and also I think Tkinter copies the bytes of its data input into its internal format (32-bit RGBA?). I guess this saves me one copy compared to the recipe above?



from Convert a C or numpy array to a Tkinter PhotoImage with a minimum number of copies

No comments:

Post a Comment