Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ jobs:
sed -i "/numba/d" environment.yml
# And on Windows and macOS PySide6.9.0 segfaults
elif [[ "$RUNNER_OS" == "macOS" ]]; then
sed -i "" "s/ - PySide6 .*/ - PySide6 <6.8/g" environment.yml
sed -i "" "s/ - PySide6 .*/ - PySide6 =6.7.3/g" environment.yml
sed -i "" "s/ - vtk .*/ - vtk =9.3.1/g" environment.yml
elif [[ "$RUNNER_OS" == "Windows" ]]; then
sed -i "s/ - PySide6 .*/ - PySide6 <6.8/g" environment.yml
fi
Expand All @@ -139,8 +140,10 @@ jobs:
with:
environment-file: ${{ env.CONDA_ENV }}
environment-name: mne
log-level: ${{ runner.debug == '1' && 'debug' || 'info' }}
create-args: >-
python=${{ env.PYTHON_VERSION }}
-v
if: ${{ !startswith(matrix.kind, 'pip') }}
timeout-minutes: 20
- run: bash ./tools/github_actions_dependencies.sh
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
repos:
# Ruff mne
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.12
rev: v0.13.0
hooks:
- id: ruff-check
name: ruff lint mne
Expand Down Expand Up @@ -58,7 +58,7 @@ repos:
args: ["--ignore-case"]

- repo: https://github.com/pappasam/toml-sort
rev: v0.24.2
rev: v0.24.3
hooks:
- id: toml-sort-fix
files: pyproject.toml
Expand All @@ -82,7 +82,7 @@ repos:

# zizmor
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.12.1
rev: v1.13.0
hooks:
- id: zizmor

Expand Down
1 change: 1 addition & 0 deletions doc/changes/dev/13398.apichange.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The default for :func:`mne.make_field_map` will change to ``"auto"`` in MNE-Python 1.12 (from ``(0., 0., 0.04)``), changes by :newcontrib:`Paul Anders`.
1 change: 1 addition & 0 deletions doc/changes/names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
.. _Pablo Mainar: https://github.com/pablomainar
.. _Pablo-Arias: https://github.com/Pablo-Arias
.. _Padma Sundaram: https://www.nmr.mgh.harvard.edu/user/8071
.. _Paul Anders: https://github.com/Mettphysik
.. _Paul Pasler: https://github.com/ppasler
.. _Paul Roujansky: https://github.com/paulroujansky
.. _Pavel Navratil: https://github.com/navrpa13
Expand Down
1 change: 1 addition & 0 deletions examples/visualization/mne_helmet.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
subject="sample",
subjects_dir=subjects_dir,
upsampling=2,
origin="auto",
)
time = 0.083
fig = mne.viz.create_3d_figure((512, 512), bgcolor="w", title="MNE helmet")
Expand Down
2 changes: 1 addition & 1 deletion mne/channels/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ def interpolate_bads(
origin = _check_origin(origin, self.info)
for ch_type, interp in method.items():
if interp == "nan":
_interpolate_bads_nan(self, ch_type, exclude=exclude)
_interpolate_bads_nan(self, ch_type=ch_type, exclude=exclude)
if method.get("eeg", "") == "spline":
_interpolate_bads_eeg(self, origin=origin, exclude=exclude)
meg_mne = method.get("meg", "") == "MNE"
Expand Down
12 changes: 6 additions & 6 deletions mne/channels/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,25 +172,25 @@ def _interpolate_bads_eeg(inst, origin, exclude=None, ecog=False, verbose=None):


@verbose
def _interpolate_bads_ecog(inst, origin, exclude=None, verbose=None):
def _interpolate_bads_ecog(inst, *, origin, exclude=None, verbose=None):
_interpolate_bads_eeg(inst, origin, exclude=exclude, ecog=True, verbose=verbose)


def _interpolate_bads_meg(
inst, mode="accurate", origin=(0.0, 0.0, 0.04), verbose=None, ref_meg=False
inst, mode="accurate", *, origin, verbose=None, ref_meg=False
):
return _interpolate_bads_meeg(
inst, mode, origin, ref_meg=ref_meg, eeg=False, verbose=verbose
inst, mode, ref_meg=ref_meg, eeg=False, origin=origin, verbose=verbose
)


@verbose
def _interpolate_bads_nan(
inst,
*,
ch_type,
ref_meg=False,
exclude=(),
*,
verbose=None,
):
info = _simplify_info(inst.info)
Expand All @@ -208,12 +208,12 @@ def _interpolate_bads_nan(
def _interpolate_bads_meeg(
inst,
mode="accurate",
origin=(0.0, 0.0, 0.04),
*,
meg=True,
eeg=True,
ref_meg=False,
exclude=(),
*,
origin,
method=None,
verbose=None,
):
Expand Down
2 changes: 1 addition & 1 deletion mne/channels/tests/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def test_interpolation_meg():
def _this_interpol(inst, ref_meg=False):
from mne.channels.interpolation import _interpolate_bads_meg

_interpolate_bads_meg(inst, ref_meg=ref_meg, mode="fast")
_interpolate_bads_meg(inst, ref_meg=ref_meg, mode="fast", origin=(0.0, 0.0, 0.04))
return inst


Expand Down
1 change: 1 addition & 0 deletions mne/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ def _check_skip_backend(name):
assert name == "notebook", name
pytest.importorskip("jupyter")
pytest.importorskip("ipympl")
pytest.importorskip("ipyevents")
pytest.importorskip("trame")
pytest.importorskip("trame_vtk")
pytest.importorskip("trame_vuetify")
Expand Down
29 changes: 22 additions & 7 deletions mne/forward/_field_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ..fixes import _safe_svd
from ..surface import get_head_surf, get_meg_helmet_surf
from ..transforms import _find_trans, transform_surface_to
from ..utils import _check_fname, _check_option, _pl, _reg_pinv, logger, verbose
from ..utils import _check_fname, _check_option, _pl, _reg_pinv, logger, verbose, warn
from ._lead_dots import (
_do_cross_dots,
_do_self_dots,
Expand Down Expand Up @@ -116,7 +116,7 @@ def _pinv_tikhonov(x, reg):
return inv, n


def _map_meg_or_eeg_channels(info_from, info_to, mode, origin, miss=None):
def _map_meg_or_eeg_channels(info_from, info_to, mode, *, origin, miss=None):
"""Find mapping from one set of channels to another.

Parameters
Expand All @@ -132,13 +132,15 @@ def _map_meg_or_eeg_channels(info_from, info_to, mode, origin, miss=None):
origin : array-like, shape (3,) | str
Origin of the sphere in the head coordinate frame and in meters.
Can be ``'auto'``, which means a head-digitization-based origin
fit. Default is ``(0., 0., 0.04)``.
fit.

Returns
-------
mapping : array, shape (n_to, n_from)
A mapping matrix.
"""
assert origin is not None # should be assured elsewhere

# no need to apply trans because both from and to coils are in device
# coordinates
info_kinds = set(ch["kind"] for ch in info_to["chs"])
Expand Down Expand Up @@ -314,7 +316,8 @@ def _make_surface_mapping(
trans=None,
mode="fast",
n_jobs=None,
origin=(0.0, 0.0, 0.04),
*,
origin,
verbose=None,
):
"""Re-map M/EEG data to a surface.
Expand All @@ -337,8 +340,6 @@ def _make_surface_mapping(
%(n_jobs)s
origin : array-like, shape (3,) | str
Origin of the sphere in the head coordinate frame and in meters.
The default is ``'auto'``, which means a head-digitization-based
origin fit.
%(verbose)s

Returns
Expand All @@ -347,6 +348,8 @@ def _make_surface_mapping(
A n_vertices x n_sensors array that remaps the MEG or EEG data,
as `new_data = np.dot(mapping, data)`.
"""
assert origin is not None # should be assured elsewhere

if not all(key in surf for key in ["rr", "nn"]):
raise KeyError('surf must have both "rr" and "nn"')
if "coord_frame" not in surf:
Expand Down Expand Up @@ -443,7 +446,7 @@ def make_field_map(
ch_type=None,
mode="fast",
meg_surf="helmet",
origin=(0.0, 0.0, 0.04),
origin=None,
n_jobs=None,
*,
upsampling=1,
Expand Down Expand Up @@ -483,6 +486,9 @@ def make_field_map(
fit. Default is ``(0., 0., 0.04)``.

.. versionadded:: 0.11
.. versionchanged:: 1.12
In 1.12 the default value is "auto".
In 1.11 and prior versions, it is ``(0., 0., 0.04)``.
%(n_jobs)s
%(helmet_upsampling)s

Expand All @@ -498,6 +504,15 @@ def make_field_map(
The surface maps to be used for field plots. The list contains
separate ones for MEG and EEG (if both MEG and EEG are present).
"""
if origin is None:
warn_message = (
'Default value for origin is "(0.0, 0.0, 0.04)" in version 1.11 '
'but will be changed to "auto" in 1.12. Set the origin parameter '
"explicitly to avoid this warning."
)
warn(warn_message, FutureWarning)
origin = (0.0, 0.0, 0.04)

info = evoked.info

if ch_type is None:
Expand Down
42 changes: 31 additions & 11 deletions mne/forward/tests/test_field_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,14 @@ def test_field_map_ctf():
evoked = Epochs(raw, events).average()
evoked.pick(evoked.ch_names[:50]) # crappy mapping but faster
# smoke test - passing trans_fname as pathlib.Path as additional check
# set origin to "(0.0, 0.0, 0.04)", which was the default until v1.12
# estimating origin from "auto" impossible due to missing digitization points
make_field_map(
evoked, trans=Path(trans_fname), subject="sample", subjects_dir=subjects_dir
evoked,
trans=Path(trans_fname),
subject="sample",
subjects_dir=subjects_dir,
origin=(0.0, 0.0, 0.04),
)


Expand Down Expand Up @@ -128,11 +134,13 @@ def test_make_field_map_eeg():
evoked.info["bads"] = ["MEG 2443", "EEG 053"] # add some bads
surf = get_head_surf("sample", subjects_dir=subjects_dir)
# we must have trans if surface is in MRI coords
pytest.raises(ValueError, _make_surface_mapping, evoked.info, surf, "eeg")
pytest.raises(
ValueError, _make_surface_mapping, evoked.info, surf, "eeg", origin="auto"
)

evoked.pick(picks="eeg")
fmd = make_field_map(
evoked, trans_fname, subject="sample", subjects_dir=subjects_dir
evoked, trans_fname, subject="sample", subjects_dir=subjects_dir, origin="auto"
)

# trans is necessary for EEG only
Expand All @@ -143,10 +151,11 @@ def test_make_field_map_eeg():
None,
subject="sample",
subjects_dir=subjects_dir,
origin="auto",
)

fmd = make_field_map(
evoked, trans_fname, subject="sample", subjects_dir=subjects_dir
evoked, trans_fname, subject="sample", subjects_dir=subjects_dir, origin="auto"
)
assert len(fmd) == 1
assert_array_equal(fmd[0]["data"].shape, (642, 59)) # maps data onto surf
Expand All @@ -163,31 +172,37 @@ def test_make_field_map_meg():
# let's reduce the number of channels by a bunch to speed it up
info["bads"] = info["ch_names"][:200]
# bad ch_type
pytest.raises(ValueError, _make_surface_mapping, info, surf, "foo")
pytest.raises(ValueError, _make_surface_mapping, info, surf, "foo", origin="auto")
# bad mode
pytest.raises(ValueError, _make_surface_mapping, info, surf, "meg", mode="foo")
pytest.raises(
ValueError, _make_surface_mapping, info, surf, "meg", mode="foo", origin="auto"
)
# no picks
evoked_eeg = evoked.copy().pick(picks="eeg")
pytest.raises(RuntimeError, _make_surface_mapping, evoked_eeg.info, surf, "meg")
pytest.raises(
RuntimeError, _make_surface_mapping, evoked_eeg.info, surf, "meg", origin="auto"
)
# bad surface def
nn = surf["nn"]
del surf["nn"]
pytest.raises(KeyError, _make_surface_mapping, info, surf, "meg")
pytest.raises(KeyError, _make_surface_mapping, info, surf, "meg", origin="auto")
surf["nn"] = nn
cf = surf["coord_frame"]
del surf["coord_frame"]
pytest.raises(KeyError, _make_surface_mapping, info, surf, "meg")
pytest.raises(KeyError, _make_surface_mapping, info, surf, "meg", origin="auto")
surf["coord_frame"] = cf

# now do it with make_field_map
evoked.pick(picks="meg")
evoked.info.normalize_proj() # avoid projection warnings
fmd = make_field_map(evoked, None, subject="sample", subjects_dir=subjects_dir)
fmd = make_field_map(
evoked, None, subject="sample", subjects_dir=subjects_dir, origin="auto"
)
assert len(fmd) == 1
assert_array_equal(fmd[0]["data"].shape, (304, 106)) # maps data onto surf
assert len(fmd[0]["ch_names"]) == 106

pytest.raises(ValueError, make_field_map, evoked, ch_type="foobar")
pytest.raises(ValueError, make_field_map, evoked, ch_type="foobar", origin="auto")

# now test the make_field_map on head surf for MEG
evoked.pick(picks="meg")
Expand All @@ -198,6 +213,7 @@ def test_make_field_map_meg():
meg_surf="head",
subject="sample",
subjects_dir=subjects_dir,
origin="auto",
)
assert len(fmd) == 1
assert_array_equal(fmd[0]["data"].shape, (642, 106)) # maps data onto surf
Expand All @@ -210,6 +226,7 @@ def test_make_field_map_meg():
meg_surf="foobar",
subjects_dir=subjects_dir,
trans=trans_fname,
origin="auto",
)


Expand All @@ -221,12 +238,15 @@ def test_make_field_map_meeg():
picks = picks[::10]
evoked.pick([evoked.ch_names[p] for p in picks])
evoked.info.normalize_proj()
# set origin to "(0.0, 0.0, 0.04)", which was the default until v1.12
# estimated origin from "auto" fails the assertions below
maps = make_field_map(
evoked,
trans_fname,
subject="sample",
subjects_dir=subjects_dir,
verbose="debug",
origin=(0.0, 0.0, 0.04),
)
assert_equal(maps[0]["data"].shape, (642, 6)) # EEG->Head
assert_equal(maps[1]["data"].shape, (304, 31)) # MEG->Helmet
Expand Down
1 change: 1 addition & 0 deletions mne/viz/tests/test_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def test_plot_evoked_field(renderer):
n_jobs=None,
ch_type=t,
upsampling=up,
origin="auto",
verbose=True,
)
log = log.getvalue()
Expand Down
6 changes: 4 additions & 2 deletions tools/install_pre_requirements.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ python -m pip install $STD_ARGS \
git+https://github.com/joblib/joblib \
git+https://github.com/h5io/h5io \
git+https://github.com/BUNPC/pysnirf2 \
git+https://github.com/the-siesta-group/edfio \
trame trame-vtk trame-vuetify jupyter ipyevents ipympl openmeeg \
imageio-ffmpeg xlrd mffpy traitlets pybv eeglabio defusedxml antio
imageio-ffmpeg xlrd mffpy traitlets pybv eeglabio defusedxml antio \
edfio
# https://github.com/the-siesta-group/edfio/issues/78
# git+https://github.com/the-siesta-group/edfio
echo "::endgroup::"

echo "::group::Make sure we're on a NumPy 2.0 variant"
Expand Down
7 changes: 6 additions & 1 deletion tutorials/evoked/20_visualize_evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,11 @@ def custom_func(x):
# <mne.Evoked.plot_field>`:

maps = mne.make_field_map(
evks["aud/left"], trans=str(trans_file), subject="sample", subjects_dir=subjects_dir
evks["aud/left"],
trans=str(trans_file),
subject="sample",
subjects_dir=subjects_dir,
origin="auto",
)
evks["aud/left"].plot_field(maps, time=0.1)

Expand All @@ -310,6 +314,7 @@ def custom_func(x):
subject="sample",
subjects_dir=subjects_dir,
meg_surf="head",
origin="auto",
)
fig = evk.plot_field(_map, time=0.1)
mne.viz.set_3d_title(fig, ch_type, size=20)
Loading
Loading