Skip to content

Commit ca9d73a

Browse files
committed
NF - add matrix vector functions from nipy
These seem to go naturally with the other affine functions. Extended the range of inputs and added default None to vector input. Extended the tests.
1 parent 0f09d69 commit ca9d73a

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

nibabel/affines.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,99 @@ def apply_affine(aff, pts):
7676
return res.reshape(shape)
7777

7878

79+
def to_matvec(transform):
80+
"""Split a transform into its matrix and vector components.
81+
82+
The tranformation must be represented in homogeneous coordinates and is
83+
split into its rotation matrix and translation vector components.
84+
85+
Parameters
86+
----------
87+
transform : array-like
88+
NxM transform matrix in homogeneous coordinates representing an affine
89+
transformation from an (N-1)-dimensional space to an (M-1)-dimensional
90+
space. An example is a 4x4 transform representing rotations and
91+
translations in 3 dimensions. A 4x3 matrix can represent a 2-dimensional
92+
plane embedded in 3 dimensional space.
93+
94+
Returns
95+
-------
96+
matrix : (N-1, M-1) array
97+
Matrix component of `transform`
98+
vector : (M-1,) array
99+
Vector compoent of `transform`
100+
101+
See Also
102+
--------
103+
from_matvec
104+
105+
Examples
106+
--------
107+
>>> aff = np.diag([2, 3, 4, 1])
108+
>>> aff[:3,3] = [9, 10, 11]
109+
>>> to_matvec(aff)
110+
(array([[2, 0, 0],
111+
[0, 3, 0],
112+
[0, 0, 4]]), array([ 9, 10, 11]))
113+
"""
114+
transform = np.asarray(transform)
115+
ndimin = transform.shape[0] - 1
116+
ndimout = transform.shape[1] - 1
117+
matrix = transform[0:ndimin, 0:ndimout]
118+
vector = transform[0:ndimin, ndimout]
119+
return matrix, vector
120+
121+
122+
def from_matvec(matrix, vector=None):
123+
""" Combine a matrix and vector into an homogeneous affine
124+
125+
Combine a rotation / scaling / shearing matrix and translation vector into a
126+
transform in homogeneous coordinates.
127+
128+
Parameters
129+
----------
130+
matrix : array-like
131+
An NxM array representing the the linear part of the transform.
132+
A transform from an M-dimensional space to an N-dimensional space.
133+
vector : None or array-like, optional
134+
None or an (N,) array representing the translation. None corresponds to
135+
an (N,) array of zeros.
136+
137+
Returns
138+
-------
139+
xform : array
140+
An (N+1, M+1) homogenous transform matrix.
141+
142+
See Also
143+
--------
144+
to_matvec
145+
146+
Examples
147+
--------
148+
>>> from_matvec(np.diag([2, 3, 4]), [9, 10, 11])
149+
array([[ 2, 0, 0, 9],
150+
[ 0, 3, 0, 10],
151+
[ 0, 0, 4, 11],
152+
[ 0, 0, 0, 1]])
153+
154+
The `vector` argument is optional:
155+
156+
>>> from_matvec(np.diag([2, 3, 4]))
157+
array([[2, 0, 0, 0],
158+
[0, 3, 0, 0],
159+
[0, 0, 4, 0],
160+
[0, 0, 0, 1]])
161+
"""
162+
matrix = np.asarray(matrix)
163+
nin, nout = matrix.shape
164+
t = np.zeros((nin+1,nout+1), matrix.dtype)
165+
t[0:nin, 0:nout] = matrix
166+
t[nin, nout] = 1.
167+
if not vector is None:
168+
t[0:nin, nout] = vector
169+
return t
170+
171+
79172
def append_diag(aff, steps, starts=()):
80173
""" Add diagonal elements `steps` and translations `starts` to affine
81174

nibabel/tests/test_affines.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import numpy as np
55

6-
from ..affines import apply_affine, append_diag
6+
from ..affines import (apply_affine, append_diag, to_matvec, from_matvec)
7+
78

89
from nose.tools import assert_equal, assert_raises
910
from numpy.testing import assert_array_equal, assert_almost_equal, \
@@ -65,6 +66,30 @@ def test_apply_affine():
6566
assert_array_almost_equal(res, exp_res)
6667

6768

69+
def test_matrix_vector():
70+
for M, N in ((4,4), (5,4), (4, 5)):
71+
xform = np.zeros((M, N))
72+
xform[:-1,:] = np.random.normal(size=(M-1, N))
73+
xform[-1,-1] = 1
74+
newmat, newvec = to_matvec(xform)
75+
mat = xform[:-1, :-1]
76+
vec = xform[:-1, -1]
77+
assert_array_equal(newmat, mat)
78+
assert_array_equal(newvec, vec)
79+
assert_equal(newvec.shape, (M-1,))
80+
assert_array_equal(from_matvec(mat, vec), xform)
81+
# Check default translation works
82+
xform_not = xform[:]
83+
xform_not[:-1,:] = 0
84+
assert_array_equal(from_matvec(mat), xform)
85+
assert_array_equal(from_matvec(mat, None), xform)
86+
# Check array-like works
87+
newmat, newvec = to_matvec(xform.tolist())
88+
assert_array_equal(newmat, mat)
89+
assert_array_equal(newvec, vec)
90+
assert_array_equal(from_matvec(mat.tolist(), vec.tolist()), xform)
91+
92+
6893
def test_append_diag():
6994
# Routine for appending diagonal elements
7095
assert_array_equal(append_diag(np.diag([2,3,1]), [1]),

0 commit comments

Comments
 (0)