Skip to content

Commit f66a254

Browse files
authored
ENH: Add on_inside kwarg to make_forward_solution (#13307)
1 parent 41d139d commit f66a254

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added ``on_inside="raise"`` parameter to :func:`mne.make_forward_solution` and :func:`mne.make_forward_dipole` to control behavior when MEG sensors are inside the outer skin surface. This is useful for forward solutions that are computed with sensors just inside the outer skin surface (e.g., with some OPM coregistrations), by `Eric Larson`_.

mne/forward/_make_forward.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@
3737
apply_trans,
3838
invert_transform,
3939
)
40-
from ..utils import _check_fname, _pl, _validate_type, logger, verbose, warn
40+
from ..utils import (
41+
_check_fname,
42+
_on_missing,
43+
_pl,
44+
_validate_type,
45+
logger,
46+
verbose,
47+
warn,
48+
)
4149
from ._compute_forward import (
4250
_compute_forwards,
4351
_compute_forwards_meeg,
@@ -437,13 +445,15 @@ def _prepare_for_forward(
437445
bem,
438446
mindist,
439447
n_jobs,
448+
*,
440449
bem_extra="",
441450
trans="",
442451
info_extra="",
443452
meg=True,
444453
eeg=True,
445454
ignore_ref=False,
446455
allow_bem_none=False,
456+
on_inside="raise",
447457
verbose=None,
448458
):
449459
"""Prepare for forward computation.
@@ -567,11 +577,12 @@ def check_inside_head(x):
567577
meg_loc = apply_trans(invert_transform(mri_head_t), meg_loc)
568578
n_inside = check_inside_head(meg_loc).sum()
569579
if n_inside:
570-
raise RuntimeError(
580+
msg = (
571581
f"Found {n_inside} MEG sensor{_pl(n_inside)} inside the "
572582
f"{check_surface}, perhaps coordinate frames and/or "
573-
"coregistration must be incorrect"
583+
"coregistration are incorrect"
574584
)
585+
_on_missing(on_inside, msg, name="on_inside", error_klass=RuntimeError)
575586

576587
if len(src):
577588
rr = np.concatenate([s["rr"][s["vertno"]] for s in src])
@@ -610,6 +621,7 @@ def make_forward_solution(
610621
mindist=0.0,
611622
ignore_ref=False,
612623
n_jobs=None,
624+
on_inside="raise",
613625
verbose=None,
614626
):
615627
"""Calculate a forward solution for a subject.
@@ -640,6 +652,13 @@ def make_forward_solution(
640652
option should be True for KIT files, since forward computation
641653
with reference channels is not currently supported.
642654
%(n_jobs)s
655+
on_inside : 'raise' | 'warn' | 'ignore'
656+
What to do if MEG sensors are inside the outer skin surface. If 'raise'
657+
(default), an error is raised. If 'warn' or 'ignore', the forward
658+
solution is computed anyway and a warning is or isn't emitted,
659+
respectively.
660+
661+
.. versionadded:: 1.10
643662
%(verbose)s
644663
645664
Returns
@@ -710,12 +729,13 @@ def make_forward_solution(
710729
bem,
711730
mindist,
712731
n_jobs,
713-
bem_extra,
714-
trans,
715-
info_extra,
716-
meg,
717-
eeg,
718-
ignore_ref,
732+
bem_extra=bem_extra,
733+
trans=trans,
734+
info_extra=info_extra,
735+
meg=meg,
736+
eeg=eeg,
737+
ignore_ref=ignore_ref,
738+
on_inside=on_inside,
719739
)
720740
del (src, mri_head_t, trans, info_extra, bem_extra, mindist, meg, eeg, ignore_ref)
721741

@@ -741,7 +761,9 @@ def make_forward_solution(
741761

742762

743763
@verbose
744-
def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=None):
764+
def make_forward_dipole(
765+
dipole, bem, info, trans=None, n_jobs=None, *, on_inside="raise", verbose=None
766+
):
745767
"""Convert dipole object to source estimate and calculate forward operator.
746768
747769
The instance of Dipole is converted to a discrete source space,
@@ -767,6 +789,13 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N
767789
The head<->MRI transform filename. Must be provided unless BEM
768790
is a sphere model.
769791
%(n_jobs)s
792+
on_inside : 'raise' | 'warn' | 'ignore'
793+
What to do if MEG sensors are inside the outer skin surface. If 'raise'
794+
(default), an error is raised. If 'warn' or 'ignore', the forward
795+
solution is computed anyway and a warning is or isn't emitted,
796+
respectively.
797+
798+
.. versionadded:: 1.10
770799
%(verbose)s
771800
772801
Returns
@@ -805,7 +834,9 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N
805834

806835
# Forward operator created for channels in info (use pick_info to restrict)
807836
# Use defaults for most params, including min_dist
808-
fwd = make_forward_solution(info, trans, src, bem, n_jobs=n_jobs, verbose=verbose)
837+
fwd = make_forward_solution(
838+
info, trans, src, bem, n_jobs=n_jobs, on_inside=on_inside, verbose=verbose
839+
)
809840
# Convert from free orientations to fixed (in-place)
810841
convert_forward_solution(
811842
fwd, surf_ori=False, force_fixed=True, copy=False, use_cps=False, verbose=None

mne/forward/tests/test_make_forward.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,11 @@ def test_sensors_inside_bem():
894894
trans["trans"][2, 3] = 0.03
895895
sphere_noshell = make_sphere_model((0.0, 0.0, 0.0), None)
896896
sphere = make_sphere_model((0.0, 0.0, 0.0), 1.01)
897-
with pytest.raises(RuntimeError, match=".* 15 MEG.*inside the scalp.*"):
898-
make_forward_solution(info, trans, fname_src, fname_bem)
897+
with pytest.warns(RuntimeWarning, match=".* 15 MEG.*inside the scalp.*"):
898+
fwd = make_forward_solution(info, trans, fname_src, fname_bem, on_inside="warn")
899+
assert fwd["nsource"] == 516
900+
assert fwd["nchan"] == 42
901+
assert np.isfinite(fwd["sol"]["data"]).all()
899902
make_forward_solution(info, trans, fname_src, fname_bem_meg) # okay
900903
make_forward_solution(info, trans, fname_src, sphere_noshell) # okay
901904
with pytest.raises(RuntimeError, match=".* 42 MEG.*outermost sphere sh.*"):

mne/simulation/raw.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
_check_preload,
4747
_pl,
4848
_validate_type,
49+
_verbose_safe_false,
4950
check_random_state,
5051
logger,
5152
verbose,
@@ -793,7 +794,14 @@ def _iter_forward_solutions(
793794
info.update(projs=[], bads=[]) # Ensure no 'projs' or 'bads'
794795
mri_head_t, trans = _get_trans(trans)
795796
sensors, rr, info, update_kwargs, bem = _prepare_for_forward(
796-
src, mri_head_t, info, bem, mindist, n_jobs, allow_bem_none=True, verbose=False
797+
src,
798+
mri_head_t,
799+
info,
800+
bem,
801+
mindist,
802+
n_jobs,
803+
allow_bem_none=True,
804+
verbose=_verbose_safe_false(),
797805
)
798806
del (src, mindist)
799807

0 commit comments

Comments
 (0)