From 07f1935890470da651f4bba0208af9e8831dd6e0 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 2 Sep 2026 11:11:31 -0400 Subject: [PATCH] Fix interpolate_to spline target positions --- doc/changes/dev/14266.bugfix.rst | 1 + mne/channels/interpolation.py | 4 +++- mne/channels/tests/test_interpolation.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 doc/changes/dev/14266.bugfix.rst diff --git a/doc/changes/dev/14266.bugfix.rst b/doc/changes/dev/14266.bugfix.rst new file mode 100644 index 00000000000..ecbe46bc309 --- /dev/null +++ b/doc/changes/dev/14266.bugfix.rst @@ -0,0 +1 @@ +Fix bug where :meth:`mne.io.Raw.interpolate_to` and related methods with ``method="spline"`` did not center the target sensor positions on the fitted sphere origin, by `Eric Larson`_. diff --git a/mne/channels/interpolation.py b/mne/channels/interpolation.py index 1ed1c4e9fd4..e379791c330 100644 --- a/mne/channels/interpolation.py +++ b/mne/channels/interpolation.py @@ -452,7 +452,9 @@ def _interpolate_to_eeg(inst, sensors, origin, method, reg): if method == "spline": origin_val = _check_origin(origin, inst.info) pos_from = inst.info._get_channel_positions(picks_good_eeg) - origin_val - pos_to = np.stack(list(ch_pos.values()), axis=0) + # Use info_to (rather than ch_pos directly) so that the target positions + # are in the head frame, and center both sets on the fitted origin + pos_to = info_to._get_channel_positions() - origin_val def _check_pos_sphere(pos): d = np.linalg.norm(pos, axis=-1) diff --git a/mne/channels/tests/test_interpolation.py b/mne/channels/tests/test_interpolation.py index 83e0bf1b6e1..218aaf606b9 100644 --- a/mne/channels/tests/test_interpolation.py +++ b/mne/channels/tests/test_interpolation.py @@ -585,6 +585,18 @@ def test_interpolate_to_eeg(montage_name, method, data_type): assert inst_interp.info["bads"] == bads +def test_interpolate_to_eeg_same_positions(): + """Test that spline interpolate_to onto src pos is a no-op (gh-14153).""" + raw = read_raw_fif(raw_fname).pick("eeg").crop(0, 1).load_data() + montage = make_dig_montage( + ch_pos=dict(zip(raw.ch_names, raw.info._get_channel_positions())), + coord_frame="head", + ) + raw_interp = raw.copy().interpolate_to(montage, method="spline") + assert raw_interp.ch_names == raw.ch_names + assert_allclose(raw_interp.get_data(), raw.get_data(), rtol=1e-5, atol=1e-12) + + @pytest.mark.slowtest # ~5s locally @pytest.mark.filterwarnings("ignore:Projection vector.* reduced .*:RuntimeWarning") def test_interpolate_to_meg(monkeypatch):