diff --git a/.circleci/bcp_anat_outputs.txt b/.circleci/bcp_anat_outputs.txt index 93c97abd..afcc591c 100644 --- a/.circleci/bcp_anat_outputs.txt +++ b/.circleci/bcp_anat_outputs.txt @@ -21,6 +21,8 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.t sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T2w_to-T1w_mode-image_xfm.h5 sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-fsnative_to-T1w_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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-cortex_mask.label.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 @@ -31,6 +33,8 @@ 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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-cortex_mask.label.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 diff --git a/.circleci/bcp_anat_t2only_outputs.txt b/.circleci/bcp_anat_t2only_outputs.txt index fdde57d0..9aea211d 100644 --- a/.circleci/bcp_anat_t2only_outputs.txt +++ b/.circleci/bcp_anat_t2only_outputs.txt @@ -19,6 +19,8 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T2w_to-MNIInfant+1_mode-image_xf 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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-cortex_mask.label.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 @@ -32,6 +34,8 @@ 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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-cortex_mask.label.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 diff --git a/.circleci/bcp_full_outputs.txt b/.circleci/bcp_full_outputs.txt index 448a7492..e5888a5a 100644 --- a/.circleci/bcp_full_outputs.txt +++ b/.circleci/bcp_full_outputs.txt @@ -21,6 +21,8 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T1w_to-fsnative_mode-image_xfm.t sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-T2w_to-T1w_mode-image_xfm.h5 sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_from-fsnative_to-T1w_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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_desc-cortex_mask.label.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 @@ -31,6 +33,8 @@ 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-cortex_mask.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_desc-cortex_mask.label.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 diff --git a/nibabies/data/io_spec_anat.json b/nibabies/data/io_spec_anat.json index f3eeffa6..dcb99653 100644 --- a/nibabies/data/io_spec_anat.json +++ b/nibabies/data/io_spec_anat.json @@ -232,6 +232,14 @@ "desc": "msmsulc", "suffix": "sphere", "extension": ".surf.gii" + }, + "cortex_mask": { + "datatype": "anat", + "hemi": ["L", "R"], + "space": null, + "desc": "cortex", + "suffix": "mask", + "extension": ".label.gii" } }, "masks": { diff --git a/nibabies/workflows/anatomical/apply.py b/nibabies/workflows/anatomical/apply.py index 276968f4..41e3de4c 100644 --- a/nibabies/workflows/anatomical/apply.py +++ b/nibabies/workflows/anatomical/apply.py @@ -62,6 +62,7 @@ def init_infant_anat_apply_wf( 'white', 'pial', 'midthickness', + 'cortex_mask', reg_sphere, # template workflow inputs 'std_t1w', @@ -75,7 +76,7 @@ def init_infant_anat_apply_wf( ) outputnode = pe.Node( - niu.IdentityInterface(fields=['anat_aseg', 'anat_aparc', 'midthickness_fsLR', 'roi']), + niu.IdentityInterface(fields=['anat_aseg', 'anat_aparc', 'midthickness_fsLR']), name='outputnode', ) @@ -235,6 +236,7 @@ def init_infant_anat_apply_wf( (reg_sphere, 'inputnode.sphere_reg_fsLR'), ]), (inputnode, morph_grayords_wf, [ + ('cortex_mask', 'inputnode.roi'), ('midthickness', 'inputnode.midthickness'), (reg_sphere, 'inputnode.sphere_reg_fsLR'), ]), @@ -242,10 +244,6 @@ def init_infant_anat_apply_wf( ('outputnode.curv', 'inputnode.curv'), ('outputnode.sulc', 'inputnode.sulc'), ('outputnode.thickness', 'inputnode.thickness'), - ('outputnode.roi', 'inputnode.roi'), - ]), - (hcp_morphometrics_wf, outputnode, [ - ('outputnode.roi', 'roi'), ]), (resample_surfaces_wf, morph_grayords_wf, [ ('outputnode.midthickness_fsLR', 'inputnode.midthickness_fsLR'), diff --git a/nibabies/workflows/anatomical/fit.py b/nibabies/workflows/anatomical/fit.py index 64ece5f0..5e10a6e4 100644 --- a/nibabies/workflows/anatomical/fit.py +++ b/nibabies/workflows/anatomical/fit.py @@ -13,7 +13,6 @@ from niworkflows.utils.connections import pop_file from smriprep.workflows.anatomical import ( _is_skull_stripped, - init_anat_ribbon_wf, init_anat_template_wf, ) from smriprep.workflows.fit.registration import init_register_template_wf @@ -21,6 +20,7 @@ init_ds_dseg_wf, init_ds_fs_registration_wf, init_ds_mask_wf, + init_ds_surface_masks_wf, init_ds_surface_metrics_wf, init_ds_surfaces_wf, init_ds_template_registration_wf, @@ -28,6 +28,8 @@ init_ds_tpms_wf, ) from smriprep.workflows.surfaces import ( + init_anat_ribbon_wf, + init_cortex_masks_wf, init_fsLR_reg_wf, init_gifti_morphometrics_wf, init_gifti_surfaces_wf, @@ -147,6 +149,7 @@ def init_infant_anat_fit_wf( 'sphere_reg', 'sphere_reg_fsLR', 'sphere_reg_msm', + 'cortex_mask', 'anat_ribbon', # Reverse transform; not computable from forward transform 'std2anat_xfm', @@ -1273,13 +1276,13 @@ def init_infant_anat_fit_wf( (fsnative_buffer, gifti_surfaces_wf, [ ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), - (gifti_surfaces_wf, surfaces_buffer, [ - (f'outputnode.{surf}', surf) for surf in surfs - ]), (sourcefile_buffer, ds_surfaces_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_surfaces_wf, ds_surfaces_wf, [ (f'outputnode.{surf}', f'inputnode.{surf}') for surf in surfs ]), + (ds_surfaces_wf, surfaces_buffer, [ + (f'outputnode.{surf}', surf) for surf in surfs + ]), ]) # fmt:skip if spheres: gifti_spheres_wf = init_gifti_surfaces_wf( @@ -1297,13 +1300,13 @@ def init_infant_anat_fit_wf( ('outputnode.subjects_dir', 'inputnode.subjects_dir'), # No transform for spheres, following HCP pipelines' lead ]), - (gifti_spheres_wf, surfaces_buffer, [ - (f'outputnode.{sphere}', sphere) for sphere in spheres - ]), (sourcefile_buffer, ds_spheres_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_spheres_wf, ds_spheres_wf, [ (f'outputnode.{sphere}', f'inputnode.{sphere}') for sphere in spheres ]), + (ds_spheres_wf, surfaces_buffer, [ + (f'outputnode.{sphere}', sphere) for sphere in spheres + ]), ]) # fmt:skip metrics = [metric for metric in needed_metrics if metric not in found_surfs] if metrics: @@ -1321,13 +1324,13 @@ def init_infant_anat_fit_wf( ('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ]), - (gifti_morph_wf, surfaces_buffer, [ - (f'outputnode.{metric}', metric) for metric in metrics - ]), (sourcefile_buffer, ds_morph_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_morph_wf, ds_morph_wf, [ (f'outputnode.{metric}', f'inputnode.{metric}') for metric in metrics ]), + (ds_morph_wf, surfaces_buffer, [ + (f'outputnode.{metric}', metric) for metric in metrics + ]), ]) # fmt:skip if 'anat_ribbon' not in precomputed: @@ -1413,6 +1416,32 @@ def init_infant_anat_fit_wf( else: LOGGER.info('ANAT Stage 9: Found pre-computed fsLR registration sphere') fsLR_buffer.inputs.sphere_reg_fsLR = sorted(precomputed['sphere_reg_fsLR']) + + # Stage 10: Cortical surface mask + if len(precomputed.get('cortex_mask', [])) < 2: + LOGGER.info('ANAT Stage 11: Creating cortical surface mask') + + cortex_masks_wf = init_cortex_masks_wf() + ds_cortex_masks_wf = init_ds_surface_masks_wf( + output_dir=output_dir, + mask_type='cortex', + name='ds_cortex_masks_wf', + ) + + workflow.connect([ + (surfaces_buffer, cortex_masks_wf, [ + ('midthickness', 'inputnode.midthickness'), + ('thickness', 'inputnode.thickness'), + ]), + (cortex_masks_wf, ds_cortex_masks_wf, [ + ('outputnode.cortex_masks', 'inputnode.mask_files'), + ('outputnode.source_files', 'inputnode.source_files'), + ]), + (ds_cortex_masks_wf, outputnode, [('outputnode.mask_files', 'cortex_mask')]), + ]) # fmt:skip + else: + LOGGER.info('ANAT Stage 11: Found pre-computed cortical surface mask') + outputnode.inputs.cortex_mask = sorted(precomputed['cortex_mask']) return workflow @@ -1476,6 +1505,7 @@ def init_infant_single_anat_fit_wf( 'sphere_reg', 'sphere_reg_fsLR', 'sphere_reg_msm', + 'cortex_mask', 'anat_ribbon', # Reverse transform; not computable from forward transform 'std2anat_xfm', @@ -2202,13 +2232,13 @@ def init_infant_single_anat_fit_wf( (fsnative_buffer, gifti_surfaces_wf, [ ('fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), ]), - (gifti_surfaces_wf, surfaces_buffer, [ - (f'outputnode.{surf}', surf) for surf in surfs - ]), (sourcefile_buffer, ds_surfaces_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_surfaces_wf, ds_surfaces_wf, [ (f'outputnode.{surf}', f'inputnode.{surf}') for surf in surfs ]), + (ds_surfaces_wf, surfaces_buffer, [ + (f'outputnode.{surf}', surf) for surf in surfs + ]), ]) # fmt:skip if spheres: gifti_spheres_wf = init_gifti_surfaces_wf( @@ -2226,13 +2256,13 @@ def init_infant_single_anat_fit_wf( ('outputnode.subjects_dir', 'inputnode.subjects_dir'), # No transform for spheres, following HCP pipelines' lead ]), - (gifti_spheres_wf, surfaces_buffer, [ - (f'outputnode.{sphere}', sphere) for sphere in spheres - ]), (sourcefile_buffer, ds_spheres_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_spheres_wf, ds_spheres_wf, [ (f'outputnode.{sphere}', f'inputnode.{sphere}') for sphere in spheres ]), + (ds_spheres_wf, surfaces_buffer, [ + (f'outputnode.{sphere}', sphere) for sphere in spheres + ]), ]) # fmt:skip metrics = [metric for metric in needed_metrics if metric not in found_surfs] if metrics: @@ -2250,13 +2280,13 @@ def init_infant_single_anat_fit_wf( ('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ]), - (gifti_morph_wf, surfaces_buffer, [ - (f'outputnode.{metric}', metric) for metric in metrics - ]), (sourcefile_buffer, ds_morph_wf, [('anat_source_files', 'inputnode.source_files')]), (gifti_morph_wf, ds_morph_wf, [ (f'outputnode.{metric}', f'inputnode.{metric}') for metric in metrics ]), + (ds_morph_wf, surfaces_buffer, [ + (f'outputnode.{metric}', metric) for metric in metrics + ]), ]) # fmt:skip if 'anat_ribbon' not in precomputed: @@ -2342,4 +2372,30 @@ def init_infant_single_anat_fit_wf( else: LOGGER.info('ANAT Stage 9: Found pre-computed fsLR registration sphere') fsLR_buffer.inputs.sphere_reg_fsLR = sorted(precomputed['sphere_reg_fsLR']) + + # Stage 10: Cortical surface mask + if len(precomputed.get('cortex_mask', [])) < 2: + LOGGER.info('ANAT Stage 11: Creating cortical surface mask') + + cortex_masks_wf = init_cortex_masks_wf() + ds_cortex_masks_wf = init_ds_surface_masks_wf( + output_dir=output_dir, + mask_type='cortex', + name='ds_cortex_masks_wf', + ) + + workflow.connect([ + (surfaces_buffer, cortex_masks_wf, [ + ('midthickness', 'inputnode.midthickness'), + ('thickness', 'inputnode.thickness'), + ]), + (cortex_masks_wf, ds_cortex_masks_wf, [ + ('outputnode.cortex_masks', 'inputnode.mask_files'), + ('outputnode.source_files', 'inputnode.source_files'), + ]), + (ds_cortex_masks_wf, outputnode, [('outputnode.mask_files', 'cortex_mask')]), + ]) # fmt:skip + else: + LOGGER.info('ANAT Stage 11: Found pre-computed cortical surface mask') + outputnode.inputs.cortex_mask = sorted(precomputed['cortex_mask']) return workflow diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index f62f53ce..6421b436 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -534,6 +534,7 @@ def init_single_subject_wf( ('outputnode.white', 'inputnode.white'), ('outputnode.pial', 'inputnode.pial'), ('outputnode.midthickness', 'inputnode.midthickness'), + ('outputnode.cortex_mask', 'inputnode.cortex_mask'), (f'outputnode.{reg_sphere}', f'inputnode.{reg_sphere}'), ('outputnode.sulc', 'inputnode.sulc'), ('outputnode.subjects_dir', 'inputnode.subjects_dir'), @@ -809,8 +810,10 @@ def init_single_subject_wf( if config.workflow.cifti_output: workflow.connect([ + (anat_fit_wf, bold_wf, [ + ('outputnode.cortex_mask', 'inputnode.cortex_mask'), + ]), (anat_apply_wf, bold_wf, [ - ('outputnode.roi', 'inputnode.cortex_mask'), ('outputnode.midthickness_fsLR', 'inputnode.midthickness_fsLR'), ('outputnode.anat_aseg', 'inputnode.anat_aseg'), ]), diff --git a/pyproject.toml b/pyproject.toml index 5414fe0e..cad12183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,7 @@ dependencies = [ "nireports >= 23.2.0", "nitime", "nitransforms >= 24.1.1", - #"niworkflows >= 1.13.1", - "niworkflows @ git+https://github.com/nipreps/niworkflows.git@master", + "niworkflows >= 1.14.1", "numpy >= 1.21.0", "packaging", "pandas < 3", @@ -35,7 +34,7 @@ dependencies = [ "pybids >= 0.15.0", "requests", "sdcflows >= 2.13.0", - "smriprep >= 0.17.0", + "smriprep >= 0.19.1", "tedana >= 23.0.2", "templateflow >= 24.2.0", "toml",