As of #29855 (according to git bisect) on nightly wheels in MNE-Python we now see:
_____________________ test_plot_raw_selection[matplotlib] ______________________
mne/viz/tests/test_raw.py:418: in test_plot_raw_selection
fig._fake_keypress(sel_fig.mne.close_key, fig=sel_fig)
mne/viz/_mpl_figure.py:2249: in _fake_keypress
_fake_keypress(fig, key)
mne/viz/utils.py:813: in _fake_keypress
fig.canvas.callbacks.process(
/opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/matplotlib/cbook.py:365: in process
func(*args, **kwargs)
/opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/matplotlib/widgets.py:2338: in on_key_press
self.clear()
/opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/matplotlib/widgets.py:2383: in clear
self.update()
/opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/matplotlib/widgets.py:2240: in update
self.canvas.restore_region(self.background)
^^^^^^^^^^^^^^^^^^^^^^^^^^
E AttributeError: 'FigureCanvasBase' object has no attribute 'restore_region'
Locally I can see:
../matplotlib/lib/matplotlib/widgets.py:2221: in update
self.canvas.restore_region(self.background)
E AttributeError: 'FigureCanvasBase' object has no attribute 'restore_region'
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /home/larsoner/python/matplotlib/lib/matplotlib/widgets.py(2221)update()
-> self.canvas.restore_region(self.background)
(Pdb) p type(self)
<class 'matplotlib.widgets.LassoSelector'>
(Pdb) p self.canvas
<matplotlib.backend_bases.FigureCanvasBase object at 0x7f47ede5ca50>
(Pdb) u
(Pdb) u
(Pdb) u
(Pdb) u
> /home/larsoner/python/mne-python/mne/viz/utils.py(813)_fake_keypress()
-> fig.canvas.callbacks.process(
(Pdb) p fig
<MNESelectionFigure size 300x700 with 3 Axes>
I'm hoping this is enough to see the problem and know the solution. But if not, let me know and I'll take the time to whittle this down to a MWE! There might also be some way we are misusing/simulating keypresses in tests or something, but we haven't changed that code in years AFAIK so if this breaks things for us I wouldn't be surprised if it breaks things for others, too.
Additionally, the swapping of the canvas currently does not play well with
blitting of matplotlib widgets; in such cases either deactivate blitting or do not
continue to use the figure (e.g. saving it after closing the window).
I'm curious how you are getting a figure in this state. Did we get lazier about giving the figure a "real" canvas or are you testing on a figure that pyplot thinks is closed?
At some point in the process later, the canvas must have been swapped out.
You're right! If I add a print(fig.canvas) below the creation line I see:
<matplotlib.backends.backend_agg.FigureCanvasAgg object at 0x73a9ba1efb10>
and running
pytest mne/viz/tests/test_raw.py -k annot_fig --pdb
...
../matplotlib/lib/matplotlib/widgets.py:2192: in update_background
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
E AttributeError: 'FigureCanvasBase' object has no attribute 'copy_from_bbox'
(Pdb) p self
<matplotlib.widgets.SpanSelector object at 0x73a9b9c9c6e0>
(Pdb) p self.ax.figure.canvas
<matplotlib.backend_bases.FigureCanvasBase object at 0x73a9b9b93b10>
I still hit the failure line, the fig._fake_keypress(key=fig.mne.close_key). So it's while it's in the process of being closed that this appears to be an issue. (If I disable blitting, the assert about FigureCanvasBase, previously unreachable, passes.)
oh 🤦🏻 it took me far too long to notice that the key-press you are testing is what is closing the figure.
Upgrading widget.useblit to be a property may be an easier short-term solution, I have a draft PR up.
It also occurs to me that this brings up a related problem that if you set
up the widgets when you have canvas that does not support blitting but later promote it to one that does we will not blit like the user asked, but that is a later problem.
Still failing, but the traceback is at least different now:
_______________________________________________________________________ test_plot_raw_selection[matplotlib] ________________________________________________________________________
mne/viz/tests/test_raw.py:419: in test_plot_raw_selection
fig._close_event(sel_fig)
mne/viz/_mpl_figure.py:2250: in _close_event
_close_event(fig)
mne/fixes.py:658: in _close_event
fig.canvas.callbacks.process(
../matplotlib/lib/matplotlib/cbook.py:365: in process
func(*args, **kwargs)
mne/viz/utils.py:1193: in _close_event
fig.lasso.disconnect()
mne/viz/utils.py:1780: in disconnect
self.canvas.draw_idle()
../matplotlib/lib/matplotlib/backend_bases.py:1981: in draw_idle
self.draw(*args, **kwargs)
../matplotlib/lib/matplotlib/backends/backend_agg.py:383: in draw
self.figure.draw(self.renderer)
../matplotlib/lib/matplotlib/artist.py:94: in draw_wrapper
result = draw(artist, renderer, *args, **kwargs)
../matplotlib/lib/matplotlib/artist.py:71: in draw_wrapper
return draw(artist, renderer)
../matplotlib/lib/matplotlib/figure.py:3275: in draw
DrawEvent("draw_event", self.canvas, renderer)._process()
../matplotlib/lib/matplotlib/backend_bases.py:1194: in _process
self.canvas.callbacks.process(self.name, self)
../matplotlib/lib/matplotlib/cbook.py:365: in process
func(*args, **kwargs)
../matplotlib/lib/matplotlib/widgets.py:1754: in _clear
self.ax.draw_artist(self._buttons)
../matplotlib/lib/matplotlib/axes/_base.py:3246: in draw_artist
a.draw(self.get_figure(root=True).canvas.get_renderer())
E AttributeError: 'FigureCanvasBase' object has no attribute 'get_renderer'
In particular before it ended with
../matplotlib/lib/matplotlib/cbook.py:365: in process
func(*args, **kwargs)
../matplotlib/lib/matplotlib/widgets.py:1746: in _clear
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
E AttributeError: 'FigureCanvasBase' object has no attribute 'copy_from_bbox'
Now it ends with
../matplotlib/lib/matplotlib/cbook.py:365: in process
func(*args, **kwargs)
../matplotlib/lib/matplotlib/widgets.py:1754: in _clear
self.ax.draw_artist(self._buttons)
../matplotlib/lib/matplotlib/axes/_base.py:3246: in draw_artist
a.draw(self.get_figure(root=True).canvas.get_renderer())
E AttributeError: 'FigureCanvasBase' object has no attribute 'get_renderer'
Note: I briefly tried to install dev versions of matplotlib and mne-tools for debugging the last issue, but failed with some meson error. Certainly solvable, but ...
If we have a whack-a-mole here, that means we don't have enough oversight / control on the topic. More generally, I'm starting to get some architectural concerns. These attribute errors come from us switching the canvas back to FigureCanvasBase and FigureCanvasBase obviously does not have the respective methods. So, is FigureCanvasBase the right class at all? Apparently it's an incomplete class that does not allow rendering. It is the default however, when we instantiate a default Figure() - so this not a new state, it's only new that we can come back to this state. I'm unclear whether that is reasonable.
I see potentially three routes forward:
Decide that FigureCanvasBase should be abstract and never be instantiated/attached to a figure. This would mean, we likely would switch to a FigureCanvasAgg as the default.
Extend FigureCanvasBase so that it provides the full API, but with no-ops.
Decide that it is correct to keep FigureCanvasBase as is and that figures can be unrenderable. In that case, we have to do the whack-a-mole and ensure that we don't have any logic (like with the above _clear) that tries to draw it without safeguards.
On an additional note, this is in RadioButtons, and I do not fully understand its drawing/blitting logic yet. There's the possibility that it has some logical quirks.
and may cause an inconsistent state as we may have switched out the canvas. Please do not hold a reference to the canvas, but always retrieve it from the figure (self.fig.canvas).
I don't fully oversee whether that fixes everything, but it's for sure a step in the right direction.
Activity
timhoffm commented on Sep 17, 2025
This is what we describe in the What's New entry:
and tracked in #30503.
The first failing tests fails on this line: https://github.com/mne-tools/mne-python/blob/662fb510097e38667a342e41fa525330154c5f74/mne/viz/tests/test_raw.py#L418
From a quick look, I think you code is valid and a proper fix must be on our side. As a workaround, you could set useblit=False on the widgets in your test - I assume they do not really depend on active blitting.
tacaswell commented on Sep 25, 2025
I'm curious how you are getting a figure in this state. Did we get lazier about giving the figure a "real" canvas or are you testing on a figure that pyplot thinks is closed?
tacaswell commented on Sep 25, 2025
3.11 can't go out till we fix this.
tacaswell commented on Sep 25, 2025
(sorry, I think I pushed the wrong comment button)
timhoffm commented on Sep 26, 2025
Sorry, I don't have time for a full debugging session right now.
The figure is created here via pyplot:
https://github.com/mne-tools/mne-python/blob/3e29187ce3a2a63b4497dc83d30869ad33ecd1be/mne/viz/_mpl_figure.py#L2360
At some point in the process later, the canvas must have been swapped out.
larsoner commented on Sep 26, 2025
You're right! If I add a print(fig.canvas) below the creation line I see:
and running
pytest mne/viz/tests/test_raw.py -k annot_fig --pdb ... ../matplotlib/lib/matplotlib/widgets.py:2192: in update_background self.background = self.canvas.copy_from_bbox(self.ax.bbox) E AttributeError: 'FigureCanvasBase' object has no attribute 'copy_from_bbox' (Pdb) p self <matplotlib.widgets.SpanSelector object at 0x73a9b9c9c6e0> (Pdb) p self.ax.figure.canvas <matplotlib.backend_bases.FigureCanvasBase object at 0x73a9b9b93b10>Notably if I add some asserts:
fig = raw.plot() assert fig.canvas.__class__.__name__ == "FigureCanvasAgg" _spawn_child_fig(fig, "fig_annotation", browser_backend, "a") assert fig.canvas.__class__.__name__ == "FigureCanvasAgg" fig._fake_keypress(key=fig.mne.close_key) assert fig.canvas.__class__.__name__ == "FigureCanvasBase"I still hit the failure line, the fig._fake_keypress(key=fig.mne.close_key). So it's while it's in the process of being closed that this appears to be an issue. (If I disable blitting, the assert about FigureCanvasBase, previously unreachable, passes.)
MNT: Make useblit a property in Widget classes
tacaswell commented on Sep 26, 2025
oh 🤦🏻 it took me far too long to notice that the key-press you are testing is what is closing the figure.
Upgrading widget.useblit to be a property may be an easier short-term solution, I have a draft PR up.
It also occurs to me that this brings up a related problem that if you set
up the widgets when you have canvas that does not support blitting but later promote it to one that does we will not blit like the user asked, but that is a later problem.
larsoner commented on Jan 5, 2026
@timhoffm I still experience a variant of this on latest main on mne-tools/mne-python#13569 which removes our workaround:
gives:
Traceback=============================================================================== test session starts ================================================================================ platform linux -- Python 3.13.7, pytest-8.3.5, pluggy-1.6.0 PySide6 6.10.1 -- Qt runtime 6.10.1 -- Qt compiled 6.10.1 MNE 1.12.0.dev16+g825652aa74 -- /home/larsoner/python/mne-python/mne rootdir: /home/larsoner/python/mne-python configfile: pyproject.toml plugins: dependency-0.6.0, anyio-4.9.0, cov-6.1.1, flaky-3.8.1, qt-4.4.0, rerunfailures-16.1, timeout-2.4.0 collected 71 items / 70 deselected / 1 selected mne/viz/tests/test_raw.py F [100%] ===================================================================================== FAILURES ===================================================================================== _______________________________________________________________________ test_plot_raw_selection[matplotlib] ________________________________________________________________________ mne/viz/tests/test_raw.py:419: in test_plot_raw_selection fig._close_event(sel_fig) mne/viz/_mpl_figure.py:2250: in _close_event _close_event(fig) mne/fixes.py:658: in _close_event fig.canvas.callbacks.process( ../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) mne/viz/utils.py:1193: in _close_event fig.lasso.disconnect() mne/viz/utils.py:1780: in disconnect self.canvas.draw_idle() ../matplotlib/lib/matplotlib/backend_bases.py:1981: in draw_idle self.draw(*args, **kwargs) ../matplotlib/lib/matplotlib/backends/backend_agg.py:383: in draw self.figure.draw(self.renderer) ../matplotlib/lib/matplotlib/artist.py:94: in draw_wrapper result = draw(artist, renderer, *args, **kwargs) ../matplotlib/lib/matplotlib/artist.py:71: in draw_wrapper return draw(artist, renderer) ../matplotlib/lib/matplotlib/figure.py:3275: in draw DrawEvent("draw_event", self.canvas, renderer)._process() ../matplotlib/lib/matplotlib/backend_bases.py:1194: in _process self.canvas.callbacks.process(self.name, self) ../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) ../matplotlib/lib/matplotlib/widgets.py:1746: in _clear self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) E AttributeError: 'FigureCanvasBase' object has no attribute 'copy_from_bbox'Maybe this one should be reopened?
timhoffm commented on Jan 15, 2026
@larsoner please cross-check that #30945 has now fixed your test.
larsoner commented on Jan 16, 2026
Still failing, but the traceback is at least different now:
_______________________________________________________________________ test_plot_raw_selection[matplotlib] ________________________________________________________________________ mne/viz/tests/test_raw.py:419: in test_plot_raw_selection fig._close_event(sel_fig) mne/viz/_mpl_figure.py:2250: in _close_event _close_event(fig) mne/fixes.py:658: in _close_event fig.canvas.callbacks.process( ../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) mne/viz/utils.py:1193: in _close_event fig.lasso.disconnect() mne/viz/utils.py:1780: in disconnect self.canvas.draw_idle() ../matplotlib/lib/matplotlib/backend_bases.py:1981: in draw_idle self.draw(*args, **kwargs) ../matplotlib/lib/matplotlib/backends/backend_agg.py:383: in draw self.figure.draw(self.renderer) ../matplotlib/lib/matplotlib/artist.py:94: in draw_wrapper result = draw(artist, renderer, *args, **kwargs) ../matplotlib/lib/matplotlib/artist.py:71: in draw_wrapper return draw(artist, renderer) ../matplotlib/lib/matplotlib/figure.py:3275: in draw DrawEvent("draw_event", self.canvas, renderer)._process() ../matplotlib/lib/matplotlib/backend_bases.py:1194: in _process self.canvas.callbacks.process(self.name, self) ../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) ../matplotlib/lib/matplotlib/widgets.py:1754: in _clear self.ax.draw_artist(self._buttons) ../matplotlib/lib/matplotlib/axes/_base.py:3246: in draw_artist a.draw(self.get_figure(root=True).canvas.get_renderer()) E AttributeError: 'FigureCanvasBase' object has no attribute 'get_renderer'In particular before it ended with
../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) ../matplotlib/lib/matplotlib/widgets.py:1746: in _clear self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) E AttributeError: 'FigureCanvasBase' object has no attribute 'copy_from_bbox'Now it ends with
../matplotlib/lib/matplotlib/cbook.py:365: in process func(*args, **kwargs) ../matplotlib/lib/matplotlib/widgets.py:1754: in _clear self.ax.draw_artist(self._buttons) ../matplotlib/lib/matplotlib/axes/_base.py:3246: in draw_artist a.draw(self.get_figure(root=True).canvas.get_renderer()) E AttributeError: 'FigureCanvasBase' object has no attribute 'get_renderer'QuLogic commented on Jan 17, 2026
Are you able to distill a simple test case for this? We seem to be playing whack-a-mole on this.
timhoffm commented on Jan 17, 2026
Note: I briefly tried to install dev versions of matplotlib and mne-tools for debugging the last issue, but failed with some meson error. Certainly solvable, but ...
If we have a whack-a-mole here, that means we don't have enough oversight / control on the topic. More generally, I'm starting to get some architectural concerns. These attribute errors come from us switching the canvas back to FigureCanvasBase and FigureCanvasBase obviously does not have the respective methods. So, is FigureCanvasBase the right class at all? Apparently it's an incomplete class that does not allow rendering. It is the default however, when we instantiate a default Figure() - so this not a new state, it's only new that we can come back to this state. I'm unclear whether that is reasonable.
I see potentially three routes forward:
On an additional note, this is in RadioButtons, and I do not fully understand its drawing/blitting logic yet. There's the possibility that it has some logical quirks.
timhoffm commented on Jan 22, 2026
@larsoner I believe the issue is now in your SelectFromCollection. You hold a reference to the canvas at
https://github.com/mne-tools/mne-python/blob/db089c9353c7cb549d090fb47ca05c0d5bbeeb21/mne/viz/utils.py#L1659
this is used e.g. in https://github.com/mne-tools/mne-python/blob/db089c9353c7cb549d090fb47ca05c0d5bbeeb21/mne/viz/utils.py#L1780
and may cause an inconsistent state as we may have switched out the canvas. Please do not hold a reference to the canvas, but always retrieve it from the figure (self.fig.canvas).
I don't fully oversee whether that fixes everything, but it's for sure a step in the right direction.
larsoner commented on Jan 23, 2026
@tacaswell was kind enough to fix in mne-tools/mne-python#13606, thanks for everyone's help on this one!