Skip to content

Commit 3b12db2

Browse files
Mathieu Scheltiennesnwnde
authored andcommitted
Stack vertices in plot_volume_source_estimates (mne-tools#12025)
1 parent b917495 commit 3b12db2

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed

doc/changes/devel.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Bugs
6060
- Add missing ``overwrite`` and ``verbose`` parameters to :meth:`Transform.save() <mne.transforms.Transform.save>` (:gh:`12004` by `Marijn van Vliet`_)
6161
- 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`_)
6262
- 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`_)
63+
- Fix :func:`~mne.viz.plot_volume_source_estimates` with :class:`~mne.VolSourceEstimate` which include a list of vertices (:gh:`12025` by `Mathieu Scheltienne`_)
6364
- Correctly handle passing ``"eyegaze"`` or ``"pupil"`` to :meth:`mne.io.Raw.pick` (:gh:`12019` by `Scott Huberty`_)
6465

6566
API changes

mne/minimum_norm/inverse.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ def _repr_html_(self):
145145
)
146146
return html
147147

148+
@property
149+
def ch_names(self):
150+
"""Name of channels attached to the inverse operator."""
151+
return self["info"].ch_names
152+
153+
@property
154+
def info(self):
155+
""":class:`~mne.Info` attached to the inverse operator."""
156+
return self["info"]
157+
148158

149159
def _pick_channels_inverse_operator(ch_names, inv):
150160
"""Return data channel indices to be used knowing an inverse operator.

mne/viz/_3d.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2650,10 +2650,9 @@ def plot_volume_source_estimates(
26502650
%(subject_none)s
26512651
If ``None``, ``stc.subject`` will be used.
26522652
%(subjects_dir)s
2653-
mode : str
2654-
The plotting mode to use. Either 'stat_map' (default) or 'glass_brain'.
2655-
For "glass_brain", activation absolute values are displayed
2656-
after being transformed to a standard MNI brain.
2653+
mode : ``'stat_map'`` | ``'glass_brain'``
2654+
The plotting mode to use. For ``'glass_brain'``, activation absolute values are
2655+
displayed after being transformed to a standard MNI brain.
26572656
bg_img : instance of SpatialImage | str
26582657
The background image used in the nilearn plotting function.
26592658
Can also be a string to use the ``bg_img`` file in the subject's
@@ -2714,10 +2713,11 @@ def plot_volume_source_estimates(
27142713
>>> morph = mne.compute_source_morph(src_sample, subject_to='fsaverage') # doctest: +SKIP
27152714
>>> fig = stc_vol_sample.plot(morph) # doctest: +SKIP
27162715
""" # noqa: E501
2717-
from matplotlib import pyplot as plt, colors
27182716
import nibabel as nib
2719-
from ..source_estimate import VolSourceEstimate
2717+
from matplotlib import pyplot as plt, colors
2718+
27202719
from ..morph import SourceMorph
2720+
from ..source_estimate import VolSourceEstimate
27212721
from ..source_space._source_space import _ensure_src
27222722

27232723
if not check_version("nilearn", "0.4"):
@@ -2745,8 +2745,9 @@ def plot_volume_source_estimates(
27452745
level="debug",
27462746
)
27472747
subject = _check_subject(src_subject, subject, first_kind=kind)
2748-
stc_ijk = np.array(np.unravel_index(stc.vertices[0], img.shape[:3], order="F")).T
2749-
assert stc_ijk.shape == (len(stc.vertices[0]), 3)
2748+
vertices = np.hstack(stc.vertices)
2749+
stc_ijk = np.array(np.unravel_index(vertices, img.shape[:3], order="F")).T
2750+
assert stc_ijk.shape == (vertices.size, 3)
27502751
del kind
27512752

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

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

28502850
def _update_vertlabel(loc_idx):
2851-
vert_legend.get_texts()[0].set_text(f"{stc.vertices[0][loc_idx]}")
2851+
vert_legend.get_texts()[0].set_text(f"{vertices[loc_idx]}")
28522852

28532853
@verbose_dec
28542854
def _onclick(event, params, verbose=None):
@@ -2932,7 +2932,7 @@ def _onclick(event, params, verbose=None):
29322932
(stc.times[time_idx],)
29332933
+ tuple(cut_coords)
29342934
+ tuple(ijk)
2935-
+ (stc.vertices[0][loc_idx],)
2935+
+ (vertices[loc_idx],)
29362936
)
29372937
)
29382938
del ijk
@@ -3046,8 +3046,7 @@ def plot_and_correct(*args, **kwargs):
30463046

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

3049-
if show:
3050-
plt.show()
3049+
plt_show(show)
30513050
fig.canvas.mpl_connect(
30523051
"button_press_event", partial(_onclick, params=params, verbose=verbose)
30533052
)

mne/viz/tests/test_3d_mpl.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@
1313
import pytest
1414

1515
from mne import (
16+
compute_covariance,
17+
compute_source_morph,
18+
make_fixed_length_epochs,
19+
make_forward_solution,
20+
read_bem_solution,
1621
read_forward_solution,
17-
VolSourceEstimate,
22+
read_trans,
23+
setup_volume_source_space,
1824
SourceEstimate,
25+
VolSourceEstimate,
1926
VolVectorSourceEstimate,
20-
compute_source_morph,
2127
)
2228
from mne.datasets import testing
29+
from mne.io import read_raw_fif
30+
from mne.minimum_norm import apply_inverse, make_inverse_operator
2331
from mne.utils import catch_logging, _record_warnings
2432
from mne.viz import plot_volume_source_estimates
2533
from mne.viz.utils import _fake_click, _fake_keypress
@@ -148,3 +156,58 @@ def test_plot_volume_source_estimates_morph():
148156
stc.plot(
149157
sample_src, "sample", subjects_dir, clim=dict(lims=[-1, 2, 3], kind="value")
150158
)
159+
160+
161+
@testing.requires_testing_data
162+
def test_plot_volume_source_estimates_on_vol_labels():
163+
"""Test plot of source estimate on srcs setup on 2 labels."""
164+
pytest.importorskip("nibabel")
165+
pytest.importorskip("dipy")
166+
pytest.importorskip("nilearn")
167+
raw = read_raw_fif(
168+
data_dir / "MEG" / "sample" / "sample_audvis_trunc_raw.fif", preload=False
169+
)
170+
raw.pick("meg").crop(0, 10)
171+
raw.pick(raw.ch_names[::2]).del_proj().load_data()
172+
epochs = make_fixed_length_epochs(raw, preload=True).apply_baseline((None, None))
173+
evoked = epochs.average()
174+
subject = "sample"
175+
bem = read_bem_solution(
176+
subjects_dir / f"{subject}" / "bem" / "sample-320-bem-sol.fif"
177+
)
178+
pos = 25.0 # spacing in mm
179+
volume_label = [
180+
"Right-Cerebral-Cortex",
181+
"Left-Cerebral-Cortex",
182+
]
183+
src = setup_volume_source_space(
184+
subject,
185+
subjects_dir=subjects_dir,
186+
pos=pos,
187+
mri=subjects_dir / subject / "mri" / "aseg.mgz",
188+
bem=bem,
189+
volume_label=volume_label,
190+
add_interpolator=False,
191+
)
192+
trans = read_trans(data_dir / "MEG" / "sample" / "sample_audvis_trunc-trans.fif")
193+
fwd = make_forward_solution(
194+
evoked.info,
195+
trans,
196+
src,
197+
bem,
198+
meg=True,
199+
eeg=False,
200+
mindist=0,
201+
n_jobs=1,
202+
)
203+
cov = compute_covariance(
204+
epochs,
205+
tmin=None,
206+
tmax=None,
207+
method="empirical",
208+
)
209+
inverse_operator = make_inverse_operator(evoked.info, fwd, cov, loose=1, depth=0.8)
210+
stc = apply_inverse(
211+
evoked, inverse_operator, 1.0 / 3**2, method="sLORETA", pick_ori=None
212+
)
213+
stc.plot(src, subject, subjects_dir, initial_time=0.03)

0 commit comments

Comments
 (0)