diff --git a/mne/tests/test_docstring_parameters.py b/mne/tests/test_docstring_parameters.py index 4405aff43e0..ee17d466251 100644 --- a/mne/tests/test_docstring_parameters.py +++ b/mne/tests/test_docstring_parameters.py @@ -372,9 +372,12 @@ def test_no_global_rng(): """Test that we use local generators and the modern numpy RNG API.""" root = pyproject_path.parent # only available in a dev/editable checkout bad = [] + # CI tests an installed wheel from site-packages, where examples/ (if any) + # belongs to other packages (e.g. trame-vuetify), not to us + installed = root.name in ("site-packages", "dist-packages") for sub in ("mne", "examples", "tutorials"): base = root / sub - if not base.is_dir(): # e.g. examples/tutorials absent from a wheel + if not base.is_dir() or (installed and sub != "mne"): continue for path in sorted(base.rglob("*.py")): rel = path.relative_to(root).as_posix() diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 32fd2122120..760066ab1bf 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -532,7 +532,6 @@ def _setup_canonical_rotation(self): def _clean(self): # resolve the reference cycle self._renderer._window_close_disconnect() - self.clear_glyphs() self.remove_annotations() # clear init actors for hemi in self.layered_meshes: @@ -541,12 +540,6 @@ def _clean(self): self.plotter.remove_actor(actor) self._silhouette_actors = None self._clear_callbacks() - self._clear_widgets() - if getattr(self, "mpl_canvas", None) is not None: - self.mpl_canvas.clear() - if getattr(self, "act_data_smooth", None) is not None: - for key in list(self.act_data_smooth.keys()): - self.act_data_smooth[key] = None # XXX this should be done in PyVista for renderer in getattr(self._renderer, "_all_renderers", ()): # VTK only renderer.RemoveAllLights() @@ -564,20 +557,8 @@ def _clean(self): # XXX end PyVista for key in ( "plotter", - "window", - "dock", - "tool_bar", - "menu_bar", - "interactor", - "mpl_canvas", - "time_actor", - "picked_renderer", - "act_data_smooth", "_scalar_bar", "_scalar_bar_ticks", - "_hover_caption", - "actions", - "widgets", "geo", "_all_data", ): @@ -636,15 +617,6 @@ def _clear_callbacks(self): if getattr(self, "iren", None) is not None: self.plotter.iren.clear_key_event_callbacks() - def _clear_widgets(self): - if not hasattr(self, "widgets"): - return - for widget in self.widgets.values(): - if widget is not None: - for key in ("triggered", "floatValueChanged"): - setattr(widget, key, None) - self.widgets.clear() - @property def interaction(self): """The interaction style.""" diff --git a/mne/viz/_brain/_time_viewer.py b/mne/viz/_brain/_time_viewer.py index 9d7e80069ea..05f6969c2f0 100644 --- a/mne/viz/_brain/_time_viewer.py +++ b/mne/viz/_brain/_time_viewer.py @@ -951,6 +951,37 @@ def _configure_ui_events(self): subscribe(self, "__colormap_range_updated", self._sync_colormap_widgets) subscribe(self, "__surface_updated", self._on_surface_updated) subscribe(self, "__overlay_added", self._on_overlay_added) + # published before the window closes, while everything is still alive + subscribe(self, "figure_closing", self._on_figure_closing) + + def _on_figure_closing(self, event): + if event.source is not self: # forwarded from a linked figure + return + self.clear_glyphs() + self._clear_widgets() + if self.mpl_canvas is not None: + self.mpl_canvas.clear() + if self.act_data_smooth is not None: + for key in list(self.act_data_smooth.keys()): + self.act_data_smooth[key] = None + # resolve the reference cycles + for key in ( + "mpl_canvas", + "time_actor", + "act_data_smooth", + "_hover_caption", + "widgets", + ): + setattr(self, key, None) + + def _clear_widgets(self): + if not hasattr(self, "widgets"): + return + for widget in self.widgets.values(): + if widget is not None: + for key in ("triggered", "floatValueChanged"): + setattr(widget, key, None) + self.widgets.clear() def _on_time_updated(self, event): self.plot_time_line(update=True) diff --git a/mne/viz/_brain/tests/test_brain.py b/mne/viz/_brain/tests/test_brain.py index 3c797c0a0e8..6b426138393 100644 --- a/mne/viz/_brain/tests/test_brain.py +++ b/mne/viz/_brain/tests/test_brain.py @@ -12,6 +12,7 @@ import numpy as np import pytest from matplotlib import image +from matplotlib.figure import Figure from matplotlib.lines import Line2D from numpy.testing import assert_allclose, assert_array_equal @@ -1430,7 +1431,13 @@ def test_brain_traces_basic(renderer_interactive_pyvistaqt, hemi, src, brain_gc) else: # volume assert "annotation" not in brain.widgets assert "extract_mode" not in brain.widgets + # closing a linked figure must not tear down this one's time viewer + fig = Figure() + ui_events.link(fig, brain) + ui_events.publish(fig, ui_events.FigureClosing()) + assert brain.widgets and brain.mpl_canvas is not None brain.close() + assert brain.widgets is None and brain.mpl_canvas is None @testing.requires_testing_data