From d93f9508703b460222f7ea7181b152a35d29198c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 11 Jul 2023 12:55:22 -0400 Subject: [PATCH 01/12] ENH: Add eSSS --- .../settings/preprocessing/maxfilter.md | 2 + mne_bids_pipeline/_config.py | 10 + .../steps/preprocessing/_03_maxfilter.py | 214 +++++++++++++++++- .../tests/configs/config_ds004229.py | 2 + 4 files changed, 221 insertions(+), 7 deletions(-) diff --git a/docs/source/settings/preprocessing/maxfilter.md b/docs/source/settings/preprocessing/maxfilter.md index 6eb5e567e..3cd32d9d7 100644 --- a/docs/source/settings/preprocessing/maxfilter.md +++ b/docs/source/settings/preprocessing/maxfilter.md @@ -17,6 +17,8 @@ tags: - mf_reference_run - mf_cal_fname - mf_ctc_fname + - mf_esss + - mf_esss_reject - mf_mc - mf_mc_t_step_min - mf_mc_t_window diff --git a/mne_bids_pipeline/_config.py b/mne_bids_pipeline/_config.py index e7eb86dd8..812248ee4 100644 --- a/mne_bids_pipeline/_config.py +++ b/mne_bids_pipeline/_config.py @@ -681,6 +681,16 @@ ``` """ # noqa : E501 +mf_esss: int = 0 +""" +Number of extended SSS (eSSS) basis projectors to use from empty-room data. +""" + +mf_esss_reject: Optional[Dict[str, float]] = None +""" +Rejection parameters to use when computing the extended SSS (eSSS) basis. +""" + mf_mc: bool = False """ If True, perform movement compensation on the data. diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index df40ad936..6283ebef6 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -14,12 +14,14 @@ The function loads machine-specific calibration files. """ +from copy import deepcopy import gc from typing import Optional from types import SimpleNamespace import numpy as np import mne +from mne.utils import _pl from mne_bids import read_raw_bids from ..._config_utils import ( @@ -43,6 +45,139 @@ from ..._run import failsafe_run, save_logs, _update_for_splits, _prep_out_files +# %% eSSS +def get_input_fnames_esss( + *, + cfg: SimpleNamespace, + subject: str, + session: Optional[str], +) -> dict: + kwargs = dict( + cfg=cfg, + subject=subject, + session=session, + ) + in_files = _get_run_rest_noise_path( + run=None, + task="noise", + kind="orig", + mf_reference_run=cfg.mf_reference_run, + **kwargs, + ) + in_files.update(_get_mf_reference_run_path(add_bads=True, **kwargs)) + return in_files + + +@failsafe_run( + get_input_fnames=get_input_fnames_esss, +) +def compute_esss_proj( + *, + cfg: SimpleNamespace, + exec_params: SimpleNamespace, + subject: str, + session: Optional[str], + in_files: dict, +) -> dict: + import matplotlib.pyplot as plt + + run, task = None, "noise" + in_key = f"raw_task-{task}_run-{run}" + bids_path_in = in_files.pop(in_key) + bids_path_bads_in = in_files.pop(f"{in_key}-bads", None) # noqa + bids_path_ref_in = in_files.pop("raw_ref_run") + bids_path_ref_bads_in = in_files.pop("raw_ref_run-bads", None) + raw_noise = import_er_data( + cfg=cfg, + bids_path_er_in=bids_path_in, + bids_path_ref_in=bids_path_ref_in, + # TODO: This must match below, so we don't pass it + # bids_path_er_bads_in=bids_path_bads_in, + bids_path_er_bads_in=None, + bids_path_ref_bads_in=bids_path_ref_bads_in, + prepare_maxwell_filter=True, + ) + logger.info( + **gen_log_kwargs( + f"Computing eSSS basis with {cfg.mf_esss} component{_pl(cfg.mf_esss)}" + ) + ) + projs = mne.compute_proj_raw( + raw_noise, + n_grad=cfg.mf_esss, + n_mag=cfg.mf_esss, + reject=cfg.mf_esss_reject, + meg="combined", + ) + out_files = dict() + out_files["esss_basis"] = bids_path_in.copy().update( + subject=subject, # need these in the case of an empty room match + session=session, + run=run, + task=task, + suffix="esssproj", + split=None, + extension=".fif", + root=cfg.deriv_root, + check=False, + ) + mne.write_proj(out_files["esss_basis"], projs, overwrite=True) + + with _open_report( + cfg=cfg, + exec_params=exec_params, + subject=subject, + session=session, + run=run, + task=task, + ) as report: + msg = "Adding eSSS projectors to report." + logger.info(**gen_log_kwargs(message=msg)) + kinds_picks = list() + for kind in ("mag", "grad"): + picks = mne.pick_types(raw_noise.info, meg=kind, exclude="bads") + if not len(picks): + continue + kinds_picks.append([kind, picks]) + n_row, n_col = len(kinds_picks), cfg.mf_esss + fig, axes = plt.subplots( + n_row, + n_col, + figsize=(n_col + 0.5, n_row + 0.5), + constrained_layout=True, + squeeze=False, + ) + # TODO: plot_projs_topomap doesn't handle meg="combined" well: + # https://github.com/mne-tools/mne-python/pull/11792 + for ii, (kind, picks) in enumerate(kinds_picks): + info = mne.pick_info(raw_noise.info, picks) + ch_names = info["ch_names"] + these_projs = deepcopy(projs) + for proj in these_projs: + sub_idx = [proj["data"]["col_names"].index(name) for name in ch_names] + proj["data"]["data"] = proj["data"]["data"][:, sub_idx] + proj["data"]["col_names"] = ch_names + mne.viz.plot_projs_topomap( + these_projs, + info=info, + axes=axes[ii], + ) + for ai, ax in enumerate(axes[ii]): + ax.set_title(f"{kind} {ai + 1}") + report.add_figure( + fig, + title="eSSS projectors", + tags=("sss", "raw"), + replace=True, + ) + plt.close(fig) + + return _prep_out_files(exec_params=exec_params, out_files=out_files) + + +# %% maxwell_filter + + def get_input_fnames_maxwell_filter( *, cfg: SimpleNamespace, @@ -64,6 +199,8 @@ def get_input_fnames_maxwell_filter( mf_reference_run=cfg.mf_reference_run, **kwargs, ) + in_key = f"raw_task-{task}_run-{run}" + assert in_key in in_files # head positions if cfg.mf_mc: if run is None and task == "noise": @@ -77,7 +214,7 @@ def get_input_fnames_maxwell_filter( kind="orig", **kwargs, )[f"raw_task-{pos_task}_run-{pos_run}"] - in_files[f"raw_task-{task}_run-{run}-pos"] = path.update( + in_files[f"{in_key}-pos"] = path.update( suffix="headpos", extension=".txt", root=cfg.deriv_root, @@ -86,6 +223,23 @@ def get_input_fnames_maxwell_filter( run=pos_run, ) + if cfg.mf_esss: + in_files["esss_basis"] = ( + in_files[in_key] + .copy() + .update( + subject=subject, + session=session, + run=None, + task="noise", + suffix="esssproj", + split=None, + extension=".fif", + root=cfg.deriv_root, + check=False, + ) + ) + # reference run (used for `destination` and also bad channels for noise) in_files.update(_get_mf_reference_run_path(add_bads=True, **kwargs)) @@ -168,15 +322,20 @@ def run_maxwell_filter( # Maxwell-filter experimental data. apply_msg = "Applying " + extra = list() if cfg.mf_st_duration: apply_msg += f"tSSS ({cfg.mf_st_duration} sec, corr={cfg.mf_st_correlation})" else: apply_msg += "SSS" + head_pos = extended_proj = None if cfg.mf_mc: - apply_msg += " with MC" + extra.append("MC") head_pos = mne.chpi.read_head_pos(in_files.pop(f"{in_key}-pos")) - else: - head_pos = None + if cfg.mf_esss: + extra.append("eSSS") + extended_proj = mne.read_proj(in_files.pop("esss_basis")) + if extra: + apply_msg += " with " + "/".join(extra) apply_msg += " to" mf_kws = dict( @@ -188,6 +347,7 @@ def run_maxwell_filter( coord_frame="head", destination=destination, head_pos=head_pos, + extended_proj=extended_proj, ) logger.info(**gen_log_kwargs(message=f"{apply_msg} {recording_type} data")) @@ -359,7 +519,21 @@ def run_maxwell_filter( return _prep_out_files(exec_params=exec_params, out_files=out_files) -def get_config( +def get_config_esss( + *, + config: SimpleNamespace, + subject: str, + session: Optional[str], +) -> SimpleNamespace: + cfg = SimpleNamespace( + mf_esss=config.mf_esss, + mf_esss_reject=config.mf_esss_reject, + **_import_data_kwargs(config=config, subject=subject), + ) + return cfg + + +def get_config_maxwell_filter( *, config: SimpleNamespace, subject: str, @@ -386,6 +560,7 @@ def get_config( mf_mc_t_window=config.mf_mc_t_window, mf_mc_rotation_velocity_limit=config.mf_mc_rotation_velocity_limit, mf_mc_translation_velocity_limit=config.mf_mc_translation_velocity_limit, + mf_esss=config.mf_esss, **_import_data_kwargs(config=config, subject=subject), ) return cfg @@ -399,16 +574,41 @@ def main(*, config: SimpleNamespace) -> None: return with get_parallel_backend(config.exec_params): + logs = list() + # First step: compute eSSS projectors + if config.mf_esss: + parallel, run_func = parallel_func( + compute_esss_proj, exec_params=config.exec_params + ) + logs += parallel( + run_func( + cfg=get_config_esss( + config=config, + subject=subject, + session=session, + ), + exec_params=config.exec_params, + subject=subject, + session=session, + ) + for subject in get_subjects(config) + for session in get_sessions(config) + ) + + # Second: maxwell_filter parallel, run_func = parallel_func( run_maxwell_filter, exec_params=config.exec_params ) # We need to guarantee that the reference_run completes before the # noise/rest runs are processed, so we split the loops. - logs = list() for which in [("runs",), ("noise", "rest")]: logs += parallel( run_func( - cfg=get_config(config=config, subject=subject, session=session), + cfg=get_config_maxwell_filter( + config=config, + subject=subject, + session=session, + ), exec_params=config.exec_params, subject=subject, session=session, diff --git a/mne_bids_pipeline/tests/configs/config_ds004229.py b/mne_bids_pipeline/tests/configs/config_ds004229.py index 1c2538eb9..1c625eb90 100644 --- a/mne_bids_pipeline/tests/configs/config_ds004229.py +++ b/mne_bids_pipeline/tests/configs/config_ds004229.py @@ -29,6 +29,8 @@ mf_filter_chpi = False # for speed, not needed as we low-pass anyway mf_mc_rotation_velocity_limit = 30.0 # deg/s for annotations mf_mc_translation_velocity_limit = 20e-3 # m/s +mf_esss = 8 +mf_esss_reject = {"grad": 10000e-13, "mag": 40000e-15} ch_types = ["meg"] l_freq = None From a57a6b4749e4b53b619bc516f21dbfc56a69fa25 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 11 Jul 2023 16:18:36 -0400 Subject: [PATCH 02/12] FIX: Not None --- mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index 6283ebef6..3c48e85e8 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -327,13 +327,16 @@ def run_maxwell_filter( apply_msg += f"tSSS ({cfg.mf_st_duration} sec, corr={cfg.mf_st_correlation})" else: apply_msg += "SSS" - head_pos = extended_proj = None if cfg.mf_mc: extra.append("MC") head_pos = mne.chpi.read_head_pos(in_files.pop(f"{in_key}-pos")) + else: + head_pos = None if cfg.mf_esss: extra.append("eSSS") extended_proj = mne.read_proj(in_files.pop("esss_basis")) + else: + extended_proj = () if extra: apply_msg += " with " + "/".join(extra) apply_msg += " to" From b01b7bfa85ce837eca02a071db5bf8c294ceb049 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 10:43:02 -0400 Subject: [PATCH 03/12] FIX: Doc --- docs/source/v1.5.md.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/v1.5.md.inc b/docs/source/v1.5.md.inc index 352abd4c7..66c5e8deb 100644 --- a/docs/source/v1.5.md.inc +++ b/docs/source/v1.5.md.inc @@ -4,6 +4,7 @@ - Added support for annotating bad segments based on head movement velocity (#757 by @larsoner) - Added examples of T1 and FLASH BEM to website (#758 by @larsoner) +- Added support for extended SSS (eSSS) in Maxwell filtering (#762 by @larsoner) [//]: # (### :warning: Behavior changes) From 11a7eed738899d0813411dd00d423342da93b4fc Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 16:05:48 -0400 Subject: [PATCH 04/12] MAINT: seaborn <-> pandas --- mne_bids_pipeline/tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne_bids_pipeline/tests/conftest.py b/mne_bids_pipeline/tests/conftest.py index 020d292d9..e17b46076 100644 --- a/mne_bids_pipeline/tests/conftest.py +++ b/mne_bids_pipeline/tests/conftest.py @@ -34,6 +34,9 @@ def pytest_configure(config): # seaborn calling tight layout, etc. ignore:The figure layout has changed to tight:UserWarning ignore:The \S+_cmap function was deprecated.*:DeprecationWarning + # seaborn->pandas + ignore:is_categorical_dtype is deprecated.*:FutureWarning + ignore:use_inf_as_na option is deprecated.*:FutureWarning # Dask distributed with jsonschema 4.18 ignore:jsonschema\.RefResolver is deprecated.*:DeprecationWarning """ From 0401c37f20e4d6d839dbe65f92f190156d9b1f36 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 16:23:45 -0400 Subject: [PATCH 05/12] FIX: Skips --- mne_bids_pipeline/tests/test_run.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/mne_bids_pipeline/tests/test_run.py b/mne_bids_pipeline/tests/test_run.py index 593a5968e..f0f408ebb 100644 --- a/mne_bids_pipeline/tests/test_run.py +++ b/mne_bids_pipeline/tests/test_run.py @@ -3,6 +3,7 @@ import shutil from pathlib import Path from typing import Collection, Dict, Optional, TypedDict +import os import pytest @@ -23,15 +24,17 @@ class _TestOptionsT(TypedDict, total=False): steps: Collection[str] task: Optional[str] env: Dict[str, str] + requires: Collection[str] # If not supplied below, the defaults are: # key: { -# 'dataset': key.split('_')[0], -# 'config': f'config_{key}.py', -# 'steps': ('preprocessing', 'sensor'), -# 'env': {}, -# 'task': None, +# "dataset": key.split("_")[0], +# "config": f"config_{key}.py", +# "steps": ("preprocessing", "sensor"), +# "env": {}, +# "task": None, +# "requires": (), # } # TEST_SUITE: Dict[str, _TestOptionsT] = { @@ -60,12 +63,15 @@ class _TestOptionsT(TypedDict, total=False): "ds000248_ica": {}, "ds000248_T1_BEM": { "steps": ("source/make_bem_surfaces",), + "requires": ("freesurfer",), }, "ds000248_FLASH_BEM": { "steps": ("source/make_bem_surfaces",), + "requires": ("freesurfer",), }, "ds000248_coreg_surfaces": { "steps": ("freesurfer/coreg_surfaces",), + "requires": ("freesurfer",), }, "ds000248_no_mri": { "steps": ("preprocessing", "sensor", "source"), @@ -120,6 +126,9 @@ def dataset_test(request): capsys = request.getfixturevalue("capsys") dataset = request.getfixturevalue("dataset") test_options = TEST_SUITE[dataset] + if "freesurfer" in test_options.get("requires", ()): + if "FREESURFER_HOME" not in os.environ: + pytest.skip("FREESURFER_HOME required but not found") dataset_name = test_options.get("dataset", dataset.split("_")[0]) with capsys.disabled(): if request.config.getoption("--download", False): # download requested From 4ce64badc65c122551b05f2efedae66812e9845b Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 16:37:05 -0400 Subject: [PATCH 06/12] FIX: Split and kwargs --- mne_bids_pipeline/_run.py | 8 +++++--- mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mne_bids_pipeline/_run.py b/mne_bids_pipeline/_run.py index 7d7bf50f0..b48c2f4cf 100644 --- a/mne_bids_pipeline/_run.py +++ b/mne_bids_pipeline/_run.py @@ -220,7 +220,9 @@ def wrapper(*args, **kwargs): emoji = "🔂" else: # Check our output file hashes - out_files_hashes = memorized_func(*args, **kwargs) + # Need to make a copy of kwargs["in_files"] in particular + use_kwargs = copy.deepcopy(kwargs) + out_files_hashes = memorized_func(*args, **use_kwargs) for key, (fname, this_hash) in out_files_hashes.items(): fname = pathlib.Path(fname) if not fname.exists(): @@ -303,8 +305,8 @@ def save_logs(*, config: SimpleNamespace, logs) -> None: # TODO add type def _update_for_splits( - files_dict: Dict[str, BIDSPath], - key: str, + files_dict: Union[Dict[str, BIDSPath], BIDSPath], + key: Optional[str], *, single: bool = False, allow_missing: bool = False, diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index 3c48e85e8..117b75f82 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -414,6 +414,7 @@ def run_maxwell_filter( # the resting-state recording. bids_path_ref_sss = bids_path_ref_in.copy().update(**bids_path_out_kwargs) + bids_path_ref_sss = _update_for_splits(bids_path_ref_sss, None, single=True) raw_exp = mne.io.read_raw_fif(bids_path_ref_sss) rank_exp = mne.compute_rank(raw_exp, rank="info")["meg"] rank_noise = mne.compute_rank(raw_sss, rank="info")["meg"] From 2926b3f16b07738d8eb3f3965d1a0d4a9d8c261c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 18:55:51 -0400 Subject: [PATCH 07/12] FIX: Raw --- .../steps/preprocessing/_03_maxfilter.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index 117b75f82..c488e346d 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -243,6 +243,22 @@ def get_input_fnames_maxwell_filter( # reference run (used for `destination` and also bad channels for noise) in_files.update(_get_mf_reference_run_path(add_bads=True, **kwargs)) + is_rest_noise = run is None and task in ("noise", "rest") + if is_rest_noise: + key = "raw_ref_run_sss" + in_files[key] = ( + in_files["raw_ref_run"] + .copy() + .update( + processing="sss", + suffix="raw", + extension=".fif", + root=cfg.deriv_root, + check=False, + ) + ) + _update_for_splits(in_files, key, single=True) + # standard files in_files["mf_cal_fname"] = cfg.mf_cal_fname in_files["mf_ctc_fname"] = cfg.mf_ctc_fname @@ -413,8 +429,7 @@ def run_maxwell_filter( # copy the bad channel selection from the reference run over to # the resting-state recording. - bids_path_ref_sss = bids_path_ref_in.copy().update(**bids_path_out_kwargs) - bids_path_ref_sss = _update_for_splits(bids_path_ref_sss, None, single=True) + bids_path_ref_sss = in_files.pop("raw_ref_run_sss") raw_exp = mne.io.read_raw_fif(bids_path_ref_sss) rank_exp = mne.compute_rank(raw_exp, rank="info")["meg"] rank_noise = mne.compute_rank(raw_sss, rank="info")["meg"] From 91ff1b77618ede97ab13988444cbf4c68670c89c Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jul 2023 20:13:07 -0400 Subject: [PATCH 08/12] FIX: Dont take params from bids_path_in --- mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py | 5 ----- mne_bids_pipeline/tests/test_run.py | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index c488e346d..12110ed1b 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -313,11 +313,6 @@ def run_maxwell_filter( ) bids_path_out = bids_path_in.copy().update(**bids_path_out_kwargs) - # Now take everything from the bids_path_in and overwrite the parameters - subject = bids_path_in.subject # noqa: F841 - session = bids_path_in.session # noqa: F841 - run = bids_path_in.run - out_files = dict() # Load dev_head_t and digitization points from MaxFilter reference run. msg = f"Loading reference run: {cfg.mf_reference_run}." diff --git a/mne_bids_pipeline/tests/test_run.py b/mne_bids_pipeline/tests/test_run.py index f0f408ebb..28e2a7e71 100644 --- a/mne_bids_pipeline/tests/test_run.py +++ b/mne_bids_pipeline/tests/test_run.py @@ -59,6 +59,7 @@ class _TestOptionsT(TypedDict, total=False): }, "ds000248_base": { "steps": ("preprocessing", "sensor", "source"), + "requires": ("freesurfer",), }, "ds000248_ica": {}, "ds000248_T1_BEM": { From 10311fb5175ae70d67f882e40f8290032153e84a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 18 Jul 2023 11:34:41 -0400 Subject: [PATCH 09/12] FIX: Review --- mne_bids_pipeline/_config_utils.py | 7 +++++++ mne_bids_pipeline/_import_data.py | 2 +- mne_bids_pipeline/steps/init/_02_find_empty_room.py | 2 +- mne_bids_pipeline/steps/preprocessing/_01_data_quality.py | 2 +- mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py | 8 ++++---- mne_bids_pipeline/steps/preprocessing/_06b_run_ssp.py | 2 +- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/mne_bids_pipeline/_config_utils.py b/mne_bids_pipeline/_config_utils.py index 04a61edf7..e08cfb06e 100644 --- a/mne_bids_pipeline/_config_utils.py +++ b/mne_bids_pipeline/_config_utils.py @@ -615,3 +615,10 @@ def _bids_kwargs(*, config: SimpleNamespace) -> dict: def _do_mf_autobad(*, cfg: SimpleNamespace) -> bool: return cfg.find_noisy_channels_meg or cfg.find_flat_channels_meg + + +# Adapted from MNE-Python +def _pl(x, *, non_pl="", pl="s"): + """Determine if plural should be used.""" + len_x = x if isinstance(x, (int, np.generic)) else len(x) + return non_pl if len_x == 1 else pl diff --git a/mne_bids_pipeline/_import_data.py b/mne_bids_pipeline/_import_data.py index ce1964604..075dc8e8c 100644 --- a/mne_bids_pipeline/_import_data.py +++ b/mne_bids_pipeline/_import_data.py @@ -2,7 +2,6 @@ from typing import Dict, Optional, Iterable, Union, List, Literal import mne -from mne.utils import _pl from mne_bids import BIDSPath, read_raw_bids, get_bids_path_from_fname import numpy as np import pandas as pd @@ -14,6 +13,7 @@ get_task, _bids_kwargs, _do_mf_autobad, + _pl, ) from ._io import _read_json, _empty_room_match_path from ._logging import gen_log_kwargs, logger diff --git a/mne_bids_pipeline/steps/init/_02_find_empty_room.py b/mne_bids_pipeline/steps/init/_02_find_empty_room.py index 33a65af9e..d9334a9cf 100644 --- a/mne_bids_pipeline/steps/init/_02_find_empty_room.py +++ b/mne_bids_pipeline/steps/init/_02_find_empty_room.py @@ -3,7 +3,6 @@ from types import SimpleNamespace from typing import Dict, Optional -from mne.utils import _pl from mne_bids import BIDSPath from ..._config_utils import ( @@ -12,6 +11,7 @@ get_subjects, get_mf_reference_run, _bids_kwargs, + _pl, ) from ..._io import _empty_room_match_path, _write_json from ..._logging import gen_log_kwargs, logger diff --git a/mne_bids_pipeline/steps/preprocessing/_01_data_quality.py b/mne_bids_pipeline/steps/preprocessing/_01_data_quality.py index 67413cfdd..655280e52 100644 --- a/mne_bids_pipeline/steps/preprocessing/_01_data_quality.py +++ b/mne_bids_pipeline/steps/preprocessing/_01_data_quality.py @@ -6,7 +6,6 @@ import pandas as pd import mne -from mne.utils import _pl from mne_bids import BIDSPath from ..._config_utils import ( @@ -16,6 +15,7 @@ get_sessions, get_runs_tasks, _do_mf_autobad, + _pl, ) from ..._import_data import ( _get_run_rest_noise_path, diff --git a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py index 12110ed1b..099336c5c 100644 --- a/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py +++ b/mne_bids_pipeline/steps/preprocessing/_03_maxfilter.py @@ -21,7 +21,6 @@ import numpy as np import mne -from mne.utils import _pl from mne_bids import read_raw_bids from ..._config_utils import ( @@ -30,6 +29,7 @@ get_subjects, get_sessions, get_runs_tasks, + _pl, ) from ..._import_data import ( import_experimental_data, @@ -149,7 +149,7 @@ def compute_esss_proj( ) # TODO: plot_projs_topomap doesn't handle meg="combined" well: # https://github.com/mne-tools/mne-python/pull/11792 - for ii, (kind, picks) in enumerate(kinds_picks): + for ax_row, (kind, picks) in zip(axes, kinds_picks): info = mne.pick_info(raw_noise.info, picks) ch_names = info["ch_names"] these_projs = deepcopy(projs) @@ -160,9 +160,9 @@ def compute_esss_proj( mne.viz.plot_projs_topomap( these_projs, info=info, - axes=axes[ii], + axes=ax_row, ) - for ai, ax in enumerate(axes[ii]): + for ai, ax in enumerate(ax_row): ax.set_title(f"{kind} {ai + 1}") report.add_figure( fig, diff --git a/mne_bids_pipeline/steps/preprocessing/_06b_run_ssp.py b/mne_bids_pipeline/steps/preprocessing/_06b_run_ssp.py index 1c0cadab9..eeb22cf36 100644 --- a/mne_bids_pipeline/steps/preprocessing/_06b_run_ssp.py +++ b/mne_bids_pipeline/steps/preprocessing/_06b_run_ssp.py @@ -10,13 +10,13 @@ from mne.preprocessing import create_eog_epochs, create_ecg_epochs from mne import compute_proj_evoked, compute_proj_epochs from mne_bids import BIDSPath -from mne.utils import _pl from ..._config_utils import ( get_runs, get_sessions, get_subjects, _bids_kwargs, + _pl, ) from ..._logging import gen_log_kwargs, logger from ..._parallel import parallel_func, get_parallel_backend From e9a09172f2699be240bd9b2e1404039242fcff4e Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 18 Jul 2023 11:37:31 -0400 Subject: [PATCH 10/12] TST: Ping From 9fcd961a733a1af315e6cabce9dcabc78abb9417 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 18 Jul 2023 11:42:11 -0400 Subject: [PATCH 11/12] TST: Ping From 421f820342d8c687594fa7ba3cc0c76964548fc6 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 18 Jul 2023 12:08:14 -0400 Subject: [PATCH 12/12] TST: Come on