diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 93028ee5668..bf915512cf7 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -76,6 +76,7 @@ Bugs - Fix bug in :func:`mne.concatenate_raws` where two raws could not be merged if the order of the bad channel lists did not match (:gh:`11502` by `Moritz Gerster`_) - Fix bug where :meth:`mne.Evoked.plot_topomap` opened an extra figure (:gh:`11607` by `Alex Rockhill`_) - Fix bug where :func:`mne.transforms.apply_volume_registration_points` modified info in place (:gh:`11612` by `Alex Rockhill`_) +- Fix bug where Maxwell-filtered data rank was not handled properly in :func:`mne.beamformer.make_lcmv` (:gh:`11664` by `Eric Larson`_) - In :class:`~mne.Report`, custom figures now show up correctly when ``image_format='svg'`` is requested (:gh:`11623` by `Richard Höchenberger`_) - Fix bug where providing ``axes`` in `mne.preprocessing.ICA.plot_components` would fail (:gh:`11654` by `Mathieu Scheltienne`_) diff --git a/environment.yml b/environment.yml index 37ae4e6582e..3d7c5926fc5 100644 --- a/environment.yml +++ b/environment.yml @@ -31,7 +31,7 @@ dependencies: - imageio-ffmpeg>=0.4.1 - vtk>=9.2 - traitlets -- pyvista>=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3,!=0.38.4,!=0.38.5 +- pyvista>=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3,!=0.38.4,!=0.38.5,!=0.38.6 - pyvistaqt>=0.4 - qdarkstyle - darkdetect diff --git a/mne/beamformer/_lcmv.py b/mne/beamformer/_lcmv.py index 3e67890da65..80cc73c24df 100644 --- a/mne/beamformer/_lcmv.py +++ b/mne/beamformer/_lcmv.py @@ -163,7 +163,7 @@ def make_lcmv( .. footbibliography:: """ # check number of sensor types present in the data and ensure a noise cov - info = _simplify_info(info) + info = _simplify_info(info, keep=("proc_history",)) noise_cov, _, allow_mismatch = _check_one_ch_type( "lcmv", info, forward, data_cov, noise_cov ) diff --git a/mne/beamformer/tests/test_lcmv.py b/mne/beamformer/tests/test_lcmv.py index ae7a64f844e..7f47c28a166 100644 --- a/mne/beamformer/tests/test_lcmv.py +++ b/mne/beamformer/tests/test_lcmv.py @@ -1,3 +1,4 @@ +from contextlib import nullcontext from copy import deepcopy from inspect import signature @@ -1072,9 +1073,9 @@ def test_depth_does_not_matter(bias_params_free, weight_norm, pick_ori): assert_allclose(d1, d2, atol=atol) -@testing.requires_testing_data -def test_lcmv_maxfiltered(): - """Test LCMV on maxfiltered data.""" +@pytest.fixture(scope="session") +def mf_data(): + """Produce Maxwell filtered data for beamforming.""" raw = mne.io.read_raw_fif(fname_raw).fix_mag_coil_types() raw_sss = mne.preprocessing.maxwell_filter(raw) events = mne.find_events(raw_sss) @@ -1084,10 +1085,26 @@ def test_lcmv_maxfiltered(): epochs = mne.Epochs(raw_sss, events) data_cov = mne.compute_covariance(epochs, tmin=0) fwd = mne.read_forward_solution(fname_fwd) + return epochs, data_cov, fwd + + +@testing.requires_testing_data +@pytest.mark.parametrize("use_rank", ("info", "computed", "full", None)) +def test_lcmv_maxfiltered(mf_data, use_rank): + """Test LCMV on maxfiltered data.""" + epochs, data_cov, fwd = mf_data rank = compute_rank(data_cov, info=epochs.info) assert rank == {"mag": 71} - for use_rank in ("info", rank, "full", None): - make_lcmv(epochs.info, fwd, data_cov, rank=use_rank) + ctx = nullcontext() + if use_rank == "computed": + use_rank = rank + elif use_rank is None: + ctx = pytest.warns(RuntimeWarning, match="rank as it exceeds") + with catch_logging() as log, ctx: + make_lcmv(epochs.info, fwd, data_cov, rank=use_rank, verbose=True) + log = log.getvalue() + n = 102 if use_rank == "full" else 71 + assert f"Making LCMV beamformer with rank {{'mag': {n}}}" in log # To reduce test time, only test combinations that should matter rather than diff --git a/mne/io/meas_info.py b/mne/io/meas_info.py index 9f08a4cd720..3f0f45273f3 100644 --- a/mne/io/meas_info.py +++ b/mne/io/meas_info.py @@ -1468,19 +1468,16 @@ def save(self, fname): write_info(fname, self) -def _simplify_info(info): +def _simplify_info(info, *, keep=()): """Return a simplified info structure to speed up picking.""" chs = [ {key: ch[key] for key in ("ch_name", "kind", "unit", "coil_type", "loc", "cal")} for ch in info["chs"] ] - sub_info = Info( - chs=chs, - bads=info["bads"], - comps=info["comps"], - projs=info["projs"], - custom_ref_applied=info["custom_ref_applied"], - ) + keys = ("bads", "comps", "projs", "custom_ref_applied") + keep + sub_info = Info((key, info[key]) for key in keys if key in info) + with sub_info._unlock(): + sub_info["chs"] = chs sub_info._update_redundant() return sub_info diff --git a/mne/utils/config.py b/mne/utils/config.py index 1d3c6d9baf3..f0805224e5e 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -202,9 +202,10 @@ def set_memmap_min_size(memmap_min_size): # These allow for partial matches, e.g. 'MNE_STIM_CHANNEL_1' is okay key _known_config_wildcards = ( - "MNE_STIM_CHANNEL", - "MNE_DATASETS_FNIRS", - "MNE_NIRS", + "MNE_STIM_CHANNEL", # can have multiple stim channels + "MNE_DATASETS_FNIRS", # mne-nirs + "MNE_NIRS", # mne-nirs + "MNE_KIT2FIFF", # mne-kit-gui ) diff --git a/requirements.txt b/requirements.txt index eeaa71cb9cc..b49caf65c23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ xlrd imageio>=2.6.1 imageio-ffmpeg>=0.4.1 traitlets -pyvista>=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3,!=0.38.4,!=0.38.5 +pyvista>=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3,!=0.38.4,!=0.38.5,!=0.38.6 pyvistaqt>=0.4 mffpy>=0.5.7 ipywidgets diff --git a/tools/azure_dependencies.sh b/tools/azure_dependencies.sh index c68247cfd01..50bf4d902e6 100755 --- a/tools/azure_dependencies.sh +++ b/tools/azure_dependencies.sh @@ -3,20 +3,14 @@ EXTRA_ARGS="" if [ "${TEST_MODE}" == "pip" ]; then python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade --only-binary ":all:" numpy scipy vtk - python -m pip install --upgrade --only-binary="numba,llvmlite" -r requirements.txt - # This can be removed once PyVistaQt 0.6 is out (including https://github.com/pyvista/pyvistaqt/pull/127) - python -m pip install --upgrade git+https://github.com/pyvista/pyvistaqt + python -m pip install --upgrade --only-binary="numba,llvmlite,numpy,scipy,vtk" -r requirements.txt elif [ "${TEST_MODE}" == "pip-pre" ]; then python -m pip install --progress-bar off --upgrade pip setuptools wheel python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" python-dateutil pytz joblib threadpoolctl six cycler kiwisolver pyparsing patsy # Broken as of 2022/09/20 # python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps --extra-index-url https://www.riverbankcomputing.com/pypi/simple PyQt6 PyQt6-sip PyQt6-Qt6 python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps PyQt6 PyQt6-sip PyQt6-Qt6 - python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy - # # SciPy<->sklearn problematic, see https://github.com/scipy/scipy/issues/18377 - python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps scipy - python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" statsmodels pandas scikit-learn dipy matplotlib + python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy scipy statsmodels pandas scikit-learn dipy matplotlib python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" h5py python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://test.pypi.org/simple" openmeeg python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://wheels.vtk.org" vtk diff --git a/tools/github_actions_dependencies.sh b/tools/github_actions_dependencies.sh index ab67af8794a..0391ef59df0 100755 --- a/tools/github_actions_dependencies.sh +++ b/tools/github_actions_dependencies.sh @@ -22,10 +22,7 @@ else # pip install $STD_ARGS --pre --only-binary ":all:" --no-deps --extra-index-url https://www.riverbankcomputing.com/pypi/simple PyQt6 pip install $STD_ARGS --pre --only-binary ":all:" PyQt6 echo "NumPy/SciPy/pandas etc." - pip install $STD_ARGS --pre --only-binary ":all:" --default-timeout=60 --extra-index-url "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy - # SciPy<->sklearn problematic, see https://github.com/scipy/scipy/issues/18377 - pip install $STD_ARGS --pre --only-binary ":all:" scipy - pip install $STD_ARGS --pre --only-binary ":all:" --default-timeout=60 --extra-index-url "https://pypi.anaconda.org/scipy-wheels-nightly/simple" scikit-learn dipy pandas matplotlib pillow statsmodels + pip install $STD_ARGS --pre --only-binary ":all:" --default-timeout=60 --extra-index-url "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy scipy scikit-learn dipy pandas matplotlib pillow statsmodels pip install $STD_ARGS --pre --only-binary ":all:" -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" h5py # No Numba because it forces an old NumPy version echo "nilearn and openmeeg"