Skip to content

Commit c4ceb2e

Browse files
authored
Merge pull request #402 from oesteban/enh/skullstrip-wf-no-fsl
ENH: Add a new ``Binarize`` interface using nibabel
2 parents 6c0cbdd + 335f742 commit c4ceb2e

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

niworkflows/anat/skullstrip.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,39 @@
1-
# -*- coding: utf-8 -*-
21
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
32
# vi: set ft=python sts=4 ts=4 sw=4 et:
4-
from __future__ import absolute_import, division, print_function, unicode_literals
5-
from nipype.interfaces import ants, afni, fsl, utility as niu
3+
"""Brain extraction workflows."""
4+
from nipype.interfaces import afni, utility as niu
65
from nipype.pipeline import engine as pe
6+
from ..interfaces.nibabel import Binarize
7+
from ..interfaces.fixes import FixN4BiasFieldCorrection as N4BiasFieldCorrection
78

89

910
def afni_wf(name='AFNISkullStripWorkflow', unifize=False, n4_nthreads=1):
1011
"""
11-
Skull-stripping workflow
12+
Create a skull-stripping workflow based on AFNI's tools.
1213
13-
Originally derived from the `codebase of the
14-
QAP <https://github.com/preprocessed-connectomes-project/\
15-
quality-assessment-protocol/blob/master/qap/anatomical_preproc.py#L105>`_.
14+
Originally derived from the `codebase of the QAP
15+
<https://github.com/preprocessed-connectomes-project/quality-assessment-protocol/blob/master/qap/anatomical_preproc.py#L105>`_.
1616
Now, this workflow includes :abbr:`INU (intensity non-uniformity)` correction
1717
using the N4 algorithm and (optionally) intensity harmonization using
1818
ANFI's ``3dUnifize``.
1919
20-
2120
"""
22-
2321
workflow = pe.Workflow(name=name)
2422
inputnode = pe.Node(niu.IdentityInterface(fields=['in_file']),
2523
name='inputnode')
2624
outputnode = pe.Node(niu.IdentityInterface(
2725
fields=['bias_corrected', 'out_file', 'out_mask', 'bias_image']), name='outputnode')
2826

2927
inu_n4 = pe.Node(
30-
ants.N4BiasFieldCorrection(dimension=3, save_bias=True, num_threads=n4_nthreads,
31-
copy_header=True),
28+
N4BiasFieldCorrection(dimension=3, save_bias=True, num_threads=n4_nthreads,
29+
rescale_intensities=True, copy_header=True),
3230
n_procs=n4_nthreads,
3331
name='inu_n4')
3432

3533
sstrip = pe.Node(afni.SkullStrip(outputtype='NIFTI_GZ'), name='skullstrip')
3634
sstrip_orig_vol = pe.Node(afni.Calc(
3735
expr='a*step(b)', outputtype='NIFTI_GZ'), name='sstrip_orig_vol')
38-
binarize = pe.Node(fsl.Threshold(args='-bin', thresh=1.e-3), name='binarize')
36+
binarize = pe.Node(Binarize(thresh_low=0.0), name='binarize')
3937

4038
if unifize:
4139
# Add two unifize steps, pre- and post- skullstripping.
@@ -64,7 +62,7 @@ def afni_wf(name='AFNISkullStripWorkflow', unifize=False, n4_nthreads=1):
6462
(sstrip, sstrip_orig_vol, [('out_file', 'in_file_b')]),
6563
(inputnode, inu_n4, [('in_file', 'input_image')]),
6664
(sstrip_orig_vol, binarize, [('out_file', 'in_file')]),
67-
(binarize, outputnode, [('out_file', 'out_mask')]),
65+
(binarize, outputnode, [('out_mask', 'out_mask')]),
6866
(inu_n4, outputnode, [('bias_image', 'bias_image')]),
6967
])
7068
return workflow

niworkflows/interfaces/nibabel.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
"""Nibabel-based interfaces."""
4+
5+
import nibabel as nb
6+
from nipype import logging
7+
from nipype.utils.filemanip import fname_presuffix
8+
from nipype.interfaces.base import (
9+
traits, TraitedSpec, BaseInterfaceInputSpec, File,
10+
SimpleInterface
11+
)
12+
13+
IFLOGGER = logging.getLogger('nipype.interface')
14+
15+
16+
class _BinarizeInputSpec(BaseInterfaceInputSpec):
17+
in_file = File(exists=True, mandatory=True, desc='input image')
18+
thresh_low = traits.Float(mandatory=True,
19+
desc='non-inclusive lower threshold')
20+
21+
22+
class _BinarizeOutputSpec(TraitedSpec):
23+
out_file = File(exists=True, desc='masked file')
24+
out_mask = File(exists=True, desc='output mask')
25+
26+
27+
class Binarize(SimpleInterface):
28+
"""Binarizes the input image applying the given thresholds."""
29+
30+
input_spec = _BinarizeInputSpec
31+
output_spec = _BinarizeOutputSpec
32+
33+
def _run_interface(self, runtime):
34+
img = nb.load(self.inputs.in_file)
35+
36+
self._results['out_file'] = fname_presuffix(
37+
self.inputs.in_file, suffix='_masked', newpath=runtime.cwd)
38+
self._results['out_mask'] = fname_presuffix(
39+
self.inputs.in_file, suffix='_mask', newpath=runtime.cwd)
40+
41+
data = img.get_fdata()
42+
mask = data > self.inputs.thresh_low
43+
data[~mask] = 0.0
44+
masked = img.__class__(data, img.affine, img.header)
45+
masked.to_filename(self._results['out_file'])
46+
47+
img.header.set_data_dtype('uint8')
48+
maskimg = img.__class__(mask.astype('uint8'), img.affine,
49+
img.header)
50+
maskimg.to_filename(self._results['out_mask'])
51+
52+
return runtime
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""test nibabel interfaces."""
2+
import os
3+
import numpy as np
4+
import nibabel as nb
5+
6+
from ..nibabel import Binarize
7+
8+
9+
def test_Binarize(tmp_path):
10+
"""Test binarization interface."""
11+
os.chdir(str(tmp_path))
12+
13+
mask = np.zeros((20, 20, 20), dtype=bool)
14+
mask[5:15, 5:15, 5:15] = bool
15+
16+
data = np.zeros_like(mask, dtype='float32')
17+
data[mask] = np.random.gamma(2, size=mask.sum())
18+
19+
in_file = tmp_path / 'input.nii.gz'
20+
nb.Nifti1Image(data, np.eye(4), None).to_filename(str(in_file))
21+
22+
binif = Binarize(thresh_low=0.0, in_file=str(in_file)).run()
23+
newmask = nb.load(binif.outputs.out_mask).get_fdata().astype(bool)
24+
assert np.all(mask == newmask)

0 commit comments

Comments
 (0)