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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ venv/
.hypothesis/
.ruff_cache/
.ipynb_checkpoints/
/.claude/
/.claude/
uv.lock
8 changes: 3 additions & 5 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,6 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
for hemi in ("lh", "rh"):
for ri, ci, v in self._iter_views(hemi):
self.show_view(view=v, row=ri, col=ci)
self._renderer._process_events()

self._renderer._update()
# finally, show the MplCanvas
Expand Down Expand Up @@ -1028,7 +1027,7 @@ def _refresh_colormap_widgets(self):
fmid=self._data["fmid"],
fmax=self._data["fmax"],
)
self._renderer._process_events()
self._renderer._update()

def _configure_dock_trace_widget(self, name):
if not self.show_traces:
Expand Down Expand Up @@ -1520,7 +1519,7 @@ def _on_time_change(self, event):
if "current_time" in self.widgets:
self.widgets["current_time"].set_value(f"{self._current_time: .3f}")
self.plot_time_line(update=True)
self._renderer._process_events()
self._renderer._update()

def _on_colormap_range(self, event):
"""Respond to the colormap_range UI event."""
Expand All @@ -1541,7 +1540,7 @@ def _on_colormap_range(self, event):
self.widgets[entry_key].set_value(val * self._data["fscale"])
# Update the render.
self._update_colormap_range(**lims)
self._renderer._process_events()
self._renderer._update()

def _on_vertex_select(self, event):
"""Respond to vertex_select UI event."""
Expand Down Expand Up @@ -3683,7 +3682,6 @@ def show_view(
self._set_camera(**view_params, align=align)
if update:
self._renderer._update()
self._renderer._process_events()

def _set_camera(
self,
Expand Down
15 changes: 14 additions & 1 deletion mne/viz/_brain/tests/test_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,9 @@ def test_image_screenshot(
"""Test screenshot and image saving."""
size = (300, 300)
brain = _create_testing_brain(hemi="rh", show_traces=False, size=size)
azimuth, elevation = 180.0, 90.0
for mesh in brain.layered_meshes.values():
mesh._actor.SetVisibility(True)
azimuth, elevation = 360.0, 90.0
fname = tmp_path / "test.png"
assert not fname.is_file()
brain.save_image(fname)
Expand All @@ -938,6 +940,17 @@ def test_image_screenshot(
div = 2 if np.allclose(img.shape[:2], want_size[:2] / 2.0, atol=15) else 1
want_size[:2] /= div
assert_allclose(img.shape, want_size, atol=15)

# Test whether the renderer has properly updated before the screenshot was taken.
brain.set_time(1)
img1 = brain.screenshot(mode="rgba")
brain.set_time(2)
img2 = brain.screenshot(mode="rgba")
brain.set_time(1)
img3 = brain.screenshot(mode="rgba")
assert not np.array_equal(img1, img2)
assert_array_equal(img1, img3)

brain.close()


Expand Down
43 changes: 19 additions & 24 deletions mne/viz/backends/_pyvista.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@

import numpy as np
import pyvista
from pyvista import Line, Plotter, PolyData, close_all # noqa: F401 # re-exported
from pyvista import (
Line,
Plotter, # noqa: F401 # re-exported
PolyData, # noqa: F401 # re-exported
close_all,
)
from pyvista.plotting.plotter import _ALL_PLOTTERS
from pyvistaqt import BackgroundPlotter
from vtkmodules.util.numpy_support import numpy_to_vtk
Expand Down Expand Up @@ -50,12 +55,7 @@
from ...fixes import _compare_version
from ...surface import _vtk_smooth
from ...transforms import _cart_to_sph, _sph_to_cart, apply_trans
from ...utils import (
_check_option,
_require_version,
_validate_type,
warn,
)
from ...utils import _check_option, _require_version, _validate_type, warn
from ._abstract import Figure3D, _AbstractRenderer
from ._utils import (
ALLOWED_QUIVER_MODES,
Expand Down Expand Up @@ -156,7 +156,6 @@ def _build(self):
if self.plotter.iren is not None:
self.plotter.iren.initialize()
_process_events(self.plotter)
_process_events(self.plotter)
return self.plotter

def _is_active(self):
Expand Down Expand Up @@ -289,14 +288,10 @@ def _hide_axes(self):
def _update(self):
for plotter in self._all_plotters:
# PyVistaQt resolves plotter.update() to QWidget.update(), which only
# schedules a repaint, and it makes Plotter.render() asynchronous (the
# synchronous one being _render()). So render synchronously to update the
# scene, schedule the repaint, then flush it: without the flush the paint
# is delivered whenever events happen to be processed next, which can be
# long after the scene has changed again.
getattr(plotter, "_render", plotter.render)()
plotter.update()
_process_events(plotter)
# schedules a repaint, and it makes Plotter.render() asynchronous. This is
# probably fine for most cases. If you want to synchronously, i.e. wait
# until it has actually gone through, use Plotter._render().
plotter.render()

def _index_to_loc(self, idx):
_ncols = self.figure._ncols
Expand Down Expand Up @@ -990,9 +985,9 @@ def _actor(self, mapper=None):
_hide_testing_actor(actor)
return actor

def _process_events(self):
def _process_events(self, level=0):
for plotter in self._all_plotters:
_process_events(plotter)
_process_events(plotter, level=level + 1)

def _update_picking_callback(
self, on_mouse_move, on_button_press, on_button_release, on_pick
Expand Down Expand Up @@ -1394,7 +1389,6 @@ def _set_3d_view(

if update:
figure.plotter.update()
_process_events(figure.plotter)


def _set_3d_title(figure, title, size=16, *, color="white", position="upper_left"):
Expand All @@ -1406,7 +1400,6 @@ def _set_3d_title(figure, title, size=16, *, color="white", position="upper_left
name="title",
)
figure.plotter.update()
_process_events(figure.plotter)
return handle


Expand All @@ -1416,7 +1409,7 @@ def _check_3d_figure(figure):

def _clear_3d_figure(figure):
figure.plotter.clear() # remove all actors, lights are restored on the next plot
_process_events(figure.plotter)
figure.plotter.update()


def _close_3d_figure(figure):
Expand All @@ -1428,17 +1421,19 @@ def _close_3d_figure(figure):
# free memory and deregister from the scraper
plotter.deep_clean() # remove internal references
_ALL_PLOTTERS.pop(plotter._id_name, None)
_process_events(plotter)


def _take_3d_screenshot(figure, mode="rgb", filename=None):
_process_events(figure.plotter)
# force the render to happen right now if it's an option (not available on
# notebooks)
meth = getattr(figure.plotter, "_render", figure.plotter.render)
meth()
return figure.plotter.screenshot(
transparent_background=(mode == "rgba"), filename=filename
)


def _process_events(plotter):
def _process_events(plotter, level=0):
if hasattr(plotter, "app"):
with warnings.catch_warnings(record=True):
warnings.filterwarnings("ignore", "constrained_layout")
Expand Down
6 changes: 3 additions & 3 deletions mne/viz/evoked_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def current_time_func():
current_time_func=current_time_func,
times=evoked.times,
)
if not self._in_brain_figure or "time_slider" not in fig.widgets:

# Draw the time label
self._time_label = time_label
if time_label is not None:
Expand Down Expand Up @@ -373,7 +373,7 @@ def _update(self):
x_window=0.01, y_window=0.01, text=time_label
)

self._renderer.plotter.update()
self._renderer._update()

def _configure_dock(self):
"""Configure the widgets shown in the dock on the left."""
Expand All @@ -383,7 +383,7 @@ def _configure_dock(self):
r._dock_initialize()

# Fieldline configuration
layout = r._dock_add_group_box("Fieldlines")
layout = r._dock_add_group_box("Fieldlines", collapse=True)

r._dock_add_label(value="max value", align=True, layout=layout)

Expand Down
1 change: 1 addition & 0 deletions tools/vulture_allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
_use_test_3d_backend
verbose_debug
metadata_routing
Plotter

# Decoding
_.multi_class
Expand Down
Loading