Skip to content

Add sourcespace to Report #12848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7c80b70
Add src to Report.add_bem
vferat Sep 13, 2024
e464260
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 13, 2024
1710b11
Update report.py
vferat Oct 4, 2024
690945a
Revert "Add src to Report.add_bem"
vferat Oct 4, 2024
00c60fa
Add fig parameter to src.plot
vferat Oct 4, 2024
b2da283
test forward rendering
vferat Oct 4, 2024
924c029
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2024
26b6a42
wip
vferat Feb 3, 2025
f7fa3b8
Merge branch 'dev-report-src' of https://github.com/vferat/mne-python…
vferat Feb 3, 2025
712ba43
Merge branch 'main' into dev-report-src
vferat Feb 3, 2025
45a4fa1
wip
vferat Feb 10, 2025
031fa66
Merge branch 'dev-report-src' of https://github.com/vferat/mne-python…
vferat Feb 10, 2025
dd59dfe
Merge remote-tracking branch 'upstream/main' into dev-report-src
vferat Feb 10, 2025
175a622
fix section
vferat Feb 10, 2025
9226b32
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 10, 2025
b7529b5
Update test_misc.py
vferat Feb 10, 2025
3f80b8f
Create 12848.newfeature.rst
vferat Feb 10, 2025
d8a7a41
Merge branch 'dev-report-src' of https://github.com/vferat/mne-python…
vferat Feb 10, 2025
0136108
Merge remote-tracking branch 'upstream/main' into dev-report-src
vferat Feb 21, 2025
5f38c72
wip
vferat Feb 21, 2025
6103859
Update 12848.newfeature.rst
vferat Feb 21, 2025
8664b45
Update 12848.newfeature.rst
vferat Feb 22, 2025
ea20780
Merge branch 'dev-report-src' of https://github.com/vferat/mne-python…
vferat Mar 17, 2025
d24ebde
Merge remote-tracking branch 'upstream/main' into dev-report-src
vferat Mar 17, 2025
64c1e13
Change forward figures
vferat Mar 17, 2025
aac99fb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 17, 2025
5d9ef52
Edit views
vferat Apr 14, 2025
665610b
Merge branch 'dev-report-src' of https://github.com/vferat/mne-python…
vferat Apr 14, 2025
0f556f7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 14, 2025
0d547dd
Merge remote-tracking branch 'upstream/main' into dev-report-src
vferat May 26, 2025
0468218
Add test_add_forward
vferat May 26, 2025
7f70ad5
Update test_report.py
vferat May 26, 2025
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
1 change: 1 addition & 0 deletions doc/changes/devel/12848.newfeature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add source space(s) visualization(s) in :func:`mne.Report.add_forward`, by `Victor Ferat`_.
139 changes: 134 additions & 5 deletions mne/report/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,17 @@ def _fig_to_img(


def _get_bem_contour_figs_as_arrays(
*, sl, n_jobs, mri_fname, surfaces, orientation, src, show, show_orientation, width
*,
sl,
n_jobs,
mri_fname,
surfaces,
orientation,
src,
trans,
show,
show_orientation,
width,
):
"""Render BEM surface contours on MRI slices.

Expand All @@ -494,6 +504,7 @@ def _get_bem_contour_figs_as_arrays(
surfaces=surfaces,
orientation=orientation,
src=src,
trans=trans,
show=show,
show_orientation=show_orientation,
width=width,
Expand All @@ -507,6 +518,21 @@ def _get_bem_contour_figs_as_arrays(
return out


def _iterate_alignment_views(function, alpha, **kwargs):
"""Auxiliary function to iterate over views in trans fig."""
from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING

# TODO: Eventually maybe we should expose the size option?
size = (80, 80) if MNE_3D_BACKEND_TESTING else (800, 800)
fig = create_3d_figure(size, bgcolor=(0.5, 0.5, 0.5))
from ..viz.backends.renderer import backend

try:
return _itv(function, fig, **kwargs)
finally:
backend._close_3d_figure(fig)


def _iterate_trans_views(function, alpha, **kwargs):
"""Auxiliary function to iterate over views in trans fig."""
from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING
Expand All @@ -530,7 +556,18 @@ def _itv(function, fig, *, max_width=MAX_IMG_WIDTH, max_res=MAX_IMG_RES, **kwarg

function(fig=fig, **kwargs)

views = ("frontal", "lateral", "medial", "axial", "rostral", "coronal")
views = (
"lateral_r",
"frontlat_r",
"frontal",
"frontlat_l",
"lateral_l",
"top",
"backlat_r",
"back",
"backlat_l",
"bot",
)

images = []
for view in views:
Expand All @@ -543,7 +580,7 @@ def _itv(function, fig, *, max_width=MAX_IMG_WIDTH, max_res=MAX_IMG_RES, **kwarg
images.append(im)

images = np.concatenate(
[np.concatenate(images[:3], axis=1), np.concatenate(images[3:], axis=1)], axis=0
[np.concatenate(images[:5], axis=1), np.concatenate(images[5:], axis=1)], axis=0
)

try:
Expand Down Expand Up @@ -2639,6 +2676,8 @@ def add_bem(
self._add_bem(
subject=subject,
subjects_dir=subjects_dir,
src=None,
trans=None,
decim=decim,
n_jobs=n_jobs,
width=width,
Expand Down Expand Up @@ -3229,6 +3268,8 @@ def _render_one_bem_axis(
surfaces,
image_format,
orientation,
src=None,
trans=None,
decim=2,
n_jobs=None,
width=512,
Expand All @@ -3249,7 +3290,8 @@ def _render_one_bem_axis(
mri_fname=mri_fname,
surfaces=surfaces,
orientation=orientation,
src=None,
src=src,
trans=trans,
show=False,
show_orientation="always",
width=width,
Expand Down Expand Up @@ -3558,6 +3600,89 @@ def _add_forward(
replace=replace,
)

if subject:
src = forward["src"]
trans = forward["mri_head_t"]
# Alignment
kwargs = dict(
info=forward["info"],
trans=trans,
src=src,
subject=subject,
subjects_dir=subjects_dir,
meg=["helmet", "sensors"],
show_axes=True,
eeg=dict(original=0.2, projected=0.8),
coord_frame="mri",
)
img, _ = _iterate_trans_views(
function=plot_alignment,
alpha=0.5,
max_width=self.img_max_width,
max_res=self.img_max_res,
**kwargs,
)
self._add_image(
img=img,
title="Alignment",
section=section,
caption=None,
image_format="png",
tags=tags,
replace=replace,
)
# Source space
kwargs = dict(
trans=trans,
subjects_dir=subjects_dir,
)

self._add_bem(
subject=subject,
subjects_dir=subjects_dir,
src=src,
trans=trans,
decim=1,
n_jobs=1,
width=512,
image_format=image_format,
title="Source space(s) (BEM view)",
section=section,
tags=tags,
replace=replace,
)

if src.kind == "surface" or src.kind == "mixed":
surfaces = dict(head=0.1, white=0.5)
else:
surfaces = dict(head=0.1)

kwargs = dict(
trans=trans,
src=src,
subject=subject,
subjects_dir=subjects_dir,
show_axes=False,
coord_frame="mri",
surfaces=surfaces,
)
img, _ = _iterate_alignment_views(
function=plot_alignment,
alpha=0.5,
max_width=self.img_max_width,
max_res=self.img_max_res,
**kwargs,
)
self._add_image(
img=img,
title="Source space(s) (3D view)",
section=section,
caption=None,
image_format="png",
tags=tags,
replace=replace,
)

def _add_inverse_operator(
self,
*,
Expand Down Expand Up @@ -4405,13 +4530,15 @@ def _add_bem(
*,
subject,
subjects_dir,
src,
trans,
decim,
n_jobs,
width=512,
image_format,
title,
tags,
section,
tags,
replace,
):
"""Render mri+bem (only PNG)."""
Expand All @@ -4437,6 +4564,8 @@ def _add_bem(
mri_fname=mri_fname,
surfaces=surfaces,
orientation=orientation,
src=src,
trans=trans,
decim=decim,
n_jobs=n_jobs,
width=width,
Expand Down
24 changes: 24 additions & 0 deletions mne/report/tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,29 @@ def test_add_bem_n_jobs(n_jobs, monkeypatch):
assert 0.778 < corr < 0.80


@pytest.mark.filterwarnings("ignore:Distances could not be calculated.*:RuntimeWarning")
@pytest.mark.slowtest
@testing.requires_testing_data
def test_add_forward():
"""Test add_forward."""
report = Report(subjects_dir=subjects_dir, image_format="png")
report.add_forward(
forward=fwd_fname,
subject="sample",
subjects_dir=subjects_dir,
title="Forward solution",
)
assert len(report.html) == 4

report = Report(subjects_dir=subjects_dir, image_format="png")
report.add_forward(
forward=fwd_fname,
subjects_dir=subjects_dir,
title="Forward solution",
)
assert len(report.html) == 1


@testing.requires_testing_data
def test_render_mri_without_bem(tmp_path):
"""Test rendering MRI without BEM for mne report."""
Expand Down Expand Up @@ -882,6 +905,7 @@ def test_survive_pickle(tmp_path):

@pytest.mark.slowtest # ~30 s on Azure Windows
@testing.requires_testing_data
@pytest.mark.filterwarnings("ignore:Distances could not be calculated.*:RuntimeWarning")
def test_manual_report_2d(tmp_path, invisible_fig):
"""Simulate user manually creating report by adding one file at a time."""
pytest.importorskip("sklearn")
Expand Down
7 changes: 7 additions & 0 deletions mne/source_space/_source_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def plot(
skull=None,
subjects_dir=None,
trans=None,
fig=None,
verbose=None,
):
"""Plot the source space.
Expand Down Expand Up @@ -358,6 +359,11 @@ def plot(
produced during coregistration. If trans is None, an identity
matrix is assumed. This is only needed when the source space is in
head coordinates.
fig : Figure3D | None
PyVista scene in which to plot the alignment.
If ``None``, creates a new 600x600 pixel figure with black background.

.. versionadded:: 1.9
%(verbose)s

Returns
Expand Down Expand Up @@ -427,6 +433,7 @@ def plot(
ecog=False,
bem=bem,
src=self,
fig=fig,
)

def __getitem__(self, *args, **kwargs):
Expand Down
20 changes: 19 additions & 1 deletion mne/viz/_brain/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
azimuth=180.0, elevation=0.0, focalpoint=ORIGIN, roll=0, distance=DIST
),
}

_both_views_dict = {
"lateral_r": dict(azimuth=180.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"frontlat_r": dict(azimuth=120.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"frontal": dict(azimuth=90.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"frontlat_l": dict(azimuth=60.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"lateral_l": dict(azimuth=180.0, elevation=-90.0, focalpoint=ORIGIN, distance=DIST),
"backlat_r": dict(azimuth=-120.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"back": dict(azimuth=90.0, elevation=-90.0, focalpoint=ORIGIN, distance=DIST),
"backlat_l": dict(azimuth=-60.0, elevation=90.0, focalpoint=ORIGIN, distance=DIST),
"top": dict(azimuth=180.0, elevation=0.0, focalpoint=ORIGIN, distance=DIST),
"bot": dict(azimuth=180, elevation=180, focalpoint=ORIGIN, distance=DIST),
}

# add short-size version entries into the dict
lh_views_dict = _lh_views_dict.copy()
for k, v in _lh_views_dict.items():
Expand All @@ -49,6 +63,10 @@
rh_views_dict["flat"] = dict(
azimuth=0, elevation=0, focalpoint=ORIGIN, roll=0, distance=DIST
)

both_views_dict = _both_views_dict.copy()


views_dicts = dict(
lh=lh_views_dict, vol=lh_views_dict, both=lh_views_dict, rh=rh_views_dict
lh=lh_views_dict, vol=lh_views_dict, both=both_views_dict, rh=rh_views_dict
)
20 changes: 13 additions & 7 deletions mne/viz/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from ..fixes import _safe_svd
from ..rank import compute_rank
from ..surface import read_surface
from ..transforms import _frame_to_str, apply_trans
from ..transforms import apply_trans
from ..utils import (
_check_option,
_mask_to_onsets_offsets,
Expand Down Expand Up @@ -360,6 +360,7 @@ def _plot_mri_contours(
mri_fname,
surfaces,
src,
trans=None,
orientation="coronal",
slices=None,
show=True,
Expand Down Expand Up @@ -439,14 +440,15 @@ def _plot_mri_contours(
sources = list()
if src is not None:
_ensure_src(src, extra=" or None")
# Eventually we can relax this by allowing ``trans`` if need be
if src[0]["coord_frame"] != FIFF.FIFFV_COORD_MRI:
raise ValueError(
"Source space must be in MRI coordinates, got "
f"{_frame_to_str[src[0]['coord_frame']]}"
)
for src_ in src:
points = src_["rr"][src_["inuse"].astype(bool)]
if src_["coord_frame"] != FIFF.FIFFV_COORD_MRI:
if trans is None:
raise ValueError(
"Source space must be in MRI coordinates, or provide a trans."
)
else:
points = apply_trans(np.linalg.inv(trans["trans"]), points)
sources.append(apply_trans(mri_rasvox_t, points * 1e3))
sources = np.concatenate(sources, axis=0)

Expand Down Expand Up @@ -600,6 +602,7 @@ def plot_bem(
slices=None,
brain_surfaces=None,
src=None,
trans=None,
show=True,
show_indices=True,
mri="T1.mgz",
Expand Down Expand Up @@ -629,6 +632,8 @@ def plot_bem(
.. versionchanged:: 0.20
All sources are shown on the nearest slice rather than some
being omitted.
%(trans)s
.. versionadded:: 1.9
show : bool
Show figure if True.
show_indices : bool
Expand Down Expand Up @@ -722,6 +727,7 @@ def plot_bem(
mri_fname=mri_fname,
surfaces=surfaces,
src=src,
trans=trans,
orientation=orientation,
slices=slices,
show=show,
Expand Down
2 changes: 1 addition & 1 deletion mne/viz/tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def test_plot_bem():
src=src_fname,
)
assert len(fig.axes[0].collections) == 4 # 3 BEM surfaces + 1 src contour
with pytest.raises(ValueError, match="MRI coordinates, got head"):
with pytest.raises(ValueError, match="Source space must be in MRI coordinates"):
plot_bem(subject="sample", subjects_dir=subjects_dir, src=inv_fname)


Expand Down
Loading