Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ignoring sessions that have no data #1035

Merged
merged 13 commits into from
Jan 24, 2025
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
command: |
pip install --upgrade --progress-bar off pip
# TODO: Restore https://api.github.com/repos/mne-tools/mne-bids/zipball/main pending https://github.com/mne-tools/mne-bids/pull/1349/files#r1885104885
pip install --upgrade --progress-bar off "autoreject @ https://api.github.com/repos/autoreject/autoreject/zipball/master" "mne[hdf5] @ git+https://github.com/mne-tools/mne-python@main" "mne-bids[full]" numba
pip install --upgrade --progress-bar off "autoreject @ https://api.github.com/repos/autoreject/autoreject/zipball/master" "mne[hdf5] @ git+https://github.com/mne-tools/mne-python@main" "mne-bids[full] @ git+https://github.com/mne-tools/mne-bids@main" numba
pip install -ve .[tests]
pip install "PyQt6!=6.6.1" "PyQt6-Qt6!=6.6.1,!=6.6.2,!=6.6.3,!=6.7.0"
- run:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
with:
python-version: "3.12"
- run: pip install --upgrade pip
- run: pip install -ve .[tests] codespell tomli --only-binary="numpy,scipy,pandas,matplotlib,pyarrow,numexpr"
- run: pip install -ve .[tests] "mne-bids[full] @ git+https://github.com/mne-tools/mne-bids@main" codespell tomli --only-binary="numpy,scipy,pandas,matplotlib,pyarrow,numexpr"
- run: make codespell-error
- run: pytest mne_bids_pipeline -m "not dataset_test"
- uses: codecov/codecov-action@v5
Expand Down
12 changes: 12 additions & 0 deletions mne_bids_pipeline/_config_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import pathlib
from dataclasses import field
from functools import partial
from inspect import signature
from types import SimpleNamespace
from typing import Any

import matplotlib
import mne
import numpy as np
from mne_bids import get_entity_vals
from pydantic import BaseModel, ConfigDict, ValidationError

from ._logging import gen_log_kwargs, logger
Expand Down Expand Up @@ -324,6 +326,16 @@ def _check_config(config: SimpleNamespace, config_path: PathLike | None) -> None
"Please set process_empty_room = True"
)

if (
config.allow_missing_sessions
and "ignore_suffixes" not in signature(get_entity_vals).parameters
):
raise ConfigError(
"You've requested to `allow_missing_sessions`, but this functionality "
"requires a newer version of `mne_bids` than you have available. Please "
"update MNE-BIDS (or if on the latest version, install the dev version)."
)

bl = config.baseline
if bl is not None:
if (bl[0] is not None and bl[0] < config.epochs_tmin) or (
Expand Down
49 changes: 38 additions & 11 deletions mne_bids_pipeline/_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import functools
import pathlib
from collections.abc import Iterable, Sized
from inspect import signature
from types import ModuleType, SimpleNamespace
from typing import Any, Literal, TypeVar

Expand Down Expand Up @@ -142,26 +143,52 @@ def _get_sessions(config: SimpleNamespace) -> tuple[str, ...]:
return tuple(str(x) for x in sessions)


def get_subjects_sessions(config: SimpleNamespace) -> dict[str, list[str] | list[None]]:
subj_sessions: dict[str, list[str] | list[None]] = dict()
def get_subjects_sessions(
config: SimpleNamespace,
) -> dict[str, tuple[None] | tuple[str, ...]]:
subjects = get_subjects(config)
cfg_sessions = _get_sessions(config)
for subject in get_subjects(config):
# Only traverse through the current subject's directory
# easy case first: datasets that don't have (named) sessions
if not cfg_sessions:
return {subj: (None,) for subj in subjects}

# find which tasks to ignore when deciding if a subj has data for a session
ignore_datatypes = _get_ignore_datatypes(config)
if config.task == "":
ignore_tasks = None
else:
all_tasks = _get_entity_vals_cached(
root=config.bids_root,
entity_key="task",
ignore_datatypes=ignore_datatypes,
)
ignore_tasks = tuple(set(all_tasks) - set([config.task]))

# loop over subjs and check for available sessions
subj_sessions: dict[str, tuple[None] | tuple[str, ...]] = dict()
kwargs = (
dict(ignore_suffixes=("scans", "coordsystem"))
if "ignore_suffixes" in signature(mne_bids.get_entity_vals).parameters
else dict()
)
for subject in subjects:
valid_sessions_subj = _get_entity_vals_cached(
config.bids_root / f"sub-{subject}",
entity_key="session",
ignore_datatypes=_get_ignore_datatypes(config),
ignore_tasks=ignore_tasks,
ignore_acquisitions=("calibration", "crosstalk"),
ignore_datatypes=ignore_datatypes,
**kwargs,
)
missing_sessions = set(cfg_sessions) - set(valid_sessions_subj)
missing_sessions = sorted(set(cfg_sessions) - set(valid_sessions_subj))
if missing_sessions and not config.allow_missing_sessions:
raise RuntimeError(
f"Subject {subject} is missing session{_pl(missing_sessions)} "
f"{tuple(sorted(missing_sessions))}, and "
"`config.allow_missing_sessions` is False"
f"{missing_sessions}, and `config.allow_missing_sessions` is False"
)
subj_sessions[subject] = sorted(set(cfg_sessions) & set(valid_sessions_subj))
if subj_sessions[subject] == []:
subj_sessions[subject] = [None]
Copy link
Member Author

@drammock drammock Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wasn't doing what was intended. We want [None] (or (None,)) for datasets where sessions aren't tracked (ie there's just one session). But for datasets where sessions are tracked, we should omit an entry from this subj_sessions dict if there aren't any sessions found for that subj.

keep_sessions = tuple(sorted(set(cfg_sessions) & set(valid_sessions_subj)))
if len(keep_sessions):
subj_sessions[subject] = keep_sessions
return subj_sessions


Expand Down
2 changes: 1 addition & 1 deletion mne_bids_pipeline/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def test_missing_sessions(
context = (
nullcontext()
if allow_missing_sessions
else pytest.raises(RuntimeError, match=r"Subject 1 is missing session \('b',\)")
else pytest.raises(RuntimeError, match=r"Subject 1 is missing session \['b'\]")
)
# run
command = [
Expand Down
Loading