rotation_matrix_from_to

odl.tomo.util.utility.rotation_matrix_from_to(from_vec, to_vec)[source]

Return a matrix that rotates from_vec to to_vec in 2d or 3d.

Since a rotation from one vector to another in 3 dimensions has (at least) one degree of freedom, this function makes deliberate but still arbitrary choices to fix these free parameters. See Notes for details. For the applied formula in 3d, see this Wikipedia page about Rodrigues' rotation formula.

Parameters:
from_vec, to_vecarray-like, shape (2,) or (3,)

Vectors between which the returned matrix rotates. They should not be very close to zero or collinear.

Returns:
matrixnumpy.ndarray, shape (2, 2) or (3, 3)

A matrix rotating from_vec to to_vec. Note that the matrix does not include scaling, i.e. it is not guaranteed that matrix.dot(from_vec) == to_vec.

Notes

In 3d, the matrix corresponds to a rotation around the normal vector \hat n = \hat u \times \hat v, where \hat u and \hat v are the normalized versions of u, the vector from which to rotate, and v, the vector to which should be rotated.

The rotation angle is determined as \alpha = \pm \arccos(\langle \hat u, \hat v \rangle). Its sign corresponds to the sign of \langle \hat b, \hat v\rangle, where \hat b = \hat n \times \hat u is the binormal vector.

In the case that \hat u and \hat v are collinear, a perpendicular vector is chosen as \hat n = (1, 0, 0) if v_1 = v_2 = 0, else \hat n = (-v_2, v_1, v_3). The angle in this case is \alpha = 0 if \langle \hat u, \hat v \rangle > 0, otherwise \alpha = \pi.

Examples

In two dimensions, rotation is simple:

>>> from_vec, to_vec = [1, 0], [1, 1]
>>> mat = rotation_matrix_from_to(from_vec, to_vec)
>>> to_vec_normalized = np.divide(to_vec, np.linalg.norm(to_vec))
>>> np.allclose(mat.dot([1, 0]), to_vec_normalized)
True
>>> from_vec, to_vec = [1, 0], [-1, 1]
>>> mat = rotation_matrix_from_to(from_vec, to_vec)
>>> to_vec_normalized = np.divide(to_vec, np.linalg.norm(to_vec))
>>> np.allclose(mat.dot([1, 0]), to_vec_normalized)
True

Rotation in 3d by less than pi:

>>> from_vec, to_vec = [1, 0, 0], [-1, 1, 0]
>>> mat = rotation_matrix_from_to(from_vec, to_vec)
>>> to_vec_normalized = np.divide(to_vec, np.linalg.norm(to_vec))
>>> np.allclose(mat.dot([1, 0, 0]), to_vec_normalized)
True

Rotation by more than pi:

>>> from_vec, to_vec = [1, 0, 0], [-1, -1, 0]
>>> mat = rotation_matrix_from_to(from_vec, to_vec)
>>> to_vec_normalized = np.divide(to_vec, np.linalg.norm(to_vec))
>>> np.allclose(mat.dot([1, 0, 0]), to_vec_normalized)
True