diff --git a/.circleci/bcp_anat_t2only_outputs.txt b/.circleci/bcp_anat_t2only_outputs.txt index d16fc222..fdde57d0 100644 --- a/.circleci/bcp_anat_t2only_outputs.txt +++ b/.circleci/bcp_anat_t2only_outputs.txt @@ -12,7 +12,9 @@ sub-01/ses-1mo sub-01/ses-1mo/anat 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_from-MNI152NLin6Asym_to-T2w_mode-image_xfm.h5 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-MNI152NLin6Asym_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 @@ -20,6 +22,9 @@ 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_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_den-32k_midthickness.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_den-32k_pial.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_space-fsLR_den-32k_white.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_space-fsaverage_desc-reg_sphere.surf.gii sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-L_sphere.surf.gii @@ -30,6 +35,9 @@ 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_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_den-32k_midthickness.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_den-32k_pial.surf.gii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_space-fsLR_den-32k_white.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_space-fsaverage_desc-reg_sphere.surf.gii sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_hemi-R_sphere.surf.gii @@ -52,4 +60,10 @@ sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T2w_dseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T2w_label-CSF_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T2w_label-GM_probseg.nii.gz sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-T2w_label-WM_probseg.nii.gz +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_curv.dscalar.nii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_curv.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_sulc.dscalar.nii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_sulc.json +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_thickness.dscalar.nii +sub-01/ses-1mo/anat/sub-01_ses-1mo_run-001_space-fsLR_den-91k_thickness.json sub-01_ses-1mo.html diff --git a/.circleci/config.yml b/.circleci/config.yml index 831c0ccc..ebda159a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,6 @@ _python_defaults: &python_defaults password: $DOCKER_PAT working_directory: /tmp/src/nibabies - _docker_auth: &docker_auth name: Docker authentication command: | @@ -160,7 +159,6 @@ jobs: - /tmp/docker - /tmp/images - get_data: !!merge <<: *python_defaults working_directory: /home/circleci/data @@ -279,7 +277,6 @@ jobs: - store_artifacts: path: /tmp/data/reports - test_bcp: !!merge <<: *machine_defaults environment: @@ -412,7 +409,7 @@ jobs: --nthreads 4 -vv --age-months 2 --sloppy \ --surface-recon-method infantfs \ --derivatives precomputed=/tmp/data/${DATASET}-t2only/derivatives/bibsnet \ - --output-layout bids --anat-only + --output-layout bids --anat-only --cifti-output - run: name: Checking outputs of T2-only nibabies anat command: | @@ -427,7 +424,6 @@ jobs: - store_artifacts: path: /tmp/bcp/derivatives - deploy_docker_patches: !!merge <<: *machine_defaults working_directory: *src @@ -464,7 +460,6 @@ jobs: docker tag nipreps/nibabies:dev nipreps/nibabies:${CIRCLE_BRANCH#docker/} docker push nipreps/nibabies:${CIRCLE_BRANCH#docker/} - deploy_docker: !!merge <<: *machine_defaults steps: @@ -503,7 +498,6 @@ jobs: fi fi - test_deploy_pypi: !!merge <<: *python_defaults steps: @@ -559,7 +553,6 @@ jobs: - store_artifacts: path: /tmp/src/nibabies/wrapper/dist - deploy_pypi: !!merge <<: *python_defaults steps: @@ -584,14 +577,12 @@ jobs: # upload wrapper python -m twine upload wrapper/dist/nibabies* - deployable: docker: - image: busybox:latest steps: - run: echo Deploying! - workflows: version: 2 build_test_deploy: diff --git a/nibabies/workflows/anatomical/apply.py b/nibabies/workflows/anatomical/apply.py index 8741cea4..36a693e4 100644 --- a/nibabies/workflows/anatomical/apply.py +++ b/nibabies/workflows/anatomical/apply.py @@ -11,13 +11,13 @@ from smriprep.workflows.surfaces import ( init_hcp_morphometrics_wf, init_morph_grayords_wf, - init_resample_midthickness_wf, + init_resample_surfaces_wf, init_surface_derivatives_wf, ) from nibabies import config from nibabies.workflows.anatomical.outputs import init_ds_seg_wf -from nibabies.workflows.anatomical.surfaces import init_resample_midthickness_dhcp_wf +from nibabies.workflows.anatomical.surfaces import init_resample_surfaces_dhcp_wf if ty.TYPE_CHECKING: from niworkflows.utils.spaces import SpatialReferences @@ -57,6 +57,8 @@ def init_infant_anat_apply_wf( 'sulc', 'template', 'thickness', + 'white', + 'pial', 'midthickness', reg_sphere, # template workflow inputs @@ -185,17 +187,28 @@ def init_infant_anat_apply_wf( if cifti_output: hcp_morphometrics_wf = init_hcp_morphometrics_wf(omp_nthreads=omp_nthreads) if recon_method == 'mcribs': - resample_midthickness_wf = init_resample_midthickness_dhcp_wf( - grayord_density=cifti_output + resample_surfaces_wf = init_resample_surfaces_dhcp_wf( + surfaces=['white', 'pial', 'midthickness'], + grayord_density=cifti_output, ) else: - resample_midthickness_wf = init_resample_midthickness_wf( - grayord_density=cifti_output + resample_surfaces_wf = init_resample_surfaces_wf( + surfaces=['white', 'pial', 'midthickness'], grayord_density=cifti_output ) morph_grayords_wf = init_morph_grayords_wf( grayord_density=cifti_output, omp_nthreads=omp_nthreads ) + ds_fsLR_surfaces_wf = init_ds_surfaces_wf( + output_dir=output_dir, + surfaces=['white', 'pial', 'midthickness'], + entities={ + 'space': 'dhcpAsym' if recon_method == 'mcribs' else 'fsLR', + 'density': '32k' if cifti_output == '91k' else '59k', + }, + name='ds_fsLR_surfaces_wf', + ) + ds_grayord_metrics_wf = init_ds_grayord_metrics_wf( bids_root=bids_root, output_dir=output_dir, @@ -213,7 +226,9 @@ def init_infant_anat_apply_wf( (surface_derivatives_wf, hcp_morphometrics_wf, [ ('outputnode.curv', 'inputnode.curv'), ]), - (inputnode, resample_midthickness_wf, [ + (inputnode, resample_surfaces_wf, [ + ('white', 'inputnode.white'), + ('pial', 'inputnode.pial'), ('midthickness', 'inputnode.midthickness'), (reg_sphere, 'inputnode.sphere_reg_fsLR'), ]), @@ -230,12 +245,20 @@ def init_infant_anat_apply_wf( (hcp_morphometrics_wf, outputnode, [ ('outputnode.roi', 'roi'), ]), - (resample_midthickness_wf, morph_grayords_wf, [ + (resample_surfaces_wf, morph_grayords_wf, [ ('outputnode.midthickness_fsLR', 'inputnode.midthickness_fsLR'), ]), - (resample_midthickness_wf, outputnode, [ + (inputnode, ds_fsLR_surfaces_wf, [ + ('anat_valid_list', 'inputnode.source_files'), + ]), + (resample_surfaces_wf, outputnode, [ ('outputnode.midthickness_fsLR', 'midthickness_fsLR'), ]), + (resample_surfaces_wf, ds_fsLR_surfaces_wf, [ + ('outputnode.white_fsLR', 'inputnode.white'), + ('outputnode.pial_fsLR', 'inputnode.pial'), + ('outputnode.midthickness_fsLR', 'inputnode.midthickness'), + ]), (inputnode, ds_grayord_metrics_wf, [ ('anat_valid_list', 'inputnode.source_files'), ]), diff --git a/nibabies/workflows/anatomical/surfaces.py b/nibabies/workflows/anatomical/surfaces.py index d5ea117f..cec467db 100644 --- a/nibabies/workflows/anatomical/surfaces.py +++ b/nibabies/workflows/anatomical/surfaces.py @@ -415,9 +415,10 @@ def init_make_midthickness_wf( return workflow -def init_resample_midthickness_dhcp_wf( +def init_resample_surfaces_dhcp_wf( + surfaces: list[str], grayord_density: ty.Literal['91k', '170k'], - name: str = 'resample_midthickness_wf', + name: str = 'resample_surfaces_dhcp_wf', ): """ Resample subject midthickness surface to specified density. @@ -427,20 +428,20 @@ def init_resample_midthickness_dhcp_wf( :graph2use: colored :simple_form: yes - from nibabies.workflows.anatomical.surfaces import init_resample_midthickness_wf - wf = init_resample_midthickness_wf(grayord_density="91k") + from nibabies.workflows.anatomical.surfaces import init_resample_surfaces_dhcp_wf + wf = init_resample_surfaces_dhcp_wf(surfaces=['white', grayord_density='91k') Parameters ---------- grayord_density : :obj:`str` Either `91k` or `170k`, representing the total of vertices or *grayordinates*. name : :obj:`str` - Unique name for the subworkflow (default: ``"resample_midthickness_wf"``) + Unique name for the subworkflow (default: ``"resample_surfaces_dhcp_wf``) Inputs ------ - midthickness - GIFTI surface mesh corresponding to the midthickness surface + ```` + Left and right GIFTIs for each surface name passed to ``surfaces`` sphere_reg_fsLR GIFTI surface mesh corresponding to the subject's fsLR registration sphere @@ -454,11 +455,19 @@ def init_resample_midthickness_dhcp_wf( fslr_density = '32k' if grayord_density == '91k' else '59k' inputnode = pe.Node( - niu.IdentityInterface(fields=['midthickness', 'sphere_reg_fsLR']), + niu.IdentityInterface(fields=[*surfaces, 'sphere_reg_fsLR']), name='inputnode', ) - outputnode = pe.Node(niu.IdentityInterface(fields=['midthickness_fsLR']), name='outputnode') + outputnode = pe.Node( + niu.IdentityInterface(fields=[f'{surf}_fsLR' for surf in surfaces]), name='outputnode' + ) + + surface_list = pe.Node( + niu.Merge(len(surfaces), ravel_inputs=True), + name='surface_list', + run_without_submitting=True, + ) resampler = pe.MapNode( SurfaceResample(method='BARYCENTRIC'), @@ -477,20 +486,45 @@ def init_resample_midthickness_dhcp_wf( extension='.surf.gii', ) ) + # Order matters. Iterate over surfaces, then hemis to get L R L R L R + for _surf in surfaces for hemi in ['L', 'R'] ] + surface_groups = pe.Node( + niu.Split(splits=[2] * len(surfaces)), + name='surface_groups', + run_without_submitting=True, + ) + workflow.connect([ + (inputnode, surface_list, [ + ((surf, _sorted_by_basename), f'in{i}') + for i, surf in enumerate(surfaces, start=1) + ]), (inputnode, resampler, [ - ('midthickness', 'surface_in'), - ('sphere_reg_fsLR', 'current_sphere'), + (('sphere_reg_fsLR', _repeat, len(surfaces)), 'current_sphere'), + ]), + (surface_list, resampler, [('out', 'surface_in')]), + (resampler, surface_groups, [('surface_out', 'inlist')]), + (surface_groups, outputnode, [ + (f'out{i}', f'{surf}_fsLR') for i, surf in enumerate(surfaces, start=1) ]), - (resampler, outputnode, [('surface_out', 'midthickness_fsLR')]), ]) # fmt:skip return workflow +def _sorted_by_basename(inlist): + from os.path import basename + + return sorted(inlist, key=lambda x: str(basename(x))) + + +def _repeat(seq: list, count: int) -> list: + return seq * count + + def _parent(p): from pathlib import Path diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index 3c2bf859..9a4a236c 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -513,6 +513,8 @@ def init_single_subject_wf( ('outputnode.anat_dseg', 'inputnode.anat_dseg'), ('outputnode.anat_tpms', 'inputnode.anat_tpms'), ('outputnode.fsnative2anat_xfm', 'inputnode.fsnative2anat_xfm'), + ('outputnode.white', 'inputnode.white'), + ('outputnode.pial', 'inputnode.pial'), ('outputnode.midthickness', 'inputnode.midthickness'), (f'outputnode.{reg_sphere}', f'inputnode.{reg_sphere}'), ('outputnode.sulc', 'inputnode.sulc'), diff --git a/pyproject.toml b/pyproject.toml index 5c5966c8..07fca109 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,8 +34,7 @@ dependencies = [ "pybids >= 0.15.0", "requests", "sdcflows >= 2.10.0", -# "smriprep >= 0.16.1", - "smriprep @ git+https://github.com/nipreps/smriprep.git@dev-nibabies", + "smriprep >= 0.17.0", "tedana >= 23.0.2", "templateflow >= 24.2.0", "toml",