Skip to content

Commit 953ea8b

Browse files
committed
Merge pull request #96 from matthew-brett/add-matvec-functions
NF - add matrix vector functions from nipy
2 parents b037526 + ca9d73a commit 953ea8b

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)