From 6191b53cf7d8fe485590fc082f991ddbbb33b9f3 Mon Sep 17 00:00:00 2001 From: ualsbombe Date: Fri, 26 Jun 2026 12:23:19 +0200 Subject: [PATCH 1/4] remove plotting abs as the defauls for plotting volumetric --- mne/source_estimate.py | 1 + mne/viz/_3d.py | 7 +++++-- tutorials/inverse/50_beamformer_lcmv.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mne/source_estimate.py b/mne/source_estimate.py index edb1ae45764..60b82522fea 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -2399,6 +2399,7 @@ def plot( initial_time=None, initial_pos=None, verbose=None, + plot_abs=False, ): data = self.magnitude() if self._data_ndim == 3 else self return plot_volume_source_estimates( diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 2c5e081b8f0..fa427768707 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2828,7 +2828,7 @@ def _click_to_cut_coords(event, params): # We don't care about directionality, just which is which dim codes = codes.replace("L", "R").replace("P", "A").replace("I", "S") idx = codes.index(dict(x="R", y="A", z="S")[ax]) - img_data = np.abs(_get_img_fdata(params["img_idx"])) + img_data = _get_img_fdata(params["img_idx"]) ijk = _cut_coords_to_ijk(cut_coords, params["img_idx"]) if idx == 0: ijk[0] = np.argmax(img_data[:, ijk[1], ijk[2]]) @@ -2952,6 +2952,8 @@ def _plot_and_correct(*, params, cut_coords): symmetric_cbar=True, title="", ) + if mode == 'glass_brain': + plot_kwargs['plot_abs']=False params["axes"].clear() if params.get("fig_anat") is not None and plot_kwargs["colorbar"]: params["fig_anat"]._cbar.ax.clear() @@ -2988,6 +2990,7 @@ def plot_volume_source_estimates( initial_time=None, initial_pos=None, verbose=None, + ): """Plot Nutmeg style volumetric source estimates using nilearn. @@ -3184,7 +3187,7 @@ def plot_volume_source_estimates( lx = ax_time.axvline(stc.times[time_idx], color="g") params.update(fig=fig, ax_time=ax_time, lx=lx, axes=axes) - allow_pos_lims = mode != "glass_brain" + allow_pos_lims = True mapdata = _process_clim(clim, colormap, transparent, stc.data, allow_pos_lims) _separate_map(mapdata) diverging = "pos_lims" in mapdata["clim"] diff --git a/tutorials/inverse/50_beamformer_lcmv.py b/tutorials/inverse/50_beamformer_lcmv.py index bd7228fa840..ff639ee7d9d 100644 --- a/tutorials/inverse/50_beamformer_lcmv.py +++ b/tutorials/inverse/50_beamformer_lcmv.py @@ -253,7 +253,7 @@ # On MNI glass brain (orthoview; 2D) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -stc.plot(mode="glass_brain", clim=dict(kind="value", lims=lims), **kwargs) +stc.plot(mode="glass_brain", clim=dict(kind="value", pos_lims=lims), **kwargs) # %% # Volumetric rendering (3D) with vectors From 972dcd7b7b921e802a8d590eec58fa6aabc36545 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:25:49 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/viz/_3d.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index fa427768707..bcf24cce565 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2952,8 +2952,8 @@ def _plot_and_correct(*, params, cut_coords): symmetric_cbar=True, title="", ) - if mode == 'glass_brain': - plot_kwargs['plot_abs']=False + if mode == "glass_brain": + plot_kwargs["plot_abs"] = False params["axes"].clear() if params.get("fig_anat") is not None and plot_kwargs["colorbar"]: params["fig_anat"]._cbar.ax.clear() @@ -2990,7 +2990,6 @@ def plot_volume_source_estimates( initial_time=None, initial_pos=None, verbose=None, - ): """Plot Nutmeg style volumetric source estimates using nilearn. From e12369d5c970dd28855accd57dbf45d02e0b5c18 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 10 Sep 2026 09:25:18 -0400 Subject: [PATCH 3/4] FIX: More --- mne/source_estimate.py | 1 - mne/viz/_3d.py | 20 +++++++++++++------- mne/viz/tests/test_3d_mpl.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/mne/source_estimate.py b/mne/source_estimate.py index 60b82522fea..edb1ae45764 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -2399,7 +2399,6 @@ def plot( initial_time=None, initial_pos=None, verbose=None, - plot_abs=False, ): data = self.magnitude() if self._data_ndim == 3 else self return plot_volume_source_estimates( diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index bcf24cce565..e1652bc6226 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2828,7 +2828,7 @@ def _click_to_cut_coords(event, params): # We don't care about directionality, just which is which dim codes = codes.replace("L", "R").replace("P", "A").replace("I", "S") idx = codes.index(dict(x="R", y="A", z="S")[ax]) - img_data = _get_img_fdata(params["img_idx"]) + img_data = np.abs(_get_img_fdata(params["img_idx"])) ijk = _cut_coords_to_ijk(cut_coords, params["img_idx"]) if idx == 0: ijk[0] = np.argmax(img_data[:, ijk[1], ijk[2]]) @@ -2953,7 +2953,8 @@ def _plot_and_correct(*, params, cut_coords): title="", ) if mode == "glass_brain": - plot_kwargs["plot_abs"] = False + # signed MIP (value with max abs) for diverging colormaps + plot_kwargs["plot_abs"] = not params["diverging"] params["axes"].clear() if params.get("fig_anat") is not None and plot_kwargs["colorbar"]: params["fig_anat"]._cbar.ax.clear() @@ -3007,8 +3008,14 @@ def plot_volume_source_estimates( If ``None``, ``stc.subject`` will be used. %(subjects_dir)s mode : ``'stat_map'`` | ``'glass_brain'`` - The plotting mode to use. For ``'glass_brain'``, activation absolute values are - displayed after being transformed to a standard MNI brain. + The plotting mode to use. For ``'glass_brain'``, activations are displayed + after being transformed to a standard MNI brain. With a diverging colormap + (e.g., ``clim=dict(pos_lims=...)``), the signed value with the maximum + absolute value along each projection is shown; otherwise, absolute values + are shown. + + .. versionchanged:: 1.13.1 + Signed values can be shown in ``'glass_brain'`` mode. bg_img : instance of SpatialImage | str The background image used in the nilearn plotting function. Can also be a string to use the ``bg_img`` file in the subject's @@ -3186,8 +3193,7 @@ def plot_volume_source_estimates( lx = ax_time.axvline(stc.times[time_idx], color="g") params.update(fig=fig, ax_time=ax_time, lx=lx, axes=axes) - allow_pos_lims = True - mapdata = _process_clim(clim, colormap, transparent, stc.data, allow_pos_lims) + mapdata = _process_clim(clim, colormap, transparent, stc.data) _separate_map(mapdata) diverging = "pos_lims" in mapdata["clim"] ticks = _get_map_ticks(mapdata) @@ -3200,7 +3206,7 @@ def plot_volume_source_estimates( dup_neg = False if stc.data.min() < 0: ax_time.axhline(0.0, color="0.5", ls="-", lw=0.5, zorder=2) - dup_neg = not diverging # glass brain with signed data + dup_neg = not diverging # signed data with one-sided colormap yticks = list(ticks) if dup_neg: yticks += [0] + list(-np.array(ticks)) diff --git a/mne/viz/tests/test_3d_mpl.py b/mne/viz/tests/test_3d_mpl.py index 35c495a7f93..fde2b372261 100644 --- a/mne/viz/tests/test_3d_mpl.py +++ b/mne/viz/tests/test_3d_mpl.py @@ -106,6 +106,36 @@ def test_plot_volume_source_estimates_basic( assert use_ax is not None label = use_ax.get_legend().get_texts()[0].get_text() assert re.match("[0-9]*", label) is not None, label + if mode != "glass_brain" or stype != "s": + return + + # signed data: diverging colormap (default) shows signed MIP, one-sided abs + stc.data -= 0.5 + for clim, signed in ( + ("auto", True), + (dict(kind="value", lims=[0.1, 0.2, 0.5]), False), + ): + with _record_warnings(): + fig = stc.plot( + sample_src, + subject="sample", + subjects_dir=subjects_dir, + mode=mode, + clim=clim, + ) + mips = [ + np.ma.asarray(im.get_array()) + for ax in fig.axes + for im in ax.images + if im.get_array().ndim == 2 + ] + assert len(mips) == 3 + for mip in mips: + assert mip.max() > 0.4 + if signed: + assert mip.min() < -0.4 + else: + assert mip.min() >= 0 @pytest.mark.slowtest # can be slow on OSX From 2788705c608b558ef8e23c1b6939c2e62c12f911 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 10 Sep 2026 10:27:29 -0400 Subject: [PATCH 4/4] DOC: Changes --- doc/changes/dev/13989.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/dev/13989.bugfix.rst diff --git a/doc/changes/dev/13989.bugfix.rst b/doc/changes/dev/13989.bugfix.rst new file mode 100644 index 00000000000..02368b6eecb --- /dev/null +++ b/doc/changes/dev/13989.bugfix.rst @@ -0,0 +1 @@ +Fix bug where :func:`mne.viz.plot_volume_source_estimates` with ``mode="glass_brain"`` always showed absolute values, by `Lau Møller Andersen`_.