diff --git a/petprep/cli/parser.py b/petprep/cli/parser.py index 50114f31..7c470800 100644 --- a/petprep/cli/parser.py +++ b/petprep/cli/parser.py @@ -188,8 +188,13 @@ def _bids_filter(value, parser): 'identifier (the sub- prefix can be removed)', ) # Re-enable when option is actually implemented - # g_bids.add_argument('-s', '--session-id', action='store', default='single_session', - # help='Select a specific session to be processed') + g_bids.add_argument( + '--session-label', + nargs='+', + type=lambda label: label.removeprefix('ses-'), + help='A space delimited list of session identifiers or a single ' + 'identifier (the ses- prefix can be removed)', + ) # Re-enable when option is actually implemented # g_bids.add_argument('-r', '--run-id', action='store', default='single_run', # help='Select a specific run to be processed') @@ -749,6 +754,13 @@ def parse_args(args=None, namespace=None): config.execution.log_level = int(max(25 - 5 * opts.verbose_count, logging.DEBUG)) config.from_dict(vars(opts), init=['nipype']) + if config.execution.session_label: + config.execution.bids_filters = config.execution.bids_filters or {} + config.execution.bids_filters['pet'] = { + **config.execution.bids_filters.get('pet', {}), + 'session': config.execution.session_label, + } + pvc_vals = (opts.pvc_tool, opts.pvc_method, opts.pvc_psf) if any(val is not None for val in pvc_vals) and not all(val is not None for val in pvc_vals): parser.error('Options --pvc-tool, --pvc-method and --pvc-psf must be used together.') @@ -908,5 +920,16 @@ def parse_args(args=None, namespace=None): f'One or more participant labels were not found in the BIDS directory: {", ".join(missing_subjects)}.' ) + if config.execution.session_label: + available_sessions = set( + config.execution.layout.get_sessions(subject=list(participant_label) or None) + ) + missing_sessions = set(config.execution.session_label) - available_sessions + if missing_sessions: + parser.error( + 'One or more session labels were not found in the BIDS directory: ' + f'{", ".join(sorted(missing_sessions))}.' + ) + config.execution.participant_label = sorted(participant_label) config.workflow.skull_strip_template = config.workflow.skull_strip_template[0] diff --git a/petprep/cli/tests/test_parser.py b/petprep/cli/tests/test_parser.py index 78d47ebb..d259f6f8 100644 --- a/petprep/cli/tests/test_parser.py +++ b/petprep/cli/tests/test_parser.py @@ -24,6 +24,8 @@ from argparse import ArgumentError +import nibabel as nb +import numpy as np import pytest from packaging.version import Version @@ -225,6 +227,45 @@ def test_derivatives(tmp_path): _reset_config() +def test_session_label_only_filters_pet(tmp_path): + bids = tmp_path / 'bids' + out_dir = tmp_path / 'out' + work_dir = tmp_path / 'work' + bids.mkdir() + (bids / 'dataset_description.json').write_text('{"Name": "Test", "BIDSVersion": "1.8.0"}') + + anat_path = bids / 'sub-01' / 'anat' / 'sub-01_T1w.nii.gz' + anat_path.parent.mkdir(parents=True, exist_ok=True) + nb.Nifti1Image(np.zeros((5, 5, 5)), np.eye(4)).to_filename(anat_path) + + pet_path = bids / 'sub-01' / 'ses-blocked' / 'pet' / 'sub-01_ses-blocked_pet.nii.gz' + pet_path.parent.mkdir(parents=True, exist_ok=True) + nb.Nifti1Image(np.zeros((5, 5, 5, 1)), np.eye(4)).to_filename(pet_path) + (pet_path.with_suffix('').with_suffix('.json')).write_text( + '{"FrameTimesStart": [0], "FrameDuration": [1]}' + ) + + try: + parse_args( + args=[ + str(bids), + str(out_dir), + 'participant', + '--session-label', + 'blocked', + '--skip-bids-validation', + '-w', + str(work_dir), + ] + ) + + filters = config.execution.bids_filters + assert filters.get('pet', {}).get('session') == ['blocked'] + assert 'session' not in filters.get('anat', {}) + finally: + _reset_config() + + def test_pvc_argument_handling(tmp_path, minimal_bids): out_dir = tmp_path / 'out' work_dir = tmp_path / 'work' diff --git a/petprep/config.py b/petprep/config.py index c61319b1..2cfc100b 100644 --- a/petprep/config.py +++ b/petprep/config.py @@ -432,6 +432,8 @@ class execution(_Config): """Unique identifier of this particular run.""" participant_label = None """List of participant identifiers that are to be preprocessed.""" + session_label = None + """List of session identifiers that are to be preprocessed.""" task_id = None """Select a particular task from all available in the dataset.""" templateflow_home = _templateflow_home