Skip to content

Commit 9a04815

Browse files
committed
Merge branch 'orientation-codes' into main-master
* orientation-codes: BF - add future import to support python 2.5 BF - py3k fixes for trackvis points changes RF+TST - add constructor args, fixes, tests for trackvis class interface RF - options to read / write voxel and mm points RF - new interface for get / set affine routines NF - orientations to labels routine with tests NF - check and ignore warnings context managers TST - add tests for version checks and endian writing
2 parents 41b82e4 + 277c1ca commit 9a04815

File tree

7 files changed

+981
-108
lines changed

7 files changed

+981
-108
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/checkwarns.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
4+
#
5+
# See COPYING file distributed along with the NiBabel package for the
6+
# copyright and license terms.
7+
#
8+
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9+
''' Contexts for *with* statement allowing checks for warnings
10+
11+
When we give up 2.5 compatibility we can use python's own
12+
``tests.test_support.check_warnings``
13+
14+
'''
15+
from __future__ import with_statement
16+
17+
import warnings
18+
19+
20+
class ErrorWarnings(object):
21+
""" Context manager to check for warnings as errors. Usually used with
22+
``assert_raises`` in the with block
23+
24+
Examples
25+
--------
26+
>>> with ErrorWarnings():
27+
... try:
28+
... warnings.warn('Message', UserWarning)
29+
... except UserWarning:
30+
... print 'I consider myself warned'
31+
I consider myself warned
32+
33+
Notes
34+
-----
35+
The manager will raise a RuntimeError if another warning filter gets put on
36+
top of the one it has just added.
37+
"""
38+
def __init__(self):
39+
self.added = None
40+
41+
def __enter__(self):
42+
warnings.simplefilter('error')
43+
self.added = warnings.filters[0]
44+
45+
def __exit__(self, exc, value, tb):
46+
if warnings.filters[0] != self.added:
47+
raise RuntimeError('Somone has done something to the filters')
48+
warnings.filters.pop(0)
49+
return False # allow any exceptions to propagate
50+
51+
52+
class IgnoreWarnings(ErrorWarnings):
53+
""" Context manager to ignore warnings
54+
55+
Examples
56+
--------
57+
>>> with IgnoreWarnings():
58+
... warnings.warn('Message', UserWarning)
59+
60+
(and you get no warning)
61+
62+
Notes
63+
-----
64+
The manager will raise a RuntimeError if another warning filter gets put on
65+
top of the one it has just added.
66+
"""
67+
68+
def __enter__(self):
69+
warnings.simplefilter('ignore')
70+
self.added = warnings.filters[0]

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_checkwarns.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
""" Tests for warnings context managers
2+
"""
3+
4+
from __future__ import with_statement
5+
6+
from warnings import warn, simplefilter, filters
7+
8+
from ..checkwarns import ErrorWarnings, IgnoreWarnings
9+
10+
from nose.tools import assert_true, assert_equal, assert_raises
11+
12+
13+
def test_warn_error():
14+
# Check warning error context manager
15+
n_warns = len(filters)
16+
with ErrorWarnings():
17+
assert_raises(UserWarning, warn, 'A test')
18+
assert_equal(n_warns, len(filters))
19+
def f():
20+
with ErrorWarnings():
21+
simplefilter('ignore')
22+
try:
23+
assert_raises(RuntimeError, f)
24+
assert_equal(n_warns+2, len(filters))
25+
finally:
26+
filters.pop(0)
27+
filters.pop(0)
28+
# Check other errors are propagated
29+
def f():
30+
with ErrorWarnings():
31+
raise ValueError('An error')
32+
assert_raises(ValueError, f)
33+
34+
35+
def test_warn_ignore():
36+
# Check warning ignore context manager
37+
n_warns = len(filters)
38+
with IgnoreWarnings():
39+
warn('Here is a warning, you will not see it')
40+
warn('Nor this one', DeprecationWarning)
41+
assert_equal(n_warns, len(filters))
42+
def f():
43+
with IgnoreWarnings():
44+
simplefilter('error')
45+
try:
46+
assert_raises(RuntimeError, f)
47+
assert_equal(n_warns+2, len(filters))
48+
finally:
49+
filters.pop(0)
50+
filters.pop(0)
51+
# Check other errors are propagated
52+
def f():
53+
with IgnoreWarnings():
54+
raise ValueError('An error')
55+
assert_raises(ValueError, f)

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)