diff --git a/nibabies/cli/parser.py b/nibabies/cli/parser.py index b9b850c1..aac9a0bb 100644 --- a/nibabies/cli/parser.py +++ b/nibabies/cli/parser.py @@ -501,6 +501,12 @@ def _slice_time_ref(value, parser): dest="hires", help="disable sub-millimeter (hires) reconstruction", ) + g_surfs.add_argument( + "--no-msm", + action="store_false", + dest="run_msmsulc", + help="Disable Multimodal Surface Matching surface registration.", + ) g_surfs_xor = g_surfs.add_mutually_exclusive_group() g_surfs_xor.add_argument( "--cifti-output", diff --git a/nibabies/config.py b/nibabies/config.py index 25251c72..51257f34 100644 --- a/nibabies/config.py +++ b/nibabies/config.py @@ -570,6 +570,8 @@ class workflow(_Config): """Threshold for DVARS.""" regressors_fd_th = None """Threshold for :abbr:`FD (frame-wise displacement)`.""" + run_msmsulc = False + """Run Multimodal Surface Matching surface registration.""" run_reconall = True """Run FreeSurfer's surface reconstruction.""" skull_strip_fixed_seed = False @@ -597,6 +599,12 @@ class workflow(_Config): """Run *fieldmap-less* susceptibility-derived distortions estimation in the absence of any alternatives.""" + @classmethod + def init(cls): + # Avoid additional runtime if not required + if not cls.cifti_output: + cls.run_msmsulc = False + class loggers: """Keep loggers easily accessible (see :py:func:`init`).""" diff --git a/nibabies/workflows/anatomical/base.py b/nibabies/workflows/anatomical/base.py index dc475b43..8c3d1ff3 100644 --- a/nibabies/workflows/anatomical/base.py +++ b/nibabies/workflows/anatomical/base.py @@ -27,6 +27,7 @@ def init_infant_anat_wf( freesurfer: bool, hires: bool | None, longitudinal: bool, + msm_sulc: bool, omp_nthreads: int, output_dir: str | Path, segmentation_atlases: str | Path | None, @@ -486,7 +487,7 @@ def init_infant_anat_wf( 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() + sphere_reg_wf = init_mcribs_sphere_reg_wf(sloppy=sloppy, msm_sulc=msm_sulc) # fmt:off wf.connect([ @@ -505,7 +506,7 @@ def init_infant_anat_wf( from smriprep.workflows.surfaces import init_sphere_reg_wf # fsaverage to fsLR - sphere_reg_wf = init_sphere_reg_wf() + sphere_reg_wf = init_sphere_reg_wf(msm_sulc=msm_sulc) # fmt:off wf.connect([ diff --git a/nibabies/workflows/anatomical/surfaces.py b/nibabies/workflows/anatomical/surfaces.py index c5c3f81d..7a6ce993 100644 --- a/nibabies/workflows/anatomical/surfaces.py +++ b/nibabies/workflows/anatomical/surfaces.py @@ -202,7 +202,9 @@ def init_mcribs_surface_recon_wf( return wf -def init_mcribs_sphere_reg_wf(*, name="mcribs_sphere_reg_wf"): +def init_mcribs_sphere_reg_wf( + *, msm_sulc: bool = False, sloppy: bool = False, name="mcribs_sphere_reg_wf" +): """ Generate GIFTI registration sphere files from MCRIBS template to dHCP42 (32k). @@ -210,11 +212,12 @@ def init_mcribs_sphere_reg_wf(*, name="mcribs_sphere_reg_wf"): """ from smriprep.interfaces.surf import FixGiftiMetadata from smriprep.interfaces.workbench import SurfaceSphereProjectUnproject + from smriprep.workflows.surfaces import init_msm_sulc_wf workflow = LiterateWorkflow(name=name) inputnode = pe.Node( - niu.IdentityInterface(["subjects_dir", "subject_id"]), + niu.IdentityInterface(["subjects_dir", "subject_id", "sulc"]), name="inputnode", ) outputnode = pe.Node( @@ -228,14 +231,16 @@ def init_mcribs_sphere_reg_wf(*, name="mcribs_sphere_reg_wf"): run_without_submitting=True, ) + get_surfaces = pe.Node(nio.FreeSurferSource(), name="get_surfaces") + # Via FreeSurfer2CaretConvertAndRegisterNonlinear.sh#L270-L273 # # See https://github.com/DCAN-Labs/DCAN-HCP/tree/9291324 - sphere_gii = pe.MapNode( - fs.MRIsConvert(out_datatype="gii"), iterfield="in_file", name="sphere_gii" + sphere_reg_gii = pe.MapNode( + fs.MRIsConvert(out_datatype="gii"), iterfield="in_file", name="sphere_reg_gii" ) - fix_meta = pe.MapNode(FixGiftiMetadata(), iterfield="in_file", name="fix_meta") + fix_reg_meta = pe.MapNode(FixGiftiMetadata(), iterfield="in_file", name="fix_reg_meta") # load template files atlases = load_resource('atlases') @@ -266,14 +271,42 @@ def init_mcribs_sphere_reg_wf(*, name="mcribs_sphere_reg_wf"): ('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ]), - (get_spheres, sphere_gii, [(('out', _sorted_by_basename), 'in_file')]), - (sphere_gii, fix_meta, [('converted', 'in_file')]), - (fix_meta, project_unproject, [('out_file', 'sphere_in')]), - (sphere_gii, outputnode, [('converted', 'sphere_reg')]), - (project_unproject, outputnode, [('sphere_out', 'sphere_reg_fsLR')]), + (inputnode, get_surfaces, [ + ('subjects_dir', 'subjects_dir'), + ('subject_id', 'subject_id'), + ]), + (get_spheres, sphere_reg_gii, [(('out', _sorted_by_basename), 'in_file')]), + (sphere_reg_gii, fix_reg_meta, [('converted', 'in_file')]), + (fix_reg_meta, project_unproject, [('out_file', 'sphere_in')]), + (sphere_reg_gii, outputnode, [('converted', 'sphere_reg')]), ]) # fmt:on + if not msm_sulc: + workflow.connect(project_unproject, 'sphere_out', outputnode, 'sphere_reg_fsLR') + return workflow + + sphere_gii = pe.MapNode( + fs.MRIsConvert(out_datatype='gii'), + iterfield='in_file', + name='sphere_gii', + ) + fix_sphere_meta = pe.MapNode( + FixGiftiMetadata(), + iterfield='in_file', + name='fix_sphere_meta', + ) + msm_sulc_wf = init_msm_sulc_wf(sloppy=sloppy) + # fmt:off + workflow.connect([ + (get_surfaces, sphere_gii, [(('sphere', _sorted_by_basename), 'in_file')]), + (sphere_gii, fix_sphere_meta, [('converted', 'in_file')]), + (fix_sphere_meta, msm_sulc_wf, [('out_file', 'inputnode.sphere')]), + (inputnode, msm_sulc_wf, [('sulc', 'inputnode.sulc')]), + (project_unproject, msm_sulc_wf, [('sphere_out', 'inputnode.sphere_reg_fsLR')]), + (msm_sulc_wf, outputnode, [('outputnode.sphere_reg_fsLR', 'sphere_reg_fsLR')]), + ]) + # fmt:on return workflow diff --git a/nibabies/workflows/base.py b/nibabies/workflows/base.py index eecfc5af..85638540 100644 --- a/nibabies/workflows/base.py +++ b/nibabies/workflows/base.py @@ -356,6 +356,7 @@ def init_single_subject_wf( freesurfer=config.workflow.run_reconall, hires=config.workflow.hires, longitudinal=config.workflow.longitudinal, + msm_sulc=config.workflow.run_msmsulc, omp_nthreads=config.nipype.omp_nthreads, output_dir=nibabies_dir, segmentation_atlases=config.execution.segmentation_atlases_dir,