matplotlib icon indicating copy to clipboard operation
matplotlib copied to clipboard

[Bug]: blitting after closing second tkinter embed causes silent crash

Open Alex-x90 opened this issue 3 years ago • 3 comments

Bug summary

After opening a tkinter popup and embedding a figure a second time and closing it, blitting the figure again causes a silent complete program crash.

Code for reproduction

from tkinter import *
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

mainWindow = Tk()

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
img = ax.imshow(np.random.rand(28,28), cmap="gray", interpolation="None")
fig.canvas.draw()

canvas = FigureCanvasTkAgg(fig, master=mainWindow)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1)

def openPopup():
    popup = Tk()

    popupCanvas = FigureCanvasTkAgg(fig, master=popup)
    popupCanvas.draw()
    popupCanvas.get_tk_widget().grid(row=1, column=1)

    popup.protocol("WM_DELETE_WINDOW", popup.destroy)
    popup.mainloop()

def doBlit():
    img.set_data(np.random.rand(28,28))
    ax.draw_artist(img)
    
    fig.canvas.blit(ax.bbox) # this blit line is the cause of the crash

    fig.canvas.flush_events()

button_1 = Button(mainWindow,command=(openPopup),text="open popup")
button_1.grid(row=1,column=2)
button_2 = Button(mainWindow,command=(doBlit),text="do blitting")
button_2.grid(row=1,column=3)

mainWindow.mainloop()

Actual outcome

Program crashes with no error message.

Expected outcome

Either the program should crash with an error message, or the blit should succeed and the new image should be rendered.

Additional information

I think this is probably related to #16580, but the lack of any error message is definitely different. In the larger program where I'm trying this blitting doesn't actively crash in this way with a separate figure that has 2 scatterplots and a title which are being blitted and embedded in the same manner, but the other figure which is just an image is causing the program to crash. If anyone knows a way to fix this in the meantime I would be very grateful.

Operating system

Windows

Matplotlib Version

3.4.3

Matplotlib Backend

Agg

Python version

3.8.1

Jupyter version

6.4.6

Installation

pip

Alex-x90 avatar Sep 06 '22 08:09 Alex-x90

Do either of the fixes I suggested in #16580 help?

anntzer avatar Sep 06 '22 11:09 anntzer

The first fix stops the complete crash, but the blitting still fails with the following error message(s).

Traceback (most recent call last):
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 117, in blit
    photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError: invalid command name "pyimage4"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1883, in __call__
    return self.func(*args)
  File "errorExample.py", line 32, in doBlit
    fig.canvas.blit(ax.bbox)
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\backend_tkagg.py", line 13, in blit
    _backend_tk.blit(self._tkphoto, self.renderer.buffer_rgba(),
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 122, in blit
    photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 117, in blit
    photoimage.tk.call(_blit_tcl_name, argsid)
_tkinter.TclError: invalid command name "pyimage4"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1883, in __call__
    return self.func(*args)
  File "errorExample.py", line 27, in openPopup
    popup.mainloop()
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\tkinter\__init
__.py", line 1420, in mainloop
    self.tk.mainloop(n)
  File "C:\Users\sasha\AppData\Local\Programs\Python\Python38\lib\site-packages\
matplotlib\backends\_backend_tk.py", line 63, in _blit
    photoimage, dataptr, offsets, bboxptr, blank = _blit_args.pop(argsid)
KeyError: '1510834383744'

The second fix still crashes completely silently. The fact that the scatterplot blitting isn't having any issues in my circumstances but blitting the image does have issues seems strange to me. Also something I just noticed, if I open the popup and press the blit button only the popup blits, the main window stays completely static. Is there some way to force the blitting to stay locked onto the main window and not switch to the popup?

Alex-x90 avatar Sep 07 '22 10:09 Alex-x90

It crashes because you use the same figure for the two windows. When you close your popup, you need to change the canvas of the figure so it refers to the main window again. Also, try not to use pyplot with tkinter, instead use Figure.

from tkinter import *
import numpy as np
import matplotlib
from matplotlib.figure import Figure
matplotlib.use("Agg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

mainWindow = Tk()

fig = Figure()
ax = fig.add_subplot(1,1,1)
img = ax.imshow(np.random.rand(28,28), cmap="gray", interpolation="None")
fig.canvas.draw()

canvas = FigureCanvasTkAgg(fig, master=mainWindow)
canvas.draw()
canvas.get_tk_widget().grid(row=1, column=1)

def close_popup(popup):
    fig.set_canvas(canvas)
    canvas.draw() # This is necessary, no idea why
    popup.quit()
    popup.destroy()

def openPopup():
    popup = Toplevel()

    popupCanvas = FigureCanvasTkAgg(fig, master=popup)
    popupCanvas.draw()
    popupCanvas.get_tk_widget().grid(row=1, column=1)

    popup.protocol("WM_DELETE_WINDOW", lambda p=popup: close_popup(popup))
    popup.mainloop()

def doBlit():
    img.set_data(np.random.rand(28,28))
    ax.draw_artist(img)
    
    fig.canvas.blit(fig.bbox) # this blit line is the cause of the crash

    fig.canvas.flush_events()

button_1 = Button(mainWindow,command=(openPopup),text="open popup")
button_1.grid(row=1,column=2)
button_2 = Button(mainWindow,command=(doBlit),text="do blitting")
button_2.grid(row=1,column=3)

mainWindow.mainloop()

hugochrist1 avatar Sep 19 '22 12:09 hugochrist1