Skip to content

Commit 44de877

Browse files
committed
ENH: Add on_inside kwarg to make_forward_solution
1 parent d193166 commit 44de877

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
lines changed

doc/changes/devel/newfeature.rst

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.forward.make_forward_solution` and :func:`mne.forward.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
invert_transform,
3838
transform_surface_to,
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 _compute_forwards
4250
from .forward import _FWD_ORDER, Forward, _merge_fwds, convert_forward_solution
4351

@@ -433,13 +441,15 @@ def _prepare_for_forward(
433441
bem,
434442
mindist,
435443
n_jobs,
444+
*,
436445
bem_extra="",
437446
trans="",
438447
info_extra="",
439448
meg=True,
440449
eeg=True,
441450
ignore_ref=False,
442451
allow_bem_none=False,
452+
on_inside="raise",
443453
verbose=None,
444454
):
445455
"""Prepare for forward computation.
@@ -563,11 +573,12 @@ def check_inside(x):
563573
)
564574
n_inside = check_inside(meg_loc).sum()
565575
if n_inside:
566-
raise RuntimeError(
576+
msg = (
567577
f"Found {n_inside} MEG sensor{_pl(n_inside)} inside the "
568578
f"{check_surface}, perhaps coordinate frames and/or "
569-
"coregistration must be incorrect"
579+
"coregistration are incorrect"
570580
)
581+
_on_missing(on_inside, msg, name="on_inside", error_klass=RuntimeError)
571582

572583
rr = np.concatenate([s["rr"][s["vertno"]] for s in src])
573584
if len(rr) < 1:
@@ -603,6 +614,7 @@ def make_forward_solution(
603614
mindist=0.0,
604615
ignore_ref=False,
605616
n_jobs=None,
617+
on_inside="raise",
606618
verbose=None,
607619
):
608620
"""Calculate a forward solution for a subject.
@@ -633,6 +645,13 @@ def make_forward_solution(
633645
option should be True for KIT files, since forward computation
634646
with reference channels is not currently supported.
635647
%(n_jobs)s
648+
on_inside : 'raise' | 'warn' | 'ignore'
649+
What to do if MEG sensors are inside the outer skin surface. If 'raise'
650+
(default), an error is raised. If 'warn' or 'ignore', the forward
651+
solution is computed anyway and a warning is or isn't emitted,
652+
respectively.
653+
654+
.. versionadded:: 1.10
636655
%(verbose)s
637656
638657
Returns
@@ -703,12 +722,13 @@ def make_forward_solution(
703722
bem,
704723
mindist,
705724
n_jobs,
706-
bem_extra,
707-
trans,
708-
info_extra,
709-
meg,
710-
eeg,
711-
ignore_ref,
725+
bem_extra=bem_extra,
726+
trans=trans,
727+
info_extra=info_extra,
728+
meg=meg,
729+
eeg=eeg,
730+
ignore_ref=ignore_ref,
731+
on_inside=on_inside,
712732
)
713733
del (src, mri_head_t, trans, info_extra, bem_extra, mindist, meg, eeg, ignore_ref)
714734

@@ -734,7 +754,9 @@ def make_forward_solution(
734754

735755

736756
@verbose
737-
def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=None):
757+
def make_forward_dipole(
758+
dipole, bem, info, trans=None, n_jobs=None, *, on_inside="raise", verbose=None
759+
):
738760
"""Convert dipole object to source estimate and calculate forward operator.
739761
740762
The instance of Dipole is converted to a discrete source space,
@@ -760,6 +782,13 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N
760782
The head<->MRI transform filename. Must be provided unless BEM
761783
is a sphere model.
762784
%(n_jobs)s
785+
on_inside : 'raise' | 'warn' | 'ignore'
786+
What to do if MEG sensors are inside the outer skin surface. If 'raise'
787+
(default), an error is raised. If 'warn' or 'ignore', the forward
788+
solution is computed anyway and a warning is or isn't emitted,
789+
respectively.
790+
791+
.. versionadded:: 1.10
763792
%(verbose)s
764793
765794
Returns
@@ -798,7 +827,9 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N
798827

799828
# Forward operator created for channels in info (use pick_info to restrict)
800829
# Use defaults for most params, including min_dist
801-
fwd = make_forward_solution(info, trans, src, bem, n_jobs=n_jobs, verbose=verbose)
830+
fwd = make_forward_solution(
831+
info, trans, src, bem, n_jobs=n_jobs, on_inside=on_inside, verbose=verbose
832+
)
802833
# Convert from free orientations to fixed (in-place)
803834
convert_forward_solution(
804835
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
@@ -839,8 +839,11 @@ def test_sensors_inside_bem():
839839
trans["trans"][2, 3] = 0.03
840840
sphere_noshell = make_sphere_model((0.0, 0.0, 0.0), None)
841841
sphere = make_sphere_model((0.0, 0.0, 0.0), 1.01)
842-
with pytest.raises(RuntimeError, match=".* 15 MEG.*inside the scalp.*"):
843-
make_forward_solution(info, trans, fname_src, fname_bem)
842+
with pytest.warns(RuntimeWarning, match=".* 15 MEG.*inside the scalp.*"):
843+
fwd = make_forward_solution(info, trans, fname_src, fname_bem, on_inside="warn")
844+
assert fwd["nsource"] == 516
845+
assert fwd["nchan"] == 42
846+
assert np.isfinite(fwd["sol"]["data"]).all()
844847
make_forward_solution(info, trans, fname_src, fname_bem_meg) # okay
845848
make_forward_solution(info, trans, fname_src, sphere_noshell) # okay
846849
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,
@@ -792,7 +793,14 @@ def _iter_forward_solutions(
792793
info.update(projs=[], bads=[]) # Ensure no 'projs' or 'bads'
793794
mri_head_t, trans = _get_trans(trans)
794795
sensors, rr, info, update_kwargs, bem = _prepare_for_forward(
795-
src, mri_head_t, info, bem, mindist, n_jobs, allow_bem_none=True, verbose=False
796+
src,
797+
mri_head_t,
798+
info,
799+
bem,
800+
mindist,
801+
n_jobs,
802+
allow_bem_none=True,
803+
verbose=_verbose_safe_false(),
796804
)
797805
del (src, mindist)
798806

0 commit comments

Comments
 (0)