@@ -19,42 +19,43 @@ class OrientationError(Exception):
1919def io_orientation (affine , tol = None ):
2020 ''' Orientation of input axes in terms of output axes for `affine`
2121
22- Valid for an affine transformation from ``m `` dimensions to ``n ``
23- dimensions (``affine.shape == (n+ 1, m+ 1)``).
22+ Valid for an affine transformation from ``p `` dimensions to ``q ``
23+ dimensions (``affine.shape == (q + 1, p + 1)``).
2424
2525 The calculated orientations can be used to transform associated
26- arrays to best match the output orientations. If ``n `` > ``m ``, then
26+ arrays to best match the output orientations. If ``p `` > ``q ``, then
2727 some of the output axes should be considered dropped in this
2828 orientation.
2929
3030 Parameters
3131 ----------
32- affine : (n +1,m +1) ndarray-like
33- Transformation affine from ``m `` inputs to ``n `` outputs.
34- Usually this will be a shape (4,4) matrix, transforming 3 inputs
35- to 3 outputs, but the code also handles the more general case
32+ affine : (q +1, p +1) ndarray-like
33+ Transformation affine from ``p `` inputs to ``q `` outputs. Usually this
34+ will be a shape (4,4) matrix, transforming 3 inputs to 3 outputs, but the
35+ code also handles the more general case
3636 tol : {None, float}, optional
37- threshold below which SVD values of the affine are considered
38- zero. If `tol` is None, and ``S`` is an array with singular
39- values for `affine`, and ``eps`` is the epsilon value for
40- datatype of ``S``, then `tol` set to ``S.max() * eps``.
37+ threshold below which SVD values of the affine are considered zero. If
38+ `tol` is None, and ``S`` is an array with singular values for `affine`,
39+ and ``eps`` is the epsilon value for datatype of ``S``, then `tol` set to
40+ ``S.max() * eps``.
4141
4242 Returns
4343 -------
44- orientations : (n,2) ndarray
45- one row per input axis, where the first value in each row is the
46- closest corresponding output axis, and the second value is 1 if
47- the input axis is in the same direction as the corresponding
48- output axis, , and -1 if it is in the opposite direction, to the
49- corresponding output axis. If a row is [np.nan, np.nan], which
50- can happen when n > m, then this row should be considered
51- dropped.
44+ orientations : (p, 2) ndarray
45+ one row per input axis, where the first value in each row is the closest
46+ corresponding output axis. The second value in each row is 1 if the input
47+ axis is in the same direction as the corresponding output axis and -1 if
48+ it is in the opposite direction. If a row is [np.nan, np.nan], which can
49+ happen when p > q, then this row should be considered dropped.
5250 '''
5351 affine = np .asarray (affine )
54- n , m = affine .shape [0 ]- 1 , affine .shape [1 ]- 1
55- # extract the underlying rotation matrix
56- RZS = affine [:n , :m ]
52+ q , p = affine .shape [0 ]- 1 , affine .shape [1 ]- 1
53+ # extract the underlying rotation, zoom, shear matrix
54+ RZS = affine [:q , :p ]
5755 zooms = np .sqrt (np .sum (RZS * RZS , axis = 0 ))
56+ # Zooms can be zero, in which case all elements in the column are zero, and
57+ # we can leave them as they are
58+ zooms [zooms == 0 ] = 1
5859 RS = RZS / zooms
5960 # Transform below is polar decomposition, returning the closest
6061 # shearless matrix R to RS
@@ -66,13 +67,13 @@ def io_orientation(affine, tol=None):
6667 R = np .dot (P [:, keep ], Qs [keep ])
6768 # the matrix R is such that np.dot(R,R.T) is projection onto the
6869 # columns of P[:,keep] and np.dot(R.T,R) is projection onto the rows
69- # of Qs[keep]. R (== np.dot(R, np.eye(m ))) gives rotation of the
70+ # of Qs[keep]. R (== np.dot(R, np.eye(p ))) gives rotation of the
7071 # unit input vectors to output coordinates. Therefore, the row
7172 # index of abs max R[:,N], is the output axis changing most as input
7273 # axis N changes. In case there are ties, we choose the axes
7374 # iteratively, removing used axes from consideration as we go
74- ornt = np .ones ((n , 2 ), dtype = np .int8 ) * np .nan
75- for in_ax in range (m ):
75+ ornt = np .ones ((p , 2 ), dtype = np .int8 ) * np .nan
76+ for in_ax in range (p ):
7677 col = R [:, in_ax ]
7778 if not np .alltrue (np .equal (col , 0 )):
7879 out_ax = np .argmax (np .abs (col ))
@@ -88,51 +89,6 @@ def io_orientation(affine, tol=None):
8889 return ornt
8990
9091
91- def _ornt_to_affine (orientations ):
92- ''' Create affine transformation matrix determined by orientations.
93-
94- This transformation will simply flip, transpose, and possibly drop some
95- coordinates.
96-
97- Parameters
98- ----------
99- orientations : (n,2) ndarray
100- one row per input axis, where the first value in each row is the
101- closest corresponding output axis, and the second value is 1 if
102- the input axis is in the same direction as the corresponding
103- output axis, and -1 if it is in the opposite direction, to the
104- corresponding output axis. If a row has first entry np.nan, then
105- this axis is dropped from the output.
106-
107- Returns
108- -------
109- affine : (m+1,n+1) ndarray
110- matrix representing flipping / dropping axes. m is equal to the
111- number of rows of orientations[:,0] that are not np.nan
112- '''
113- ornt = np .asarray (orientations )
114- n = 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 (n )[keep ]
123- m = 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 ((m + 1 , n + 1 ))
129- P [- 1 , - 1 ] = 1
130- for idx in range (m ):
131- axs , flip = ornt [axes_kept [idx ]]
132- P [idx , axs ] = flip
133- return P
134-
135-
13692def apply_orientation (arr , ornt ):
13793 ''' Apply transformations implied by `ornt` to the first
13894 n axes of the array `arr`
@@ -164,7 +120,6 @@ def apply_orientation(arr, ornt):
164120 if np .any (np .isnan (ornt [:, 0 ])):
165121 raise OrientationError ('Cannot drop coordinates when '
166122 'applying orientation to data' )
167- shape = t_arr .shape
168123 # apply ornt transformations
169124 for ax , flip in enumerate (ornt [:, 1 ]):
170125 if flip == - 1 :
@@ -176,8 +131,8 @@ def apply_orientation(arr, ornt):
176131 return t_arr
177132
178133
179- def orientation_affine (ornt , shape ):
180- ''' Affine transform resulting from transforms implied in `ornt`
134+ def inv_ornt_aff (ornt , shape ):
135+ ''' Affine transform reversing transforms implied in `ornt`
181136
182137 Imagine you have an array ``arr`` of shape `shape`, and you apply the
183138 transforms implied by `ornt` (more below), to get ``tarr``.
@@ -187,41 +142,55 @@ def orientation_affine(ornt, shape):
187142
188143 Parameters
189144 ----------
190- ornt : (n,2) ndarray
191- orientation transform. ``ornt[N,1]` is flip of axis N of the
192- array implied by `shape`, where 1 means no flip and -1 means
193- flip. For example, if ``N==0`` and ``ornt[0,1] == -1``, and
194- there's an array ``arr`` of shape `shape`, the flip would
195- correspond to the effect of ``np.flipud(arr)``. ``ornt[:,0]`` is
196- the transpose that needs to be done to the implied array, as in
197- ``arr.transpose(ornt[:,0])``
198-
199- 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
200154 shape of array you may transform with `ornt`
201155
202156 Returns
203157 -------
204- transformed_affine : (n+1,n+1) ndarray
205- An array ``arr`` (shape `shape`) might be transformed according
206- to `ornt`, resulting in a transformed array ``tarr``.
207- `transformed_affine` is the transform that takes you from array
208- coordinates in ``tarr`` to array coordinates in ``arr``.
158+ transform_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
209170 '''
210171 ornt = np .asarray (ornt )
211- n = ornt .shape [0 ]
212- 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 ]
213176 # ornt implies a flip, followed by a transpose. We need the affine
214177 # that inverts these. Thus we need the affine that first undoes the
215178 # effect of the transpose, then undoes the effects of the flip.
216179 # ornt indicates the transpose that has occurred to get the current
217- # ordering, relative to canonical, so we just use that
218- 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 ], :]
219183 undo_flip = np .diag (list (ornt [:, 1 ]) + [1.0 ])
220184 center_trans = - (shape - 1 ) / 2.0
221- undo_flip [:n , n ] = (ornt [:, 1 ] * center_trans ) - center_trans
185+ undo_flip [:p , p ] = (ornt [:, 1 ] * center_trans ) - center_trans
222186 return np .dot (undo_flip , undo_reorder )
223187
224188
189+ @np .deprecate_with_doc ("Please use inv_ornt_aff instead" )
190+ def orientation_affine (ornt , shape ):
191+ return inv_ornt_aff (ornt , shape )
192+
193+
225194def flip_axis (arr , axis = 0 ):
226195 ''' Flip contents of `axis` in array `arr`
227196
0 commit comments