nipype.interfaces.mixins.fixheader module

CopyHeaderInterface

Link to code

Bases: BaseInterface

Copy headers if the copy_header input is True

This interface mixin adds a post-run hook that allows for copying an input header to an output file. The subclass should specify a _copy_header_map that maps the output image to the input image whose header should be copied.

This feature is intended for tools that are intended to adjust voxel data without modifying the header, but for some reason do not reliably preserve the header.

Here we show an example interface that takes advantage of the mixin by simply setting the data block:

>>> import os
>>> import numpy as np
>>> import nibabel as nb
>>> from nipype.interfaces.base import SimpleInterface, TraitedSpec, File
>>> from nipype.interfaces.mixins import CopyHeaderInputSpec, CopyHeaderInterface
>>> class ZerofileInputSpec(CopyHeaderInputSpec):
...     in_file = File(mandatory=True, exists=True)
>>> class ZerofileOutputSpec(TraitedSpec):
...     out_file = File()
>>> class ZerofileInterface(SimpleInterface, CopyHeaderInterface):
...     input_spec = ZerofileInputSpec
...     output_spec = ZerofileOutputSpec
...     _copy_header_map = {'out_file': 'in_file'}
...
...     def _run_interface(self, runtime):
...         img = nb.load(self.inputs.in_file)
...         # Just set the data. Let the CopyHeaderInterface mixin fix the affine and header.
...         nb.Nifti1Image(np.zeros(img.shape, dtype=np.uint8), None).to_filename('out.nii')
...         self._results = {'out_file':  os.path.abspath('out.nii')}
...         return runtime

Consider a file of all ones and a non-trivial affine:

>>> in_file = 'test.nii'
>>> nb.Nifti1Image(np.ones((5,5,5), dtype=np.int16),
...                affine=np.diag((4, 3, 2, 1))).to_filename(in_file)

The default behavior would produce a file with similar data:

>>> res = ZerofileInterface(in_file=in_file).run()
>>> out_img = nb.load(res.outputs.out_file)
>>> out_img.shape
(5, 5, 5)
>>> np.all(out_img.get_fdata() == 0)
True

An updated data type:

>>> out_img.get_data_dtype()
dtype('uint8')

But a different affine:

>>> np.array_equal(out_img.affine, np.diag((4, 3, 2, 1)))
False

With copy_header=True, then the affine is also equal:

>>> res = ZerofileInterface(in_file=in_file, copy_header=True).run()
>>> out_img = nb.load(res.outputs.out_file)
>>> np.array_equal(out_img.affine, np.diag((4, 3, 2, 1)))
True

The data properties remain as expected:

>>> out_img.shape
(5, 5, 5)
>>> out_img.get_data_dtype()
dtype('uint8')
>>> np.all(out_img.get_fdata() == 0)
True

By default, the data type of the output file is permitted to vary from the inputs. That is, the data type is preserved. If the data type of the original file is preferred, the _copy_header_map can indicate the output data type should not be preserved by providing a tuple of the input and False.

>>> ZerofileInterface._copy_header_map['out_file'] = ('in_file', False)
>>> res = ZerofileInterface(in_file=in_file, copy_header=True).run()
>>> out_img = nb.load(res.outputs.out_file)
>>> out_img.get_data_dtype()
dtype('<i2')

Again, the affine is updated.

>>> np.array_equal(out_img.affine, np.diag((4, 3, 2, 1)))
True
>>> out_img.shape
(5, 5, 5)
>>> np.all(out_img.get_fdata() == 0)
True

Providing a tuple where the second value is True is also permissible to achieve the default behavior.