FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[Bug]: Regression in widget behavior · Issue #30575 · matplotlib/matplotlib · GitHub

Repository navigation

[Bug]: Regression in widget behavior #30575

Description

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>

MNESelectionFigure subclasses MNEFigure which subclasses matplotlib.figure.Figure.

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.

Activity

  1. changed the title [-][Bug]: Regression in widget behavion[/-] [+][Bug]: Regression in widget behavior[/+] on Sep 17, 2025
  2. timhoffm commented on Sep 17, 2025

    Member

    This is what we describe in the What's New entry:

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

    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.

  3. tacaswell commented on Sep 25, 2025

    Member

    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?

  4. added this to the v3.11.0 milestone on Sep 25, 2025
  5. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Sep 25, 2025
  6. tacaswell commented on Sep 25, 2025

    Member

    3.11 can't go out till we fix this.

  7. tacaswell commented on Sep 25, 2025

    Member

    (sorry, I think I pushed the wrong comment button)

  8. timhoffm commented on Sep 26, 2025

    Member

    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.

  9. larsoner commented on Sep 26, 2025

    ContributorAuthor

    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>
    

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

  10. added a commit that references this issue on Sep 26, 2025
    b81caa9
  11. tacaswell commented on Sep 26, 2025

    Member

    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.

  12. larsoner commented on Jan 5, 2026

    ContributorAuthor

    @timhoffm I still experience a variant of this on latest main on mne-tools/mne-python#13569 which removes our workaround:

    $ pytest mne/viz/tests/test_raw.py -k test_plot_raw_selection[matplotlib]
    

    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?

  13. timhoffm commented on Jan 15, 2026

    Member

    @larsoner please cross-check that #30945 has now fixed your test.

  14. larsoner commented on Jan 16, 2026

    ContributorAuthor

    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'
    
  15. QuLogic commented on Jan 17, 2026

    Member

    Are you able to distill a simple test case for this? We seem to be playing whack-a-mole on this.

  16. timhoffm commented on Jan 17, 2026

    Member

    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.

  17. timhoffm commented on Jan 22, 2026

    Member

    @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.

  18. larsoner commented on Jan 23, 2026

    ContributorAuthor

    @tacaswell was kind enough to fix in mne-tools/mne-python#13606, thanks for everyone's help on this one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.status: confirmed bug

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL