Skip to content

Commit fd43bc5

Browse files
committed
NF - orientations to labels routine with tests
1 parent acf305d commit fd43bc5

File tree

3 files changed

+117
-4
lines changed

3 files changed

+117
-4
lines changed

nibabel/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@
4949
from .nifti1 import Nifti1Header, Nifti1Image, Nifti1Pair
5050
from .minc import MincImage
5151
from .funcs import (squeeze_image, concat_images, four_to_three,
52-
as_closest_canonical)
52+
as_closest_canonical)
5353
from .orientations import (io_orientation, orientation_affine,
54-
flip_axis, OrientationError,
55-
apply_orientation)
54+
flip_axis, OrientationError,
55+
apply_orientation, aff2axcodes)
5656
from .imageclasses import class_map, ext_map
5757
from . import trackvis
5858

nibabel/orientations.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,76 @@ def flip_axis(arr, axis=0):
260260
return arr.swapaxes(axis, 0)
261261

262262

263+
def ornt2axcodes(ornt, labels=None):
264+
""" Convert orientation `ornt` to labels for axis directions
265+
266+
Parameters
267+
----------
268+
ornt : (N,2) array-like
269+
orientation array - see io_orientation docstring
270+
labels : optional, None or sequence of (2,) sequences
271+
(2,) sequences are labels for (beginning, end) of output axis. That is,
272+
if the first row in `ornt` is ``[1, 1]``, and the second (2,) sequence
273+
in `labels` is ('back', 'front') then the first returned axis code will
274+
be ``'front'``. If the first row in `ornt` had been ``[1, -1]`` then
275+
the first returned value would have been ``'back'``. If None,
276+
equivalent to ``(('L','R'),('P','A'),('I','S'))`` - that is - RAS axes.
277+
278+
Returns
279+
-------
280+
axcodes : (N,) tuple
281+
labels for positive end of voxel axes. Dropped axes get a label of
282+
None.
283+
284+
Examples
285+
--------
286+
>>> ornt2axcodes([[1, 1],[0,-1],[2,1]], (('L','R'),('B','F'),('D','U')))
287+
('F', 'L', 'U')
288+
"""
289+
if labels is None:
290+
labels = zip('LPI', 'RAS')
291+
axcodes = []
292+
for axno, direction in np.asarray(ornt):
293+
if np.isnan(axno):
294+
axcodes.append(None)
295+
continue
296+
axint = int(np.round(axno))
297+
if axint != axno:
298+
raise ValueError('Non integer axis number %f' % axno)
299+
elif direction == 1:
300+
axcode = labels[axint][1]
301+
elif direction == -1:
302+
axcode = labels[axint][0]
303+
else:
304+
raise ValueError('Direction should be -1 or 1')
305+
axcodes.append(axcode)
306+
return tuple(axcodes)
307+
308+
309+
def aff2axcodes(aff, labels=None, tol=None):
310+
""" axis direction codes for affine `aff`
311+
312+
Parameters
313+
----------
314+
aff : (N,M) array-like
315+
affine transformation matrix
316+
labels : optional, None or sequence of (2,) sequences
317+
Labels for negative and positive ends of output axes of `aff`. See
318+
docstring for ``ornt2axcodes`` for more detail
319+
tol : None or float
320+
Tolerance for SVD of affine - see ``io_orientation`` for more detail.
321+
322+
Returns
323+
-------
324+
axcodes : (N,) tuple
325+
labels for positive end of voxel axes. Dropped axes get a label of
326+
None.
327+
328+
Examples
329+
--------
330+
>>> aff = [[0,1,0,10],[-1,0,0,20],[0,0,1,30],[0,0,0,1]]
331+
>>> aff2axcodes(aff, (('L','R'),('B','F'),('D','U')))
332+
('B', 'R', 'U')
333+
"""
334+
ornt = io_orientation(aff, tol)
335+
return ornt2axcodes(ornt, labels)

nibabel/tests/test_orientations.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616

1717
from ..orientations import (io_orientation, orientation_affine,
1818
flip_axis, _ornt_to_affine,
19-
apply_orientation, OrientationError)
19+
apply_orientation, OrientationError,
20+
ornt2axcodes, aff2axcodes)
2021

2122

2223
IN_ARRS = [np.eye(4),
@@ -165,6 +166,45 @@ def test_io_orientation():
165166
assert_true(same_transform(taff, ornt, shape))
166167

167168

169+
def test_ornt2axcodes():
170+
# Recoding orientation to axis codes
171+
labels = (('left', 'right'),('back', 'front'), ('down', 'up'))
172+
assert_equal(ornt2axcodes([[0,1],
173+
[1,1],
174+
[2,1]], labels), ('right', 'front', 'up'))
175+
assert_equal(ornt2axcodes([[0,-1],
176+
[1,-1],
177+
[2,-1]], labels), ('left', 'back', 'down'))
178+
assert_equal(ornt2axcodes([[2,-1],
179+
[1,-1],
180+
[0,-1]], labels), ('down', 'back', 'left'))
181+
assert_equal(ornt2axcodes([[1,1],
182+
[2,-1],
183+
[0,1]], labels), ('front', 'down', 'right'))
184+
# default is RAS output directions
185+
assert_equal(ornt2axcodes([[0,1],
186+
[1,1],
187+
[2,1]]), ('R', 'A', 'S'))
188+
# dropped axes produce None
189+
assert_equal(ornt2axcodes([[0,1],
190+
[np.nan,np.nan],
191+
[2,1]]), ('R', None, 'S'))
192+
# Non integer axes raises error
193+
assert_raises(ValueError, ornt2axcodes, [[0.1,1]])
194+
# As do directions not in range
195+
assert_raises(ValueError, ornt2axcodes, [[0,0]])
196+
197+
198+
def test_aff2axcodes():
199+
labels = (('left', 'right'),('back', 'front'), ('down', 'up'))
200+
assert_equal(aff2axcodes(np.eye(4)), tuple('RAS'))
201+
aff = [[0,1,0,10],[-1,0,0,20],[0,0,1,30],[0,0,0,1]]
202+
assert_equal(aff2axcodes(aff, (('L','R'),('B','F'),('D','U'))),
203+
('B', 'R', 'U'))
204+
assert_equal(aff2axcodes(aff, (('L','R'),('B','F'),('D','U'))),
205+
('B', 'R', 'U'))
206+
207+
168208
def test_drop_coord():
169209
# given a 5x4 affine from slicing an fmri,
170210
# the orientations code should easily reorder and drop the t

0 commit comments

Comments
 (0)