Преобразование массива C или numpy в Tkinter PhotoImage с минимальным количеством копий - PullRequest
0 голосов
/ 22 сентября 2018

Я знаю рецепт для отображения массива MxNx3 в виде изображения RGB через Tkinter, но мой рецепт делает несколько копий массива в процессе:

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()

Как мне достичьэквивалентный результат с минимальным количеством копий?

В идеале, я бы создал массив с нулевыми значениями, который представлял бы те же байты, которые использовал объект Tkinter PhotoImage, фактически давая мне PhotoImage с изменяемыми значениями пикселей, что делает его дешевым и быстрым для обновления дисплея Tkinter.Я не знаю, как извлечь этот указатель из Tkinter.

Возможно, есть путь через ctypes, как намекал ?

Метод PhotoImage.put() кажется оченьмедленно, но, возможно, я ошибаюсь, и это путь вперед?

Я попытался создать bytearray(), содержащий заголовок ppm и значения пикселей изображения, а затем с помощью numpy.frombuffer() просмотреть значения пикселей изображениякак пустой массив, но я думаю, что конструктор PhotoImage хочет объект bytes(), а не объект bytearray(), а также я думаю, что Tkinter копирует байты своего data ввода во свой внутренний формат (32-битный RGBA?).Я полагаю, это экономит мне одну копию по сравнению с рецептом выше?

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018

Я могу уменьшить его до 1 (возможно, 2) копии, используя PIL и метку:

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

a = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Original
root = tk.Tk()
img = ImageTk.PhotoImage(Image.fromarray(a)) # First and maybe second copy.
lbl = tk.Label(root, image=img)
lbl.pack()
root.mainloop()

Однако это все еще не изменяемо.Если вы хотите этого, я думаю, вам нужно заново изобрести изображение, поместив пиксель на холст самостоятельно.Я сделал это однажды с этим проектом и обнаружил, что самым быстрым обновлением была анимация matplotlib, которая работает очень хорошо для вас, поскольку вы уже используете массивы np.

Мой код для использования tk.Canvas , PIL Image (с использованием putpixel ()) и matplotlib .

0 голосов
/ 25 сентября 2018
  • Вы можете исключить 1-ю и 2-ю копии

Вы получаете numpy.ndarray по произвольным данным с numpy.frombuffer:

shape=(100,100,3)
ppm_header = b'P6\n%i %i\n255\n'%(shape[0], shape[1])
ppm_bytes = ppm_header + b'\0'*(shape[0]*shape[1]*shape[2])
array_image = np.frombuffer(ppm_bytes, dtype=np.uint8, offset=len(ppm_header)).reshape(shape)
  • 3-я и 4-я копии неизбежны (см. Ниже), но 3-й сбрасывается сразу после звонка

  • 5-я копия фактически не сделана (также см. Ниже)

  • этап рисования включает в себя копию на экран через систему управления окнамиAPI рисования , что также неизбежно.


Tcl - безопасный язык для сборки мусора, такой как Python, и объекты Tcl не поддерживают ни «буферный протокол»или используя память для своих данных, которыми они не владеют (хотя объекты могут совместно использоваться.

...