#python #matplotlib #tkinter
#python #matplotlib #tkinter
Вопрос:
Я работаю над графическим интерфейсом tkinter для отображения изображений из определенных каталогов. В графическом интерфейсе есть кнопка построения, которая обновляет изображение. В полнофункциональном графическом интерфейсе изображение может быть обновлено с различными изменениями, включая источник изображения, но приведенный ниже пример этого не включает и отображает только идентичное изображение. Функция plot использует фигуру matplotlib и tk Canvas для возврата объекта, который может быть привязан к сетке, а также исходную фигуру для сбора мусора.
При втором вызове функции plot графический интерфейс закрывается без какого-либо сообщения об ошибке. В приведенном ниже примере функция plot используется при инициализации, а затем снова при нажатии кнопки. Тем не менее, я пробовал это с помощью упрощенной функции plot при инициализации (показано ниже в примере), и графический интерфейс закрывается только при втором запуске функции real plot.
import tkinter as tk
from PIL import Image, ImageTk
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
def plot_image(img_path, root, ratio=2.0):
'''
Plots image using plt.imshow and tk.Canvas
Input:
-img_path: string, filepath to plt.imread-acceptable source
-root: root Tk object for returned widget
-ratio: float, aspect ratio (W:H)
Output:
-tuple: (tkinter Canvas of plt Figure, plt subplot)
Note: plt subplot garbage collection must be handled on application end
'''
# Create image
img = Image.open(img_path)
img_arr = np.asarray(img)
# Create plot
fig = Figure()
base_size = 2.5
fig, plot1 = plt.subplots(1, subplot_kw={'aspect': 'auto'},
figsize=(ratio*base_size, base_size))
xleft, xright = plot1.get_xlim()
ybottom, ytop = plot1.get_ylim()
plot1.set_aspect(abs((xright-xleft)/(ybottom-ytop))*ratio)
plot1.imshow(img_arr, rasterized=True, aspect='auto')
# Return tk.Canvas
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas = canvas.get_tk_widget()
return (canvas, fig)
class UI(tk.Frame):
'''
Frame for displaying a single image
'''
def __init__(self, master, **options):
tk.Frame.__init__(self, master, **options)
self.img_path = "image.png"
self.wgt_img, self.img = plot_image(self.img_path, self)
self.wgt_img.grid(row=0, column=0)
btn_load = tk.Button(self, text="Load",
command=lambda: self.update_image())
btn_load.grid(row=1, column=0)
def update_image(self):
self.wgt_img.grid_forget()
plt.close(self.img)
self.wgt_img, self.img = plot_image(self.img_path, self)
self.wgt_img.grid(row=0, column=0)
def run():
root = tk.Tk()
gui = UI(root)
gui.pack()
root.mainloop()
run()
def load_image(img_path, root):
'''
Plots image using ImageTk.PhotoImage
Input:
-img_path: string, filepath to plt.imread-acceptable source
-root: root Tk object for returned widget
Output:
-tuple: (tkinter Label, tkinter PhotoImage)
'''
img = Image.open(img_path)
img = ImageTk.PhotoImage(img)
return (tk.Label(root, image=img), img)
Я пробовал добавлять операторы печати к функциям. Похоже, что он может выйти из функции, но сразу после выхода из нее происходит сбой. На данный момент я в некоторой растерянности. Я думаю, что это как-то связано с использованием Canvas вместо Label, но я не знаю, кроме этого. Есть какие-нибудь идеи?
Комментарии:
1. Просто предположение, но
self.wgt_img.grid_forget()
на самом деле не уничтожает виджет, а просто удаляет его из макета. Переназначение его на две строки вниз может привести к нечистому GC старогоwgt_img
.2. Используйте
self.wgt_img.destroy()
вместоself.wgt_img.grid_forget()
3. @TorbenKlein это имеет смысл, но, похоже, это не исправило
4. как я уже сказал, просто догадываюсь. Если бы я был уверен, я бы отправил его в качестве ответа… в любом случае, проголосуйте за публикацию решения.
Ответ №1:
Обновить
У меня это работает. Проблема заключалась в том, что холст зависел от существующей фигуры, поэтому, как только фигура была закрыта, холсту нечего было отображать, и он закрылся сам. Я не уверен, почему для этого нет сообщения об ошибке, но, по крайней мере, оно работает сейчас. Отредактированный код:
def update_image(self):
self.wgt_img.destroy()
old_img = self.img
self.wgt_img, self.img = plot_image(self.img_path, self)
plt.close(old_img)
self.wgt_img.grid(row=0, column=0)