Skip to content

ENH: Add on_inside kwarg to make_forward_solution #13307

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 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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/13307.newfeature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +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`_.
53 changes: 42 additions & 11 deletions mne/forward/_make_forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@
invert_transform,
transform_surface_to,
)
from ..utils import _check_fname, _pl, _validate_type, logger, verbose, warn
from ..utils import (
_check_fname,
_on_missing,
_pl,
_validate_type,
logger,
verbose,
warn,
)
from ._compute_forward import _compute_forwards
from .forward import _FWD_ORDER, Forward, _merge_fwds, convert_forward_solution

Expand Down Expand Up @@ -433,13 +441,15 @@ def _prepare_for_forward(
bem,
mindist,
n_jobs,
*,
bem_extra="",
trans="",
info_extra="",
meg=True,
eeg=True,
ignore_ref=False,
allow_bem_none=False,
on_inside="raise",
verbose=None,
):
"""Prepare for forward computation.
Expand Down Expand Up @@ -563,11 +573,12 @@ def check_inside(x):
)
n_inside = check_inside(meg_loc).sum()
if n_inside:
raise RuntimeError(
msg = (
f"Found {n_inside} MEG sensor{_pl(n_inside)} inside the "
f"{check_surface}, perhaps coordinate frames and/or "
"coregistration must be incorrect"
"coregistration are incorrect"
)
_on_missing(on_inside, msg, name="on_inside", error_klass=RuntimeError)

rr = np.concatenate([s["rr"][s["vertno"]] for s in src])
if len(rr) < 1:
Expand Down Expand Up @@ -603,6 +614,7 @@ def make_forward_solution(
mindist=0.0,
ignore_ref=False,
n_jobs=None,
on_inside="raise",
verbose=None,
):
"""Calculate a forward solution for a subject.
Expand Down Expand Up @@ -633,6 +645,13 @@ def make_forward_solution(
option should be True for KIT files, since forward computation
with reference channels is not currently supported.
%(n_jobs)s
on_inside : 'raise' | 'warn' | 'ignore'
What to do if MEG sensors are inside the outer skin surface. If 'raise'
(default), an error is raised. If 'warn' or 'ignore', the forward
solution is computed anyway and a warning is or isn't emitted,
respectively.

.. versionadded:: 1.10
%(verbose)s

Returns
Expand Down Expand Up @@ -703,12 +722,13 @@ def make_forward_solution(
bem,
mindist,
n_jobs,
bem_extra,
trans,
info_extra,
meg,
eeg,
ignore_ref,
bem_extra=bem_extra,
trans=trans,
info_extra=info_extra,
meg=meg,
eeg=eeg,
ignore_ref=ignore_ref,
on_inside=on_inside,
)
del (src, mri_head_t, trans, info_extra, bem_extra, mindist, meg, eeg, ignore_ref)

Expand All @@ -734,7 +754,9 @@ def make_forward_solution(


@verbose
def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=None):
def make_forward_dipole(
dipole, bem, info, trans=None, n_jobs=None, *, on_inside="raise", verbose=None
):
"""Convert dipole object to source estimate and calculate forward operator.

The instance of Dipole is converted to a discrete source space,
Expand All @@ -760,6 +782,13 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N
The head<->MRI transform filename. Must be provided unless BEM
is a sphere model.
%(n_jobs)s
on_inside : 'raise' | 'warn' | 'ignore'
What to do if MEG sensors are inside the outer skin surface. If 'raise'
(default), an error is raised. If 'warn' or 'ignore', the forward
solution is computed anyway and a warning is or isn't emitted,
respectively.

.. versionadded:: 1.10
%(verbose)s

Returns
Expand Down Expand Up @@ -798,7 +827,9 @@ def make_forward_dipole(dipole, bem, info, trans=None, n_jobs=None, *, verbose=N

# Forward operator created for channels in info (use pick_info to restrict)
# Use defaults for most params, including min_dist
fwd = make_forward_solution(info, trans, src, bem, n_jobs=n_jobs, verbose=verbose)
fwd = make_forward_solution(
info, trans, src, bem, n_jobs=n_jobs, on_inside=on_inside, verbose=verbose
)
# Convert from free orientations to fixed (in-place)
convert_forward_solution(
fwd, surf_ori=False, force_fixed=True, copy=False, use_cps=False, verbose=None
Expand Down
7 changes: 5 additions & 2 deletions mne/forward/tests/test_make_forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,11 @@ def test_sensors_inside_bem():
trans["trans"][2, 3] = 0.03
sphere_noshell = make_sphere_model((0.0, 0.0, 0.0), None)
sphere = make_sphere_model((0.0, 0.0, 0.0), 1.01)
with pytest.raises(RuntimeError, match=".* 15 MEG.*inside the scalp.*"):
make_forward_solution(info, trans, fname_src, fname_bem)
with pytest.warns(RuntimeWarning, match=".* 15 MEG.*inside the scalp.*"):
fwd = make_forward_solution(info, trans, fname_src, fname_bem, on_inside="warn")
assert fwd["nsource"] == 516
assert fwd["nchan"] == 42
assert np.isfinite(fwd["sol"]["data"]).all()
make_forward_solution(info, trans, fname_src, fname_bem_meg) # okay
make_forward_solution(info, trans, fname_src, sphere_noshell) # okay
with pytest.raises(RuntimeError, match=".* 42 MEG.*outermost sphere sh.*"):
Expand Down
10 changes: 9 additions & 1 deletion mne/simulation/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
_check_preload,
_pl,
_validate_type,
_verbose_safe_false,
check_random_state,
logger,
verbose,
Expand Down Expand Up @@ -792,7 +793,14 @@ def _iter_forward_solutions(
info.update(projs=[], bads=[]) # Ensure no 'projs' or 'bads'
mri_head_t, trans = _get_trans(trans)
sensors, rr, info, update_kwargs, bem = _prepare_for_forward(
src, mri_head_t, info, bem, mindist, n_jobs, allow_bem_none=True, verbose=False
src,
mri_head_t,
info,
bem,
mindist,
n_jobs,
allow_bem_none=True,
verbose=_verbose_safe_false(),
)
del (src, mindist)

Expand Down