Skip to content

Commit c4d56cf

Browse files
committed
RF: Move mask/aseg processing into preproc workflow
1 parent 569f6dd commit c4d56cf

File tree

3 files changed

+90
-44
lines changed

3 files changed

+90
-44
lines changed

nibabies/workflows/anatomical/base.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,6 @@ def init_infant_anat_wf(
217217
longitudinal=longitudinal,
218218
omp_nthreads=omp_nthreads,
219219
sloppy=sloppy,
220-
precomputed_mask=bool(precomp_mask),
221-
precomputed_aseg=bool(precomp_aseg),
222220
name="t1w_template_wf",
223221
)
224222

@@ -233,7 +231,11 @@ def init_infant_anat_wf(
233231

234232
# Clean up each anatomical template
235233
# Denoise, INU, + Clipping
236-
t1w_preproc_wf = init_anat_preproc_wf(name="t1w_preproc_wf")
234+
t1w_preproc_wf = init_anat_preproc_wf(
235+
precomputed_mask=bool(precomp_mask),
236+
precomputed_aseg=bool(precomp_aseg),
237+
name="t1w_preproc_wf",
238+
)
237239
t2w_preproc_wf = init_anat_preproc_wf(name="t2w_preproc_wf")
238240

239241
if skull_strip_mode != "force":
@@ -343,10 +345,10 @@ def init_infant_anat_wf(
343345

344346
if precomp_mask:
345347
# Ensure the mask is conformed along with the T1w
346-
t1w_template_wf.inputs.inputnode.anat_mask = precomp_mask
348+
t1w_preproc_wf.inputs.inputnode.in_mask = precomp_mask
347349
# fmt:off
348350
wf.connect([
349-
(t1w_template_wf, coregistration_wf, [("outputnode.anat_mask", "inputnode.in_mask")]),
351+
(t1w_preproc_wf, coregistration_wf, [("outputnode.anat_mask", "inputnode.in_mask")]),
350352
(t2w_preproc_wf, coregistration_wf, [("outputnode.anat_preproc", "inputnode.in_t2w")])
351353
])
352354
# fmt:on
@@ -376,8 +378,8 @@ def init_infant_anat_wf(
376378

377379
if precomp_aseg:
378380
# Ensure the segmentation is conformed along with the T1w
379-
t1w_template_wf.inputs.inputnode.anat_aseg = precomp_aseg
380-
wf.connect(t1w_template_wf, "outputnode.anat_aseg", anat_seg_wf, "inputnode.anat_aseg")
381+
t1w_preproc_wf.inputs.inputnode.in_aseg = precomp_aseg
382+
wf.connect(t1w_preproc_wf, "outputnode.anat_aseg", anat_seg_wf, "inputnode.anat_aseg")
381383

382384
if not freesurfer:
383385
return wf
@@ -395,14 +397,46 @@ def init_infant_anat_wf(
395397
age_months=age_months,
396398
use_aseg=use_aseg,
397399
)
400+
398401
elif config.workflow.surface_recon_method == 'mcribs':
402+
from nipype.interfaces.ants import DenoiseImage
403+
399404
from .surfaces import init_mcribs_surface_recon_wf
400405

406+
# Denoise raw T2w, since using the template / preproc resulted in intersection errors
407+
denoise_raw_t2w = pe.Node(
408+
DenoiseImage(dimension=3, noise_model="Rician"), name='denoise_raw_t2w'
409+
)
410+
401411
surface_recon_wf = init_mcribs_surface_recon_wf(
412+
omp_nthreads=omp_nthreads,
402413
use_aseg=bool(precomp_aseg),
414+
use_mask=bool(precomp_mask),
403415
mcribs_dir=str(config.execution.mcribs_dir), # Needed to preserve runs
404416
)
405417

418+
# Transformed gives
419+
if precomp_aseg:
420+
surface_recon_wf.inputs.inputnode.ants_segs = precomp_aseg
421+
if precomp_mask:
422+
surface_recon_wf.inputs.inputnode.anat_mask = precomp_mask
423+
# fmt:off
424+
wf.connect([
425+
(inputnode, denoise_raw_t2w, [('t2w', 'input_image')]),
426+
(denoise_raw_t2w, surface_recon_wf, [('output_image', 'inputnode.t2w')])
427+
])
428+
# fmt:on
429+
430+
if config.workflow.surface_recon_method in ('freesurfer', 'infantfs'):
431+
# fmt:off
432+
wf.connect([
433+
(t2w_preproc_wf, surface_recon_wf, [
434+
("outputnode.anat_preproc", "inputnode.t2w")]),
435+
(anat_seg_wf, surface_recon_wf, [
436+
("outputnode.anat_aseg", "inputnode.ants_segs")]),
437+
])
438+
# fmt:on
439+
406440
# Anatomical ribbon file using HCP signed-distance volume method
407441
anat_ribbon_wf = init_anat_ribbon_wf()
408442

@@ -411,11 +445,6 @@ def init_infant_anat_wf(
411445
(inputnode, surface_recon_wf, [
412446
("subject_id", "inputnode.subject_id"),
413447
("subjects_dir", "inputnode.subjects_dir")]),
414-
(t2w_preproc_wf, surface_recon_wf, [
415-
("outputnode.anat_preproc", "inputnode.t2w")]),
416-
(anat_seg_wf, surface_recon_wf, [
417-
("outputnode.anat_aseg", "inputnode.ants_segs"),
418-
]),
419448
(t1w_template_wf, surface_recon_wf, [
420449
("outputnode.anat_ref", "inputnode.t1w"),
421450
]),

nibabies/workflows/anatomical/preproc.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55

66
def init_anat_preproc_wf(
7-
*, bspline_fitting_distance: int = 200, name: str = "anat_preproc_wf"
7+
*,
8+
bspline_fitting_distance: int = 200,
9+
precomputed_mask: bool = False,
10+
precomputed_aseg: bool = False,
11+
name: str = "anat_preproc_wf",
812
) -> LiterateWorkflow:
913
"""Polish up raw anatomical data.
1014
@@ -27,12 +31,21 @@ def init_anat_preproc_wf(
2731
Preprocessed anatomical image (Denoising/INU/Clipping)
2832
"""
2933
from nipype.interfaces.ants import DenoiseImage, N4BiasFieldCorrection
30-
from niworkflows.interfaces.nibabel import IntensityClip
34+
from niworkflows.interfaces.header import ValidateImage
35+
from niworkflows.interfaces.nibabel import IntensityClip, RegridToZooms
3136

3237
wf = LiterateWorkflow(name=name)
33-
inputnode = pe.Node(niu.IdentityInterface(fields=["in_anat"]), name="inputnode")
34-
outputnode = pe.Node(niu.IdentityInterface(fields=["anat_preproc"]), name="outputnode")
38+
inputnode = pe.Node(
39+
niu.IdentityInterface(fields=["in_anat", "in_mask", "in_aseg"]),
40+
name="inputnode",
41+
)
42+
outputnode = pe.Node(
43+
niu.IdentityInterface(fields=["anat_preproc", "anat_mask", "anat_aseg"]),
44+
name="outputnode",
45+
)
3546

47+
# validate image
48+
validate = pe.Node(ValidateImage(), name="anat_validate", run_without_submitting=True)
3649
clip = pe.Node(IntensityClip(p_min=10.0, p_max=99.5), name="clip")
3750
denoise = pe.Node(DenoiseImage(dimension=3, noise_model="Rician"), name="denoise")
3851
n4_correct = pe.Node(
@@ -51,13 +64,43 @@ def init_anat_preproc_wf(
5164

5265
final_clip = pe.Node(IntensityClip(p_min=5.0, p_max=99.5), name="final_clip")
5366

67+
if precomputed_mask:
68+
validate_mask = pe.Node(ValidateImage(), name="validate_mask")
69+
regrid_mask = pe.Node(RegridToZooms(), name="regrid_mask")
70+
# fmt:off
71+
wf.connect([
72+
(inputnode, validate_mask, [("in_mask", "in_file")]),
73+
(validate_mask, regrid_mask, [("out_file", "in_file")]),
74+
(final_clip, regrid_mask, [(("out_file", _get_zooms), "zooms")]),
75+
(regrid_mask, outputnode, [("out_file", "anat_mask")]),
76+
])
77+
# fmt:on
78+
if precomputed_aseg:
79+
validate_aseg = pe.Node(ValidateImage(), name="validate_aseg")
80+
regrid_aseg = pe.Node(RegridToZooms(), name="regrid_aseg")
81+
# fmt:off
82+
wf.connect([
83+
(inputnode, validate_aseg, [("in_aseg", "in_file")]),
84+
(validate_aseg, regrid_aseg, [("out_file", "in_file")]),
85+
(final_clip, regrid_aseg, [(("out_file", _get_zooms), "zooms")]),
86+
(regrid_aseg, outputnode, [("out_file", "anat_aseg")]),
87+
])
88+
# fmt:on
89+
5490
# fmt:off
5591
wf.connect([
56-
(inputnode, clip, [("in_anat", "in_file")]),
92+
(inputnode, validate, [("in_anat", "in_file")]),
93+
(validate, clip, [("out_file", "in_file")]),
5794
(clip, denoise, [("out_file", "input_image")]),
5895
(denoise, n4_correct, [("output_image", "input_image")]),
5996
(n4_correct, final_clip, [("output_image", "in_file")]),
6097
(final_clip, outputnode, [("out_file", "anat_preproc")]),
6198
])
6299
# fmt:on
63100
return wf
101+
102+
103+
def _get_zooms(in_file):
104+
import nibabel as nb
105+
106+
return tuple(nb.load(in_file).header.get_zooms()[:3])

nibabies/workflows/anatomical/template.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ def init_anat_template_wf(
1212
longitudinal: bool = False,
1313
bspline_fitting_distance: int = 200,
1414
sloppy: bool = False,
15-
precomputed_mask: bool = False,
16-
precomputed_aseg: bool = False,
1715
name: str = "anat_template_wf",
1816
) -> LiterateWorkflow:
1917
"""
@@ -62,6 +60,7 @@ def init_anat_template_wf(
6260
from nipype.interfaces.image import Reorient
6361
from niworkflows.interfaces.freesurfer import PatchedLTAConvert as LTAConvert
6462
from niworkflows.interfaces.freesurfer import StructuralReference
63+
from niworkflows.interfaces.header import ValidateImage
6564
from niworkflows.interfaces.images import Conform, TemplateDimensions
6665
from niworkflows.interfaces.nibabel import IntensityClip
6766
from niworkflows.interfaces.nitransforms import ConcatenateXFMs
@@ -90,9 +89,6 @@ def init_anat_template_wf(
9089
"anat_valid_list",
9190
"anat_realign_xfm",
9291
"out_report",
93-
# only if precomputed inputs
94-
"anat_mask",
95-
"anat_aseg",
9692
],
9793
),
9894
name="outputnode",
@@ -115,28 +111,6 @@ def init_anat_template_wf(
115111
# fmt:on
116112

117113
if num_files == 1:
118-
if precomputed_mask:
119-
anat_mask_conform = pe.Node(Conform(), name='anat_mask_conform')
120-
# fmt:off
121-
wf.connect([
122-
(inputnode, anat_mask_conform, [('anat_mask', 'in_file')]),
123-
(anat_ref_dimensions, anat_mask_conform, [
124-
('target_zooms', 'target_zooms'),
125-
('target_shape', 'target_shape')]),
126-
(anat_mask_conform, outputnode, [('out_file', 'anat_mask')]),
127-
])
128-
# fmt:on
129-
if precomputed_aseg:
130-
anat_aseg_conform = pe.Node(Conform(), name='anat_aseg_conform')
131-
# fmt:off
132-
wf.connect([
133-
(inputnode, anat_aseg_conform, [('anat_aseg', 'in_file')]),
134-
(anat_ref_dimensions, anat_aseg_conform, [
135-
('target_zooms', 'target_zooms'),
136-
('target_shape', 'target_shape')]),
137-
(anat_aseg_conform, outputnode, [('out_file', 'anat_aseg')])
138-
])
139-
# fmt:on
140114
get1st = pe.Node(niu.Select(index=[0]), name="get1st")
141115
outputnode.inputs.anat_realign_xfm = [
142116
get_file("smriprep", "data/itkIdentityTransform.txt")

0 commit comments

Comments
 (0)