Skip to content

Commit

Permalink
Stack vertices in plot_volume_source_estimates (mne-tools#12025)
Browse files Browse the repository at this point in the history
  • Loading branch information
mscheltienne authored and snwnde committed Mar 20, 2024
1 parent b917495 commit 3b12db2
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 17 deletions.
1 change: 1 addition & 0 deletions doc/changes/devel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Bugs
- Add missing ``overwrite`` and ``verbose`` parameters to :meth:`Transform.save() <mne.transforms.Transform.save>` (:gh:`12004` by `Marijn van Vliet`_)
- Fix parsing of eye-link :class:`~mne.Annotations` when ``apply_offsets=False`` is provided to :func:`~mne.io.read_raw_eyelink` (:gh:`12003` by `Mathieu Scheltienne`_)
- Correctly prune channel-specific :class:`~mne.Annotations` when creating :class:`~mne.Epochs` without the channel(s) included in the channel specific annotations (:gh:`12010` by `Mathieu Scheltienne`_)
- Fix :func:`~mne.viz.plot_volume_source_estimates` with :class:`~mne.VolSourceEstimate` which include a list of vertices (:gh:`12025` by `Mathieu Scheltienne`_)
- Correctly handle passing ``"eyegaze"`` or ``"pupil"`` to :meth:`mne.io.Raw.pick` (:gh:`12019` by `Scott Huberty`_)

API changes
Expand Down
10 changes: 10 additions & 0 deletions mne/minimum_norm/inverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ def _repr_html_(self):
)
return html

@property
def ch_names(self):
"""Name of channels attached to the inverse operator."""
return self["info"].ch_names

@property
def info(self):
""":class:`~mne.Info` attached to the inverse operator."""
return self["info"]


def _pick_channels_inverse_operator(ch_names, inv):
"""Return data channel indices to be used knowing an inverse operator.
Expand Down
29 changes: 14 additions & 15 deletions mne/viz/_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -2650,10 +2650,9 @@ def plot_volume_source_estimates(
%(subject_none)s
If ``None``, ``stc.subject`` will be used.
%(subjects_dir)s
mode : str
The plotting mode to use. Either 'stat_map' (default) or 'glass_brain'.
For "glass_brain", activation absolute values are displayed
after being transformed to a standard MNI brain.
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.
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
Expand Down Expand Up @@ -2714,10 +2713,11 @@ def plot_volume_source_estimates(
>>> morph = mne.compute_source_morph(src_sample, subject_to='fsaverage') # doctest: +SKIP
>>> fig = stc_vol_sample.plot(morph) # doctest: +SKIP
""" # noqa: E501
from matplotlib import pyplot as plt, colors
import nibabel as nib
from ..source_estimate import VolSourceEstimate
from matplotlib import pyplot as plt, colors

from ..morph import SourceMorph
from ..source_estimate import VolSourceEstimate
from ..source_space._source_space import _ensure_src

if not check_version("nilearn", "0.4"):
Expand Down Expand Up @@ -2745,8 +2745,9 @@ def plot_volume_source_estimates(
level="debug",
)
subject = _check_subject(src_subject, subject, first_kind=kind)
stc_ijk = np.array(np.unravel_index(stc.vertices[0], img.shape[:3], order="F")).T
assert stc_ijk.shape == (len(stc.vertices[0]), 3)
vertices = np.hstack(stc.vertices)
stc_ijk = np.array(np.unravel_index(vertices, img.shape[:3], order="F")).T
assert stc_ijk.shape == (vertices.size, 3)
del kind

# XXX this assumes zooms are uniform, should probably mult by zooms...
Expand All @@ -2756,12 +2757,11 @@ def _cut_coords_to_idx(cut_coords, img):
"""Convert voxel coordinates to index in stc.data."""
ijk = _cut_coords_to_ijk(cut_coords, img)
del cut_coords
logger.debug(" Affine remapped cut coords to [%d, %d, %d] idx" % tuple(ijk))
logger.debug(" Affine remapped cut coords to [%d, %d, %d] idx", tuple(ijk))
dist, loc_idx = dist_to_verts.query(ijk[np.newaxis])
dist, loc_idx = dist[0], loc_idx[0]
logger.debug(
" Using vertex %d at a distance of %d voxels"
% (stc.vertices[0][loc_idx], dist)
" Using vertex %d at a distance of %d voxels", (vertices[loc_idx], dist)
)
return loc_idx

Expand Down Expand Up @@ -2848,7 +2848,7 @@ def _update_timeslice(idx, params):
plot_map_callback(params["img_idx"], title="", cut_coords=cut_coords)

def _update_vertlabel(loc_idx):
vert_legend.get_texts()[0].set_text(f"{stc.vertices[0][loc_idx]}")
vert_legend.get_texts()[0].set_text(f"{vertices[loc_idx]}")

@verbose_dec
def _onclick(event, params, verbose=None):
Expand Down Expand Up @@ -2932,7 +2932,7 @@ def _onclick(event, params, verbose=None):
(stc.times[time_idx],)
+ tuple(cut_coords)
+ tuple(ijk)
+ (stc.vertices[0][loc_idx],)
+ (vertices[loc_idx],)
)
)
del ijk
Expand Down Expand Up @@ -3046,8 +3046,7 @@ def plot_and_correct(*args, **kwargs):

plot_and_correct(stat_map_img=params["img_idx"], title="", cut_coords=cut_coords)

if show:
plt.show()
plt_show(show)
fig.canvas.mpl_connect(
"button_press_event", partial(_onclick, params=params, verbose=verbose)
)
Expand Down
67 changes: 65 additions & 2 deletions mne/viz/tests/test_3d_mpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@
import pytest

from mne import (
compute_covariance,
compute_source_morph,
make_fixed_length_epochs,
make_forward_solution,
read_bem_solution,
read_forward_solution,
VolSourceEstimate,
read_trans,
setup_volume_source_space,
SourceEstimate,
VolSourceEstimate,
VolVectorSourceEstimate,
compute_source_morph,
)
from mne.datasets import testing
from mne.io import read_raw_fif
from mne.minimum_norm import apply_inverse, make_inverse_operator
from mne.utils import catch_logging, _record_warnings
from mne.viz import plot_volume_source_estimates
from mne.viz.utils import _fake_click, _fake_keypress
Expand Down Expand Up @@ -148,3 +156,58 @@ def test_plot_volume_source_estimates_morph():
stc.plot(
sample_src, "sample", subjects_dir, clim=dict(lims=[-1, 2, 3], kind="value")
)


@testing.requires_testing_data
def test_plot_volume_source_estimates_on_vol_labels():
"""Test plot of source estimate on srcs setup on 2 labels."""
pytest.importorskip("nibabel")
pytest.importorskip("dipy")
pytest.importorskip("nilearn")
raw = read_raw_fif(
data_dir / "MEG" / "sample" / "sample_audvis_trunc_raw.fif", preload=False
)
raw.pick("meg").crop(0, 10)
raw.pick(raw.ch_names[::2]).del_proj().load_data()
epochs = make_fixed_length_epochs(raw, preload=True).apply_baseline((None, None))
evoked = epochs.average()
subject = "sample"
bem = read_bem_solution(
subjects_dir / f"{subject}" / "bem" / "sample-320-bem-sol.fif"
)
pos = 25.0 # spacing in mm
volume_label = [
"Right-Cerebral-Cortex",
"Left-Cerebral-Cortex",
]
src = setup_volume_source_space(
subject,
subjects_dir=subjects_dir,
pos=pos,
mri=subjects_dir / subject / "mri" / "aseg.mgz",
bem=bem,
volume_label=volume_label,
add_interpolator=False,
)
trans = read_trans(data_dir / "MEG" / "sample" / "sample_audvis_trunc-trans.fif")
fwd = make_forward_solution(
evoked.info,
trans,
src,
bem,
meg=True,
eeg=False,
mindist=0,
n_jobs=1,
)
cov = compute_covariance(
epochs,
tmin=None,
tmax=None,
method="empirical",
)
inverse_operator = make_inverse_operator(evoked.info, fwd, cov, loose=1, depth=0.8)
stc = apply_inverse(
evoked, inverse_operator, 1.0 / 3**2, method="sLORETA", pick_ori=None
)
stc.plot(src, subject, subjects_dir, initial_time=0.03)

0 comments on commit 3b12db2

Please sign in to comment.