From 1c2a081997922870494797eead449f65df8b22c5 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 14 Mar 2024 12:13:59 -0400 Subject: [PATCH] MAINT: Check that all options are used (#889) --- docs/hooks.py | 235 +--------------- docs/source/v1.8.md.inc | 4 +- mne_bids_pipeline/_config.py | 11 - mne_bids_pipeline/_docs.py | 260 ++++++++++++++++++ .../tests/configs/config_ERP_CORE.py | 1 - .../configs/config_MNE_phantom_KIT_data.py | 1 - .../tests/configs/config_ds000117.py | 1 - .../tests/configs/config_ds000246.py | 1 - .../tests/configs/config_ds000247.py | 5 +- .../configs/config_ds000248_FLASH_BEM.py | 1 - .../tests/configs/config_ds000248_T1_BEM.py | 1 - .../tests/configs/config_ds000248_base.py | 1 - .../configs/config_ds000248_coreg_surfaces.py | 1 - .../tests/configs/config_ds000248_ica.py | 1 - .../tests/configs/config_ds000248_no_mri.py | 1 - .../tests/configs/config_ds001810.py | 1 - .../tests/configs/config_ds001971.py | 1 - .../tests/configs/config_ds003104.py | 1 - .../tests/configs/config_ds003392.py | 1 - .../tests/configs/config_ds003775.py | 1 - .../tests/configs/config_ds004107.py | 5 +- .../tests/configs/config_ds004229.py | 1 - .../configs/config_eeg_matchingpennies.py | 1 - mne_bids_pipeline/tests/test_documented.py | 18 ++ 24 files changed, 285 insertions(+), 270 deletions(-) create mode 100644 mne_bids_pipeline/_docs.py diff --git a/docs/hooks.py b/docs/hooks.py index b1d505916..81213a9c2 100644 --- a/docs/hooks.py +++ b/docs/hooks.py @@ -1,250 +1,17 @@ """Custom hooks for MkDocs-Material.""" -import ast -import inspect import logging -from collections import defaultdict -from pathlib import Path from typing import Any from mkdocs.config.defaults import MkDocsConfig from mkdocs.structure.files import Files from mkdocs.structure.pages import Page -from tqdm import tqdm -from mne_bids_pipeline import _config_utils +from mne_bids_pipeline._docs import _ParseConfigSteps logger = logging.getLogger("mkdocs") config_updated = False - - -class _ParseConfigSteps: - def __init__(self): - self.steps = defaultdict(list) - # We don't need to parse the config itself, just the steps - no_config = { - "freesurfer/_01_recon_all", - } - ignore_options = { - "PIPELINE_NAME", - "VERSION", - "CODE_URL", - } - ignore_calls = { - # TODO: These are used a lot at the very beginning, so adding them will lead - # to long lists. Instead, let's just mention at the top of General that - # messing with basic BIDS params will affect almost every step. - "_bids_kwargs", - "_import_data_kwargs", - "get_runs", - "get_subjects", - "get_sessions", - } - manual_kws = { - "source/_04_make_forward:get_config:t1_bids_path": ( - "mri_t1_path_generator", - ), - "source/_04_make_forward:get_config:landmarks_kind": ( - "mri_landmarks_kind", - ), - "preprocessing/_01_data_quality:get_config:extra_kwargs": ( - "mf_cal_fname", - "mf_ctc_fname", - "mf_head_origin", - "find_flat_channels_meg", - "find_noisy_channels_meg", - ), - } - # Add a few helper functions - for func in ( - _config_utils.get_eeg_reference, - _config_utils.get_all_contrasts, - _config_utils.get_decoding_contrasts, - _config_utils.get_fs_subject, - _config_utils.get_fs_subjects_dir, - _config_utils.get_mf_cal_fname, - _config_utils.get_mf_ctc_fname, - ): - this_list = [] - for attr in ast.walk(ast.parse(inspect.getsource(func))): - if not isinstance(attr, ast.Attribute): - continue - if not (isinstance(attr.value, ast.Name) and attr.value.id == "config"): - continue - if attr.attr not in this_list: - this_list.append(attr.attr) - manual_kws[func.__name__] = tuple(this_list) - - for module in tqdm( - sum(_config_utils._get_step_modules().values(), tuple()), - desc="Generating option->step mapping", - ): - step = "/".join(module.__name__.split(".")[-2:]) - found = False # found at least one? - # Walk the module file for "get_config*" functions (can be multiple!) - for func in ast.walk(ast.parse(Path(module.__file__).read_text("utf-8"))): - if not isinstance(func, ast.FunctionDef): - continue - where = f"{step}:{func.name}" - # Also look at config.* args in main(), e.g. config.recreate_bem - # and config.recreate_scalp_surface - if func.name == "main": - for call in ast.walk(func): - if not isinstance(call, ast.Call): - continue - for keyword in call.keywords: - if not isinstance(keyword.value, ast.Attribute): - continue - if keyword.value.value.id != "config": - continue - if keyword.value.attr in ("exec_params",): - continue - self._add_step_option(step, keyword.value.attr) - # Also look for root-level conditionals like use_maxwell_filter - # or spatial_filter - for cond in ast.iter_child_nodes(func): - # is a conditional - if not isinstance(cond, ast.If): - continue - # has a return statement - if not any(isinstance(c, ast.Return) for c in ast.walk(cond)): - continue - # look at all attributes in the conditional - for attr in ast.walk(cond.test): - if not isinstance(attr, ast.Attribute): - continue - if attr.value.id != "config": - continue - self._add_step_option(step, attr.attr) - # Now look at get_config* functions - if not func.name.startswith("get_config"): - continue - found = True - for call in ast.walk(func): - if not isinstance(call, ast.Call): - continue - if call.func.id != "SimpleNamespace": - continue - break - else: - raise RuntimeError(f"Could not find SimpleNamespace in {func}") - assert call.args == [] - for keyword in call.keywords: - if isinstance(keyword.value, ast.Call): - key = keyword.value.func.id - if key in ignore_calls: - continue - if key in manual_kws: - for option in manual_kws[key]: - self._add_step_option(step, option) - continue - if keyword.value.func.id == "_sanitize_callable": - assert len(keyword.value.args) == 1 - assert isinstance(keyword.value.args[0], ast.Attribute) - assert keyword.value.args[0].value.id == "config" - self._add_step_option(step, keyword.value.args[0].attr) - continue - raise RuntimeError( - f"{where} cannot handle call {keyword.value.func.id=}" - ) - if isinstance(keyword.value, ast.Name): - key = f"{where}:{keyword.value.id}" - if key in manual_kws: - for option in manual_kws[f"{where}:{keyword.value.id}"]: - self._add_step_option(step, option) - continue - raise RuntimeError(f"{where} cannot handle Name {key=}") - if isinstance(keyword.value, ast.IfExp): # conditional - if keyword.arg == "processing": # inline conditional for proc - continue - if not isinstance(keyword.value, ast.Attribute): - raise RuntimeError( - f"{where} cannot handle type {keyword.value=}" - ) - option = keyword.value.attr - if option in ignore_options: - continue - assert keyword.value.value.id == "config", f"{where} {keyword.value.value.id}" # noqa: E501 # fmt: skip - self._add_step_option(step, option) - if step in no_config: - assert not found, f"Found unexpected get_config* in {step}" - else: - assert found, f"Could not find get_config* in {step}" - # Some don't show up so force them to be empty - force_empty = ( - # Eventually we could deduplicate these with the execution.md list - "n_jobs", - "parallel_backend", - "dask_open_dashboard", - "dask_temp_dir", - "dask_worker_memory_limit", - "log_level", - "mne_log_level", - "on_error", - "memory_location", - "memory_file_method", - "memory_subdir", - "memory_verbose", - "config_validation", - "interactive", - # Plus some BIDS one we don't detect because _bids_kwargs etc. above, - # which we could cross-check against the general.md list. A notable - # exception is random_state, since this does have more localized effects. - "study_name", - "bids_root", - "deriv_root", - "subjects_dir", - "sessions", - "acq", - "proc", - "rec", - "space", - "task", - "runs", - "exclude_runs", - "subjects", - "crop_runs", - "process_empty_room", - "process_rest", - "eeg_bipolar_channels", - "eeg_reference", - "eeg_template_montage", - "drop_channels", - "reader_extra_params", - "read_raw_bids_verbose", - "plot_psd_for_runs", - "find_breaks", - "min_break_duration", - "t_break_annot_start_after_previous_event", - "t_break_annot_stop_before_next_event", - "rename_events", - "on_rename_missing_events", - "mf_reference_run", # TODO: Make clearer that this changes a lot - "fix_stim_artifact", - "stim_artifact_tmin", - "stim_artifact_tmax", - # And some that we force to be empty because they affect too many things - # and what they affect is an incomplete list anyway - "exclude_subjects", - "ch_types", - "task_is_rest", - "data_type", - ) - for key in force_empty: - self.steps[key] = list() - for key, val in self.steps.items(): - assert len(val) == len(set(val)), f"{key} {val}" - self.steps = {k: tuple(v) for k, v in self.steps.items()} # no defaultdict - - def _add_step_option(self, step, option): - if step not in self.steps[option]: - self.steps[option].append(step) - - def __call__(self, option: str) -> list[str]: - return self.steps[option] - - _parse_config_steps = _ParseConfigSteps() diff --git a/docs/source/v1.8.md.inc b/docs/source/v1.8.md.inc index dfce7a2a7..f9f583fb9 100644 --- a/docs/source/v1.8.md.inc +++ b/docs/source/v1.8.md.inc @@ -20,7 +20,7 @@ ### :medical_symbol: Code health -- We removed the unused setting `shortest_event`. It was a relic of early days of the pipeline - and hasn't been in use for a long time. (#888 by @hoechenberger) +- We removed the unused settings `shortest_event` and `study_name`. They were relics of early days of the pipeline + and haven't been in use for a long time. (#888, #889 by @hoechenberger and @larsoner) [//]: # (- Whatever (#000 by @whoever)) diff --git a/mne_bids_pipeline/_config.py b/mne_bids_pipeline/_config.py index 213d26651..07c41ece7 100644 --- a/mne_bids_pipeline/_config.py +++ b/mne_bids_pipeline/_config.py @@ -17,17 +17,6 @@ # %% # # General settings -study_name: str = "" -""" -Specify the name of your study. It will be used to populate filenames for -saving the analysis results. - -???+ example "Example" - ```python - study_name = 'my-study' - ``` -""" - bids_root: Optional[PathLike] = None """ Specify the BIDS root directory. Pass an empty string or ```None` to use diff --git a/mne_bids_pipeline/_docs.py b/mne_bids_pipeline/_docs.py new file mode 100644 index 000000000..440b34690 --- /dev/null +++ b/mne_bids_pipeline/_docs.py @@ -0,0 +1,260 @@ +import ast +import inspect +import re +from collections import defaultdict +from pathlib import Path + +from tqdm import tqdm + +from . import _config_utils, _import_data + +_CONFIG_RE = re.compile(r"config\.([a-zA-Z_]+)") + +_NO_CONFIG = { + "freesurfer/_01_recon_all", +} +_IGNORE_OPTIONS = { + "PIPELINE_NAME", + "VERSION", + "CODE_URL", +} +# We don't need to parse the config itself, just the steps +_MANUAL_KWS = { + "source/_04_make_forward:get_config:t1_bids_path": ("mri_t1_path_generator",), + "source/_04_make_forward:get_config:landmarks_kind": ("mri_landmarks_kind",), + "preprocessing/_01_data_quality:get_config:extra_kwargs": ( + "mf_cal_fname", + "mf_ctc_fname", + "mf_head_origin", + "find_flat_channels_meg", + "find_noisy_channels_meg", + ), +} +# Some don't show up so force them to be empty +_EXECUTION_OPTIONS = ( + # Eventually we could deduplicate these with the execution.md list + "n_jobs", + "parallel_backend", + "dask_open_dashboard", + "dask_temp_dir", + "dask_worker_memory_limit", + "log_level", + "mne_log_level", + "on_error", + "memory_location", + "memory_file_method", + "memory_subdir", + "memory_verbose", + "config_validation", + "interactive", +) +_FORCE_EMPTY = _EXECUTION_OPTIONS + ( + # Plus some BIDS one we don't detect because _bids_kwargs etc. above, + # which we could cross-check against the general.md list. A notable + # exception is random_state, since this does have more localized effects. + # These are used a lot at the very beginning, so adding them will lead + # to long lists. Instead, let's just mention at the top of General that + # messing with basic BIDS params will affect almost every step. + "bids_root", + "deriv_root", + "subjects_dir", + "sessions", + "acq", + "proc", + "rec", + "space", + "task", + "runs", + "exclude_runs", + "subjects", + "crop_runs", + "process_empty_room", + "process_rest", + "eeg_bipolar_channels", + "eeg_reference", + "eeg_template_montage", + "drop_channels", + "reader_extra_params", + "read_raw_bids_verbose", + "plot_psd_for_runs", + "shortest_event", + "find_breaks", + "min_break_duration", + "t_break_annot_start_after_previous_event", + "t_break_annot_stop_before_next_event", + "rename_events", + "on_rename_missing_events", + "mf_reference_run", # TODO: Make clearer that this changes a lot + "fix_stim_artifact", + "stim_artifact_tmin", + "stim_artifact_tmax", + # And some that we force to be empty because they affect too many things + # and what they affect is an incomplete list anyway + "exclude_subjects", + "ch_types", + "task_is_rest", + "data_type", +) +# Eventually we could parse AST to get these, but this is simple enough +_EXTRA_FUNCS = { + "_bids_kwargs": ("get_task",), + "_import_data_kwargs": ("get_mf_reference_run",), + "get_runs": ("get_runs_all_subjects",), +} + + +class _ParseConfigSteps: + def __init__(self, force_empty=None): + self._force_empty = _FORCE_EMPTY if force_empty is None else force_empty + self.steps = defaultdict(list) + # Add a few helper functions + for func in ( + _config_utils.get_eeg_reference, + _config_utils.get_all_contrasts, + _config_utils.get_decoding_contrasts, + _config_utils.get_fs_subject, + _config_utils.get_fs_subjects_dir, + _config_utils.get_mf_cal_fname, + _config_utils.get_mf_ctc_fname, + ): + this_list = [] + for attr in ast.walk(ast.parse(inspect.getsource(func))): + if not isinstance(attr, ast.Attribute): + continue + if not (isinstance(attr.value, ast.Name) and attr.value.id == "config"): + continue + if attr.attr not in this_list: + this_list.append(attr.attr) + _MANUAL_KWS[func.__name__] = tuple(this_list) + + for module in tqdm( + sum(_config_utils._get_step_modules().values(), tuple()), + desc="Generating option->step mapping", + ): + step = "/".join(module.__name__.split(".")[-2:]) + found = False # found at least one? + # Walk the module file for "get_config*" functions (can be multiple!) + for func in ast.walk(ast.parse(Path(module.__file__).read_text("utf-8"))): + if not isinstance(func, ast.FunctionDef): + continue + where = f"{step}:{func.name}" + # Also look at config.* args in main(), e.g. config.recreate_bem + # and config.recreate_scalp_surface + if func.name == "main": + for call in ast.walk(func): + if not isinstance(call, ast.Call): + continue + for keyword in call.keywords: + if not isinstance(keyword.value, ast.Attribute): + continue + if keyword.value.value.id != "config": + continue + if keyword.value.attr in ("exec_params",): + continue + self._add_step_option(step, keyword.value.attr) + # Also look for root-level conditionals like use_maxwell_filter + # or spatial_filter + for cond in ast.iter_child_nodes(func): + # is a conditional + if not isinstance(cond, ast.If): + continue + # has a return statement + if not any(isinstance(c, ast.Return) for c in ast.walk(cond)): + continue + # look at all attributes in the conditional + for attr in ast.walk(cond.test): + if not isinstance(attr, ast.Attribute): + continue + if attr.value.id != "config": + continue + self._add_step_option(step, attr.attr) + # Now look at get_config* functions + if not func.name.startswith("get_config"): + continue + found = True + for call in ast.walk(func): + if not isinstance(call, ast.Call): + continue + if call.func.id != "SimpleNamespace": + continue + break + else: + raise RuntimeError(f"Could not find SimpleNamespace in {func}") + assert call.args == [] + for keyword in call.keywords: + if isinstance(keyword.value, ast.Call): + key = keyword.value.func.id + if key in _MANUAL_KWS: + for option in _MANUAL_KWS[key]: + self._add_step_option(step, option) + continue + if keyword.value.func.id == "_sanitize_callable": + assert len(keyword.value.args) == 1 + assert isinstance(keyword.value.args[0], ast.Attribute) + assert keyword.value.args[0].value.id == "config" + self._add_step_option(step, keyword.value.args[0].attr) + continue + if key not in ( + "_bids_kwargs", + "_import_data_kwargs", + "get_runs", + "get_subjects", + "get_sessions", + ): + raise RuntimeError( + f"{where} cannot handle call {keyword.value.func.id=} " + f"for {key}" + ) + # Get the source and regex for config values + if key == "_import_data_kwargs": + funcs = [getattr(_import_data, key)] + else: + funcs = [getattr(_config_utils, key)] + for func_name in _EXTRA_FUNCS.get(key, ()): + funcs.append(getattr(_config_utils, func_name)) + for fi, func in enumerate(funcs): + source = inspect.getsource(func) + assert "config: SimpleNamespace" in source, key + if fi == 0: + for func_name in _EXTRA_FUNCS.get(key, ()): + assert f"{func_name}(" in source, (key, func_name) + attrs = _CONFIG_RE.findall(source) + assert len(attrs), f"No config.* found in source of {key}" + for attr in attrs: + self._add_step_option(step, attr) + continue + if isinstance(keyword.value, ast.Name): + key = f"{where}:{keyword.value.id}" + if key in _MANUAL_KWS: + for option in _MANUAL_KWS[f"{where}:{keyword.value.id}"]: + self._add_step_option(step, option) + continue + raise RuntimeError(f"{where} cannot handle Name {key=}") + if isinstance(keyword.value, ast.IfExp): # conditional + if keyword.arg == "processing": # inline conditional for proc + continue + if not isinstance(keyword.value, ast.Attribute): + raise RuntimeError( + f"{where} cannot handle type {keyword.value=}" + ) + option = keyword.value.attr + if option in _IGNORE_OPTIONS: + continue + assert keyword.value.value.id == "config", f"{where} {keyword.value.value.id}" # noqa: E501 # fmt: skip + self._add_step_option(step, option) + if step in _NO_CONFIG: + assert not found, f"Found unexpected get_config* in {step}" + else: + assert found, f"Could not find get_config* in {step}" + for key in self._force_empty: + self.steps[key] = list() + for key, val in self.steps.items(): + assert len(val) == len(set(val)), f"{key} {val}" + self.steps = {k: tuple(v) for k, v in self.steps.items()} # no defaultdict + + def _add_step_option(self, step, option): + if step not in self.steps[option]: + self.steps[option].append(step) + + def __call__(self, option: str) -> list[str]: + return self.steps[option] diff --git a/mne_bids_pipeline/tests/configs/config_ERP_CORE.py b/mne_bids_pipeline/tests/configs/config_ERP_CORE.py index 0ae706aa9..34a534791 100644 --- a/mne_bids_pipeline/tests/configs/config_ERP_CORE.py +++ b/mne_bids_pipeline/tests/configs/config_ERP_CORE.py @@ -28,7 +28,6 @@ import mne -study_name = "ERP-CORE" bids_root = "~/mne_data/ERP_CORE" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ERP_CORE" diff --git a/mne_bids_pipeline/tests/configs/config_MNE_phantom_KIT_data.py b/mne_bids_pipeline/tests/configs/config_MNE_phantom_KIT_data.py index ef3347a53..49689bd3e 100644 --- a/mne_bids_pipeline/tests/configs/config_MNE_phantom_KIT_data.py +++ b/mne_bids_pipeline/tests/configs/config_MNE_phantom_KIT_data.py @@ -4,7 +4,6 @@ https://mne.tools/dev/documentation/datasets.html#kit-phantom-dataset """ -study_name = "MNE-phantom-KIT-data" bids_root = "~/mne_data/MNE-phantom-KIT-data" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/MNE-phantom-KIT-data" task = "phantom" diff --git a/mne_bids_pipeline/tests/configs/config_ds000117.py b/mne_bids_pipeline/tests/configs/config_ds000117.py index 65e213e24..14fd77499 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000117.py +++ b/mne_bids_pipeline/tests/configs/config_ds000117.py @@ -1,6 +1,5 @@ """Faces dataset.""" -study_name = "ds000117" bids_root = "~/mne_data/ds000117" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000117" diff --git a/mne_bids_pipeline/tests/configs/config_ds000246.py b/mne_bids_pipeline/tests/configs/config_ds000246.py index 0c516796d..d1a9610d4 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000246.py +++ b/mne_bids_pipeline/tests/configs/config_ds000246.py @@ -4,7 +4,6 @@ information. """ -study_name = "ds000246" bids_root = "~/mne_data/ds000246" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000246" diff --git a/mne_bids_pipeline/tests/configs/config_ds000247.py b/mne_bids_pipeline/tests/configs/config_ds000247.py index 395b538ab..fc4f42464 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000247.py +++ b/mne_bids_pipeline/tests/configs/config_ds000247.py @@ -2,9 +2,8 @@ import numpy as np -study_name = "ds000247" -bids_root = f"~/mne_data/{study_name}" -deriv_root = f"~/mne_data/derivatives/mne-bids-pipeline/{study_name}" +bids_root = "~/mne_data/ds000247" +deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000247" subjects = ["0002"] sessions = ["01"] diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_FLASH_BEM.py b/mne_bids_pipeline/tests/configs/config_ds000248_FLASH_BEM.py index 547721753..5d37fde67 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_FLASH_BEM.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_FLASH_BEM.py @@ -1,6 +1,5 @@ """MNE Sample Data: BEM from FLASH images.""" -study_name = "ds000248" bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_FLASH_BEM" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_T1_BEM.py b/mne_bids_pipeline/tests/configs/config_ds000248_T1_BEM.py index 76fee45e3..0fdfdbf76 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_T1_BEM.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_T1_BEM.py @@ -1,6 +1,5 @@ """MNE Sample Data: BEM from T1 images.""" -study_name = "ds000248" bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_T1_BEM" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_base.py b/mne_bids_pipeline/tests/configs/config_ds000248_base.py index 89f39cd8b..2f4db6a10 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_base.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_base.py @@ -2,7 +2,6 @@ import mne -study_name = "ds000248" bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_base" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_coreg_surfaces.py b/mne_bids_pipeline/tests/configs/config_ds000248_coreg_surfaces.py index 475ca5d67..dba51f97d 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_coreg_surfaces.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_coreg_surfaces.py @@ -1,6 +1,5 @@ """MNE Sample Data: Head surfaces from FreeSurfer surfaces for coregistration step.""" -study_name = "ds000248" bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_coreg_surfaces" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_ica.py b/mne_bids_pipeline/tests/configs/config_ds000248_ica.py index e1b090e30..6dfb49c7b 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_ica.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_ica.py @@ -1,6 +1,5 @@ """MNE Sample Data: ICA.""" -study_name = 'MNE "sample" dataset' bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_ica" diff --git a/mne_bids_pipeline/tests/configs/config_ds000248_no_mri.py b/mne_bids_pipeline/tests/configs/config_ds000248_no_mri.py index 3b83b0e6e..08b98e9bb 100644 --- a/mne_bids_pipeline/tests/configs/config_ds000248_no_mri.py +++ b/mne_bids_pipeline/tests/configs/config_ds000248_no_mri.py @@ -1,6 +1,5 @@ """MNE Sample Data: Using the `fsaverage` template MRI.""" -study_name = "ds000248" bids_root = "~/mne_data/ds000248" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds000248_no_mri" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds001810.py b/mne_bids_pipeline/tests/configs/config_ds001810.py index 606fee3c8..3771d6cd3 100644 --- a/mne_bids_pipeline/tests/configs/config_ds001810.py +++ b/mne_bids_pipeline/tests/configs/config_ds001810.py @@ -1,6 +1,5 @@ """tDCS EEG.""" -study_name = "ds001810" bids_root = "~/mne_data/ds001810" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds001810" diff --git a/mne_bids_pipeline/tests/configs/config_ds001971.py b/mne_bids_pipeline/tests/configs/config_ds001971.py index befc0f30e..349dbe23e 100644 --- a/mne_bids_pipeline/tests/configs/config_ds001971.py +++ b/mne_bids_pipeline/tests/configs/config_ds001971.py @@ -3,7 +3,6 @@ See ds001971 on OpenNeuro: https://github.com/OpenNeuroDatasets/ds001971 """ -study_name = "ds001971" bids_root = "~/mne_data/ds001971" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds001971" diff --git a/mne_bids_pipeline/tests/configs/config_ds003104.py b/mne_bids_pipeline/tests/configs/config_ds003104.py index 3e4b9e44d..d47a0a64c 100644 --- a/mne_bids_pipeline/tests/configs/config_ds003104.py +++ b/mne_bids_pipeline/tests/configs/config_ds003104.py @@ -1,6 +1,5 @@ """Somato.""" -study_name = "MNE-somato-data-anonymized" bids_root = "~/mne_data/ds003104" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds003104" subjects_dir = f"{bids_root}/derivatives/freesurfer/subjects" diff --git a/mne_bids_pipeline/tests/configs/config_ds003392.py b/mne_bids_pipeline/tests/configs/config_ds003392.py index b004c4345..b8ee82d2e 100644 --- a/mne_bids_pipeline/tests/configs/config_ds003392.py +++ b/mne_bids_pipeline/tests/configs/config_ds003392.py @@ -1,6 +1,5 @@ """hMT+ Localizer.""" -study_name = "localizer" bids_root = "~/mne_data/ds003392" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds003392" diff --git a/mne_bids_pipeline/tests/configs/config_ds003775.py b/mne_bids_pipeline/tests/configs/config_ds003775.py index 980bed232..219b1e23a 100644 --- a/mne_bids_pipeline/tests/configs/config_ds003775.py +++ b/mne_bids_pipeline/tests/configs/config_ds003775.py @@ -1,6 +1,5 @@ """SRM Resting-state EEG.""" -study_name = "ds003775" bids_root = "~/mne_data/ds003775" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds003775" diff --git a/mne_bids_pipeline/tests/configs/config_ds004107.py b/mne_bids_pipeline/tests/configs/config_ds004107.py index a46679e54..0dd70a5ef 100644 --- a/mne_bids_pipeline/tests/configs/config_ds004107.py +++ b/mne_bids_pipeline/tests/configs/config_ds004107.py @@ -9,9 +9,8 @@ # This has auditory, median, indx, visual, rest, and emptyroom but let's just # process the auditory (it's the smallest after rest) -study_name = "ds004107" -bids_root = f"~/mne_data/{study_name}" -deriv_root = f"~/mne_data/derivatives/mne-bids-pipeline/{study_name}" +bids_root = "~/mne_data/ds004107" +deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds004107" subjects = ["mind002"] sessions = ["01"] conditions = ["left", "right"] # there are also tone and noise diff --git a/mne_bids_pipeline/tests/configs/config_ds004229.py b/mne_bids_pipeline/tests/configs/config_ds004229.py index 9cedd6491..878b743b0 100644 --- a/mne_bids_pipeline/tests/configs/config_ds004229.py +++ b/mne_bids_pipeline/tests/configs/config_ds004229.py @@ -6,7 +6,6 @@ import mne import numpy as np -study_name = "amnoise" bids_root = "~/mne_data/ds004229" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/ds004229" diff --git a/mne_bids_pipeline/tests/configs/config_eeg_matchingpennies.py b/mne_bids_pipeline/tests/configs/config_eeg_matchingpennies.py index fbe34b11a..5cb0b1390 100644 --- a/mne_bids_pipeline/tests/configs/config_eeg_matchingpennies.py +++ b/mne_bids_pipeline/tests/configs/config_eeg_matchingpennies.py @@ -1,6 +1,5 @@ """Matchingpennies EEG experiment.""" -study_name = "eeg_matchingpennies" bids_root = "~/mne_data/eeg_matchingpennies" deriv_root = "~/mne_data/derivatives/mne-bids-pipeline/eeg_matchingpennies" diff --git a/mne_bids_pipeline/tests/test_documented.py b/mne_bids_pipeline/tests/test_documented.py index db5bbbd06..a6275a4c8 100644 --- a/mne_bids_pipeline/tests/test_documented.py +++ b/mne_bids_pipeline/tests/test_documented.py @@ -9,6 +9,7 @@ import yaml from mne_bids_pipeline._config_import import _get_default_config +from mne_bids_pipeline._docs import _EXECUTION_OPTIONS, _ParseConfigSteps from mne_bids_pipeline.tests.datasets import DATASET_OPTIONS from mne_bids_pipeline.tests.test_run import TEST_SUITE @@ -67,6 +68,23 @@ def test_options_documented(): assert in_config.difference(in_doc_all) == set(), f"Values missing from {what}" +def test_config_options_used(): + """Test that all config options are used somewhere.""" + config = _get_default_config() + config_names = set(d for d in dir(config) if not d.startswith("__")) + for key in ("_epochs_split_size", "_raw_split_size"): + config_names.add(key) + for key in _EXECUTION_OPTIONS: + config_names.remove(key) + pcs = _ParseConfigSteps(force_empty=()) + missing_from_config = sorted(set(pcs.steps) - config_names) + assert missing_from_config == [], f"Missing from config: {missing_from_config}" + missing_from_steps = sorted(config_names - set(pcs.steps)) + assert missing_from_steps == [], f"Missing from steps: {missing_from_steps}" + for key, val in pcs.steps.items(): + assert val, f"No steps for {key}" + + def test_datasets_in_doc(): """Test that all datasets in tests are in the doc.""" # There are four things to keep in sync: