Skip to content

Commit 5efab1a

Browse files
committed
ENH: Extract anatomical -> fsLR into separate workflow
1 parent 3300052 commit 5efab1a

File tree

1 file changed

+279
-0
lines changed

1 file changed

+279
-0
lines changed
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import typing as ty
2+
3+
import nipype.interfaces.utils as niu
4+
import nipype.pipeline.engine as pe
5+
import templateflow.api as tf
6+
from niworkflows.engine.workflows import LiterateWorkflow
7+
from smriprep.interfaces.workbench import SurfaceResample
8+
from smriprep.workflows.surfaces import (
9+
_collate,
10+
_sorted_by_basename,
11+
init_morph_grayords_wf,
12+
)
13+
14+
from nibabies.config import DEFAULT_MEMORY_MIN_GB
15+
from nibabies.data import load_resource
16+
from nibabies.interfaces.utils import CiftiSelect
17+
18+
19+
def init_anat_fsLR_resampling_wf(
20+
grayord_density: ty.Literal["91k"], mcribs: bool, name="anat_fsLR_resampling_wf"
21+
) -> LiterateWorkflow:
22+
"""Resample the surfaces into fsLR space"""
23+
workflow = LiterateWorkflow(name=name)
24+
fslr_density = "32k" if grayord_density == "91k" else "59k"
25+
26+
workflow.__desc__ = """\
27+
The BOLD time-series were resampled onto the left/right-symmetric template
28+
"fsLR" [@hcppipelines].
29+
"""
30+
31+
inputnode = pe.Node(
32+
niu.IdentityInterface(
33+
fields=[
34+
'subject_id',
35+
'subjects_dir',
36+
'surfaces',
37+
'morphometrics',
38+
'sphere_reg_fsLR',
39+
]
40+
),
41+
name='inputnode',
42+
)
43+
44+
itersource = pe.Node(
45+
niu.IdentityInterface(fields=['hemi']),
46+
name='itersource',
47+
iterables=[('hemi', ['L', 'R'])],
48+
)
49+
50+
outputnode = pe.Node(niu.IdentityInterface(fields=['fsLR_midthickness']), name='outputnode')
51+
52+
# select white, midthickness and pial surfaces based on hemi
53+
select_surfaces = pe.Node(CiftiSelect(), name='select_surfaces')
54+
55+
if mcribs:
56+
atlases = load_resource('atlases')
57+
# use dHCP 32k fsLR instead
58+
select_surfaces.inputs.template_spheres = [
59+
str(atlases / 'dHCP' / 'dHCP.week42.L.sphere.surf.gii'),
60+
str(atlases / 'dHCP' / 'dHCP.week42.R.sphere.surf.gii'),
61+
]
62+
else:
63+
select_surfaces.inputs.template_spheres = [
64+
str(sphere)
65+
for sphere in tf.get(
66+
template='fsLR',
67+
density=fslr_density,
68+
suffix='sphere',
69+
space=None,
70+
extension='.surf.gii',
71+
)
72+
]
73+
74+
# Line 393 of FreeSurfer2CaretConvertAndRegisterNonlinear.sh
75+
downsampled_midthickness = pe.Node(
76+
SurfaceResample(method="BARYCENTRIC"),
77+
name="downsampled_midthickness",
78+
mem_gb=DEFAULT_MEMORY_MIN_GB,
79+
)
80+
81+
joinnode = pe.JoinNode(
82+
niu.IdentityInterface(fields=['fsLR_midthickness']),
83+
name='joinnode',
84+
joinsource='itersource',
85+
)
86+
87+
# resample surfaces / morphometrics to 32k
88+
if mcribs:
89+
morph_grayords_wf = init_mcribs_morph_grayords_wf(grayord_density)
90+
workflow.connect(
91+
joinnode,
92+
"fsLR_midthickness",
93+
morph_grayords_wf,
94+
"inputnode.fsLR_midthickness",
95+
)
96+
else:
97+
morph_grayords_wf = init_morph_grayords_wf(grayord_density)
98+
99+
workflow.connect(
100+
[
101+
(
102+
inputnode,
103+
select_surfaces,
104+
[("surfaces", "surfaces"), ("sphere_reg_fsLR", "spherical_registrations")],
105+
),
106+
(itersource, select_surfaces, [("hemi", "hemi")]),
107+
# Downsample midthickness to fsLR density
108+
(
109+
select_surfaces,
110+
downsampled_midthickness,
111+
[
112+
("midthickness", "surface_in"),
113+
("sphere_reg", "current_sphere"),
114+
("template_sphere", "new_sphere"),
115+
],
116+
),
117+
(downsampled_midthickness, joinnode, [("surface_out", "fsLR_midthickness")]),
118+
(joinnode, outputnode, [("surface_out", "fsLR_midthickness")]),
119+
# resample surfaces
120+
(
121+
inputnode,
122+
morph_grayords_wf,
123+
[
124+
("subject_id", "inputnode.subject_id"),
125+
("subjects_dir", "inputnode.subjects_dir"),
126+
],
127+
),
128+
(
129+
morph_grayords_wf,
130+
outputnode,
131+
[
132+
("outputnode.cifti_morph", "cifti_morph"),
133+
("outputnode.cifti_metadata", "cifti_metadata"),
134+
],
135+
),
136+
]
137+
)
138+
return workflow
139+
140+
141+
def init_mcribs_morph_grayords_wf(
142+
grayord_density: ty.Literal['91k', '170k'],
143+
name: str = "morph_grayords_wf",
144+
):
145+
"""
146+
Sample Grayordinates files onto the fsLR atlas.
147+
148+
If `mcribs` is disabled (default), the fsaverage sphere will be resampled to fsLR.
149+
If `mcribs` is enabled, the M-CRIB-S sphere will be resampled to dHCP 42 week.
150+
151+
Outputs are in CIFTI2 format.
152+
153+
Workflow Graph
154+
.. workflow::
155+
:graph2use: colored
156+
:simple_form: yes
157+
158+
from smriprep.workflows.surfaces import init_morph_grayords_wf
159+
wf = init_morph_grayords_wf(grayord_density="91k")
160+
161+
Parameters
162+
----------
163+
grayord_density : :obj:`str`
164+
Either `91k` or `170k`, representing the total of vertices or *grayordinates*.
165+
name : :obj:`str`
166+
Unique name for the subworkflow (default: ``"morph_grayords_wf"``)
167+
168+
Inputs
169+
------
170+
subject_id : :obj:`str`
171+
FreeSurfer subject ID
172+
subjects_dir : :obj:`str`
173+
FreeSurfer SUBJECTS_DIR
174+
175+
Outputs
176+
-------
177+
cifti_morph : :obj:`list` of :obj:`str`
178+
Paths of CIFTI dscalar files
179+
cifti_metadata : :obj:`list` of :obj:`str`
180+
Paths to JSON files containing metadata corresponding to ``cifti_morph``
181+
182+
"""
183+
import templateflow.api as tf
184+
from nipype.interfaces.io import FreeSurferSource
185+
from nipype.interfaces.workbench import MetricResample
186+
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
187+
from smriprep.interfaces.cifti import GenerateDScalar
188+
189+
workflow = Workflow(name=name)
190+
workflow.__desc__ = f"""\
191+
*Grayordinate* "dscalar" files [@hcppipelines] containing {grayord_density} samples were
192+
also generated using the highest-resolution ``fsaverage`` as an intermediate standardized
193+
surface space.
194+
"""
195+
196+
fslr_density = "32k" if grayord_density == "91k" else "59k"
197+
198+
inputnode = pe.Node(
199+
niu.IdentityInterface(fields=["subject_id", "subjects_dir", "fsLR_midthickness"]),
200+
name="inputnode",
201+
)
202+
203+
outputnode = pe.Node(
204+
niu.IdentityInterface(fields=["cifti_morph", "cifti_metadata"]),
205+
name="outputnode",
206+
)
207+
208+
get_surfaces = pe.Node(FreeSurferSource(), name="get_surfaces")
209+
210+
surfmorph_list = pe.Node(
211+
niu.Merge(3, ravel_inputs=True),
212+
name="surfmorph_list",
213+
run_without_submitting=True,
214+
)
215+
216+
# Setup Workbench command. LR ordering for hemi can be assumed, as it is imposed
217+
# by the iterfield of the MapNode in the surface sampling workflow above.
218+
resample = pe.MapNode(
219+
MetricResample(method="ADAP_BARY_AREA", area_metrics=True),
220+
name="resample",
221+
iterfield=[
222+
"in_file",
223+
"out_file",
224+
"new_sphere",
225+
"new_area",
226+
"current_sphere",
227+
"current_area",
228+
],
229+
)
230+
231+
atlases = load_resource('atlases')
232+
resample.inputs.current_sphere = [
233+
str(atlases / 'mcribs' / 'lh.sphere.reg.dHCP42.surf.gii'),
234+
str(atlases / 'mcribs' / 'rh.sphere.reg.dHCP42.surf.gii'),
235+
] * 3
236+
# current area: FreeSurfer directory midthickness
237+
resample.inputs.new_sphere = [
238+
str(atlases / 'dHCP' / 'dHCP.week42.L.sphere.surf.gii'),
239+
str(atlases / 'dHCP' / 'dHCP.week42.R.sphere.surf.gii'),
240+
] * 3
241+
# new area: dHCP midthickness
242+
243+
resample.inputs.out_file = [
244+
f"space-fsLR_hemi-{h}_den-{grayord_density}_{morph}.shape.gii"
245+
# Order: curv-L, curv-R, sulc-L, sulc-R, thickness-L, thickness-R
246+
for morph in ('curv', 'sulc', 'thickness')
247+
for h in "LR"
248+
]
249+
250+
gen_cifti = pe.MapNode(
251+
GenerateDScalar(
252+
grayordinates=grayord_density,
253+
),
254+
iterfield=['scalar_name', 'scalar_surfs'],
255+
name="gen_cifti",
256+
)
257+
gen_cifti.inputs.scalar_name = ['curv', 'sulc', 'thickness']
258+
259+
# fmt: off
260+
workflow.connect([
261+
(inputnode, get_surfaces, [
262+
('subject_id', 'subject_id'),
263+
('subjects_dir', 'subjects_dir'),
264+
]),
265+
(get_surfaces, surfmorph_list, [
266+
(('curv', _sorted_by_basename), 'in1'),
267+
(('sulc', _sorted_by_basename), 'in2'),
268+
(('thickness', _sorted_by_basename), 'in3'),
269+
]),
270+
# (surfmorph_list, surf2surf, [('out', 'source_file')]),
271+
# (surf2surf, resample, [('out_file', 'in_file')]),
272+
(resample, gen_cifti, [
273+
(("out_file", _collate), "scalar_surfs")]),
274+
(gen_cifti, outputnode, [("out_file", "cifti_morph"),
275+
("out_metadata", "cifti_metadata")]),
276+
])
277+
# fmt: on
278+
279+
return workflow

0 commit comments

Comments
 (0)