Skip to content

Commit 91a44e1

Browse files
committed
ENH: Add M-CRIB-S -> dHCP spherical registration workflow
1 parent af0138a commit 91a44e1

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

nibabies/workflows/anatomical/base.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ def init_infant_anat_wf(
162162
"anat_aparc",
163163
"anat_ribbon",
164164
"template",
165+
# registration sphere space is dependent on surface recon method
166+
"sphere_reg",
167+
"sphere_reg_fsLR",
165168
]
166169
),
167170
name="outputnode",
@@ -413,6 +416,9 @@ def init_infant_anat_wf(
413416
mcribs_dir=str(config.execution.mcribs_dir), # Needed to preserve runs
414417
)
415418

419+
# M-CRIB-S to dHCP42week (32k)
420+
sphere_reg_wf = init_mcribs_sphere_reg_wf()
421+
416422
# Transformed gives
417423
if precomp_aseg:
418424
surface_recon_wf.inputs.inputnode.ants_segs = precomp_aseg
@@ -421,11 +427,18 @@ def init_infant_anat_wf(
421427
# fmt:off
422428
wf.connect([
423429
(inputnode, denoise_raw_t2w, [('t2w', 'input_image')]),
424-
(denoise_raw_t2w, surface_recon_wf, [('output_image', 'inputnode.t2w')])
430+
(denoise_raw_t2w, surface_recon_wf, [('output_image', 'inputnode.t2w')]),
425431
])
426432
# fmt:on
433+
else:
434+
raise NotImplementedError
427435

428436
if config.workflow.surface_recon_method in ('freesurfer', 'infantfs'):
437+
from smriprep.workflows.surfaces import init_sphere_reg_wf
438+
439+
# fsaverage to fsLR
440+
sphere_reg_wf = init_sphere_reg_wf()
441+
429442
# fmt:off
430443
wf.connect([
431444
(t2w_preproc_wf, surface_recon_wf, [
@@ -472,6 +485,10 @@ def init_infant_anat_wf(
472485
(anat_ribbon_wf, anat_derivatives_wf, [
473486
("outputnode.anat_ribbon", "inputnode.anat_ribbon"),
474487
]),
488+
(surface_recon_wf, sphere_reg_wf, [
489+
('outputnode.subject_id', 'inputnode.subject_id'),
490+
('outputnode.subjects_dir', 'inputnode.subjects_dir'),
491+
]),
475492
(surface_recon_wf, anat_reports_wf, [
476493
("outputnode.subject_id", "inputnode.subject_id"),
477494
("outputnode.subjects_dir", "inputnode.subjects_dir"),
@@ -484,6 +501,12 @@ def init_infant_anat_wf(
484501
("outputnode.surfaces", "inputnode.surfaces"),
485502
("outputnode.morphometrics", "inputnode.morphometrics"),
486503
]),
504+
(sphere_reg_wf, outputnode, [
505+
('outputnode.sphere_reg', 'sphere_reg'),
506+
('outputnode.sphere_reg_fsLR', 'sphere_reg_fsLR')]),
507+
(sphere_reg_wf, anat_derivatives_wf, [
508+
('outputnode.sphere_reg', 'inputnode.sphere_reg'),
509+
('outputnode.sphere_reg_fsLR', 'inputnode.sphere_reg_fsLR')]),
487510
])
488511
# fmt: on
489512

nibabies/workflows/anatomical/surfaces.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from smriprep.workflows.surfaces import init_gifti_surface_wf
1313

1414
from ...config import DEFAULT_MEMORY_MIN_GB
15+
from ...data import load_resource
1516

1617
SURFACE_INPUTS = [
1718
"subjects_dir",
@@ -201,6 +202,81 @@ def init_mcribs_surface_recon_wf(
201202
return wf
202203

203204

205+
def init_mcribs_sphere_reg_wf(*, name="mcribs_sphere_reg_wf"):
206+
"""
207+
Generate GIFTI registration sphere files from MCRIBS template to dHCP42 (32k).
208+
209+
TODO: Clarify any distinction with fsLR
210+
"""
211+
from smriprep.interfaces.surf import FixGiftiMetadata
212+
from smriprep.interfaces.workbench import SurfaceSphereProjectUnproject
213+
214+
workflow = LiterateWorkflow(name=name)
215+
216+
inputnode = pe.Node(
217+
niu.IdentityInterface(["subjects_dir", "subject_id"]),
218+
name="inputnode",
219+
)
220+
outputnode = pe.Node(
221+
niu.IdentityInterface(["sphere_reg", "sphere_reg_fsLR"]),
222+
name="outputnode",
223+
)
224+
225+
get_spheres = pe.Node(
226+
niu.Function(function=_get_dhcp_spheres),
227+
name='get_spheres',
228+
run_without_submitting=True,
229+
)
230+
231+
# Via FreeSurfer2CaretConvertAndRegisterNonlinear.sh#L270-L273
232+
#
233+
# See https://github.com/DCAN-Labs/DCAN-HCP/tree/9291324
234+
sphere_gii = pe.MapNode(
235+
fs.MRIsConvert(out_datatype="gii"), iterfield="in_file", name="sphere_gii"
236+
)
237+
238+
fix_meta = pe.MapNode(FixGiftiMetadata(), iterfield="in_file", name="fix_meta")
239+
240+
# load template files
241+
atlases = load_resource('atlases')
242+
243+
# SurfaceSphereProjectUnProject
244+
# project to 41k dHCP atlas sphere
245+
# - sphere-in: Individual native sphere in surf directory registered to 41k atlas sphere
246+
# - sphere-to: the 41k atlas sphere, in the fsaverage directory
247+
# - sphere-unproject-from: 41k atlas sphere registered to dHCP 42wk sphere, in the fsaverage directory
248+
# - sphere-out: lh.sphere.reg2.dHCP42.native.surf.gii
249+
project_unproject = pe.MapNode(
250+
SurfaceSphereProjectUnproject(),
251+
iterfield=["sphere_in", "sphere_project_to", "sphere_unproject_from"],
252+
name="project_unproject",
253+
)
254+
project_unproject.inputs.sphere_project_to = [
255+
atlases / 'mcribs' / 'lh.sphere.reg2.surf.gii',
256+
atlases / 'mcribs' / 'rh.sphere.reg2.surf.gii',
257+
]
258+
project_unproject.inputs.sphere_unproject_from = [
259+
atlases / 'mcribs' / 'lh.sphere.reg.dHCP42.surf.gii',
260+
atlases / 'mcribs' / 'rh.sphere.reg.dHCP42.surf.gii',
261+
]
262+
263+
# fmt:off
264+
workflow.connect([
265+
(inputnode, get_spheres, [
266+
('subjects_dir', 'subjects_dir'),
267+
('subject_id', 'subject_id'),
268+
]),
269+
(get_spheres, sphere_gii, [(('out', _sorted_by_basename), 'in_file')]),
270+
(sphere_gii, fix_meta, [('converted', 'in_file')]),
271+
(fix_meta, project_unproject, [('out_file', 'sphere_in')]),
272+
(sphere_gii, outputnode, [('converted', 'sphere_reg')]),
273+
(project_unproject, outputnode, [('sphere_out', 'sphere_reg_fsLR')]),
274+
])
275+
# fmt:on
276+
277+
return workflow
278+
279+
204280
def init_infantfs_surface_recon_wf(
205281
*, age_months, use_aseg=False, name="infantfs_surface_recon_wf"
206282
):
@@ -483,3 +559,15 @@ def _sorted_by_basename(inlist):
483559
from os.path import basename
484560

485561
return sorted(inlist, key=lambda x: str(basename(x)))
562+
563+
564+
def _get_dhcp_spheres(subject_id: str, subjects_dir: str) -> list:
565+
from pathlib import Path
566+
567+
out = []
568+
for hemi in 'lr':
569+
sphere = Path(subjects_dir) / subject_id / 'surf' / f'{hemi}h.sphere.reg2'
570+
if not sphere.exists():
571+
raise OSError("MCRIBS spherical registration not found.")
572+
out.append(str(sphere))
573+
return out

0 commit comments

Comments
 (0)