Description of the problem
With ch_type as a list of strings and projection=False, set_eeg_reference builds a single channel selection from the union of the listed types and applies one average over that union. The projection=True path instead loops over the types and adds one projector per type, with an explicit joint flag to opt into union behavior. There is no equivalent on the projection=False path: no per-type loop, no joint flag, no warning, no error.
The signature accepts list of str | str in both modes. The docstring's versionchanged 1.2 note ("list-of-str is now supported with projection=True") does not say it is unsupported when projection=False.
Source pointer (mne/_fiff/reference.py, 1.12.1): the projection=True branch loops for this_ch_type in ch_type; the projection=False branch builds ch_sel from pick_types(..., **{t: True for t in ch_type}) and calls _apply_reference once.
Impact: a one-line call like set_eeg_reference(raw, "average", ch_type=["seeg", "ecog"]) on mixed intracranial data silently returns a pooled-mean reference that does not zero either subset.
Steps to reproduce
The `sample` dataset has only one electrode-referenceable channel type, so the MWE uses a synthetic `RawArray` with seeg at +10 µV and ecog at −10 µV. Union mean is zero, which makes the per-type vs. union difference one line of output.
import numpy as np
import mne
sfreq = 1000.0
n = 1000
data = np.vstack([np.full((4, n), 10.0), np.full((4, n), -10.0)]) * 1e-6
ch_names = [f"S{i}" for i in range(4)] + [f"E{i}" for i in range(4)]
ch_types = ["seeg"] * 4 + ["ecog"] * 4
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
raw = mne.io.RawArray(data, info, verbose="ERROR")
raw_list = raw.copy()
mne.set_eeg_reference(
raw_list, ref_channels="average", projection=False,
ch_type=["seeg", "ecog"], copy=False, verbose="ERROR",
)
print("list ch_type:")
print(f" seeg mean: {raw_list.get_data(picks='seeg').mean() * 1e6:+.4f} uV")
print(f" ecog mean: {raw_list.get_data(picks='ecog').mean() * 1e6:+.4f} uV")
raw_loop = raw.copy()
for t in ("seeg", "ecog"):
mne.set_eeg_reference(
raw_loop, ref_channels="average", projection=False,
ch_type=t, copy=False, verbose="ERROR",
)
print("per-type loop:")
print(f" seeg mean: {raw_loop.get_data(picks='seeg').mean() * 1e6:+.4f} uV")
print(f" ecog mean: {raw_loop.get_data(picks='ecog').mean() * 1e6:+.4f} uV")
Reproduces on 1.11.0 and 1.12.1.
Link to data
No response
Expected results
Either the list path matches the per-type loop (both subsets zero-mean), or projection=False with a list ch_type raises a clear error.
list ch_type: seeg +0.0000 uV ecog +0.0000 uV
per-type loop: seeg +0.0000 uV ecog +0.0000 uV
Actual results
The list path subtracts the union mean (zero) and leaves each subset's original offset. No warning, no error.
list ch_type: seeg +10.0000 uV ecog -10.0000 uV
per-type loop: seeg +0.0000 uV ecog +0.0000 uV
Additional information
Platform macOS-15.4.1-arm64-arm-64bit
Python 3.12.12
CPU Apple M4 Pro (14 cores)
Memory 24.0 GiB
Core
├☒ mne 1.11.0 (also reproduced on 1.12.1)
├☑ numpy 2.4.3
├☑ scipy 1.17.1
└☑ matplotlib 3.10.8
Numerical (optional)
├☑ sklearn 1.8.0
├☑ nibabel 5.4.2
├☑ nilearn 0.13.1
├☑ pandas 3.0.2
└☑ h5py 3.16.0
Ecosystem (optional)
├☑ mne-bids 0.18.0
└☑ pybv 0.7.6
Description of the problem
With
ch_typeas a list of strings andprojection=False,set_eeg_referencebuilds a single channel selection from the union of the listed types and applies one average over that union. Theprojection=Truepath instead loops over the types and adds one projector per type, with an explicitjointflag to opt into union behavior. There is no equivalent on theprojection=Falsepath: no per-type loop, nojointflag, no warning, no error.The signature accepts
list of str | strin both modes. The docstring'sversionchanged 1.2note ("list-of-stris now supported withprojection=True") does not say it is unsupported whenprojection=False.Source pointer (
mne/_fiff/reference.py, 1.12.1): theprojection=Truebranch loopsfor this_ch_type in ch_type; theprojection=Falsebranch buildsch_selfrompick_types(..., **{t: True for t in ch_type})and calls_apply_referenceonce.Impact: a one-line call like
set_eeg_reference(raw, "average", ch_type=["seeg", "ecog"])on mixed intracranial data silently returns a pooled-mean reference that does not zero either subset.Steps to reproduce
Link to data
No response
Expected results
Either the list path matches the per-type loop (both subsets zero-mean), or
projection=Falsewith a listch_typeraises a clear error.Actual results
The list path subtracts the union mean (zero) and leaves each subset's original offset. No warning, no error.
Additional information