From 0688b555e5dbf6d78608f01ce96a77e4af398878 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Fri, 29 Sep 2023 16:23:17 -0400 Subject: [PATCH 01/14] FEAT: Allow single anatomical outputs --- nibabies/workflows/anatomical/outputs.py | 418 +++++++++++++---------- 1 file changed, 244 insertions(+), 174 deletions(-) diff --git a/nibabies/workflows/anatomical/outputs.py b/nibabies/workflows/anatomical/outputs.py index a934e013..e7ceb6a6 100644 --- a/nibabies/workflows/anatomical/outputs.py +++ b/nibabies/workflows/anatomical/outputs.py @@ -1,12 +1,20 @@ # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """Writing outputs.""" +from __future__ import annotations + +import typing as ty +from pathlib import Path + from nipype.interfaces import utility as niu from nipype.pipeline import engine as pe from niworkflows.engine.workflows import LiterateWorkflow as Workflow from ...interfaces import DerivativesDataSink +if ty.TYPE_CHECKING: + from niworkflows.utils.spaces import SpatialReferences + BIDS_TISSUE_ORDER = ("GM", "WM", "CSF") @@ -74,13 +82,19 @@ def init_coreg_report_wf(*, output_dir, name="coreg_report_wf"): return workflow -def init_anat_reports_wf(*, freesurfer, output_dir, sloppy, name="anat_reports_wf"): +def init_anat_reports_wf( + *, + surface_recon: ty.Literal['freesurfer', 'infantfs', 'mcribs'] | None, + output_dir: str, + sloppy: bool, + name="anat_reports_wf", +) -> Workflow: """ Patched workflow for reports to allow no resolution for templates Set up a battery of datasinks to store reports in the right location. Parameters ---------- - freesurfer : :obj:`bool` + recon_method : :obj:`bool` FreeSurfer was enabled output_dir : :obj:`str` Directory in which to save derivatives @@ -127,10 +141,10 @@ def init_anat_reports_wf(*, freesurfer, output_dir, sloppy, name="anat_reports_w inputfields = [ "source_file", - "t1w_conform_report", - "t1w_preproc", - "t1w_dseg", - "t1w_mask", + "anat_conform_report", + "anat_preproc", + "anat_dseg", + "anat_mask", "template", "std_t1w", "std_mask", @@ -143,34 +157,34 @@ def init_anat_reports_wf(*, freesurfer, output_dir, sloppy, name="anat_reports_w seg_rpt = pe.Node(ROIsPlot(colors=["b", "magenta"], levels=[1.5, 2.5]), name="seg_rpt") - t1w_conform_check = pe.Node( + anat_conform_check = pe.Node( niu.Function(function=_empty_report), - name="t1w_conform_check", + name="anat_conform_check", run_without_submitting=True, ) - ds_t1w_conform_report = pe.Node( + ds_anat_conform_report = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="conform", datatype="figures"), - name="ds_t1w_conform_report", + name="ds_anat_conform_report", run_without_submitting=True, ) - ds_t1w_dseg_mask_report = pe.Node( + ds_anat_dseg_mask_report = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix="dseg", datatype="figures"), - name="ds_t1w_dseg_mask_report", + name="ds_anat_dseg_mask_report", run_without_submitting=True, ) # fmt: off workflow.connect([ - (inputnode, t1w_conform_check, [('t1w_conform_report', 'in_file')]), - (t1w_conform_check, ds_t1w_conform_report, [('out', 'in_file')]), - (inputnode, ds_t1w_conform_report, [('source_file', 'source_file')]), - (inputnode, ds_t1w_dseg_mask_report, [('source_file', 'source_file')]), - (inputnode, seg_rpt, [('t1w_preproc', 'in_file'), - ('t1w_mask', 'in_mask'), - ('t1w_dseg', 'in_rois')]), - (seg_rpt, ds_t1w_dseg_mask_report, [('out_report', 'in_file')]), + (inputnode, anat_conform_check, [('anat_conform_report', 'in_file')]), + (anat_conform_check, ds_anat_conform_report, [('out', 'in_file')]), + (inputnode, ds_anat_conform_report, [('source_file', 'source_file')]), + (inputnode, ds_anat_dseg_mask_report, [('source_file', 'source_file')]), + (inputnode, seg_rpt, [('anat_preproc', 'in_file'), + ('anat_mask', 'in_mask'), + ('anat_dseg', 'in_rois')]), + (seg_rpt, ds_anat_dseg_mask_report, [('out_report', 'in_file')]), ]) # fmt: on @@ -216,39 +230,43 @@ def init_anat_reports_wf(*, freesurfer, output_dir, sloppy, name="anat_reports_w ]) # fmt: on - if freesurfer: - from smriprep.interfaces.reports import FSSurfaceReport + if not surface_recon: + return workflow - recon_report = pe.Node(FSSurfaceReport(), name="recon_report") - recon_report.interface._always_run = True + # TODO: Separate report for MCRIBS? + from smriprep.interfaces.reports import FSSurfaceReport - ds_recon_report = pe.Node( - DerivativesDataSink(base_directory=output_dir, desc="reconall", datatype="figures"), - name="ds_recon_report", - run_without_submitting=True, - ) - # fmt: off - workflow.connect([ - (inputnode, recon_report, [('subjects_dir', 'subjects_dir'), - ('subject_id', 'subject_id')]), - (recon_report, ds_recon_report, [('out_report', 'in_file')]), - (inputnode, ds_recon_report, [('source_file', 'source_file')]) - ]) - # fmt: on + recon_report = pe.Node(FSSurfaceReport(), name="recon_report") + recon_report.interface._always_run = True + + ds_recon_report = pe.Node( + DerivativesDataSink(base_directory=output_dir, desc="reconall", datatype="figures"), + name="ds_recon_report", + run_without_submitting=True, + ) + # fmt: off + workflow.connect([ + (inputnode, recon_report, [('subjects_dir', 'subjects_dir'), + ('subject_id', 'subject_id')]), + (recon_report, ds_recon_report, [('out_report', 'in_file')]), + (inputnode, ds_recon_report, [('source_file', 'source_file')]) + ]) + # fmt: on return workflow def init_anat_derivatives_wf( *, - bids_root, - freesurfer, - num_t1w, - output_dir, - spaces, - cifti_output, - name="anat_derivatives_wf", - tpm_labels=BIDS_TISSUE_ORDER, + bids_root: Path | str, + output_dir: Path | str, + spaces: SpatialReferences, + cifti_output: bool, + num_t1w: int | None, + num_t2w: int | None, + surface_recon: ty.Literal['freesurfer', 'infantfs', 'mcribs'] | None, + tpm_labels: ty.Tuple[str, str, str] = BIDS_TISSUE_ORDER, + name: str = "anat_derivatives_wf", ): """ Set up a battery of datasinks to store derivatives in the right location. @@ -335,23 +353,29 @@ def init_anat_derivatives_wf( niu.IdentityInterface( fields=[ "template", - "source_files", + # T1w + "t1w_source_files", "t1w_ref_xfms", "t1w_preproc", - "t1w_mask", - "t1w_dseg", - "t1w_tpms", + # T2w + "t2w_source_files", + "t2w_ref_xfms", + "t2w_preproc", + # Can be in either T1w/T2w space + "anat_mask", + "anat_dseg", + "anat_tpms", "anat2std_xfm", "std2anat_xfm", - "t1w2fsnative_xfm", - "fsnative2t1w_xfm", + "anat2fsnative_xfm", + "fsnative2anat_xfm", + # FS + "anat_fs_aseg", + "anat_fs_aparc", + "anat_ribbon", "surfaces", "morphometrics", - "t1w_fs_aseg", - "t1w_fs_aparc", - "t2w_source_files", - "t2w_preproc", - "anat_ribbon", + # CIFTI "cifti_metadata", "cifti_density", "cifti_morph", @@ -361,16 +385,100 @@ def init_anat_derivatives_wf( ), name="inputnode", ) + # The preferred space to use for to/from entities + source_files = "t1w_source_files" if num_t1w else "t2w_source_files" + space = "T1w" if num_t1w else "T2w" + + if num_t1w: + raw_sources = pe.Node(niu.Function(function=_bids_relative), name="t1w_raw_sources") + raw_sources.inputs.bids_root = bids_root + + ds_t1w_preproc = pe.Node( + DerivativesDataSink(base_directory=output_dir, desc="preproc", compress=True), + name="ds_t1w_preproc", + run_without_submitting=True, + ) + ds_t1w_preproc.inputs.SkullStripped = False + + if num_t1w > 1: + # Please note the dictionary unpacking to provide the from argument. + # It is necessary because from is a protected keyword (not allowed as argument name). + ds_t1w_ref_xfms = pe.MapNode( + DerivativesDataSink( + base_directory=output_dir, + to="T1w", + mode="image", + suffix="xfm", + extension="txt", + **{"from": "orig"}, + ), + iterfield=["source_file", "in_file"], + name="ds_t1w_ref_xfms", + run_without_submitting=True, + ) + # fmt:off + workflow.connect([ + (inputnode, ds_t1w_ref_xfms, [('t1w_source_files', 'source_file'), + ('t1w_ref_xfms', 'in_file')]), + ]) + # fmt:on + + if num_t2w: + + if not num_t1w: + raw_sources = pe.Node(niu.Function(function=_bids_relative), name="t2w_raw_sources") + raw_sources.inputs.bids_root = bids_root + + ds_t2w_preproc = pe.Node( + DerivativesDataSink(base_directory=output_dir, desc="preproc", compress=True), + name="ds_t2w_preproc", + run_without_submitting=True, + ) + if num_t1w: + ds_t2w_preproc.inputs.space = "T1w" + + if num_t2w > 1: + # Please note the dictionary unpacking to provide the from argument. + # It is necessary because from is a protected keyword (not allowed as argument name). + ds_t2w_ref_xfms = pe.MapNode( + DerivativesDataSink( + base_directory=output_dir, + to="T1w", + mode="image", + suffix="xfm", + extension="txt", + **{"from": "orig"}, + ), + iterfield=["source_file", "in_file"], + name="ds_t2w_ref_xfms", + run_without_submitting=True, + ) + # fmt:off + workflow.connect([ + (inputnode, ds_t2w_ref_xfms, [('t2w_source_files', 'source_file'), + ('t2w_ref_xfms', 'in_file')]), + ]) + # fmt:on + + ds_anat_mask = pe.Node( + DerivativesDataSink(base_directory=output_dir, desc="brain", suffix="mask", compress=True), + name="ds_anat_mask", + run_without_submitting=True, + ) + ds_anat_mask.inputs.Type = "Brain" - raw_sources = pe.Node(niu.Function(function=_bids_relative), name="raw_sources") - raw_sources.inputs.bids_root = bids_root + ds_anat_dseg = pe.Node( + DerivativesDataSink(base_directory=output_dir, suffix="dseg", compress=True), + name="ds_anat_dseg", + run_without_submitting=True, + ) - ds_t1w_preproc = pe.Node( - DerivativesDataSink(base_directory=output_dir, desc="preproc", compress=True), - name="ds_t1w_preproc", + ds_anat_tpms = pe.Node( + DerivativesDataSink(base_directory=output_dir, suffix="probseg", compress=True), + name="ds_anat_tpms", run_without_submitting=True, ) - ds_t1w_preproc.inputs.SkullStripped = False + ds_anat_tpms.inputs.label = tpm_labels ds_anat_ribbon = pe.Node( DerivativesDataSink( @@ -384,106 +492,67 @@ def init_anat_derivatives_wf( run_without_submitting=True, ) - ds_t1w_mask = pe.Node( - DerivativesDataSink(base_directory=output_dir, desc="brain", suffix="mask", compress=True), - name="ds_t1w_mask", - run_without_submitting=True, - ) - ds_t1w_mask.inputs.Type = "Brain" - - ds_t1w_dseg = pe.Node( - DerivativesDataSink(base_directory=output_dir, suffix="dseg", compress=True), - name="ds_t1w_dseg", - run_without_submitting=True, - ) - - ds_t2w_preproc = pe.Node( - DerivativesDataSink( - data_dtype="i2", base_directory=output_dir, space="T1w", desc="preproc", compress=True - ), - name="ds_t2w_preproc", - run_without_submitting=True, - ) + if num_t1w: + # fmt:off + workflow.connect([ + (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), + ('t1w_source_files', 'source_file')]), + ]) + # fmt:on - ds_t1w_tpms = pe.Node( - DerivativesDataSink(base_directory=output_dir, suffix="probseg", compress=True), - name="ds_t1w_tpms", - run_without_submitting=True, - ) - ds_t1w_tpms.inputs.label = tpm_labels + if num_t2w: + # fmt:off + workflow.connect([ + (inputnode, ds_t2w_preproc, [('t2w_preproc', 'in_file'), + ('t2w_source_files', 'source_file')]), + ]) + # fmt:on - # fmt: off + # fmt:off workflow.connect([ - (inputnode, raw_sources, [('source_files', 'in_files')]), - (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), - ('source_files', 'source_file')]), - (inputnode, ds_t2w_preproc, [('t2w_preproc', 'in_file'), - ('t2w_source_files', 'source_file')]), - (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file'), - ('source_files', 'source_file')]), - (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'), - ('source_files', 'source_file')]), - (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'), - ('source_files', 'source_file')]), + (inputnode, raw_sources, [(source_files, 'in_files')]), + (inputnode, ds_anat_mask, [('anat_mask', 'in_file'), + (source_files, 'source_file')]), + (inputnode, ds_anat_tpms, [('anat_tpms', 'in_file'), + (source_files, 'source_file')]), + (inputnode, ds_anat_dseg, [('anat_dseg', 'in_file'), + (source_files, 'source_file')]), (inputnode, ds_anat_ribbon, [('anat_ribbon', 'in_file'), - ('source_files', 'source_file')]), - (raw_sources, ds_t1w_mask, [('out', 'RawSources')]), + (source_files, 'source_file')]), + (raw_sources, ds_anat_mask, [('out', 'RawSources')]), ]) - # fmt: on + # fmt:on # Transforms if spaces.get_spaces(nonstandard=False, dim=(3,)): - ds_std2t1w_xfm = pe.MapNode( - DerivativesDataSink(base_directory=output_dir, to="T1w", mode="image", suffix="xfm"), + ds_std2anat_xfm = pe.MapNode( + DerivativesDataSink(base_directory=output_dir, to=space, mode="image", suffix="xfm"), iterfield=("in_file", "from"), - name="ds_std2t1w_xfm", + name="ds_std2anat_xfm", run_without_submitting=True, ) - ds_t1w2std_xfm = pe.MapNode( + ds_anat2std_xfm = pe.MapNode( DerivativesDataSink( - base_directory=output_dir, mode="image", suffix="xfm", **{"from": "T1w"} + base_directory=output_dir, mode="image", suffix="xfm", **{"from": space} ), iterfield=("in_file", "to"), - name="ds_t1w2std_xfm", + name="ds_anat2std_xfm", run_without_submitting=True, ) - # fmt: off + # fmt:off workflow.connect([ - (inputnode, ds_t1w2std_xfm, [ + (inputnode, ds_anat2std_xfm, [ ('anat2std_xfm', 'in_file'), (('template', _combine_cohort), 'to'), - ('source_files', 'source_file')]), - (inputnode, ds_std2t1w_xfm, [ + (source_files, 'source_file')]), + (inputnode, ds_std2anat_xfm, [ ('std2anat_xfm', 'in_file'), (('template', _combine_cohort), 'from'), - ('source_files', 'source_file')]), + (source_files, 'source_file')]), ]) - # fmt: on - - if num_t1w > 1: - # Please note the dictionary unpacking to provide the from argument. - # It is necessary because from is a protected keyword (not allowed as argument name). - ds_t1w_ref_xfms = pe.MapNode( - DerivativesDataSink( - base_directory=output_dir, - to="T1w", - mode="image", - suffix="xfm", - extension="txt", - **{"from": "orig"}, - ), - iterfield=["source_file", "in_file"], - name="ds_t1w_ref_xfms", - run_without_submitting=True, - ) - # fmt: off - workflow.connect([ - (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'), - ('t1w_ref_xfms', 'in_file')]), - ]) - # fmt: on + # fmt:on # Write derivatives in standard spaces specified by --output-spaces if getattr(spaces, "_cached") is not None and spaces.cached.references: @@ -515,8 +584,7 @@ def init_anat_derivatives_wf( gen_ref = pe.Node(GenerateSamplingReference(), name="gen_ref", mem_gb=0.01) - # Mask T1w preproc images - mask_t1w = pe.Node(ApplyMask(), name="mask_t1w") + mask_anat = pe.Node(ApplyMask(), name="mask_anat") # Resample T1w-space inputs anat2std_t1w = pe.Node( @@ -581,15 +649,17 @@ def init_anat_derivatives_wf( # output in the data/io_spec.json file. ds_std_tpms.inputs.label = tpm_labels + preproc_file = 't1w_preproc' if num_t1w else 't2w_preproc' + # fmt: off workflow.connect([ - (inputnode, mask_t1w, [('t1w_preproc', 'in_file'), - ('t1w_mask', 'in_mask')]), - (mask_t1w, anat2std_t1w, [('out_file', 'input_image')]), - (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]), - (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]), - (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]), - (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]), + (inputnode, mask_anat, [(preproc_file, 'in_file'), + ('anat_mask', 'in_mask')]), + (mask_anat, anat2std_t1w, [('out_file', 'input_image')]), + (inputnode, anat2std_mask, [('anat_mask', 'input_image')]), + (inputnode, anat2std_dseg, [('anat_dseg', 'input_image')]), + (inputnode, anat2std_tpms, [('anat_tpms', 'input_image')]), + (inputnode, gen_ref, [(preproc_file, 'moving_image')]), (inputnode, select_xfm, [ ('anat2std_xfm', 'anat2std_xfm'), ('template', 'keys')]), @@ -622,7 +692,7 @@ def init_anat_derivatives_wf( ] # Connect the source_file input of these datasinks + [ - (inputnode, n, [("source_files", "source_file")]) + (inputnode, n, [(source_files, "source_file")]) for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms) ] # Connect the space input of these datasinks @@ -636,7 +706,7 @@ def init_anat_derivatives_wf( ] ) - if not freesurfer: + if not surface_recon: return workflow from niworkflows.interfaces.nitransforms import ConcatenateXFMs @@ -645,28 +715,28 @@ def init_anat_derivatives_wf( # FS native space transforms lta2itk_fwd = pe.Node(ConcatenateXFMs(), name="lta2itk_fwd", run_without_submitting=True) lta2itk_inv = pe.Node(ConcatenateXFMs(), name="lta2itk_inv", run_without_submitting=True) - ds_t1w_fsnative = pe.Node( + ds_anat_fsnative = pe.Node( DerivativesDataSink( base_directory=output_dir, mode="image", to="fsnative", suffix="xfm", extension="txt", - **{"from": "T1w"}, + **{"from": space}, ), - name="ds_t1w_fsnative", + name="ds_anat_fsnative", run_without_submitting=True, ) - ds_fsnative_t1w = pe.Node( + ds_fsnative_anat = pe.Node( DerivativesDataSink( base_directory=output_dir, mode="image", - to="T1w", + to=space, suffix="xfm", extension="txt", **{"from": "fsnative"}, ), - name="ds_fsnative_t1w", + name="ds_fsnative_anat", run_without_submitting=True, ) # Surfaces @@ -722,49 +792,49 @@ def init_anat_derivatives_wf( run_without_submitting=True, ) # Parcellations - ds_t1w_fsaseg = pe.Node( + ds_anat_fsaseg = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="aseg", suffix="dseg", compress=True), - name="ds_t1w_fsaseg", + name="ds_anat_fsaseg", run_without_submitting=True, ) - ds_t1w_fsparc = pe.Node( + ds_anat_fsparc = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="aparcaseg", suffix="dseg", compress=True ), - name="ds_t1w_fsparc", + name="ds_anat_fsparc", run_without_submitting=True, ) # fmt: off workflow.connect([ - (inputnode, lta2itk_fwd, [('t1w2fsnative_xfm', 'in_xfms')]), - (inputnode, lta2itk_inv, [('fsnative2t1w_xfm', 'in_xfms')]), - (inputnode, ds_t1w_fsnative, [('source_files', 'source_file')]), - (lta2itk_fwd, ds_t1w_fsnative, [('out_xfm', 'in_file')]), - (inputnode, ds_fsnative_t1w, [('source_files', 'source_file')]), - (lta2itk_inv, ds_fsnative_t1w, [('out_xfm', 'in_file')]), + (inputnode, lta2itk_fwd, [('anat2fsnative_xfm', 'in_xfms')]), + (inputnode, lta2itk_inv, [('fsnative2anat_xfm', 'in_xfms')]), + (inputnode, ds_anat_fsnative, [(source_files, 'source_file')]), + (lta2itk_fwd, ds_anat_fsnative, [('out_xfm', 'in_file')]), + (inputnode, ds_fsnative_anat, [(source_files, 'source_file')]), + (lta2itk_inv, ds_fsnative_anat, [('out_xfm', 'in_file')]), (inputnode, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_surfs, [('surfaces', 'in_file'), - ('source_files', 'source_file')]), + (source_files, 'source_file')]), (name_surfs, ds_surfs, [('hemi', 'hemi'), ('suffix', 'suffix')]), (inputnode, name_regs, [('sphere_reg', 'in_file')]), (inputnode, ds_regs, [('sphere_reg', 'in_file'), - ('source_files', 'source_file')]), + (source_files, 'source_file')]), (name_regs, ds_regs, [('hemi', 'hemi')]), (inputnode, name_reg_fsLR, [('sphere_reg_fsLR', 'in_file')]), (inputnode, ds_reg_fsLR, [('sphere_reg_fsLR', 'in_file'), - ('source_files', 'source_file')]), + (source_files, 'source_file')]), (name_reg_fsLR, ds_reg_fsLR, [('hemi', 'hemi')]), (inputnode, name_morphs, [('morphometrics', 'in_file')]), (inputnode, ds_morphs, [('morphometrics', 'in_file'), - ('source_files', 'source_file')]), + (source_files, 'source_file')]), (name_morphs, ds_morphs, [('hemi', 'hemi'), ('suffix', 'suffix')]), - (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file'), - ('source_files', 'source_file')]), - (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file'), - ('source_files', 'source_file')]), + (inputnode, ds_anat_fsaseg, [('anat_fs_aseg', 'in_file'), + (source_files, 'source_file')]), + (inputnode, ds_anat_fsparc, [('anat_fs_aparc', 'in_file'), + (source_files, 'source_file')]), ]) # fmt: on if cifti_output: @@ -782,7 +852,7 @@ def init_anat_derivatives_wf( # fmt:off workflow.connect([ (inputnode, ds_cifti_morph, [('cifti_morph', 'in_file'), - ('source_files', 'source_file'), + (source_files, 'source_file'), ('cifti_density', 'density'), (('cifti_metadata', _read_jsons), 'meta_dict')]) ]) From 23471097c1a581437ca07be6415c195c92c8aeea Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 2 Oct 2023 17:05:03 -0400 Subject: [PATCH 02/14] FEAT: Remove T1w only check, reflect modality used on wf desc --- nibabies/workflows/anatomical/segmentation.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/nibabies/workflows/anatomical/segmentation.py b/nibabies/workflows/anatomical/segmentation.py index 38a92e41..ce903749 100644 --- a/nibabies/workflows/anatomical/segmentation.py +++ b/nibabies/workflows/anatomical/segmentation.py @@ -37,11 +37,6 @@ def init_anat_segmentations_wf( from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.utils.connections import listify - if anat_modality != "T1w": - raise NotImplementedError( - "Only T1w images are currently accepted for the segmentation workflow." - ) - if precomp_aseg and template_dir: print("Found precomputed aseg; skipping JointLabelFusion", file=sys.stderr) template_dir = None @@ -67,7 +62,8 @@ def init_anat_segmentations_wf( from nipype.interfaces.fsl.base import Info as FSLInfo wf.__desc__ += ( - f"the brain-extracted T1w using FSL FAST {FSLInfo.version() or '(version unknown)'}." + f"the brain-extracted {anat_modality} using FSL FAST " + f"{FSLInfo.version() or '(version unknown)'}." ) # Use FSL FAST for segmentations @@ -99,7 +95,7 @@ def init_anat_segmentations_wf( from nipype.interfaces.ants.base import Info as ANTsInfo wf.__desc__ += ( - "the brain-extracted T1w using ANTs JointFusion, distributed with ANTs " + f"the brain-extracted {anat_modality} using ANTs JointFusion, distributed with ANTs " f"{ANTsInfo.version() or '(version unknown)'}." ) tmpl_anats, tmpl_segs = _parse_segmentation_atlases(anat_modality, template_dir) From eee58b54871c75a7d9f77882757631631851b44b Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 2 Oct 2023 17:07:21 -0400 Subject: [PATCH 03/14] FEAT: Add separate workflow for single modality anatomical processing --- nibabies/workflows/anatomical/base.py | 534 ++++++++++++++++++++------ 1 file changed, 421 insertions(+), 113 deletions(-) diff --git a/nibabies/workflows/anatomical/base.py b/nibabies/workflows/anatomical/base.py index dc475b43..1a226e59 100644 --- a/nibabies/workflows/anatomical/base.py +++ b/nibabies/workflows/anatomical/base.py @@ -5,16 +5,63 @@ from pathlib import Path from nipype.interfaces import utility as niu +from nipype.interfaces.ants.base import Info as ANTsInfo from nipype.pipeline import engine as pe from niworkflows.engine.workflows import LiterateWorkflow from niworkflows.utils.spaces import Reference, SpatialReferences - -from ... import config +from smriprep.workflows.norm import init_anat_norm_wf + +from nibabies import config +from nibabies.utils.misc import fix_multi_source_name + +# Relative imports to avoid verbosity +from .brain_extraction import init_infant_brain_extraction_wf +from .outputs import ( + init_anat_derivatives_wf, + init_anat_reports_wf, + init_coreg_report_wf, +) +from .preproc import init_anat_preproc_wf +from .registration import init_coregister_derivatives_wf, init_coregistration_wf +from .segmentation import init_anat_segmentations_wf +from .surfaces import init_anat_ribbon_wf +from .template import init_anat_template_wf if ty.TYPE_CHECKING: from nibabies.utils.bids import Derivatives +ANAT_OUT_FIELDS = [ + "anat_preproc", + "anat_brain", + "anat_mask", + "anat_dseg", + "anat_tpms", + "anat_ref_xfms", + "std_preproc", + "std_brain", + "std_dseg", + "std_tpms", + "subjects_dir", + "subject_id", + "anat2std_xfm", + "std2anat_xfm", + "t1w2fsnative_xfm", + "fsnative2t1w_xfm", + "surfaces", + "morphometrics", + "anat_aseg", + "anat_mcrib", + "anat_aparc", + "anat_ribbon", + "template", + # registration sphere space is dependent on surface recon method + "sphere_reg", + "sphere_reg_fsLR", + "midthickness_fsLR", +] + + def init_infant_anat_wf( *, age_months: int, @@ -33,7 +80,7 @@ def init_infant_anat_wf( skull_strip_mode: str, skull_strip_template: Reference, sloppy: bool, - spaces: SpatialReferences | None, + spaces: SpatialReferences, cifti_output: ty.Literal['91k', '170k'] | None, name: str = "infant_anat_wf", ) -> LiterateWorkflow: @@ -86,118 +133,13 @@ def init_infant_anat_wf( surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) """ - from nipype.interfaces.ants.base import Info as ANTsInfo - from smriprep.workflows.norm import init_anat_norm_wf - - from ...utils.misc import fix_multi_source_name - from .brain_extraction import init_infant_brain_extraction_wf - from .outputs import ( - init_anat_derivatives_wf, - init_anat_reports_wf, - init_coreg_report_wf, - ) - from .preproc import init_anat_preproc_wf - from .registration import init_coregister_derivatives_wf, init_coregistration_wf - from .segmentation import init_anat_segmentations_wf - from .surfaces import init_anat_ribbon_wf - from .template import init_anat_template_wf - - # for now, T1w only - num_t1w = len(t1w) if t1w else 0 - num_t2w = len(t2w) if t2w else 0 - - # Expected derivatives: Prioritize T1w space if available, otherwise fall back to T2w - deriv_mask = derivatives.mask - deriv_aseg = derivatives.aseg + if not t1w or not t2w: + # Error type? + raise RuntimeError("Both T1w and T2w images are required to run this workflow.") + num_t1w = len(t1w) + num_t2w = len(t2w) wf = LiterateWorkflow(name=name) - desc = f"""\n -Anatomical data preprocessing - -: A total of {num_t1w} T1w and {num_t2w} T2w images were found within the input -BIDS dataset.""" - - inputnode = pe.Node( - niu.IdentityInterface(fields=["t1w", "t2w", "subject_id", "subjects_dir"]), # FLAIR / ROI? - name="inputnode", - ) - outputnode = pe.Node( - niu.IdentityInterface( - fields=[ - "anat_preproc", - "anat_brain", - "anat_mask", - "anat_dseg", - "anat_tpms", - "anat_ref_xfms", - "std_preproc", - "std_brain", - "std_dseg", - "std_tpms", - "subjects_dir", - "subject_id", - "anat2std_xfm", - "std2anat_xfm", - "t1w2fsnative_xfm", - "fsnative2t1w_xfm", - "surfaces", - "morphometrics", - "anat_aseg", - "anat_mcrib", - "anat_aparc", - "anat_ribbon", - "template", - # registration sphere space is dependent on surface recon method - "sphere_reg", - "sphere_reg_fsLR", - "midthickness_fsLR", - ] - ), - name="outputnode", - ) - - desc += ( - """\ -All of the T1-weighted images were denoised and corrected for intensity non-uniformity (INU)""" - if num_t1w > 1 - else """\ -The T1-weighted (T1w) image was denoised and corrected for intensity non-uniformity (INU)""" - ) - - desc += """\ -with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ -[@ants, RRID:SCR_004757]""" - desc += ".\n" if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" - - desc += ( - "A previously computed mask was used to skull-strip the anatomical image." - if deriv_mask - else """\ -The T1w-reference was then skull-stripped with a modified implementation of -the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} -as target template. -""" - ) - - wf.__desc__ = desc.format( - ants_ver=ANTsInfo.version() or "(version unknown)", - skullstrip_tpl=skull_strip_template.fullname, - ) - wf.__postdesc__ = "" - - # Define output workflows - anat_reports_wf = init_anat_reports_wf( - freesurfer=freesurfer, output_dir=output_dir, sloppy=sloppy - ) - - anat_derivatives_wf = init_anat_derivatives_wf( - bids_root=bids_root, - freesurfer=freesurfer, - num_t1w=num_t1w, - output_dir=output_dir, - spaces=spaces, - cifti_output=cifti_output, - ) # Derivatives used based on the following truth table: # |--------|--------|---------------------------------|------------------| @@ -234,6 +176,39 @@ def init_infant_anat_wf( t2w_aseg, ) + desc = _gen_anat_wf_desc( + t1w=t1w, + t2w=t2w, + mask=t1w_mask or t2w_mask, + aseg=t1w_aseg or t2w_aseg, + ) + + wf.__desc__ = desc.format( + ants_ver=ANTsInfo.version() or "(version unknown)", + skullstrip_tpl=skull_strip_template.fullname, + ) + wf.__postdesc__ = "" + + inputnode = pe.Node( + niu.IdentityInterface(fields=["t1w", "t2w", "subject_id", "subjects_dir"]), # FLAIR / ROI? + name="inputnode", + ) + outputnode = pe.Node(niu.IdentityInterface(fields=ANAT_OUT_FIELDS), name="outputnode") + + # Define output workflows + anat_reports_wf = init_anat_reports_wf( + freesurfer=freesurfer, output_dir=output_dir, sloppy=sloppy + ) + + anat_derivatives_wf = init_anat_derivatives_wf( + bids_root=bids_root, + freesurfer=freesurfer, + num_t1w=num_t1w, + output_dir=output_dir, + spaces=spaces, + cifti_output=cifti_output, + ) + t1w_template_wf = init_anat_template_wf( contrast="T1w", num_files=num_t1w, @@ -606,3 +581,336 @@ def init_infant_anat_wf( # fmt:on return wf + + +def init_infant_single_anat_wf( + *, + age_months: int, + ants_affine_init: bool, + t1w: list | None, + t2w: list | None, + contrast: ty.Literal['T1w', 'T2w'], + bids_root: str | Path, + derivatives: Derivatives, + freesurfer: bool, + hires: bool | None, + longitudinal: bool, + omp_nthreads: int, + output_dir: str | Path, + segmentation_atlases: str | Path | None, + skull_strip_mode: str, + skull_strip_template: Reference, + sloppy: bool, + spaces: SpatialReferences, + cifti_output: ty.Literal['91k', '170k'] | None, + name: str = "infant_single_anat_wf", +) -> LiterateWorkflow: + """""" + if t1w and t2w: + # Error type? + raise RuntimeError( + "This workflow uses only T1w or T2w inputs, but both contrasts are available." + ) + + anat_files = t1w or t2w + num_files = len(anat_files) + workflow = LiterateWorkflow(name=name) + + # Precomputed derivatives + if contrast == 'T1w': + mask = derivatives.t1w_mask + aseg = derivatives.t1w_aseg + elif contrast == 'T2w': + mask = derivatives.t2w_mask + aseg = derivatives.t2w_aseg + + config.loggers.workflow.info( + f"Derivatives used (%s):\n\t\n\t\n\t", contrast, bool(mask), bool(aseg) + ) + + inputnode = pe.Node( + niu.IdentityInterface(fields=["t1w", "t2w", "subject_id", "subjects_dir"]), # FLAIR / ROI? + name="inputnode", + ) + outputnode = pe.Node(niu.IdentityInterface(fields=ANAT_OUT_FIELDS), name="outputnode") + + desc = _gen_anat_wf_desc( + t1w=anat_files if contrast == 'T1w' else None, + t2w=anat_files if contrast == 'T2w' else None, + mask=bool(mask), + ) + workflow.__desc__ = desc.format( + ants_ver=ANTsInfo.version() or "(version unknown)", + skullstrip_tpl=skull_strip_template.fullname, + ) + workflow.__postdesc__ = "" + + # outputs + recon_method = config.workflow.surface_recon_method # TODO: Make workflow parameter + anat_reports_wf = init_anat_reports_wf( + surface_recon=recon_method, output_dir=output_dir, sloppy=sloppy + ) + + # TODO: Update transforms TO-FROM to reflect contrast + anat_derivatives_wf = init_anat_derivatives_wf( + bids_root=bids_root, + output_dir=output_dir, + surface_recon=recon_method, + num_t1w=num_files if contrast == 'T1w' else None, + num_t2w=num_files if contrast == 'T2w' else None, + spaces=spaces, + cifti_output=bool(cifti_output), + ) + + # template + anat_template_wf = init_anat_template_wf( + contrast=contrast, + num_files=num_files, + longitudinal=longitudinal, + omp_nthreads=omp_nthreads, + sloppy=sloppy, + has_mask=bool(mask), + has_aseg=bool(aseg), + name=f"{contrast.lower()}_template_wf", + ) + # preproc + anat_preproc_wf = init_anat_preproc_wf(name=f"{contrast.lower()}_preproc_wf") + # T2-only brain extraction + anat_seg_wf = init_anat_segmentations_wf( + anat_modality=contrast, + template_dir=segmentation_atlases, + sloppy=sloppy, + omp_nthreads=omp_nthreads, + precomp_aseg=bool(aseg), + ) + # T2-only segmentation + anat_norm_wf = init_anat_norm_wf( + sloppy=sloppy, + omp_ntheads=omp_nthreads, + templates=spaces.get_spaces(nonstandard=False, dim=(3,)), + ) + + if mask: + from niworkflows.interfaces.nibabel import ApplyMask + + mask_ref = derivatives.references[f'{contrast.lower()}_mask'] + anat_preproc_wf.inputnode.inputs.mask_reference = mask_ref + + apply_deriv_mask = pe.Node(ApplyMask(), name='apply_deriv_mask') + # fmt:off + workflow.connect([ + (anat_preproc_wf, anat_norm_wf, [ + ('outputnode.anat_mask', 'inputnode.moving_mask')]), + (anat_preproc_wf, apply_deriv_mask, [ + ('outputnode.anat_preproc', 'in_file')]), + (anat_template_wf, apply_deriv_mask, [ + ('outputnode.anat_mask', 'in_mask')]), + (apply_deriv_mask, anat_seg_wf, [ + ("out_mask", "inputnode.anat_brain")]), + (anat_template_wf, anat_derivatives_wf, [ + ('outputnode.anat_mask', 'inputnode.anat_mask')]), + (anat_template_wf, outputnode, [ + ('outputnode.anat_mask', 'anat_mask')]), + ]) + # fmt:on + + else: + brain_extraction_wf = init_infant_brain_extraction_wf( + age_months=age_months, + ants_affine_init=ants_affine_init, + skull_strip_template=skull_strip_template.space, + template_specs=skull_strip_template.spec, + omp_nthreads=omp_nthreads, + sloppy=sloppy, + debug="registration" in config.execution.debug, + ) + # fmt:off + workflow.connect([ + (anat_preproc_wf, brain_extraction_wf, [('outputnode.anat_preproc', 'inputnode.t2w_preproc')]), + (brain_extraction_wf, anat_seg_wf, [('outputnode.t2w_brain', 'inputnode.anat_brain')]), + (brain_extraction_wf, anat_norm_wf, [('outputnode.out_mask', 'inputnode.moving_mask')]), + (brain_extraction_wf, anat_derivatives_wf, [('outputnode.out_mask', 'inputnode.anat_mask')]), + ]) + # fmt:on + + # fmt:off + workflow.connect([ + (inputnode, anat_template_wf, [("anat_file", "inputnode.anat_files")]), + (inputnode, anat_reports_wf, [("anat_file", "inputnode.source_file")]), + (inputnode, anat_norm_wf, [(("anat_file", fix_multi_source_name), "inputnode.orig_t1w")]), + + (anat_template_wf, outputnode, [ + ("outputnode.anat_realign_xfm", "anat_ref_xfms")]), + (anat_template_wf, anat_preproc_wf, [ + ("outputnode.anat_ref", "inputnode.in_anat")]), + (anat_template_wf, anat_derivatives_wf, [ + ("outputnode.anat_valid_list", f"inputnode.{contrast.lower()}_source_files"), + ("outputnode.anat_realign_xfm", f"inputnode.{contrast.lower()}_ref_xfms")]), + (anat_template_wf, anat_reports_wf, [ + ("outputnode.out_report", "inputnode.anat_conform_report")]), + (anat_preproc_wf, anat_norm_wf, [ + ('outputnode.anat_preproc', 'inputnode.moving_image')]), + (anat_preproc_wf, anat_derivatives_wf, [ + ('outputnode.anat_preproc', f'inputnode.{contrast.lower()}_preproc')]), + (anat_seg_wf, outputnode, [ + ("outputnode.anat_dseg", "anat_dseg"), + ("outputnode.anat_tpms", "anat_tpms")]), + (anat_seg_wf, anat_derivatives_wf, [ + ("outputnode.anat_dseg", "inputnode.anat_dseg"), + ("outputnode.anat_tpms", "inputnode.anat_tpms"), + ]), + (anat_seg_wf, anat_norm_wf, [ + ("outputnode.anat_dseg", "inputnode.moving_segmentation"), + ("outputnode.anat_tpms", "inputnode.moving_tpms")]), + + (anat_norm_wf, anat_reports_wf, [("poutputnode.template", "inputnode.template")]), + (anat_norm_wf, outputnode, [ + ("poutputnode.standardized", "std_preproc"), + ("poutputnode.std_mask", "std_mask"), + ("poutputnode.std_dseg", "std_dseg"), + ("poutputnode.std_tpms", "std_tpms"), + ("outputnode.template", "template"), + ("outputnode.anat2std_xfm", "anat2std_xfm"), + ("outputnode.std2anat_xfm", "std2anat_xfm")]), + (anat_norm_wf, anat_derivatives_wf, [ + ("outputnode.template", "inputnode.template"), + ("outputnode.anat2std_xfm", "inputnode.anat2std_xfm"), + ("outputnode.std2anat_xfm", "inputnode.std2anat_xfm")]), + (outputnode, anat_reports_wf, [ + ("anat_preproc", "inputnode.t1w_preproc"), + ("anat_mask", "inputnode.anat_mask"), + ("anat_dseg", "inputnode.anat_dseg"), + ("std_preproc", "inputnode.std_t1w"), + ("std_mask", "inputnode.std_mask"), + ]), + ]) + # fmt:on + # TODO: Remove `freesurfer` option + if not recon_method: + return workflow + + elif recon_method == 'freesurfer': + from smriprep.workflows.surfaces import init_surface_recon_wf + + surface_recon_wf = init_surface_recon_wf(omp_nthreads=omp_nthreads, hires=hires) + elif recon_method == 'infantfs': + from .surfaces import init_infantfs_surface_recon_wf + + # if running with precomputed aseg, or JLF, pass the aseg along to FreeSurfer + use_aseg = bool(derivatives.aseg or segmentation_atlases) + surface_recon_wf = init_infantfs_surface_recon_wf( + age_months=age_months, + use_aseg=use_aseg, + ) + + elif recon_method == 'mcribs': + from .surfaces import init_mcribs_sphere_reg_wf, init_mcribs_surface_recon_wf + + # t2w mask, t2w aseg + surface_recon_wf = init_mcribs_surface_recon_wf( + omp_nthreads=omp_nthreads, + use_aseg=bool(aseg), # TODO: Incorporate mcribs segmentation + use_mask=bool(mask), # TODO: Pass in mask regardless of derivatives + mcribs_dir=str(config.execution.mcribs_dir), # Needed to preserve runs + ) + # M-CRIB-S to dHCP42week (32k) + sphere_reg_wf = init_mcribs_sphere_reg_wf() + + # fmt:off + workflow.connect([ + (anat_preproc_wf, surface_recon_wf, [('outputnode.anat_preproc', 'inputnode.t2w')]), + ]) + # fmt:on + if aseg: + workflow.connect( + anat_template_wf, 'outputnode.anat_aseg', surface_recon_wf, 'inputnode.ants_segs' + ) + else: + # TODO: Use MCRIBS segmentation + ... + if mask: + workflow.connect( + anat_template_wf, 'outputnode.anat_mask', surface_recon_wf, 'inputnode.anat_mask' + ) + else: + workflow.connect( + brain_extraction_wf, 'outputnode.out_mask', surface_recon_wf, 'inputnode.anat_mask' + ) + else: + raise NotImplementedError + + return workflow + + +def _gen_anat_wf_desc(t1w: list | None, t2w: list | None, mask: bool) -> str: + """Generate the anatomical workflow description.""" + if not t1w and not t2w: + return '' + + # If only a single anatomical modality is provided + modality = None + anat = None + if not t1w or not t2w: + anat = t1w or t2w + modality = 'T1w' if t1w else 'T2w' + + desc = """\n\nAnatomical data preprocessing\n:""" + + # Anatomicals found + if anat is not None: + desc += ( + f"A total of {len(anat)} {modality} images were found " + "within the input BIDS dataset.\n" + ) + else: + desc += ( + f"A total of {len(t1w)} T1w and {len(t2w)} T2w images " + "were found within the input BIDS dataset.\n" + ) + + # Template + Preproc workflows + if t1w: + if len(t1w) == 1: + desc += ( + f"The T1-weighted (T1w) image was denoised " + "and corrected for intensity non-uniformity (INU)" + ) + else: + desc += ( + "All of the T1-weighted images were corrected for intensity " + "non-uniformity (INU)" + ) + desc += ( + "with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} " + "[@ants, RRID:SCR_004757]" + ) + desc += ".\n" if len(t1w) > 1 else ", and used as T1w-reference throughout the workflow.\n" + if t2w: + if len(t2w) == 1: + desc += ( + "The T2-weighted (T2w) image was denoised and corrected for intensity " + "non-uniformity (INU)" + ) + else: + desc += ( + "All of the T2-weighted images were corrected for intensity " + "non-uniformity (INU)" + ) + desc += ( + "with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} " + "[@ants, RRID:SCR_004757]" + ) + desc += ".\n" if len(t2w) > 1 else ", and used as T2w-reference throughout the workflow.\n" + + # Precomputed derivatives + if mask: + desc += "A previously computed mask was used to skull-strip the anatomical image." + + else: + desc += ( + f"The {modality or 'T2w'}-reference was then skull-stripped with a modified " + "implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using " + "{skullstrip_tpl} as target template." + ) + + return desc From 95ab5eef694ce136cc87174b3c6508528bcde586 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 2 Oct 2023 22:25:09 -0400 Subject: [PATCH 04/14] RF: Call alternative workflow if missing anatomicals --- nibabies/workflows/base.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index eecfc5af..10b39499 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -206,7 +206,7 @@ def init_single_subject_wf( from ..utils.bids import Derivatives from ..utils.misc import fix_multi_source_name - from .anatomical import init_infant_anat_wf + from .anatomical import init_infant_anat_wf, init_infant_single_anat_wf name = ( f"single_subject_{subject_id}_{session_id}_wf" @@ -344,6 +344,33 @@ def init_single_subject_wf( run_without_submitting=True, ) + wf_args = dict( + ants_affine_init=True, + age_months=age, + anat_modality=anat_modality, + t1w=subject_data["t1w"], + t2w=subject_data["t2w"], + bids_root=config.execution.bids_dir, + derivatives=derivatives, + freesurfer=config.workflow.run_reconall, + hires=config.workflow.hires, + longitudinal=config.workflow.longitudinal, + omp_nthreads=config.nipype.omp_nthreads, + output_dir=nibabies_dir, + segmentation_atlases=config.execution.segmentation_atlases_dir, + skull_strip_mode=config.workflow.skull_strip_t1w, + skull_strip_template=Reference.from_string(config.workflow.skull_strip_template)[0], + sloppy=config.execution.sloppy, + spaces=spaces, + cifti_output=config.workflow.cifti_output, + ) + + if subject_data['t1w'] and subject_data['t2w']: + anat_preproc_wf = init_infant_anat_wf(**wf_args) + else: + anat_preproc_wf = init_infant_single_anat_wf( + contrast='T1w' if subject_data['t1w'] else 'T2w', **wf_args + ) # Preprocessing of anatomical (includes registration to UNCInfant) anat_preproc_wf = init_infant_anat_wf( ants_affine_init=True, From d0d938739a73c96502af4372b3c9caa16ed1c608 Mon Sep 17 00:00:00 2001 From: Mathias Goncalves Date: Tue, 3 Oct 2023 14:44:59 -0400 Subject: [PATCH 05/14] FIX: Missing connections, surfaces, syntax --- nibabies/workflows/anatomical/__init__.py | 2 +- nibabies/workflows/anatomical/base.py | 175 +++++++++++++++++----- nibabies/workflows/base.py | 42 ++---- 3 files changed, 150 insertions(+), 69 deletions(-) diff --git a/nibabies/workflows/anatomical/__init__.py b/nibabies/workflows/anatomical/__init__.py index 33d993b2..021d133d 100644 --- a/nibabies/workflows/anatomical/__init__.py +++ b/nibabies/workflows/anatomical/__init__.py @@ -1 +1 @@ -from .base import init_infant_anat_wf +from .base import init_infant_anat_wf, init_infant_single_anat_wf diff --git a/nibabies/workflows/anatomical/base.py b/nibabies/workflows/anatomical/base.py index 1a226e59..ca3d71df 100644 --- a/nibabies/workflows/anatomical/base.py +++ b/nibabies/workflows/anatomical/base.py @@ -46,8 +46,8 @@ "subject_id", "anat2std_xfm", "std2anat_xfm", - "t1w2fsnative_xfm", - "fsnative2t1w_xfm", + "anat2fsnative_xfm", + "fsnative2anat_xfm", "surfaces", "morphometrics", "anat_aseg", @@ -68,7 +68,7 @@ def init_infant_anat_wf( ants_affine_init: bool, t1w: list, t2w: list, - anat_modality: str, + contrast: ty.Literal['T1w', 'T2w'], bids_root: str | Path, derivatives: Derivatives, freesurfer: bool, @@ -252,7 +252,7 @@ def init_infant_anat_wf( # Segmentation - initial implementation should be simple: JLF anat_seg_wf = init_anat_segmentations_wf( - anat_modality=anat_modality.capitalize(), # TODO: Revisit this option + anat_modality=contrast.capitalize(), # TODO: Revisit this option template_dir=segmentation_atlases, sloppy=sloppy, omp_nthreads=omp_nthreads, @@ -625,7 +625,10 @@ def init_infant_single_anat_wf( aseg = derivatives.t2w_aseg config.loggers.workflow.info( - f"Derivatives used (%s):\n\t\n\t\n\t", contrast, bool(mask), bool(aseg) + f"Derivatives used (%s):\n\t\t\n\t\t\n", + contrast, + bool(mask), + bool(aseg), ) inputnode = pe.Node( @@ -635,8 +638,8 @@ def init_infant_single_anat_wf( outputnode = pe.Node(niu.IdentityInterface(fields=ANAT_OUT_FIELDS), name="outputnode") desc = _gen_anat_wf_desc( - t1w=anat_files if contrast == 'T1w' else None, - t2w=anat_files if contrast == 'T2w' else None, + t1w=t1w or None, + t2w=t2w or None, mask=bool(mask), ) workflow.__desc__ = desc.format( @@ -686,31 +689,33 @@ def init_infant_single_anat_wf( # T2-only segmentation anat_norm_wf = init_anat_norm_wf( sloppy=sloppy, - omp_ntheads=omp_nthreads, + omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3,)), ) + # Aggregate mask, applied mask + mask_buffer = pe.Node( + niu.IdentityInterface(fields=['anat_mask', 'anat_brain']), + name='mask_buffer', + ) if mask: from niworkflows.interfaces.nibabel import ApplyMask + anat_template_wf.inputs.inputnode.anat_mask = mask mask_ref = derivatives.references[f'{contrast.lower()}_mask'] - anat_preproc_wf.inputnode.inputs.mask_reference = mask_ref + anat_template_wf.inputs.inputnode.mask_reference = mask_ref apply_deriv_mask = pe.Node(ApplyMask(), name='apply_deriv_mask') # fmt:off workflow.connect([ - (anat_preproc_wf, anat_norm_wf, [ - ('outputnode.anat_mask', 'inputnode.moving_mask')]), + (anat_template_wf, mask_buffer, [ + ('outputnode.anat_mask', 'anat_mask')]), (anat_preproc_wf, apply_deriv_mask, [ ('outputnode.anat_preproc', 'in_file')]), (anat_template_wf, apply_deriv_mask, [ ('outputnode.anat_mask', 'in_mask')]), - (apply_deriv_mask, anat_seg_wf, [ - ("out_mask", "inputnode.anat_brain")]), - (anat_template_wf, anat_derivatives_wf, [ - ('outputnode.anat_mask', 'inputnode.anat_mask')]), - (anat_template_wf, outputnode, [ - ('outputnode.anat_mask', 'anat_mask')]), + (apply_deriv_mask, mask_buffer, [ + ('out_file', 'anat_brain')]), ]) # fmt:on @@ -726,18 +731,28 @@ def init_infant_single_anat_wf( ) # fmt:off workflow.connect([ - (anat_preproc_wf, brain_extraction_wf, [('outputnode.anat_preproc', 'inputnode.t2w_preproc')]), - (brain_extraction_wf, anat_seg_wf, [('outputnode.t2w_brain', 'inputnode.anat_brain')]), - (brain_extraction_wf, anat_norm_wf, [('outputnode.out_mask', 'inputnode.moving_mask')]), - (brain_extraction_wf, anat_derivatives_wf, [('outputnode.out_mask', 'inputnode.anat_mask')]), + (anat_preproc_wf, brain_extraction_wf, [ + ('outputnode.anat_preproc', 'inputnode.t2w_preproc')]), + (brain_extraction_wf, mask_buffer, [ + ('outputnode.t2w_brain', 'anat_brain'), + ('outputnode.out_mask', 'anat_mask')]), ]) # fmt:on + if aseg: + anat_template_wf.inputs.inputnode.anat_aseg = aseg + aseg_ref = derivatives.references[f'{contrast.lower()}_aseg'] + anat_template_wf.inputs.inputnode.aseg_reference = aseg_ref + + workflow.connect( + anat_template_wf, 'outputnode.anat_aseg', anat_seg_wf, 'inputnode.anat_aseg' + ) + # fmt:off workflow.connect([ - (inputnode, anat_template_wf, [("anat_file", "inputnode.anat_files")]), - (inputnode, anat_reports_wf, [("anat_file", "inputnode.source_file")]), - (inputnode, anat_norm_wf, [(("anat_file", fix_multi_source_name), "inputnode.orig_t1w")]), + (inputnode, anat_template_wf, [(contrast.lower(), "inputnode.anat_files")]), + (inputnode, anat_reports_wf, [(contrast.lower(), "inputnode.source_file")]), + (inputnode, anat_norm_wf, [((contrast.lower(), fix_multi_source_name), "inputnode.orig_t1w")]), (anat_template_wf, outputnode, [ ("outputnode.anat_realign_xfm", "anat_ref_xfms")]), @@ -750,19 +765,26 @@ def init_infant_single_anat_wf( ("outputnode.out_report", "inputnode.anat_conform_report")]), (anat_preproc_wf, anat_norm_wf, [ ('outputnode.anat_preproc', 'inputnode.moving_image')]), + (anat_preproc_wf, outputnode, [ + ('outputnode.anat_preproc', 'anat_preproc')]), (anat_preproc_wf, anat_derivatives_wf, [ ('outputnode.anat_preproc', f'inputnode.{contrast.lower()}_preproc')]), + (mask_buffer, anat_derivatives_wf, [ + ('anat_mask', 'inputnode.anat_mask')]), + (mask_buffer, outputnode, [ + ('anat_mask', 'anat_mask')]), + (mask_buffer, anat_seg_wf, [('anat_brain', 'inputnode.anat_brain')]), (anat_seg_wf, outputnode, [ ("outputnode.anat_dseg", "anat_dseg"), ("outputnode.anat_tpms", "anat_tpms")]), (anat_seg_wf, anat_derivatives_wf, [ ("outputnode.anat_dseg", "inputnode.anat_dseg"), - ("outputnode.anat_tpms", "inputnode.anat_tpms"), - ]), + ("outputnode.anat_tpms", "inputnode.anat_tpms")]), + (mask_buffer, anat_norm_wf, [ + ('anat_mask', 'inputnode.moving_mask')]), (anat_seg_wf, anat_norm_wf, [ ("outputnode.anat_dseg", "inputnode.moving_segmentation"), ("outputnode.anat_tpms", "inputnode.moving_tpms")]), - (anat_norm_wf, anat_reports_wf, [("poutputnode.template", "inputnode.template")]), (anat_norm_wf, outputnode, [ ("poutputnode.standardized", "std_preproc"), @@ -777,7 +799,7 @@ def init_infant_single_anat_wf( ("outputnode.anat2std_xfm", "inputnode.anat2std_xfm"), ("outputnode.std2anat_xfm", "inputnode.std2anat_xfm")]), (outputnode, anat_reports_wf, [ - ("anat_preproc", "inputnode.t1w_preproc"), + ("anat_preproc", "inputnode.anat_preproc"), ("anat_mask", "inputnode.anat_mask"), ("anat_dseg", "inputnode.anat_dseg"), ("std_preproc", "inputnode.std_t1w"), @@ -828,17 +850,98 @@ def init_infant_single_anat_wf( else: # TODO: Use MCRIBS segmentation ... - if mask: - workflow.connect( - anat_template_wf, 'outputnode.anat_mask', surface_recon_wf, 'inputnode.anat_mask' - ) - else: - workflow.connect( - brain_extraction_wf, 'outputnode.out_mask', surface_recon_wf, 'inputnode.anat_mask' - ) else: raise NotImplementedError + # Anatomical ribbon file using HCP signed-distance volume method + anat_ribbon_wf = init_anat_ribbon_wf() + + # fmt:off + workflow.connect([ + (inputnode, surface_recon_wf, [ + ("subject_id", "inputnode.subject_id"), + ("subjects_dir", "inputnode.subjects_dir")]), + (anat_template_wf, surface_recon_wf, [ + ("outputnode.anat_ref", "inputnode.t1w"), + ]), + (mask_buffer, surface_recon_wf, [ + ("anat_brain", "inputnode.skullstripped_t1"), + ("anat_mask", "inputnode.anat_mask")]), + (anat_preproc_wf, surface_recon_wf, [ + ("outputnode.anat_preproc", "inputnode.corrected_t1")]), + (surface_recon_wf, outputnode, [ + ("outputnode.subjects_dir", "subjects_dir"), + ("outputnode.subject_id", "subject_id"), + ("outputnode.t1w2fsnative_xfm", "anat2fsnative_xfm"), + ("outputnode.fsnative2t1w_xfm", "fsnative2anat_xfm"), + ("outputnode.surfaces", "surfaces"), + ("outputnode.morphometrics", "morphometrics"), + ("outputnode.out_aparc", "anat_aparc"), + ("outputnode.out_aseg", "anat_aseg"), + ]), + (mask_buffer, anat_ribbon_wf, [ + ("anat_mask", "inputnode.t1w_mask"), + ]), + (surface_recon_wf, anat_ribbon_wf, [ + ("outputnode.surfaces", "inputnode.surfaces"), + ]), + (anat_ribbon_wf, outputnode, [ + ("outputnode.anat_ribbon", "anat_ribbon") + ]), + (anat_ribbon_wf, anat_derivatives_wf, [ + ("outputnode.anat_ribbon", "inputnode.anat_ribbon"), + ]), + (surface_recon_wf, sphere_reg_wf, [ + ('outputnode.subject_id', 'inputnode.subject_id'), + ('outputnode.subjects_dir', 'inputnode.subjects_dir'), + ]), + (surface_recon_wf, anat_reports_wf, [ + ("outputnode.subject_id", "inputnode.subject_id"), + ("outputnode.subjects_dir", "inputnode.subjects_dir"), + ]), + (surface_recon_wf, anat_derivatives_wf, [ + ("outputnode.out_aseg", "inputnode.anat_fs_aseg"), + ("outputnode.out_aparc", "inputnode.anat_fs_aparc"), + ("outputnode.t1w2fsnative_xfm", "inputnode.anat2fsnative_xfm"), + ("outputnode.fsnative2t1w_xfm", "inputnode.fsnative2anat_xfm"), + ("outputnode.surfaces", "inputnode.surfaces"), + ("outputnode.morphometrics", "inputnode.morphometrics"), + ]), + (sphere_reg_wf, outputnode, [ + ('outputnode.sphere_reg', 'sphere_reg'), + ('outputnode.sphere_reg_fsLR', 'sphere_reg_fsLR')]), + (sphere_reg_wf, anat_derivatives_wf, [ + ('outputnode.sphere_reg', 'inputnode.sphere_reg'), + ('outputnode.sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR')]), + ]) + # fmt: on + + if cifti_output: + from nibabies.workflows.anatomical.resampling import ( + init_anat_fsLR_resampling_wf, + ) + + is_mcribs = recon_method == "mcribs" + # handles morph_grayords_wf + anat_fsLR_resampling_wf = init_anat_fsLR_resampling_wf(cifti_output, mcribs=is_mcribs) + anat_derivatives_wf.get_node('inputnode').inputs.cifti_density = cifti_output + # fmt:off + workflow.connect([ + (sphere_reg_wf, anat_fsLR_resampling_wf, [ + ('outputnode.sphere_reg', 'inputnode.sphere_reg'), + ('outputnode.sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR')]), + (surface_recon_wf, anat_fsLR_resampling_wf, [ + ('outputnode.subject_id', 'inputnode.subject_id'), + ('outputnode.subjects_dir', 'inputnode.subjects_dir'), + ('outputnode.surfaces', 'inputnode.surfaces'), + ('outputnode.morphometrics', 'inputnode.morphometrics')]), + (anat_fsLR_resampling_wf, anat_derivatives_wf, [ + ("outputnode.cifti_morph", "inputnode.cifti_morph"), + ("outputnode.cifti_metadata", "inputnode.cifti_metadata")]), + (anat_fsLR_resampling_wf, outputnode, [ + ("outputnode.midthickness_fsLR", "midthickness_fsLR")]) + ]) + # fmt:on return workflow diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index 10b39499..9f5758ab 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -229,7 +229,8 @@ def init_single_subject_wf( anat_only = config.workflow.anat_only derivatives = Derivatives(bids_root=config.execution.layout.root) - anat_modality = "t1w" if subject_data["t1w"] else "t2w" + contrast = "T1w" if subject_data["t1w"] else "T2w" + single_modality = not (subject_data['t1w'] and subject_data['t2w']) # Make sure we always go through these two checks if not anat_only and not subject_data["bold"]: task_id = config.execution.task_id @@ -347,7 +348,7 @@ def init_single_subject_wf( wf_args = dict( ants_affine_init=True, age_months=age, - anat_modality=anat_modality, + contrast=contrast, t1w=subject_data["t1w"], t2w=subject_data["t2w"], bids_root=config.execution.bids_dir, @@ -364,33 +365,10 @@ def init_single_subject_wf( spaces=spaces, cifti_output=config.workflow.cifti_output, ) - - if subject_data['t1w'] and subject_data['t2w']: - anat_preproc_wf = init_infant_anat_wf(**wf_args) - else: - anat_preproc_wf = init_infant_single_anat_wf( - contrast='T1w' if subject_data['t1w'] else 'T2w', **wf_args - ) - # Preprocessing of anatomical (includes registration to UNCInfant) - anat_preproc_wf = init_infant_anat_wf( - ants_affine_init=True, - age_months=age, - anat_modality=anat_modality, - t1w=subject_data["t1w"], - t2w=subject_data["t2w"], - bids_root=config.execution.bids_dir, - derivatives=derivatives, - freesurfer=config.workflow.run_reconall, - hires=config.workflow.hires, - longitudinal=config.workflow.longitudinal, - omp_nthreads=config.nipype.omp_nthreads, - output_dir=nibabies_dir, - segmentation_atlases=config.execution.segmentation_atlases_dir, - skull_strip_mode=config.workflow.skull_strip_t1w, - skull_strip_template=Reference.from_string(config.workflow.skull_strip_template)[0], - sloppy=config.execution.sloppy, - spaces=spaces, - cifti_output=config.workflow.cifti_output, + anat_preproc_wf = ( + init_infant_anat_wf(**wf_args) + if not single_modality + else init_infant_single_anat_wf(**wf_args) ) # fmt: off @@ -424,17 +402,17 @@ def init_single_subject_wf( workflow.connect([ (bidssrc, bids_info, [ - (('t1w', fix_multi_source_name), 'in_file'), + ((contrast.lower(), fix_multi_source_name), 'in_file'), ]), (bidssrc, summary, [ ('t1w', 't1w'), ('t2w', 't2w'), ]), (bidssrc, ds_report_summary, [ - (('t1w', fix_multi_source_name), 'source_file'), + ((contrast.lower(), fix_multi_source_name), 'source_file'), ]), (bidssrc, ds_report_about, [ - (('t1w', fix_multi_source_name), 'source_file'), + ((contrast.lower(), fix_multi_source_name), 'source_file'), ]), ]) # fmt: on From 7edad3184f9808ca4415479d90e2619b00272310 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Tue, 3 Oct 2023 21:07:27 -0400 Subject: [PATCH 06/14] RF: Reflect new changes to dual modality workflow --- nibabies/workflows/anatomical/base.py | 38 +++++++++++++-------------- nibabies/workflows/base.py | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/nibabies/workflows/anatomical/base.py b/nibabies/workflows/anatomical/base.py index ca3d71df..262c37df 100644 --- a/nibabies/workflows/anatomical/base.py +++ b/nibabies/workflows/anatomical/base.py @@ -124,10 +124,10 @@ def init_infant_anat_wf( Inverse transform of the above. subject_id FreeSurfer subject ID - t1w2fsnative_xfm + anat2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space - fsnative2t1w_xfm + fsnative2anat_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces @@ -180,7 +180,6 @@ def init_infant_anat_wf( t1w=t1w, t2w=t2w, mask=t1w_mask or t2w_mask, - aseg=t1w_aseg or t2w_aseg, ) wf.__desc__ = desc.format( @@ -197,13 +196,14 @@ def init_infant_anat_wf( # Define output workflows anat_reports_wf = init_anat_reports_wf( - freesurfer=freesurfer, output_dir=output_dir, sloppy=sloppy + surface_recon=freesurfer, output_dir=output_dir, sloppy=sloppy ) anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, - freesurfer=freesurfer, + surface_recon=freesurfer, num_t1w=num_t1w, + num_t2w=num_t2w, output_dir=output_dir, spaces=spaces, cifti_output=cifti_output, @@ -278,10 +278,10 @@ def init_infant_anat_wf( ("outputnode.anat_realign_xfm", "anat_ref_xfms")]), (t1w_template_wf, t1w_preproc_wf, [("outputnode.anat_ref", "inputnode.in_anat")]), (t1w_template_wf, anat_derivatives_wf, [ - ("outputnode.anat_valid_list", "inputnode.source_files"), + ("outputnode.anat_valid_list", "inputnode.t1w_source_files"), ("outputnode.anat_realign_xfm", "inputnode.t1w_ref_xfms")]), (t1w_template_wf, anat_reports_wf, [ - ("outputnode.out_report", "inputnode.t1w_conform_report")]), + ("outputnode.out_report", "inputnode.anat_conform_report")]), (t2w_template_wf, t2w_preproc_wf, [("outputnode.anat_ref", "inputnode.in_anat")]), (t2w_template_wf, anat_derivatives_wf, [ @@ -298,7 +298,7 @@ def init_infant_anat_wf( ("outputnode.t1w_mask", "inputnode.moving_mask")]), (coregistration_wf, anat_seg_wf, [("outputnode.t1w_brain", "inputnode.anat_brain")]), (coregistration_wf, anat_derivatives_wf, [ - ("outputnode.t1w_mask", "inputnode.t1w_mask"), + ("outputnode.t1w_mask", "inputnode.anat_mask"), ("outputnode.t1w_preproc", "inputnode.t1w_preproc"), ("outputnode.t2w_preproc", "inputnode.t2w_preproc"), ]), @@ -312,8 +312,8 @@ def init_infant_anat_wf( ("outputnode.anat_dseg", "anat_dseg"), ("outputnode.anat_tpms", "anat_tpms")]), (anat_seg_wf, anat_derivatives_wf, [ - ("outputnode.anat_dseg", "inputnode.t1w_dseg"), - ("outputnode.anat_tpms", "inputnode.t1w_tpms"), + ("outputnode.anat_dseg", "inputnode.anat_dseg"), + ("outputnode.anat_tpms", "inputnode.anat_tpms"), ]), (anat_seg_wf, anat_norm_wf, [ ("outputnode.anat_dseg", "inputnode.moving_segmentation"), @@ -334,9 +334,9 @@ def init_infant_anat_wf( ("outputnode.std2anat_xfm", "inputnode.std2anat_xfm")]), (outputnode, anat_reports_wf, [ - ("anat_preproc", "inputnode.t1w_preproc"), - ("anat_mask", "inputnode.t1w_mask"), - ("anat_dseg", "inputnode.t1w_dseg"), + ("anat_preproc", "inputnode.anat_preproc"), + ("anat_mask", "inputnode.anat_mask"), + ("anat_dseg", "inputnode.anat_dseg"), ("std_preproc", "inputnode.std_t1w"), ("std_mask", "inputnode.std_mask"), ]), @@ -509,8 +509,8 @@ def init_infant_anat_wf( (surface_recon_wf, outputnode, [ ("outputnode.subjects_dir", "subjects_dir"), ("outputnode.subject_id", "subject_id"), - ("outputnode.t1w2fsnative_xfm", "t1w2fsnative_xfm"), - ("outputnode.fsnative2t1w_xfm", "fsnative2t1w_xfm"), + ("outputnode.t1w2fsnative_xfm", "anat2fsnative_xfm"), + ("outputnode.fsnative2t1w_xfm", "fsnative2anat_xfm"), ("outputnode.surfaces", "surfaces"), ("outputnode.morphometrics", "morphometrics"), ("outputnode.out_aparc", "anat_aparc"), @@ -537,10 +537,10 @@ def init_infant_anat_wf( ("outputnode.subjects_dir", "inputnode.subjects_dir"), ]), (surface_recon_wf, anat_derivatives_wf, [ - ("outputnode.out_aseg", "inputnode.t1w_fs_aseg"), - ("outputnode.out_aparc", "inputnode.t1w_fs_aparc"), - ("outputnode.t1w2fsnative_xfm", "inputnode.t1w2fsnative_xfm"), - ("outputnode.fsnative2t1w_xfm", "inputnode.fsnative2t1w_xfm"), + ("outputnode.out_aseg", "inputnode.anat_fs_aseg"), + ("outputnode.out_aparc", "inputnode.anat_fs_aparc"), + ("outputnode.t1w2fsnative_xfm", "inputnode.anat2fsnative_xfm"), + ("outputnode.fsnative2t1w_xfm", "inputnode.fsnative2anat_xfm"), ("outputnode.surfaces", "inputnode.surfaces"), ("outputnode.morphometrics", "inputnode.morphometrics"), ]), diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index 9f5758ab..b47bac04 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -477,8 +477,8 @@ def init_single_subject_wf( # Undefined if --fs-no-reconall, but this is safe ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id'), - ('outputnode.t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), - ('outputnode.fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), + ('outputnode.anat2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), + ('outputnode.fsnative2anat_xfm', 'inputnode.fsnative2t1w_xfm'), ('outputnode.surfaces', 'inputnode.surfaces'), ('outputnode.morphometrics', 'inputnode.morphometrics'), ('outputnode.anat_ribbon', 'inputnode.anat_ribbon'), From 4b715c96f64ce9e1f8af994f0a9c344fed28af69 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 4 Oct 2023 11:49:15 -0400 Subject: [PATCH 07/14] STY: Fix indentations, add pep8 config --- .pep8speaks.yml | 6 ++++++ nibabies/workflows/anatomical/outputs.py | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .pep8speaks.yml diff --git a/.pep8speaks.yml b/.pep8speaks.yml new file mode 100644 index 00000000..eecf4012 --- /dev/null +++ b/.pep8speaks.yml @@ -0,0 +1,6 @@ +scanner: + diff_only: True + linter: pycodestyle + +pycodestyle: + max-line-length: 100 diff --git a/nibabies/workflows/anatomical/outputs.py b/nibabies/workflows/anatomical/outputs.py index e7ceb6a6..bf62340a 100644 --- a/nibabies/workflows/anatomical/outputs.py +++ b/nibabies/workflows/anatomical/outputs.py @@ -247,7 +247,7 @@ def init_anat_reports_wf( # fmt: off workflow.connect([ (inputnode, recon_report, [('subjects_dir', 'subjects_dir'), - ('subject_id', 'subject_id')]), + ('subject_id', 'subject_id')]), (recon_report, ds_recon_report, [('out_report', 'in_file')]), (inputnode, ds_recon_report, [('source_file', 'source_file')]) ]) @@ -654,7 +654,7 @@ def init_anat_derivatives_wf( # fmt: off workflow.connect([ (inputnode, mask_anat, [(preproc_file, 'in_file'), - ('anat_mask', 'in_mask')]), + ('anat_mask', 'in_mask')]), (mask_anat, anat2std_t1w, [('out_file', 'input_image')]), (inputnode, anat2std_mask, [('anat_mask', 'input_image')]), (inputnode, anat2std_dseg, [('anat_dseg', 'input_image')]), @@ -832,9 +832,9 @@ def init_anat_derivatives_wf( (name_morphs, ds_morphs, [('hemi', 'hemi'), ('suffix', 'suffix')]), (inputnode, ds_anat_fsaseg, [('anat_fs_aseg', 'in_file'), - (source_files, 'source_file')]), + (source_files, 'source_file')]), (inputnode, ds_anat_fsparc, [('anat_fs_aparc', 'in_file'), - (source_files, 'source_file')]), + (source_files, 'source_file')]), ]) # fmt: on if cifti_output: From 6c38c073db5ef7dbec3238339715da880cc777aa Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 8 Nov 2023 17:02:12 -0500 Subject: [PATCH 08/14] FIX: Connections for T1-only --- nibabies/workflows/anatomical/base.py | 15 +++++++++------ nibabies/workflows/anatomical/outputs.py | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/nibabies/workflows/anatomical/base.py b/nibabies/workflows/anatomical/base.py index 262c37df..d004080c 100644 --- a/nibabies/workflows/anatomical/base.py +++ b/nibabies/workflows/anatomical/base.py @@ -463,12 +463,10 @@ def init_infant_anat_wf( # M-CRIB-S to dHCP42week (32k) sphere_reg_wf = init_mcribs_sphere_reg_wf() - # fmt:off wf.connect([ (t2w_template_wf, denoise_t2w, [('outputnode.anat_ref', 'input_image')]), (denoise_t2w, surface_recon_wf, [('output_image', 'inputnode.t2w')]), - ]) - # fmt:on + ]) # fmt:skip if derivatives.aseg: wf.connect(deriv_buffer, 't2w_aseg', surface_recon_wf, 'inputnode.ants_segs') if derivatives.mask: @@ -811,7 +809,7 @@ def init_infant_single_anat_wf( if not recon_method: return workflow - elif recon_method == 'freesurfer': + if recon_method == 'freesurfer': from smriprep.workflows.surfaces import init_surface_recon_wf surface_recon_wf = init_surface_recon_wf(omp_nthreads=omp_nthreads, hires=hires) @@ -853,6 +851,12 @@ def init_infant_single_anat_wf( else: raise NotImplementedError + if recon_method in ('freesurfer', 'infantfs'): + from smriprep.workflows.surfaces import init_sphere_reg_wf + + # fsaverage to fsLR + sphere_reg_wf = init_sphere_reg_wf() + # Anatomical ribbon file using HCP signed-distance volume method anat_ribbon_wf = init_anat_ribbon_wf() @@ -865,8 +869,7 @@ def init_infant_single_anat_wf( ("outputnode.anat_ref", "inputnode.t1w"), ]), (mask_buffer, surface_recon_wf, [ - ("anat_brain", "inputnode.skullstripped_t1"), - ("anat_mask", "inputnode.anat_mask")]), + ("anat_brain", "inputnode.skullstripped_t1")]), (anat_preproc_wf, surface_recon_wf, [ ("outputnode.anat_preproc", "inputnode.corrected_t1")]), (surface_recon_wf, outputnode, [ diff --git a/nibabies/workflows/anatomical/outputs.py b/nibabies/workflows/anatomical/outputs.py index bf62340a..3c511f93 100644 --- a/nibabies/workflows/anatomical/outputs.py +++ b/nibabies/workflows/anatomical/outputs.py @@ -424,7 +424,6 @@ def init_anat_derivatives_wf( # fmt:on if num_t2w: - if not num_t1w: raw_sources = pe.Node(niu.Function(function=_bids_relative), name="t2w_raw_sources") raw_sources.inputs.bids_root = bids_root From 853d7b2119417e91ee49c894928feef3b582a378 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 9 Nov 2023 12:02:44 -0500 Subject: [PATCH 09/14] CI: Test single anatomical workflow --- .circleci/bcp_anat_t2only_outputs.txt | 55 +++++++++++++++++++++++++++ .circleci/config.yml | 35 +++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .circleci/bcp_anat_t2only_outputs.txt diff --git a/.circleci/bcp_anat_t2only_outputs.txt b/.circleci/bcp_anat_t2only_outputs.txt new file mode 100644 index 00000000..fa30fa14 --- /dev/null +++ b/.circleci/bcp_anat_t2only_outputs.txt @@ -0,0 +1,55 @@ +.bidsignore +dataset_description.json +desc-aparcaseg_dseg.tsv +desc-aseg_dseg.tsv +logs +logs/CITATION.bib +logs/CITATION.html +logs/CITATION.md +logs/CITATION.tex +sub-01 +sub-01/ses-1mo +sub-01/ses-1mo/anat +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-aparcaseg_dseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-aseg_dseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-brain_mask.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T2w.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T2w.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-ribbon_mask.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_dseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-MNIInfant+1_to-T2w_mode-image_xfm.h5 +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T2w_to-MNIInfant+1_mode-image_xfm.h5 +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T2w_to-fsnative_mode-image_xfm.txt +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-fsnative_to-T2w_mode-image_xfm.txt +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_curv.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-reg_sphere.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_inflated.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_midthickness.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_pial.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_desc-reg_sphere.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sulc.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_thickness.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_white.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_curv.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-reg_sphere.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_inflated.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_midthickness.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_pial.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_desc-reg_sphere.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sulc.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_thickness.shape.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_white.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_label-CSF_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_label-GM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_label-WM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-brain_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-brain_mask.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T1w.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T1w.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_dseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-CSF_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-GM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-WM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T2w.nii.gz +sub-01_ses-1mo.html diff --git a/.circleci/config.yml b/.circleci/config.yml index 9755d783..079b9760 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -397,6 +397,41 @@ jobs: pip install nibabel numpy nitransforms python /tmp/src/nibabies/scripts/check_outputs.py /tmp/${DATASET}/derivatives/nibabies exit $? + - run: + name: Create copy with only T2w data + command: | + mkdir -p /tmp/data/${DATASET}-t2only + cp /tmp/data/${DATASET}/dataset_description.json /tmp/data/${DATASET}-t2only + cp -r /tmp/data/${DATASET}/sub-01 /tmp/data/${DATASET}-t2only/sub-01 + rm -f /tmp/data/${DATASET}-t2only/sub-01/ses-1mo/anat/*_T1w.* + - run: + name: Run nibabies single anatomical workflow + no_output_timeout: 1h + command: | + mkdir -p /tmp/data/${DATASET}-t2only /tmp/${DATASET}/derivatives/nibabies-t2only + nibabies-wrapper docker /tmp/data/${DATASET}-t2only /tmp/${DATASET}/derivatives/nibabies-t2only participant \ + -i nipreps/nibabies:dev \ + -e NIBABIES_DEV 1 --user $(id -u):$(id -g) \ + --network none --notrack \ + --config $PWD/nipype.cfg -w /tmp/${DATASET}/work \ + --fs-subjects-dir /tmp/data/${DATASET}/derivatives/infant-freesurfer \ + --skull-strip-template UNCInfant:cohort-1 \ + --output-spaces MNIInfant:cohort-1 func \ + --sloppy --write-graph --mem-mb 14000 \ + --nthreads 4 -vv --age-months 2 --sloppy \ + --derivatives /tmp/data/${DATASET}/derivatives/precomputed \ + --output-layout bids --anat-only + - run: + name: Checking outputs of T2-only nibabies anat + command: | + mkdir -p /tmp/${DATASET}/test + CHECK_OUTPUTS_FILE="${DATASET}_anat_t2only_outputs.txt" + cd /tmp/${DATASET}/derivatives/nibabies-t2only && tree -I 'figures|log' -lifa --noreport | sed s+^\./++ | sed '1d' | sort > /tmp/${DATASET}/test/outputs.out + cat /tmp/${DATASET}/test/outputs.out + sort -o /tmp/${DATASET}/test/expected.out /tmp/src/nibabies/.circleci/${CHECK_OUTPUTS_FILE} + diff /tmp/${DATASET}/test/expected.out /tmp/${DATASET}/test/outputs.out + rm -rf /tmp/${DATASET}/test + exit $? - store_artifacts: path: /tmp/bcp/derivatives From 3455784eafe33a3306ea359c24044f74a4ff8288 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 9 Nov 2023 12:42:25 -0500 Subject: [PATCH 10/14] FIX: Do not raise validation error if no T1 --- nibabies/utils/bids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabies/utils/bids.py b/nibabies/utils/bids.py index 765ca47e..bfd63e76 100644 --- a/nibabies/utils/bids.py +++ b/nibabies/utils/bids.py @@ -281,7 +281,7 @@ def validate_input_dir(exec_env, bids_dir, participant_label): "MISSING_TSV_COLUMN_EEG_ELECTRODES", "MISSING_SESSION", ], - "error": ["NO_T1W"], + "error": [], "ignoredFiles": ["/dataset_description.json", "/participants.tsv"], } # Limit validation only to data from requested participants From 1533815c1feb2bdedbc4d5ac236d4e7ef4fb3962 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 9 Nov 2023 13:23:42 -0500 Subject: [PATCH 11/14] CI: Use separate workdir, show tree --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 079b9760..b9260f23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -404,16 +404,17 @@ jobs: cp /tmp/data/${DATASET}/dataset_description.json /tmp/data/${DATASET}-t2only cp -r /tmp/data/${DATASET}/sub-01 /tmp/data/${DATASET}-t2only/sub-01 rm -f /tmp/data/${DATASET}-t2only/sub-01/ses-1mo/anat/*_T1w.* + tree /tmp/data/${DATASET}-t2only - run: name: Run nibabies single anatomical workflow no_output_timeout: 1h command: | - mkdir -p /tmp/data/${DATASET}-t2only /tmp/${DATASET}/derivatives/nibabies-t2only + mkdir -p /tmp/data/${DATASET}-t2only /tmp/${DATASET}/derivatives/nibabies-t2only /tmp/${DATASET}/work-t2only nibabies-wrapper docker /tmp/data/${DATASET}-t2only /tmp/${DATASET}/derivatives/nibabies-t2only participant \ -i nipreps/nibabies:dev \ -e NIBABIES_DEV 1 --user $(id -u):$(id -g) \ --network none --notrack \ - --config $PWD/nipype.cfg -w /tmp/${DATASET}/work \ + --config $PWD/nipype.cfg -w /tmp/${DATASET}/work-t2only \ --fs-subjects-dir /tmp/data/${DATASET}/derivatives/infant-freesurfer \ --skull-strip-template UNCInfant:cohort-1 \ --output-spaces MNIInfant:cohort-1 func \ From 7d1447bcb4166ab2fcd3febbb19ff9005fc81f22 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 9 Nov 2023 17:06:41 -0500 Subject: [PATCH 12/14] CI: Rename derivatives to T2w space --- .circleci/config.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9260f23..4a921f02 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -405,6 +405,14 @@ jobs: cp -r /tmp/data/${DATASET}/sub-01 /tmp/data/${DATASET}-t2only/sub-01 rm -f /tmp/data/${DATASET}-t2only/sub-01/ses-1mo/anat/*_T1w.* tree /tmp/data/${DATASET}-t2only + mkdir -p /tmp/data/${DATASET}-t2only/derivatives + cp -r /tmp/data/${DATASET}/derivatives/precomputed /tmp/data/${DATASET}-t2only/derivatives + # rename the derivatives for now - this is only possible since they share the same space + sudo apt install rename + rename 's/space-T1w/space-T2w/' /tmp/data/${DATASET}-t2only/derivatives/precomputed/sub-01/ses-1mo/anat/* + sed -i 's/_T1w./_T2w./g' /tmp/data/${DATASET}-t2only/derivatives/precomputed/sub-01/ses-1mo/anat/*json + tree /tmp/data/${DATASET}-t2only/derivatives/precomputed + - run: name: Run nibabies single anatomical workflow no_output_timeout: 1h @@ -420,7 +428,7 @@ jobs: --output-spaces MNIInfant:cohort-1 func \ --sloppy --write-graph --mem-mb 14000 \ --nthreads 4 -vv --age-months 2 --sloppy \ - --derivatives /tmp/data/${DATASET}/derivatives/precomputed \ + --derivatives /tmp/data/${DATASET}-t2only/derivatives/precomputed \ --output-layout bids --anat-only - run: name: Checking outputs of T2-only nibabies anat From b00fbd7efd947457c021a66b60f4e0a9d41cf08d Mon Sep 17 00:00:00 2001 From: mathiasg Date: Fri, 10 Nov 2023 08:47:06 -0500 Subject: [PATCH 13/14] FIX: Add metadata to T2w preproc --- nibabies/workflows/anatomical/outputs.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nibabies/workflows/anatomical/outputs.py b/nibabies/workflows/anatomical/outputs.py index 3c511f93..37892249 100644 --- a/nibabies/workflows/anatomical/outputs.py +++ b/nibabies/workflows/anatomical/outputs.py @@ -433,6 +433,8 @@ def init_anat_derivatives_wf( name="ds_t2w_preproc", run_without_submitting=True, ) + ds_t2w_preproc.inputs.SkullStripped = False + if num_t1w: ds_t2w_preproc.inputs.space = "T1w" @@ -492,20 +494,16 @@ def init_anat_derivatives_wf( ) if num_t1w: - # fmt:off workflow.connect([ (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), ('t1w_source_files', 'source_file')]), - ]) - # fmt:on + ]) # fmt:skip if num_t2w: - # fmt:off workflow.connect([ (inputnode, ds_t2w_preproc, [('t2w_preproc', 'in_file'), ('t2w_source_files', 'source_file')]), - ]) - # fmt:on + ]) # fmt:skip # fmt:off workflow.connect([ From bc7ee38b2e7ee0b2579e23deb6377a772aa6e438 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Fri, 10 Nov 2023 09:27:48 -0500 Subject: [PATCH 14/14] CI: Adjust expected outputs --- .circleci/bcp_anat_outputs.txt | 1 + .circleci/bcp_anat_t2only_outputs.txt | 5 ++--- .circleci/bcp_full_outputs.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/bcp_anat_outputs.txt b/.circleci/bcp_anat_outputs.txt index fcd4c3ee..b32939d3 100644 --- a/.circleci/bcp_anat_outputs.txt +++ b/.circleci/bcp_anat_outputs.txt @@ -51,5 +51,6 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_dseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-CSF_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-GM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-WM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T1w_desc-preproc_T2w.json sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T1w_desc-preproc_T2w.nii.gz sub-01_ses-1mo.html diff --git a/.circleci/bcp_anat_t2only_outputs.txt b/.circleci/bcp_anat_t2only_outputs.txt index fa30fa14..21824e2b 100644 --- a/.circleci/bcp_anat_t2only_outputs.txt +++ b/.circleci/bcp_anat_t2only_outputs.txt @@ -45,11 +45,10 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_label-GM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_label-WM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-brain_mask.json sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-brain_mask.nii.gz -sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T1w.json -sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T1w.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T2w.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_desc-preproc_T2w.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_dseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-CSF_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-GM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-WM_probseg.nii.gz -sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_desc-preproc_T2w.nii.gz sub-01_ses-1mo.html diff --git a/.circleci/bcp_full_outputs.txt b/.circleci/bcp_full_outputs.txt index a8ceb3aa..fff15bb7 100644 --- a/.circleci/bcp_full_outputs.txt +++ b/.circleci/bcp_full_outputs.txt @@ -51,6 +51,7 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_dseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-CSF_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-GM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-MNIInfant_cohort-1_label-WM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T1w_desc-preproc_T2w.json sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T1w_desc-preproc_T2w.nii.gz sub-01/ses-1mo/fmap sub-01/ses-1mo/fmap/sub-01_ses-1mo_run-001_fmapid-auto00000_desc-coeff_fieldmap.nii.gz