Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions mne/viz/_3d_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,10 @@ def update(self, colors=None):
self._apply()

def _clean(self):
mapper = self._actor.GetMapper()
mapper.SetLookupTable(None)
self._actor.SetMapper(None)
if hasattr(self._actor, "GetMapper"): # VTK; the browser backend draws dicts
mapper = self._actor.GetMapper()
mapper.SetLookupTable(None)
self._actor.SetMapper(None)
self._actor = None
self._polydata = None
self._renderer = None
Expand Down
2 changes: 1 addition & 1 deletion mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ def _clean(self):
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 self._renderer._all_renderers:
for renderer in getattr(self._renderer, "_all_renderers", ()): # VTK only
renderer.RemoveAllLights()
# app_window cannot be set to None because it is used in __del__
for key in ("lighting", "interactor", "_RenderWindow"):
Expand Down
22 changes: 21 additions & 1 deletion mne/viz/backends/_lite.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

import inspect
import weakref
from contextlib import nullcontext

Expand Down Expand Up @@ -206,6 +207,7 @@ def __init__(
# _PyVistaRenderer's signature, but size, shape, name and show cannot
# be honored: the canvas is fixed and written only when show() runs
_validate_type(fig, (None, _LiteFigure), "fig")
self._close_callbacks = {False: [], True: []} # keyed by ``after``
if fig is not None: # plot_alignment(fig=...) composites into it
self._figure = fig
return
Expand Down Expand Up @@ -583,13 +585,31 @@ def _update(self):
pass # the page paints after the cell finishes

def _window_close_connect(self, func, *, after=True):
pass # an output cell has no close event
# an output cell has no close event, so close() runs these; ui_events
# drops its channel here, without which its subscribers pin the figure.
# Weak for bound methods (Brain._clean) so a figure dropped without
# close() still dies by refcount rather than waiting on the collector
if inspect.ismethod(func):
func = weakref.WeakMethod(func)
self._close_callbacks[after].append(func)

def _window_close_disconnect(self, after=True):
self._close_callbacks[after].clear()

def _run_close_callbacks(self, after):
for func in list(self._close_callbacks[after]): # Brain._clean disconnects
if isinstance(func, weakref.WeakMethod):
func = func()
if func is not None:
func()

def text3d(self, x, y, z, text, font_size, color="white", *, shadow=False):
pass # no camera-facing 3D text, so sensors go unlabeled

def close(self):
self._run_close_callbacks(after=False)
_lite_release_plotter(self.plotter)
self._run_close_callbacks(after=True)

# -- things pyvista-js cannot do ----------------------------------------
def contour(self, *args, **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions mne/viz/backends/tests/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,5 +548,7 @@ def test_lite_brain(renderer_lite):
(180.0, 90.0)
)
brain.close()
assert brain._cleaned # close() ran the close callbacks (ui_events' too)
with pytest.raises(NotImplementedError, match="browser"): # two columns
mne.viz.Brain(surf="inflated", **{**kwargs, "hemi": "split"})
assert list(mne.viz.Brain._instances) == [brain] # the failed one died
Loading