Skip to content

Commit 216db0d

Browse files
authored
Merge pull request #3011 from oesteban/fix/N4-updates
ENH: Add ``--rescale-intensities`` and name_source to N4BiasFieldCorrection
2 parents beefe81 + f37b65e commit 216db0d

File tree

2 files changed

+52
-48
lines changed

2 files changed

+52
-48
lines changed

nipype/interfaces/ants/segmentation.py

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import os
99
from ...external.due import BibTeX
10-
from ...utils.filemanip import split_filename, copyfile, which
10+
from ...utils.filemanip import split_filename, copyfile, which, fname_presuffix
1111
from ..base import TraitedSpec, File, traits, InputMultiPath, OutputMultiPath, isdefined
1212
from .base import ANTSCommand, ANTSCommandInputSpec
1313

@@ -274,18 +274,20 @@ class N4BiasFieldCorrectionInputSpec(ANTSCommandInputSpec):
274274
argstr='--input-image %s',
275275
mandatory=True,
276276
desc=('input for bias correction. Negative values or values close to '
277-
'zero should be processed prior to correction'))
277+
'zero should be processed prior to correction'))
278278
mask_image = File(
279279
argstr='--mask-image %s',
280280
desc=('image to specify region to perform final bias correction in'))
281281
weight_image = File(
282282
argstr='--weight-image %s',
283283
desc=('image for relative weighting (e.g. probability map of the white '
284-
'matter) of voxels during the B-spline fitting. '))
284+
'matter) of voxels during the B-spline fitting. '))
285285
output_image = traits.Str(
286286
argstr='--output %s',
287287
desc='output file name',
288-
genfile=True,
288+
name_source=['input_image'],
289+
name_template='%s_corrected',
290+
keep_extension=True,
289291
hash_files=False)
290292
bspline_fitting_distance = traits.Float(argstr="--bspline-fitting %s")
291293
bspline_order = traits.Int(requires=['bspline_fitting_distance'])
@@ -306,6 +308,15 @@ class N4BiasFieldCorrectionInputSpec(ANTSCommandInputSpec):
306308
usedefault=True,
307309
desc='copy headers of the original image into the '
308310
'output (corrected) file')
311+
rescale_intensities = traits.Bool(
312+
False, usedefault=True, argstr='-r', min_ver='2.1.0',
313+
desc="""\
314+
[NOTE: Only ANTs>=2.1.0]
315+
At each iteration, a new intensity mapping is calculated and applied but there
316+
is nothing which constrains the new intensity range to be within certain values.
317+
The result is that the range can "drift" from the original at each iteration.
318+
This option rescales to the [min,max] range of the original image intensities
319+
within the user-specified mask.""")
309320

310321

311322
class N4BiasFieldCorrectionOutputSpec(TraitedSpec):
@@ -314,7 +325,10 @@ class N4BiasFieldCorrectionOutputSpec(TraitedSpec):
314325

315326

316327
class N4BiasFieldCorrection(ANTSCommand):
317-
"""N4 is a variant of the popular N3 (nonparameteric nonuniform normalization)
328+
"""
329+
Bias field correction.
330+
331+
N4 is a variant of the popular N3 (nonparameteric nonuniform normalization)
318332
retrospective bias correction algorithm. Based on the assumption that the
319333
corruption of the low frequency bias field can be modeled as a convolution of
320334
the intensity histogram by a Gaussian, the basic algorithmic protocol is to
@@ -373,28 +387,14 @@ class N4BiasFieldCorrection(ANTSCommand):
373387
input_spec = N4BiasFieldCorrectionInputSpec
374388
output_spec = N4BiasFieldCorrectionOutputSpec
375389

376-
def _gen_filename(self, name):
377-
if name == 'output_image':
378-
output = self.inputs.output_image
379-
if not isdefined(output):
380-
_, name, ext = split_filename(self.inputs.input_image)
381-
output = name + '_corrected' + ext
382-
return output
383-
384-
if name == 'bias_image':
385-
output = self.inputs.bias_image
386-
if not isdefined(output):
387-
_, name, ext = split_filename(self.inputs.input_image)
388-
output = name + '_bias' + ext
389-
return output
390-
return None
390+
def __init__(self, *args, **kwargs):
391+
"""Instantiate the N4BiasFieldCorrection interface."""
392+
self._out_bias_file = None
393+
super(N4BiasFieldCorrection, self).__init__(*args, **kwargs)
391394

392395
def _format_arg(self, name, trait_spec, value):
393-
if ((name == 'output_image') and
394-
(self.inputs.save_bias or isdefined(self.inputs.bias_image))):
395-
bias_image = self._gen_filename('bias_image')
396-
output = self._gen_filename('output_image')
397-
newval = '[ %s, %s ]' % (output, bias_image)
396+
if name == 'output_image' and self._out_bias_file:
397+
newval = '[ %s, %s ]' % (value, self._out_bias_file)
398398
return trait_spec.argstr % newval
399399

400400
if name == 'bspline_fitting_distance':
@@ -418,38 +418,35 @@ def _format_arg(self, name, trait_spec, value):
418418
name, trait_spec, value)
419419

420420
def _parse_inputs(self, skip=None):
421-
if skip is None:
422-
skip = []
423-
skip += ['save_bias', 'bias_image']
421+
skip = (skip or []) + ['save_bias', 'bias_image']
422+
self._out_bias_file = None
423+
if self.inputs.save_bias or isdefined(self.inputs.bias_image):
424+
bias_image = self.inputs.bias_image
425+
if not isdefined(bias_image):
426+
bias_image = fname_presuffix(os.path.basename(self.inputs.input_image),
427+
suffix='_bias')
428+
self._out_bias_file = bias_image
424429
return super(N4BiasFieldCorrection, self)._parse_inputs(skip=skip)
425430

426431
def _list_outputs(self):
427-
outputs = self._outputs().get()
428-
outputs['output_image'] = os.path.abspath(
429-
self._gen_filename('output_image'))
432+
outputs = super(N4BiasFieldCorrection, self)._list_outputs()
430433

431-
if self.inputs.save_bias or isdefined(self.inputs.bias_image):
432-
outputs['bias_image'] = os.path.abspath(
433-
self._gen_filename('bias_image'))
434-
return outputs
434+
# Fix headers
435+
if self.inputs.copy_header:
436+
self._copy_header(outputs['output_image'])
435437

436-
def _run_interface(self, runtime, correct_return_codes=(0, )):
437-
runtime = super(N4BiasFieldCorrection, self)._run_interface(
438-
runtime, correct_return_codes)
439-
440-
if self.inputs.copy_header and runtime.returncode in correct_return_codes:
441-
self._copy_header(self._gen_filename('output_image'))
442-
if self.inputs.save_bias or isdefined(self.inputs.bias_image):
443-
self._copy_header(self._gen_filename('bias_image'))
444-
445-
return runtime
438+
if self._out_bias_file:
439+
outputs['bias_image'] = os.path.abspath(self._out_bias_file)
440+
if self.inputs.copy_header:
441+
self._copy_header(outputs['bias_image'])
442+
return outputs
446443

447444
def _copy_header(self, fname):
448-
"""Copy header from input image to an output image"""
445+
"""Copy header from input image to an output image."""
449446
import nibabel as nb
450447
in_img = nb.load(self.inputs.input_image)
451448
out_img = nb.load(fname, mmap=False)
452-
new_img = out_img.__class__(out_img.get_data(), in_img.affine,
449+
new_img = out_img.__class__(out_img.get_fdata(), in_img.affine,
453450
in_img.header)
454451
new_img.set_data_dtype(out_img.get_data_dtype())
455452
new_img.to_filename(fname)

nipype/interfaces/ants/tests/test_auto_N4BiasFieldCorrection.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ def test_N4BiasFieldCorrection_inputs():
4141
),
4242
output_image=dict(
4343
argstr='--output %s',
44-
genfile=True,
4544
hash_files=False,
45+
keep_extension=True,
46+
name_source=['input_image'],
47+
name_template='%s_corrected',
48+
),
49+
rescale_intensities=dict(
50+
argstr='-r',
51+
min_ver='2.1.0',
52+
usedefault=True,
4653
),
4754
save_bias=dict(
4855
mandatory=True,

0 commit comments

Comments
 (0)