Skip to content

Commit e5c7ac4

Browse files
committed
RF+DOC: remove unused _ornt_affine function
Remove unused function and expand docstrings for the orientation_affine function, because it sure is confusing.
1 parent a943879 commit e5c7ac4

File tree

2 files changed

+53
-137
lines changed

2 files changed

+53
-137
lines changed

nibabel/orientations.py

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -89,50 +89,6 @@ def io_orientation(affine, tol=None):
8989
return ornt
9090

9191

92-
def _ornt_to_affine(orientations):
93-
''' Create affine transformation matrix determined by orientations.
94-
95-
This transformation will simply flip, transpose, and possibly drop some
96-
coordinates.
97-
98-
Parameters
99-
----------
100-
orientations : (p, 2) ndarray
101-
one row per input axis, where the first value in each row is the closest
102-
corresponding output axis. The second value in each row is 1 if the input
103-
axis is in the same direction as the corresponding output axis and -1 if
104-
it is in the opposite direction. If a row is [np.nan, np.nan], which can
105-
happen when p > q, then this row should be considered dropped.
106-
107-
Returns
108-
-------
109-
affine : (q + 1, p + 1) ndarray
110-
matrix representing flipping / dropping axes. q is equal to the number of
111-
rows of ``orientations[:, 0]`` that are not np.nan
112-
'''
113-
ornt = np.asarray(orientations)
114-
p = ornt.shape[0]
115-
keep = ~np.isnan(ornt[:, 1])
116-
# These are the input coordinate axes that do have a matching output
117-
# column in the orientation. That is, if the 2nd row is [np.nan,
118-
# np.nan] then the orientation indicates that no output axes of an
119-
# affine with this orientation matches the 2nd input coordinate
120-
# axis. This would happen if the 2nd row of the affine that
121-
# generated ornt was [0,0,0,*]
122-
axes_kept = np.arange(p)[keep]
123-
q = keep.sum(0)
124-
# the matrix P represents the affine transform impled by ornt. If
125-
# all entries of ornt are not np.nan, then P is square otherwise it
126-
# has more columns than rows indicating some coordinates were
127-
# dropped
128-
P = np.zeros((q + 1, p + 1))
129-
P[-1, -1] = 1
130-
for idx in range(q):
131-
axs, flip = ornt[axes_kept[idx]]
132-
P[idx, axs] = flip
133-
return P
134-
135-
13692
def apply_orientation(arr, ornt):
13793
''' Apply transformations implied by `ornt` to the first
13894
n axes of the array `arr`
@@ -176,7 +132,7 @@ def apply_orientation(arr, ornt):
176132

177133

178134
def orientation_affine(ornt, shape):
179-
''' Affine transform resulting from transforms implied in `ornt`
135+
''' Affine transform reversing transforms implied in `ornt`
180136
181137
Imagine you have an array ``arr`` of shape `shape`, and you apply the
182138
transforms implied by `ornt` (more below), to get ``tarr``.
@@ -186,38 +142,47 @@ def orientation_affine(ornt, shape):
186142
187143
Parameters
188144
----------
189-
ornt : (n,2) ndarray
190-
orientation transform. ``ornt[N,1]` is flip of axis N of the
191-
array implied by `shape`, where 1 means no flip and -1 means
192-
flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and
193-
there's an array ``arr`` of shape `shape`, the flip would
194-
correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is
195-
the transpose that needs to be done to the implied array, as in
196-
``arr.transpose(ornt[:,0])``
197-
198-
shape : length n sequence
145+
ornt : (p, 2) ndarray
146+
orientation transform. ``ornt[P, 1]` is flip of axis N of the array
147+
implied by `shape`, where 1 means no flip and -1 means flip. For
148+
example, if ``P==0`` and ``ornt[0, 1] == -1``, and there's an array
149+
``arr`` of shape `shape`, the flip would correspond to the effect of
150+
``np.flipud(arr)``. ``ornt[:,0]`` gives us the (reverse of the)
151+
transpose that has been done to ``arr``. If there are any NaNs in
152+
`ornt`, we raise an ``OrientationError`` (see notes)
153+
shape : length p sequence
199154
shape of array you may transform with `ornt`
200155
201156
Returns
202157
-------
203-
transformed_affine : (n+1,n+1) ndarray
204-
An array ``arr`` (shape `shape`) might be transformed according
205-
to `ornt`, resulting in a transformed array ``tarr``.
206-
`transformed_affine` is the transform that takes you from array
207-
coordinates in ``tarr`` to array coordinates in ``arr``.
158+
transformed_affine : (p + 1, p + 1) ndarray
159+
An array ``arr`` (shape `shape`) might be transformed according to
160+
`ornt`, resulting in a transformed array ``tarr``. `transformed_affine`
161+
is the transform that takes you from array coordinates in ``tarr`` to
162+
array coordinates in ``arr``.
163+
164+
Notes
165+
-----
166+
If a row in `ornt` contains NaN, this means that the input row does not
167+
influence the output space, and is thus effectively dropped from the output
168+
space. In that case one ``tarr`` coordinate maps to many ``arr``
169+
coordinates, we can't invert the transform, and we raise an error
208170
'''
209171
ornt = np.asarray(ornt)
210-
n = ornt.shape[0]
211-
shape = np.array(shape)[:n]
172+
if np.any(np.isnan(ornt)):
173+
raise OrientationError("We cannot invert orientation transform")
174+
p = ornt.shape[0]
175+
shape = np.array(shape)[:p]
212176
# ornt implies a flip, followed by a transpose. We need the affine
213177
# that inverts these. Thus we need the affine that first undoes the
214178
# effect of the transpose, then undoes the effects of the flip.
215179
# ornt indicates the transpose that has occurred to get the current
216-
# ordering, relative to canonical, so we just use that
217-
undo_reorder = np.eye(n + 1)[list(ornt[:, 0]) + [n], :]
180+
# ordering, relative to canonical, so we just use that.
181+
# undo_reorder is a row permutatation matrix
182+
undo_reorder = np.eye(p + 1)[list(ornt[:, 0]) + [p], :]
218183
undo_flip = np.diag(list(ornt[:, 1]) + [1.0])
219184
center_trans = -(shape - 1) / 2.0
220-
undo_flip[:n, n] = (ornt[:, 1] * center_trans) - center_trans
185+
undo_flip[:p, p] = (ornt[:, 1] * center_trans) - center_trans
221186
return np.dot(undo_flip, undo_reorder)
222187

223188

nibabel/tests/test_orientations.py

Lines changed: 23 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414

1515
from numpy.testing import assert_array_equal, assert_array_almost_equal
1616

17-
from ..orientations import (io_orientation, orientation_affine,
18-
flip_axis, _ornt_to_affine,
19-
apply_orientation, OrientationError,
20-
ornt2axcodes, aff2axcodes)
17+
from ..orientations import (io_orientation, orientation_affine, flip_axis,
18+
apply_orientation, OrientationError, ornt2axcodes,
19+
aff2axcodes)
2120

2221
from ..affines import from_matvec, to_matvec
2322

@@ -149,23 +148,23 @@ def test_flip_axis():
149148

150149

151150
def test_io_orientation():
152-
shape = (2,3,4)
153-
for in_arr, out_ornt in zip(IN_ARRS, OUT_ORNTS):
154-
ornt = io_orientation(in_arr)
155-
assert_array_equal(ornt, out_ornt)
156-
taff = orientation_affine(ornt, shape)
157-
assert_true(same_transform(taff, ornt, shape))
158-
for axno in range(3):
159-
arr = in_arr.copy()
160-
ex_ornt = out_ornt.copy()
161-
# flip the input axis in affine
162-
arr[:,axno] *= -1
163-
# check that result shows flip
164-
ex_ornt[axno, 1] *= -1
165-
ornt = io_orientation(arr)
166-
assert_array_equal(ornt, ex_ornt)
151+
for shape in ((2,3,4), (20, 15, 7)):
152+
for in_arr, out_ornt in zip(IN_ARRS, OUT_ORNTS):
153+
ornt = io_orientation(in_arr)
154+
assert_array_equal(ornt, out_ornt)
167155
taff = orientation_affine(ornt, shape)
168156
assert_true(same_transform(taff, ornt, shape))
157+
for axno in range(3):
158+
arr = in_arr.copy()
159+
ex_ornt = out_ornt.copy()
160+
# flip the input axis in affine
161+
arr[:,axno] *= -1
162+
# check that result shows flip
163+
ex_ornt[axno, 1] *= -1
164+
ornt = io_orientation(arr)
165+
assert_array_equal(ornt, ex_ornt)
166+
taff = orientation_affine(ornt, shape)
167+
assert_true(same_transform(taff, ornt, shape))
169168
# Test nasty hang for zero columns
170169
rzs = np.c_[np.diag([2, 3, 4, 5]), np.zeros((4,3))]
171170
arr = from_matvec(rzs, [15,16,17,18])
@@ -218,56 +217,8 @@ def test_aff2axcodes():
218217
('B', 'R', 'U'))
219218

220219

221-
def test_drop_coord():
222-
# given a 5x4 affine from slicing an fmri, the orientations codes should
223-
# reorder and drop the t axis
224-
# this affine has output coordinates '-y','z','x' and is at t=16
225-
sliced_fmri_affine = np.array([[0,-1,0,3],
226-
[0,0,2,5],
227-
[3,0,0,4],
228-
[0,0,0,16],
229-
[0,0,0,1]])
230-
# This gives a 3 by 2 matrix (because there are 3 input axes)
231-
ornt = io_orientation(sliced_fmri_affine)
232-
# 4 x 4 (3 input dimensions, no input dimension dropped)
233-
flipping_aff = _ornt_to_affine(ornt)
234-
# So we have to do the dropping part ourselves
235-
rzs, trans = to_matvec(flipping_aff)
236-
drop_flip_aff = from_matvec(np.hstack((rzs, np.zeros((3, 1)))), trans)
237-
final_affine = np.dot(drop_flip_aff, sliced_fmri_affine)
238-
# the output will be diagonal with the 'y' row having been flipped and the
239-
# 't' row dropped
240-
assert_array_equal(final_affine,
241-
np.array([[3,0,0,4],
242-
[0,1,0,-3],
243-
[0,0,2,5],
244-
[0,0,0,1]]))
245-
246-
247-
def test__ornt_to_affine():
248-
# this orientation indicates that the first output axis of the affine is
249-
# closest to the vector [0,0,-1], the last is closest to [1,0,0] and that
250-
# the y coordinate ([0,1,0]) is dropped
251-
ornt = [[2,-1],
252-
[np.nan,np.nan],
253-
[0,1]]
254-
# the reordering/flipping is represented by an affine that
255-
# takes the 3rd output coordinate and maps it to the
256-
# first, takes the 3rd, maps it to first and flips it
257-
A = np.array([[0,0,-1,0],
258-
[1,0,0,0],
259-
[0,0,0,1]])
260-
assert_array_equal(A, _ornt_to_affine(ornt))
261-
# a more complicated example. only the 1st, 3rd and 6th
262-
# coordinates appear in the output
263-
ornt = [[3,-1],
264-
[np.nan,np.nan],
265-
[0,1],
266-
[np.nan,np.nan],
267-
[np.nan,np.nan],
268-
[1,-1]]
269-
B = np.array([[0,0,0,-1,0,0,0],
270-
[1,0,0,0,0,0,0],
271-
[0,-1,0,0,0,0,0],
272-
[0,0,0,0,0,0,1]])
273-
assert_array_equal(B, _ornt_to_affine(ornt))
220+
def test_orientation_affine():
221+
# Extra tests for orientation_affine routines (also tested in
222+
# io_orientations test)
223+
assert_raises(OrientationError, orientation_affine,
224+
[[0, 1], [1, -1], [np.nan, np.nan]], (3, 4, 5))

0 commit comments

Comments
 (0)