diff --git a/src/fmripost_aroma/data/io_spec.json b/src/fmripost_aroma/data/io_spec.json index 148d1a2..9d7507d 100644 --- a/src/fmripost_aroma/data/io_spec.json +++ b/src/fmripost_aroma/data/io_spec.json @@ -63,7 +63,7 @@ ".nii" ] }, - "confounds": { + "bold_confounds": { "datatype": "func", "echo": null, "part": [ @@ -79,12 +79,27 @@ ".tsv" ] }, + "anat_mni152nlin6asym": { + "datatype": "anat", + "echo": null, + "part": [ + "mag", + null + ], + "res": "2", + "space": "MNI152NLin6Asym", + "desc": "preproc", + "suffix": [ + "T1w", + "T2w" + ], + "extension": [ + ".nii.gz", + ".nii" + ] + }, "anat_dseg": { "datatype": "anat", - "task": null, - "acquisition": null, - "run": null, - "part": null, "space": null, "res": null, "den": null, @@ -97,9 +112,8 @@ } }, "transforms": { - "hmc": { + "bold_hmc": { "datatype": "func", - "part": null, "from": "orig", "to": "boldref", "mode": "image", @@ -108,7 +122,6 @@ }, "boldref2anat": { "datatype": "func", - "part": null, "from": "boldref", "to": ["anat", "T1w", "T2w"], "mode": "image", @@ -117,18 +130,14 @@ }, "boldref2fmap": { "datatype": "func", - "part": null, - "from": "boldref", + "from": "orig", "mode": "image", "suffix": "xfm", "extension": ".txt" }, "anat2mni152nlin6asym": { "datatype": "anat", - "task": null, - "acquisition": null, "run": null, - "part": null, "from": ["anat", "T1w", "T2w"], "to": "MNI152NLin6Asym", "space": null, diff --git a/src/fmripost_aroma/tests/test_utils_bids.py b/src/fmripost_aroma/tests/test_utils_bids.py index 7c62ba7..225c00e 100644 --- a/src/fmripost_aroma/tests/test_utils_bids.py +++ b/src/fmripost_aroma/tests/test_utils_bids.py @@ -80,8 +80,8 @@ def test_collect_derivatives_minimal(minimal_ignore_list): # TODO: Add bold_mask_native to the dataset # 'bold_mask_native': 'sub-01_task-mixedgamblestask_run-01_desc-brain_mask.nii.gz', 'bold_mask_native': None, - 'confounds': None, - 'hmc': ( + 'bold_confounds': None, + 'bold_hmc': ( 'sub-01_task-mixedgamblestask_run-01_from-orig_to-boldref_mode-image_desc-hmc_xfm.txt' ), 'boldref2anat': ( @@ -123,8 +123,8 @@ def test_collect_derivatives_full(full_ignore_list): 'mask.nii.gz' ), 'bold_mask_native': None, - 'confounds': 'sub-01_task-mixedgamblestask_run-01_desc-confounds_timeseries.tsv', - 'hmc': ( + 'bold_confounds': 'sub-01_task-mixedgamblestask_run-01_desc-confounds_timeseries.tsv', + 'bold_hmc': ( 'sub-01_task-mixedgamblestask_run-01_from-orig_to-boldref_mode-image_desc-hmc_xfm.txt' ), 'boldref2anat': ( diff --git a/src/fmripost_aroma/utils/bids.py b/src/fmripost_aroma/utils/bids.py index 5a1e864..7958f32 100644 --- a/src/fmripost_aroma/utils/bids.py +++ b/src/fmripost_aroma/utils/bids.py @@ -119,26 +119,43 @@ def collect_derivatives( ) for k, q in spec['derivatives'].items(): - # Combine entities with query. Query values override file entities. - query = {**entities, **q} + if k.startswith('anat'): + # Allow anatomical derivatives at session level or subject level + query = {**{'session': [entities.get('session'), None]}, **q} + else: + # Combine entities with query. Query values override file entities. + query = {**entities, **q} + item = layout.get(return_type='filename', **query) if not item: derivs_cache[k] = None + elif not allow_multiple and len(item) > 1 and k.startswith('anat'): + # Anatomical derivatives are allowed to have multiple files (e.g., T1w and T2w) + # but we just grab the first one + derivs_cache[k] = item[0] elif not allow_multiple and len(item) > 1: raise ValueError(f'Multiple files found for {k}: {item}') else: derivs_cache[k] = item[0] if len(item) == 1 else item for k, q in spec['transforms'].items(): - # Combine entities with query. Query values override file entities. - # TODO: Drop functional entities (task, run, etc.) from anat transforms. - query = {**entities, **q} + if k.startswith('anat'): + # Allow anatomical derivatives at session level or subject level + query = {**{'session': [entities.get('session'), None]}, **q} + else: + # Combine entities with query. Query values override file entities. + query = {**entities, **q} + if k == 'boldref2fmap': query['to'] = fieldmap_id item = layout.get(return_type='filename', **query) if not item: derivs_cache[k] = None + elif not allow_multiple and len(item) > 1 and k.startswith('anat'): + # Anatomical derivatives are allowed to have multiple files (e.g., T1w and T2w) + # but we just grab the first one + derivs_cache[k] = item[0] elif not allow_multiple and len(item) > 1: raise ValueError(f'Multiple files found for {k}: {item}') else: @@ -178,8 +195,11 @@ def collect_derivatives( spaces_found, anat2outputspaces_xfm = [], [] for space in spaces.references: - # First try to find processed BOLD+mask files in the requested space - anat2space_query = {**entities, **spec['transforms']['anat2mni152nlin6asym']} + # Now try to find transform to the requested space + anat2space_query = { + **{'session': [entities.get('session'), None]}, + **spec['transforms']['anat2mni152nlin6asym'], + } anat2space_query['to'] = space.space item = layout.get(return_type='filename', **anat2space_query) anat2outputspaces_xfm.append(item[0] if item else None) diff --git a/src/fmripost_aroma/workflows/base.py b/src/fmripost_aroma/workflows/base.py index 20d2f8f..ac82bc2 100644 --- a/src/fmripost_aroma/workflows/base.py +++ b/src/fmripost_aroma/workflows/base.py @@ -322,7 +322,7 @@ def init_single_run_wf(bold_file): ), ) - if not functional_cache['confounds']: + if not functional_cache['bold_confounds']: if config.workflow.dummy_scans is None: raise ValueError( 'No confounds detected. ' @@ -357,17 +357,17 @@ def init_single_run_wf(bold_file): if config.workflow.dummy_scans is not None: skip_vols = config.workflow.dummy_scans else: - if not functional_cache['confounds']: + if not functional_cache['bold_confounds']: raise ValueError( 'No confounds detected. ' 'Automatical dummy scan detection cannot be performed. ' 'Please set the `--dummy-scans` flag explicitly.' ) - skip_vols = get_nss(functional_cache['confounds']) + skip_vols = get_nss(functional_cache['bold_confounds']) # Run ICA-AROMA ica_aroma_wf = init_ica_aroma_wf(bold_file=bold_file, metadata=bold_metadata, mem_gb=mem_gb) - ica_aroma_wf.inputs.inputnode.confounds = functional_cache['confounds'] + ica_aroma_wf.inputs.inputnode.confounds = functional_cache['bold_confounds'] ica_aroma_wf.inputs.inputnode.skip_vols = skip_vols mni6_buffer = pe.Node(niu.IdentityInterface(fields=['bold', 'bold_mask']), name='mni6_buffer') @@ -427,7 +427,7 @@ def init_single_run_wf(bold_file): jacobian='fmap-jacobian' not in config.workflow.ignore, name='bold_MNI6_wf', ) - bold_MNI6_wf.inputs.inputnode.motion_xfm = functional_cache['hmc'] + bold_MNI6_wf.inputs.inputnode.motion_xfm = functional_cache['bold_hmc'] bold_MNI6_wf.inputs.inputnode.boldref2fmap_xfm = functional_cache['boldref2fmap'] bold_MNI6_wf.inputs.inputnode.boldref2anat_xfm = functional_cache['boldref2anat'] bold_MNI6_wf.inputs.inputnode.anat2std_xfm = functional_cache['anat2mni152nlin6asym']