Skip to content

Commit c88c521

Browse files
authored
Merge pull request #435 from adelavega/enh/n4_only_workflow
ENH: Add ``n4_only`` workflow -- to skip brain extraction
2 parents ce82d76 + 298f067 commit c88c521

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

niworkflows/anat/ants.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
FixHeaderApplyTransforms as ApplyTransforms,
2929
)
3030
from ..interfaces.utils import CopyXForm
31+
from ..interfaces.nibabel import Binarize
3132

3233

3334
ATROPOS_MODELS = {
@@ -613,6 +614,164 @@ def init_atropos_wf(name='atropos_wf',
613614
return wf
614615

615616

617+
def init_n4_only_wf(atropos_model=None,
618+
atropos_refine=True,
619+
atropos_use_random_seed=True,
620+
bids_suffix='T1w',
621+
mem_gb=3.0,
622+
name='n4_only_wf',
623+
omp_nthreads=None
624+
):
625+
"""
626+
Build a workflow to sidetrack brain extraction on skull-stripped datasets.
627+
628+
An alternative workflow to "init_brain_extraction_wf", for anatomical
629+
images which have already been brain extracted.
630+
631+
1. Creates brain mask assuming all zero voxels are outside the brain
632+
2. Applies N4 bias field correction
633+
3. (Optional) apply ATROPOS and massage its outputs
634+
4. Use results from 3 to refine N4 bias field correction
635+
636+
Workflow Graph
637+
.. workflow::
638+
:graph2use: orig
639+
:simple_form: yes
640+
641+
from niworkflows.anat.ants import init_n4_only_wf
642+
wf = init_n4_only_wf()
643+
644+
Parameters
645+
----------
646+
omp_nthreads : int
647+
Maximum number of threads an individual process may use
648+
mem_gb : float
649+
Estimated peak memory consumption of the most hungry nodes
650+
bids_suffix : str
651+
Sequence type of the first input image. For a list of acceptable values see
652+
https://bids-specification.readthedocs.io/en/latest/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#anatomy-imaging-data
653+
atropos_refine : bool
654+
Enables or disables the whole ATROPOS sub-workflow
655+
atropos_use_random_seed : bool
656+
Whether ATROPOS should generate a random seed based on the
657+
system's clock
658+
atropos_model : tuple or None
659+
Allows to specify a particular segmentation model, overwriting
660+
the defaults based on ``bids_suffix``
661+
name : str, optional
662+
Workflow name (default: ``'n4_only_wf'``).
663+
664+
Inputs
665+
------
666+
in_files
667+
List of input anatomical images to be bias corrected,
668+
typically T1-weighted.
669+
If a list of anatomical images is provided, subsequently
670+
specified images are used during the segmentation process.
671+
However, only the first image is used in the registration
672+
of priors.
673+
Our suggestion would be to specify the T1w as the first image.
674+
675+
Outputs
676+
-------
677+
out_file
678+
:abbr:`INU (intensity non-uniformity)`-corrected ``in_files``
679+
out_mask
680+
Calculated brain mask
681+
bias_corrected
682+
Same as "out_file", provided for consistency with brain extraction
683+
bias_image
684+
The :abbr:`INU (intensity non-uniformity)` field estimated for each
685+
input in ``in_files``
686+
out_segm
687+
Output segmentation by ATROPOS
688+
out_tpms
689+
Output :abbr:`TPMs (tissue probability maps)` by ATROPOS
690+
691+
"""
692+
wf = pe.Workflow(name)
693+
694+
inputnode = pe.Node(niu.IdentityInterface(fields=['in_files', 'in_mask']),
695+
name='inputnode')
696+
697+
outputnode = pe.Node(niu.IdentityInterface(
698+
fields=['out_file', 'out_mask', 'bias_corrected', 'bias_image',
699+
'out_segm', 'out_tpms']),
700+
name='outputnode')
701+
702+
# Create brain mask
703+
thr_brainmask = pe.Node(Binarize(thresh_low=2), name='binarize')
704+
705+
# INU correction
706+
inu_n4_final = pe.MapNode(
707+
N4BiasFieldCorrection(
708+
dimension=3, save_bias=True, copy_header=True,
709+
n_iterations=[50] * 5, convergence_threshold=1e-7, shrink_factor=4,
710+
bspline_fitting_distance=200),
711+
n_procs=omp_nthreads, name='inu_n4_final', iterfield=['input_image'])
712+
713+
# Check ANTs version
714+
try:
715+
inu_n4_final.inputs.rescale_intensities = True
716+
except ValueError:
717+
warn("The installed ANTs version too old. Please consider upgrading to "
718+
"2.1.0 or greater.", DeprecationWarning)
719+
720+
wf.connect([
721+
(inputnode, inu_n4_final, [('in_files', 'input_image')]),
722+
(inputnode, thr_brainmask, [(('in_files', _pop), 'in_file')]),
723+
(thr_brainmask, outputnode, [('out_mask', 'out_mask')]),
724+
(inu_n4_final, outputnode, [('output_image', 'out_file')]),
725+
(inu_n4_final, outputnode, [('output_image', 'bias_corrected')]),
726+
(inu_n4_final, outputnode, [('bias_image', 'bias_image')])
727+
])
728+
729+
# If atropos refine, do in4 twice
730+
if atropos_refine:
731+
# Morphological dilation, radius=2
732+
dil_brainmask = pe.Node(ImageMath(operation='MD', op2='2'),
733+
name='dil_brainmask')
734+
# Get largest connected component
735+
get_brainmask = pe.Node(ImageMath(operation='GetLargestComponent'),
736+
name='get_brainmask')
737+
atropos_model = atropos_model or list(
738+
ATROPOS_MODELS[bids_suffix].values())
739+
atropos_wf = init_atropos_wf(
740+
use_random_seed=atropos_use_random_seed,
741+
omp_nthreads=omp_nthreads,
742+
mem_gb=mem_gb,
743+
in_segmentation_model=atropos_model,
744+
)
745+
sel_wm = pe.Node(niu.Select(index=atropos_model[-1] - 1), name='sel_wm',
746+
run_without_submitting=True)
747+
748+
inu_n4 = pe.MapNode(
749+
N4BiasFieldCorrection(
750+
dimension=3, save_bias=False, copy_header=True,
751+
n_iterations=[50] * 4, convergence_threshold=1e-7,
752+
shrink_factor=4, bspline_fitting_distance=200),
753+
n_procs=omp_nthreads, name='inu_n4', iterfield=['input_image'])
754+
755+
wf.connect([
756+
(inputnode, inu_n4, [('in_files', 'input_image')]),
757+
(inu_n4, atropos_wf, [
758+
('output_image', 'inputnode.in_files')]),
759+
(thr_brainmask, atropos_wf, [
760+
('out_mask', 'inputnode.in_mask')]),
761+
(thr_brainmask, dil_brainmask, [('out_mask', 'op1')]),
762+
(dil_brainmask, get_brainmask, [('output_image', 'op1')]),
763+
(get_brainmask, atropos_wf, [
764+
('output_image', 'inputnode.in_mask_dilated')]),
765+
(atropos_wf, sel_wm, [('outputnode.out_tpms', 'inlist')]),
766+
(sel_wm, inu_n4_final, [('out', 'weight_image')]),
767+
(atropos_wf, outputnode, [
768+
('outputnode.out_segm', 'out_segm'),
769+
('outputnode.out_tpms', 'out_tpms')]),
770+
])
771+
772+
return wf
773+
774+
616775
def _pop(in_files):
617776
if isinstance(in_files, (list, tuple)):
618777
return in_files[0]

0 commit comments

Comments
 (0)