|
28 | 28 | FixHeaderApplyTransforms as ApplyTransforms,
|
29 | 29 | )
|
30 | 30 | from ..interfaces.utils import CopyXForm
|
| 31 | +from ..interfaces.nibabel import Binarize |
31 | 32 |
|
32 | 33 |
|
33 | 34 | ATROPOS_MODELS = {
|
@@ -613,6 +614,164 @@ def init_atropos_wf(name='atropos_wf',
|
613 | 614 | return wf
|
614 | 615 |
|
615 | 616 |
|
| 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 | + |
616 | 775 | def _pop(in_files):
|
617 | 776 | if isinstance(in_files, (list, tuple)):
|
618 | 777 | return in_files[0]
|
|
0 commit comments