diff --git a/pyproject.toml b/pyproject.toml index f57a362916..138234a55b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "matplotlib >= 2.2.0", "nibabel >= 4.0.1", "nipype >= 1.7.0", - "niworkflows >= 1.8.0", + "niworkflows @ git+https://github.com/nipreps/niworkflows.git@master", "numpy", "packaging", "pybids >= 0.11.1", @@ -138,6 +138,8 @@ source = [ [tool.ruff] line-length = 99 + +[tool.ruff.lint] extend-select = [ "F", "E", @@ -162,10 +164,10 @@ extend-select = [ "Q", ] -[tool.ruff.flake8-quotes] +[tool.ruff.lint.flake8-quotes] inline-quotes = "single" -[tool.ruff.extend-per-file-ignores] +[tool.ruff.lint.extend-per-file-ignores] "*/test_*.py" = ["S101"] [tool.ruff.format] diff --git a/smriprep/data/io_spec.json b/smriprep/data/io_spec.json index 6b8fcd50fc..54edaf3ac1 100644 --- a/smriprep/data/io_spec.json +++ b/smriprep/data/io_spec.json @@ -50,8 +50,8 @@ "forward": { "datatype": "anat", "extension": [ - "h5", - "txt" + ".h5", + ".txt" ], "from": "T1w", "to": null, @@ -61,8 +61,8 @@ "reverse": { "datatype": "anat", "extension": [ - "h5", - "txt" + ".h5", + ".txt" ], "from": null, "to": "T1w", diff --git a/smriprep/utils/bids.py b/smriprep/utils/bids.py index 123f8dd6c1..c213a0afc3 100644 --- a/smriprep/utils/bids.py +++ b/smriprep/utils/bids.py @@ -25,6 +25,7 @@ from pathlib import Path from bids.layout import BIDSLayout +from niworkflows.data import load as nwf_load from ..data import load_resource @@ -39,36 +40,38 @@ def collect_derivatives(derivatives_dir, subject_id, std_spaces, spec=None, patt if patterns is None: patterns = _patterns - layout = BIDSLayout(derivatives_dir, config=['bids', 'derivatives'], validate=False) + deriv_config = nwf_load('nipreps.json') + layout = BIDSLayout(derivatives_dir, config=deriv_config, validate=False) derivs_cache = {} - for k, q in spec['baseline'].items(): - q['subject'] = subject_id - item = layout.get(return_type='filename', **q) + for key, qry in spec['baseline'].items(): + qry['subject'] = subject_id + item = layout.get(return_type='filename', **qry) if not item: continue - derivs_cache['t1w_%s' % k] = item[0] if len(item) == 1 else item + derivs_cache[f't1w_{key}'] = item[0] if len(item) == 1 else item transforms = derivs_cache.setdefault('transforms', {}) - for space in std_spaces: - for k, q in spec['transforms'].items(): - q = q.copy() - q['subject'] = subject_id - q['from'] = q['from'] or space - q['to'] = q['to'] or space - item = layout.get(return_type='filename', **q) + for _space in std_spaces: + space = _space.replace(':cohort-', '+') + for key, qry in spec['transforms'].items(): + qry = qry.copy() + qry['subject'] = subject_id + qry['from'] = qry['from'] or space + qry['to'] = qry['to'] or space + item = layout.get(return_type='filename', **qry) if not item: continue - transforms.setdefault(space, {})[k] = item[0] if len(item) == 1 else item + transforms.setdefault(_space, {})[key] = item[0] if len(item) == 1 else item - for k, q in spec['surfaces'].items(): - q['subject'] = subject_id - item = layout.get(return_type='filename', **q) + for key, qry in spec['surfaces'].items(): + qry['subject'] = subject_id + item = layout.get(return_type='filename', **qry) if not item or len(item) != 2: continue - derivs_cache[k] = sorted(item) + derivs_cache[key] = sorted(item) return derivs_cache diff --git a/smriprep/utils/tests/__init__.py b/smriprep/utils/tests/__init__.py index e69de29bb2..84a88300dc 100644 --- a/smriprep/utils/tests/__init__.py +++ b/smriprep/utils/tests/__init__.py @@ -0,0 +1,5 @@ +from niworkflows.data import Loader + +load_data = Loader(__package__) + +DERIV_SKELETON = load_data('derivatives.yml') diff --git a/smriprep/utils/tests/derivatives.yml b/smriprep/utils/tests/derivatives.yml new file mode 100644 index 0000000000..97cda9e19c --- /dev/null +++ b/smriprep/utils/tests/derivatives.yml @@ -0,0 +1,101 @@ +dataset_description: + Name: smriprep-outputs + BIDSVersion: 1.9.0 + DatasetType: derivative +'01': + anat: + - suffix: mask + desc: brain + - suffix: T1w + desc: preproc + - suffix: dseg + - suffix: probseg + label: CSF + - suffix: probseg + label: GM + - suffix: probseg + label: WM + - suffix: xfm + from: MNI152NLin2009cAsym + to: T1w + mode: image + extension: .h5 + - suffix: xfm + from: T1w + to: MNI152NLin2009cAsym + mode: image + extension: .h5 + - suffix: xfm + from: T1w + to: MNIPediatricAsym+3 + mode: image + extension: .h5 + - suffix: xfm + from: MNIPediatricAsym+3 + to: T1w + mode: image + extension: .h5 + - suffix: white + hemi: L + extension: .surf.gii + - suffix: white + hemi: R + extension: .surf.gii + - suffix: pial + hemi: L + extension: .surf.gii + - suffix: pial + hemi: R + extension: .surf.gii + - suffix: midthickness + hemi: L + extension: .surf.gii + - suffix: midthickness + hemi: R + extension: .surf.gii + - suffix: sphere + hemi: L + extension: .surf.gii + - suffix: sphere + hemi: R + extension: .surf.gii + - suffix: sphere + hemi: L + desc: reg + extension: .surf.gii + - suffix: sphere + hemi: R + desc: reg + extension: .surf.gii + - suffix: sphere + hemi: L + space: fsLR + desc: reg + extension: .surf.gii + - suffix: sphere + hemi: R + space: fsLR + desc: reg + extension: .surf.gii + - suffix: sphere + hemi: L + space: fsLR + desc: msmsulc + extension: .surf.gii + - suffix: sphere + hemi: R + space: fsLR + desc: msmsulc + extension: .surf.gii + - suffix: thickness + hemi: L + extension: .shape.gii + - suffix: thickness + hemi: R + extension: .shape.gii + - suffix: sulc + hemi: L + extension: .shape.gii + - suffix: sulc + hemi: R + extension: .shape.gii \ No newline at end of file diff --git a/smriprep/utils/tests/test_bids.py b/smriprep/utils/tests/test_bids.py new file mode 100644 index 0000000000..a8b094ea99 --- /dev/null +++ b/smriprep/utils/tests/test_bids.py @@ -0,0 +1,30 @@ +from niworkflows.utils.testing import generate_bids_skeleton + +from ..bids import collect_derivatives +from . import DERIV_SKELETON + + +def test_collect_derivatives(tmp_path): + deriv_dir = tmp_path / 'derivatives' + generate_bids_skeleton(deriv_dir, str(DERIV_SKELETON)) + output_spaces = ['MNI152NLin2009cAsym', 'MNIPediatricAsym:cohort-3'] + collected = collect_derivatives(deriv_dir, '01', output_spaces) + for suffix in ('preproc', 'mask', 'dseg'): + assert collected[f't1w_{suffix}'] + assert len(collected['t1w_tpms']) == 3 + xfms = collected['transforms'] + for space in output_spaces: + assert xfms[space]['reverse'] + assert xfms[space]['forward'] + for surface in ( + 'white', + 'pial', + 'midthickness', + 'sphere', + 'thickness', + 'sulc', + 'sphere_reg', + 'sphere_reg_fsLR', + 'sphere_reg_msm', + ): + assert len(collected[surface]) == 2 diff --git a/smriprep/workflows/fit/registration.py b/smriprep/workflows/fit/registration.py index 6e83212425..f9df9fb901 100644 --- a/smriprep/workflows/fit/registration.py +++ b/smriprep/workflows/fit/registration.py @@ -185,6 +185,12 @@ def init_register_template_wf( mem_gb=2, ) + fmt_cohort = pe.Node( + niu.Function(function=_fmt_cohort, output_names=['template', 'spec']), + name='fmt_cohort', + run_without_submitting=True, + ) + # fmt:off workflow.connect([ (inputnode, split_desc, [('template', 'template')]), @@ -202,8 +208,12 @@ def init_register_template_wf( ]), (trunc_mov, registration, [ ('output_image', 'moving_image')]), - (split_desc, outputnode, [ + (split_desc, fmt_cohort, [ ('name', 'template'), + ('spec', 'spec'), + ]), + (fmt_cohort, outputnode, [ + ('template', 'template'), ('spec', 'template_spec'), ]), (registration, outputnode, [ @@ -225,3 +235,10 @@ def _make_outputnode(workflow, out_fields, joinsource): workflow.connect([(pout, out, [(f, f) for f in out_fields])]) return pout return pe.Node(niu.IdentityInterface(fields=out_fields), name='outputnode') + + +def _fmt_cohort(template, spec): + cohort = spec.pop('cohort', None) + if cohort is not None: + template = f'{template}:cohort-{cohort}' + return template, spec