Skip to content

Commit 983c303

Browse files
markhymerseffigies
authored andcommitted
Refactor as_closest_canonical into transpose function
As per Matthew's comments, place the logic which re-orients the data into a function in SpatialImage and override this in Nifti1Pair with a version which updates dim_info. Also add new test cases using AnalyzeImage so that we cover both possibilities. Signed-off-by: Mark Hymers <[email protected]>
1 parent 36f6571 commit 983c303

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

nibabel/funcs.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -221,22 +221,9 @@ def as_closest_canonical(img, enforce_diag=False):
221221
# check if we are going to end up with something diagonal
222222
if enforce_diag and not _aff_is_diag(aff):
223223
raise OrientationError('Transformed affine is not diagonal')
224-
# we need to transform the data
225-
arr = img.get_data()
226-
t_arr = apply_orientation(arr, ornt)
227-
# Also apply the transform to the dim_info fields
228-
new_hdr = img.header.copy()
229-
new_dim = list(new_hdr.get_dim_info())
230-
for idx, value in enumerate(new_dim):
231-
# For each value, leave as None if it was that way,
232-
# otherwise check where we have mapped it to
233-
if value is None:
234-
continue
235-
new_dim[idx] = np.where(ornt[:, 0] == idx)[0]
236-
new_hdr.set_dim_info(*new_dim)
237-
238-
return img.__class__(t_arr, out_aff, new_hdr)
239224

225+
# Get the image class to transform the data for us
226+
return img.transpose(ornt, out_aff)
240227

241228
def _aff_is_diag(aff):
242229
''' Utility function returning True if affine is nearly diagonal '''

nibabel/nifti1.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .spm99analyze import SpmAnalyzeHeader
2828
from .casting import have_binary128
2929
from .pydicom_compat import have_dicom, pydicom as pdcm
30+
from .orientations import apply_orientation
3031

3132
# nifti1 flat header definition for Analyze-like first 348 bytes
3233
# first number in comments indicates offset in file header in bytes
@@ -1969,6 +1970,26 @@ def set_sform(self, affine, code=None, **kwargs):
19691970
else:
19701971
self._affine[:] = self._header.get_best_affine()
19711972

1973+
def transpose(self, ornt, new_aff):
1974+
# Override the default SpatialImage method so that we update dim_info
1975+
t_arr = apply_orientation(self.get_data(), ornt)
1976+
1977+
# Also apply the transform to the dim_info fields
1978+
new_hdr = self.header.copy()
1979+
new_dim = list(new_hdr.get_dim_info())
1980+
for idx, value in enumerate(new_dim):
1981+
# For each value, leave as None if it was that way,
1982+
# otherwise check where we have mapped it to
1983+
if value is None:
1984+
continue
1985+
new_dim[idx] = np.where(ornt[:, 0] == idx)[0]
1986+
1987+
new_hdr.set_dim_info(*new_dim)
1988+
1989+
return self.__class__(t_arr, new_aff, new_hdr)
1990+
1991+
1992+
19721993

19731994
class Nifti1Image(Nifti1Pair):
19741995
""" Class for single file NIfTI1 format image

nibabel/spatialimages.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
from .viewers import OrthoSlicer3D
142142
from .volumeutils import shape_zoom_affine
143143
from .deprecated import deprecate_with_version
144+
from .orientations import apply_orientation
144145

145146

146147
class HeaderDataError(Exception):
@@ -483,3 +484,28 @@ def orthoview(self):
483484
"""
484485
return OrthoSlicer3D(self.dataobj, self.affine,
485486
title=self.get_filename())
487+
488+
489+
def transpose(self, ornt, new_aff):
490+
"""Apply an orientation change and return a new image
491+
492+
Parameters
493+
----------
494+
ornt : (n,2) orientation array
495+
orientation transform. ``ornt[N,1]` is flip of axis N of the
496+
array implied by `shape`, where 1 means no flip and -1 means
497+
flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and
498+
there's an array ``arr`` of shape `shape`, the flip would
499+
correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is
500+
the transpose that needs to be done to the implied array, as in
501+
``arr.transpose(ornt[:,0])``
502+
503+
Notes
504+
-----
505+
Subclasses should override this if they have additional requirements
506+
when re-orienting an image.
507+
"""
508+
509+
t_arr = apply_orientation(self.get_data(), ornt)
510+
511+
return self.__class__(t_arr, new_aff, self.header)

nibabel/tests/test_funcs.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import numpy as np
1313

1414
from ..funcs import concat_images, as_closest_canonical, OrientationError
15+
from ..analyze import AnalyzeImage
1516
from ..nifti1 import Nifti1Image
1617
from ..loadsave import save
1718

@@ -128,8 +129,24 @@ def test_concat():
128129

129130

130131
def test_closest_canonical():
131-
arr = np.arange(24).reshape((2, 3, 4, 1))
132-
# no funky stuff, returns same thing
132+
# Use 32-bit data so that the AnalyzeImage class doesn't complain
133+
arr = np.arange(24).reshape((2, 3, 4, 1)).astype(np.int32)
134+
135+
# Test with an AnalyzeImage first
136+
img = AnalyzeImage(arr, np.eye(4))
137+
xyz_img = as_closest_canonical(img)
138+
assert_true(img is xyz_img)
139+
140+
# And a case where the Analyze image has to be flipped
141+
img = AnalyzeImage(arr, np.diag([-1, 1, 1, 1]))
142+
xyz_img = as_closest_canonical(img)
143+
assert_false(img is xyz_img)
144+
out_arr = xyz_img.get_data()
145+
assert_array_equal(out_arr, np.flipud(arr))
146+
147+
# Now onto the NIFTI cases (where dim_info also has to be updated)
148+
149+
# No funky stuff, returns same thing
133150
img = Nifti1Image(arr, np.eye(4))
134151
# set freq/phase/slice dim so that we can check that we
135152
# re-order them properly

0 commit comments

Comments
 (0)