/
fixheader.py
136 lines (101 loc) · 4.62 KB
/
fixheader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
from ..base import BaseInterface, BaseInterfaceInputSpec, traits
from ...utils.imagemanip import copy_header as _copy_header
class CopyHeaderInputSpec(BaseInterfaceInputSpec):
copy_header = traits.Bool(
desc="Copy headers of the input image into the output image"
)
class CopyHeaderInterface(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.
"""
_copy_header_map = None
def _post_run_hook(self, runtime):
"""Copy headers for outputs, if required."""
runtime = super()._post_run_hook(runtime)
if self._copy_header_map is None or not self.inputs.copy_header:
return runtime
inputs = self.inputs.get_traitsfree()
outputs = self.aggregate_outputs(runtime=runtime).get_traitsfree()
defined_outputs = set(outputs.keys()).intersection(self._copy_header_map.keys())
for out in defined_outputs:
inp = self._copy_header_map[out]
keep_dtype = True
if isinstance(inp, tuple):
inp, keep_dtype = inp
_copy_header(inputs[inp], outputs[out], keep_dtype=keep_dtype)
return runtime