Skip to content

Commit 15c959f

Browse files
committed
ENH: Add Reorient interface
1 parent 5a96ea5 commit 15c959f

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

nipype/interfaces/image.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# -*- coding: utf-8 -*-
2+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3+
# vi: set ft=python sts=4 ts=4 sw=4 et:
4+
5+
import numpy as np
6+
import nibabel as nb
7+
8+
from ..utils.filemanip import fname_presuffix
9+
from .base import (SimpleInterface, TraitedSpec, BaseInterfaceInputSpec,
10+
traits, File)
11+
12+
_axes = ('RL', 'AP', 'SI')
13+
_orientations = tuple(
14+
''.join((x[i], y[j], z[k]))
15+
for x in _axes for y in _axes for z in _axes
16+
if x != y != z != x
17+
for i in (0, 1) for j in (0, 1) for k in (0, 1))
18+
19+
20+
class ReorientInputSpec(BaseInterfaceInputSpec):
21+
in_file = File(exists=True, mandatory=True, desc='Input image')
22+
orientation = traits.Enum(_orientations, usedefault=True,
23+
desc='Target axis orientation')
24+
25+
26+
class ReorientOutputSpec(TraitedSpec):
27+
out_file = File(exists=True, desc='Reoriented image')
28+
transform = File(exists=True,
29+
desc='Affine transform from input orientation to output')
30+
31+
32+
class Reorient(SimpleInterface):
33+
"""Reorient an image
34+
"""
35+
input_spec = ReorientInputSpec
36+
output_spec = ReorientOutputSpec
37+
38+
def _run_interface(self, runtime):
39+
from nibabel.orientations import (
40+
axcodes2ornt, ornt_transform, inv_ornt_aff)
41+
42+
fname = self.inputs.in_file
43+
orig_img = nb.load(fname)
44+
45+
# Find transform from current (approximate) orientation to
46+
# target, in nibabel orientation matrix and affine forms
47+
orig_ornt = nb.io_orientation(orig_img.affine)
48+
targ_ornt = axcodes2ornt(self.inputs.orientation)
49+
transform = ornt_transform(orig_ornt, targ_ornt)
50+
affine_xfm = inv_ornt_aff(transform, orig_img.shape)
51+
52+
# Check can be eliminated when minimum nibabel version >= 2.2
53+
if hasattr(orig_img, 'as_reoriented'):
54+
reoriented = orig_img.as_reoriented(transform)
55+
else:
56+
reoriented = _as_reoriented_backport(orig_img, transform)
57+
58+
# Image may be reoriented
59+
if reoriented is not orig_img:
60+
out_name = fname_presuffix(fname, suffix='_ras',
61+
newpath=runtime.cwd)
62+
reoriented.to_filename(out_name)
63+
else:
64+
out_name = fname
65+
66+
mat_name = fname_presuffix(fname, suffix='.mat',
67+
newpath=runtime.cwd, use_ext=False)
68+
np.savetxt(mat_name, affine_xfm, fmt='%.08f')
69+
70+
self._results['out_file'] = out_name
71+
self._results['transform'] = mat_name
72+
73+
return runtime
74+
75+
76+
def _as_reoriented_backport(img, ornt):
77+
"""Backport of img.as_reoriented as of nibabel 2.2.0"""
78+
from nibabel.orientations import inv_ornt_aff
79+
if np.array_equal(ornt, [[0, 1], [1, 1], [2, 1]]):
80+
return img
81+
82+
t_arr = nb.apply_orientation(img.get_data(), ornt)
83+
new_aff = img.affine.dot(inv_ornt_aff(ornt, img.shape))
84+
reoriented = img.__class__(t_arr, new_aff, img.header)
85+
86+
if isinstance(reoriented, nb.Nifti1Pair):
87+
# Also apply the transform to the dim_info fields
88+
new_dim = list(reoriented.header.get_dim_info())
89+
for idx, value in enumerate(new_dim):
90+
# For each value, leave as None if it was that way,
91+
# otherwise check where we have mapped it to
92+
if value is None:
93+
continue
94+
new_dim[idx] = np.where(ornt[:, 0] == idx)[0]
95+
96+
reoriented.header.set_dim_info(*new_dim)
97+
98+
return reoriented

0 commit comments

Comments
 (0)