diff --git a/brainprep/utils/__init__.py b/brainprep/utils/__init__.py index 069feff0..afb64aed 100644 --- a/brainprep/utils/__init__.py +++ b/brainprep/utils/__init__.py @@ -24,6 +24,7 @@ print_warn, ) from .utils import ( + bids_basename, bvecbval_from_file, coerce_to_list, coerce_to_path, @@ -32,6 +33,7 @@ make_run_id, parse_bids_keys, sbref_from_file, + select_defaced_t1w, sidecar_from_file, ) diff --git a/brainprep/utils/utils.py b/brainprep/utils/utils.py index f8aea780..f4f7f4b8 100644 --- a/brainprep/utils/utils.py +++ b/brainprep/utils/utils.py @@ -33,6 +33,38 @@ print_warn, ) +def bids_basename( + entities: dict[str], +) -> str: + """ + Build a BIDS-like file basename from parsed BIDS entities. + + The recognized entities are kept in canonical BIDS order, so that + input files differing only by entities such as ``acq`` or ``rec`` + yield distinct derivative names. The ``mod`` and ``modality`` keys + are ignored: the modality is handled by the callers through the + file suffix. + + Parameters + ---------- + entities : dict[str] + A dictionary of parsed BIDS entities, as returned by + :func:`parse_bids_keys`. + + Returns + ------- + basename : str + The BIDS-like basename, e.g. ``sub-01_ses-M00_acq-3DT1_run-01``. + """ + order = ( + "sub", "ses", "task", "acq", "ce", "rec", "dir", + "echo", "part", "run", "space", "res", "recording", + ) + return "_".join( + f"{key}-{entities[key]}" + for key in order + if key in entities and entities[key] != "" + ) def coerce_to_list( value: Any, diff --git a/brainprep/workflow/defacing.py b/brainprep/workflow/defacing.py index 254e6abb..57165505 100644 --- a/brainprep/workflow/defacing.py +++ b/brainprep/workflow/defacing.py @@ -54,6 +54,7 @@ def brainprep_defacing( anatomical_file: File, output_dir: Directory, keep_intermediate: bool = False, + t1w_run_id: str | None = None, **kwargs: dict, ) -> Bunch: """ @@ -82,6 +83,10 @@ def brainprep_defacing( If True, retains intermediate results (e.g., reoriented image); useful for debugging. Default False. + t1w_run_id : str | None + Optional run identifier to select a specific defaced T1w image when + defacing T2w or FLAIR images. Required when multiple T1w images exist + in the same session. Default None. **kwargs : dict entities: dict Dictionary of parsed BIDS entities. @@ -197,10 +202,19 @@ def brainprep_defacing( "defacemask.nii.gz" ) ) - if len(t1_file) != 1 or len(mask_t1_file) != 1: + if (len(t1_file) > 1 or len(mask_t1_file) > 1): + if t1w_run_id is None: + raise ValueError( + f"Multiple T1w defaced images found: {t1_file}, {mask_t1_file}. " + "Please specify a run identifier run- using the 't1w_run_id' parameter." + ) + t1_file = [f for f in t1_file if f"run-{t1w_run_id}_" in str(f)] + mask_t1_file = [f for f in mask_t1_file if f"run-{t1w_run_id}_" in str(f)] + if len(t1_file) == 0 or len(mask_t1_file) == 0: raise ValueError( f"No T1w defaced image found: {t1_file}, {mask_t1_file}" ) + t1_file, mask_t1_file = t1_file[0], mask_t1_file[0] print_info(f"using T1w: {t1_file}") print_info(f"using defacing mask: {mask_t1_file}")