33 """Homogeneous Transformation Matrices and Quaternions. 35 A library for calculating 4x4 matrices for translating, rotating, reflecting, 36 scaling, shearing, projecting, orthogonalizing, and superimposing arrays of 37 3D homogeneous coordinates as well as for converting between rotation matrices, 38 Euler angles, and quaternions. Also includes an Arcball control object and 39 functions to decompose transformation matrices. 42 `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_ 45 Laboratory for Fluorescence Dynamics, University of California, Irvine 51 * `CPython 2.7 or 3.5 <http://www.python.org>`_ 52 * `Numpy 1.11 <http://www.numpy.org>`_ 53 * `Transformations.c 2017.02.17 <http://www.lfd.uci.edu/~gohlke/>`_ 54 (recommended for speedup of some functions) 58 The API is not stable yet and is expected to change between revisions. 60 This Python code is not optimized for speed. Refer to the transformations.c 61 module for a faster implementation of some functions. 63 Documentation in HTML format can be generated with epydoc. 65 Matrices (M) can be inverted using numpy.linalg.inv(M), be concatenated using 66 numpy.dot(M0, M1), or transform homogeneous coordinate arrays (v) using 67 numpy.dot(M, v) for shape (4, \*) column vectors, respectively 68 numpy.dot(v, M.T) for shape (\*, 4) row vectors ("array of points"). 70 This module follows the "column vectors on the right" and "row major storage" 71 (C contiguous) conventions. The translation components are in the right column 72 of the transformation matrix, i.e. M[:3, 3]. 73 The transpose of the transformation matrices may have to be used to interface 74 with other graphics systems, e.g. with OpenGL's glMultMatrixd(). See also [16]. 76 Calculations are carried out with numpy.float64 precision. 78 Vector, point, quaternion, and matrix function arguments are expected to be 79 "array like", i.e. tuple, list, or numpy arrays. 81 Return types are numpy arrays unless specified otherwise. 83 Angles are in radians unless specified otherwise. 85 Quaternions w+ix+jy+kz are represented as [w, x, y, z]. 87 A triple of Euler angles can be applied/interpreted in 24 ways, which can 88 be specified using a 4 character string or encoded 4-tuple: 90 *Axes 4-string*: e.g. 'sxyz' or 'ryxy' 92 - first character : rotations are applied to 's'tatic or 'r'otating frame 93 - remaining characters : successive rotation axis 'x',
'y',
or 'z' 95 *Axes 4-tuple*: e.g. (0, 0, 0, 0)
or (1, 1, 1, 1)
97 - inner axis: code of axis (
'x':0,
'y':1,
'z':2) of rightmost matrix.
98 - parity : even (0)
if inner axis
'x' is followed by
'y',
'y' is followed
99 by
'z',
or 'z' is followed by
'x'. Otherwise odd (1).
100 - repetition : first
and last axis are same (1)
or different (0).
101 - frame : rotations are applied to static (0)
or rotating (1) frame.
103 Other Python packages
and modules
for 3D transformations
and quaternions:
105 * `Transforms3d <https://pypi.python.org/pypi/transforms3d>`_
106 includes most code of this module.
107 * `Blender.mathutils <http://www.blender.org/api/blender_python_api>`_
108 * `numpy-dtypes <https://github.com/numpy/numpy-dtypes>`_
112 (1) Matrices
and transformations. Ronald Goldman.
113 In
"Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
114 (2) More matrices
and transformations: shear
and pseudo-perspective.
115 Ronald Goldman. In
"Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
116 (3) Decomposing a matrix into simple transformations. Spencer Thomas.
117 In
"Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
118 (4) Recovering the data
from the transformation matrix. Ronald Goldman.
119 In
"Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
120 (5) Euler angle conversion. Ken Shoemake.
121 In
"Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
122 (6) Arcball rotation control. Ken Shoemake.
123 In
"Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
124 (7) Representing attitude: Euler angles, unit quaternions,
and rotation
125 vectors. James Diebel. 2006.
126 (8) A discussion of the solution
for the best rotation to relate two sets
127 of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
128 (9) Closed-form solution of absolute orientation using unit quaternions.
129 BKP Horn. J Opt Soc Am A. 1987. 4(4):629-642.
130 (10) Quaternions. Ken Shoemake.
131 http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
132 (11) From quaternion to matrix
and back. JMP van Waveren. 2005.
133 http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
134 (12) Uniform random rotations. Ken Shoemake.
135 In
"Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
136 (13) Quaternion
in molecular modeling. CFF Karney.
137 J Mol Graph Mod, 25(5):595-604
138 (14) New method
for extracting the quaternion
from a rotation matrix.
139 Itzhack Y Bar-Itzhack, J Guid Contr Dynam. 2000. 23(6): 1085-1087.
140 (15) Multiple View Geometry
in Computer Vision. Hartley
and Zissermann.
141 Cambridge University Press; 2nd Ed. 2004. Chapter 4, Algorithm 4.7, p 130.
142 (16) Column Vectors vs. Row Vectors.
143 http://steve.hollasch.net/cgindex/math/matrix/column-vec.html
147 >>> alpha, beta, gamma = 0.123, -1.234, 2.345
148 >>> origin, xaxis, yaxis, zaxis = [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]
155 >>> numpy.allclose([alpha, beta, gamma], euler)
177 >>> numpy.allclose(scale, 1.23)
179 >>> numpy.allclose(trans, [1, 2, 3])
181 >>> numpy.allclose(shear, [0, math.tan(beta), 0])
190 >>> v2 = numpy.dot(v0, M[:3,:3].T)
196 from __future__ import division, print_function 202 __version__ = '2017.02.17' 203 __docformat__ = 'restructuredtext en' 207 def identity_matrix(): 208 """Return 4x4 identity/unit matrix.
211 >>> numpy.allclose(I, numpy.dot(I, I))
213 >>> numpy.sum(I), numpy.trace(I)
215 >>> numpy.allclose(I, numpy.identity(4))
219 return numpy.identity(4) 222 def translation_matrix(direction): 223 """Return matrix to translate by direction vector.
225 >>> v = numpy.random.random(3) - 0.5
230 M = numpy.identity(4) 231 M[:3, 3] = direction[:3] 235 def translation_from_matrix(matrix): 236 """Return translation vector
from translation matrix.
238 >>> v0 = numpy.random.random(3) - 0.5
240 >>> numpy.allclose(v0, v1)
244 return numpy.array(matrix, copy=False)[:3, 3].copy() 247 def reflection_matrix(point, normal): 248 """Return matrix to mirror at plane defined by point
and normal vector.
250 >>> v0 = numpy.random.random(4) - 0.5
252 >>> v1 = numpy.random.random(3) - 0.5
254 >>> numpy.allclose(2, numpy.trace(R))
256 >>> numpy.allclose(v0, numpy.dot(R, v0))
262 >>> numpy.allclose(v2, numpy.dot(R, v3))
266 normal = unit_vector(normal[:3]) 267 M = numpy.identity(4) 268 M[:3, :3] -= 2.0 * numpy.outer(normal, normal) 269 M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal 273 def reflection_from_matrix(matrix): 274 """Return mirror plane point
and normal vector
from reflection matrix.
276 >>> v0 = numpy.random.random(3) - 0.5
277 >>> v1 = numpy.random.random(3) - 0.5
285 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 286 # normal: unit eigenvector corresponding to eigenvalue -1 287 w, V = numpy.linalg.eig(M[:3, :3]) 288 i = numpy.where(abs(numpy.real(w) + 1.0) < 1e-8)[0] 290 raise ValueError("no unit eigenvector corresponding to eigenvalue -1") 291 normal = numpy.real(V[:, i[0]]).squeeze() 292 # point: any unit eigenvector corresponding to eigenvalue 1 293 w, V = numpy.linalg.eig(M) 294 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 296 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 297 point = numpy.real(V[:, i[-1]]).squeeze() 302 def rotation_matrix(angle, direction, point=None): 303 """Return matrix to rotate about axis defined by point
and direction.
306 >>> numpy.allclose(numpy.dot(R, [0, 0, 0, 1]), [1, -1, 0, 1])
308 >>> angle = (random.random() - 0.5) * (2*math.pi)
309 >>> direc = numpy.random.random(3) - 0.5
310 >>> point = numpy.random.random(3) - 0.5
319 >>> I = numpy.identity(4, numpy.float64)
327 sina = math.sin(angle) 328 cosa = math.cos(angle) 329 direction = unit_vector(direction[:3]) 330 # rotation matrix around unit vector 331 R = numpy.diag([cosa, cosa, cosa]) 332 R += numpy.outer(direction, direction) * (1.0 - cosa) 334 R += numpy.array([[0.0, -direction[2], direction[1]], 335 [direction[2], 0.0, -direction[0]], 336 [-direction[1], direction[0], 0.0]]) 337 M = numpy.identity(4) 339 if point is not None: 340 # rotation not around origin 341 point = numpy.array(point[:3], dtype=numpy.float64, copy=False) 342 M[:3, 3] = point - numpy.dot(R, point) 346 def rotation_from_matrix(matrix): 347 """Return rotation angle
and axis
from rotation matrix.
349 >>> angle = (random.random() - 0.5) * (2*math.pi)
350 >>> direc = numpy.random.random(3) - 0.5
351 >>> point = numpy.random.random(3) - 0.5
359 R = numpy.array(matrix, dtype=numpy.float64, copy=False) 361 # direction: unit eigenvector of R33 corresponding to eigenvalue of 1 362 w, W = numpy.linalg.eig(R33.T) 363 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 365 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 366 direction = numpy.real(W[:, i[-1]]).squeeze() 367 # point: unit eigenvector of R33 corresponding to eigenvalue of 1 368 w, Q = numpy.linalg.eig(R) 369 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 371 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 372 point = numpy.real(Q[:, i[-1]]).squeeze() 374 # rotation angle depending on direction 375 cosa = (numpy.trace(R33) - 1.0) / 2.0 376 if abs(direction[2]) > 1e-8: 377 sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2] 378 elif abs(direction[1]) > 1e-8: 379 sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1] 381 sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0] 382 angle = math.atan2(sina, cosa) 383 return angle, direction, point 386 def scale_matrix(factor, origin=None, direction=None): 387 """Return matrix to scale by factor around origin
in direction.
389 Use factor -1
for point symmetry.
391 >>> v = (numpy.random.rand(4, 5) - 0.5) * 20
394 >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
396 >>> factor = random.random() * 10 - 5
397 >>> origin = numpy.random.random(3) - 0.5
398 >>> direct = numpy.random.random(3) - 0.5
403 if direction is None: 405 M = numpy.diag([factor, factor, factor, 1.0]) 406 if origin is not None: 407 M[:3, 3] = origin[:3] 408 M[:3, 3] *= 1.0 - factor 411 direction = unit_vector(direction[:3]) 412 factor = 1.0 - factor 413 M = numpy.identity(4) 414 M[:3, :3] -= factor * numpy.outer(direction, direction) 415 if origin is not None: 416 M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction 420 def scale_from_matrix(matrix): 421 """Return scaling factor, origin
and direction
from scaling matrix.
423 >>> factor = random.random() * 10 - 5
424 >>> origin = numpy.random.random(3) - 0.5
425 >>> direct = numpy.random.random(3) - 0.5
438 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 440 factor = numpy.trace(M33) - 2.0 442 # direction: unit eigenvector corresponding to eigenvalue factor 443 w, V = numpy.linalg.eig(M33) 444 i = numpy.where(abs(numpy.real(w) - factor) < 1e-8)[0][0] 445 direction = numpy.real(V[:, i]).squeeze() 446 direction /= vector_norm(direction) 449 factor = (factor + 2.0) / 3.0 451 # origin: any eigenvector corresponding to eigenvalue 1 452 w, V = numpy.linalg.eig(M) 453 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 455 raise ValueError("no eigenvector corresponding to eigenvalue 1") 456 origin = numpy.real(V[:, i[-1]]).squeeze() 458 return factor, origin, direction 461 def projection_matrix(point, normal, direction=None, 462 perspective=None, pseudo=False): 463 """Return matrix to project onto plane defined by point
and normal.
465 Using either perspective point, projection direction,
or none of both.
467 If pseudo
is True, perspective projections will preserve relative depth
468 such that Perspective = dot(Orthogonal, PseudoPerspective).
471 >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
473 >>> point = numpy.random.random(3) - 0.5
474 >>> normal = numpy.random.random(3) - 0.5
475 >>> direct = numpy.random.random(3) - 0.5
476 >>> persp = numpy.random.random(3) - 0.5
484 >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20
486 >>> v1 = numpy.dot(P, v0)
487 >>> numpy.allclose(v1[1], v0[1])
489 >>> numpy.allclose(v1[0], 3-v1[1])
493 M = numpy.identity(4) 494 point = numpy.array(point[:3], dtype=numpy.float64, copy=False) 495 normal = unit_vector(normal[:3]) 496 if perspective is not None: 497 # perspective projection 498 perspective = numpy.array(perspective[:3], dtype=numpy.float64, 500 M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal) 501 M[:3, :3] -= numpy.outer(perspective, normal) 503 # preserve relative depth 504 M[:3, :3] -= numpy.outer(normal, normal) 505 M[:3, 3] = numpy.dot(point, normal) * (perspective+normal) 507 M[:3, 3] = numpy.dot(point, normal) * perspective 509 M[3, 3] = numpy.dot(perspective, normal) 510 elif direction is not None: 511 # parallel projection 512 direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False) 513 scale = numpy.dot(direction, normal) 514 M[:3, :3] -= numpy.outer(direction, normal) / scale 515 M[:3, 3] = direction * (numpy.dot(point, normal) / scale) 517 # orthogonal projection 518 M[:3, :3] -= numpy.outer(normal, normal) 519 M[:3, 3] = numpy.dot(point, normal) * normal 523 def projection_from_matrix(matrix, pseudo=False): 524 """Return projection plane
and perspective point
from projection matrix.
526 Return values are same
as arguments
for projection_matrix function:
527 point, normal, direction, perspective,
and pseudo.
529 >>> point = numpy.random.random(3) - 0.5
530 >>> normal = numpy.random.random(3) - 0.5
531 >>> direct = numpy.random.random(3) - 0.5
532 >>> persp = numpy.random.random(3) - 0.5
555 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 557 w, V = numpy.linalg.eig(M) 558 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 559 if not pseudo and len(i): 560 # point: any eigenvector corresponding to eigenvalue 1 561 point = numpy.real(V[:, i[-1]]).squeeze() 563 # direction: unit eigenvector corresponding to eigenvalue 0 564 w, V = numpy.linalg.eig(M33) 565 i = numpy.where(abs(numpy.real(w)) < 1e-8)[0] 567 raise ValueError("no eigenvector corresponding to eigenvalue 0") 568 direction = numpy.real(V[:, i[0]]).squeeze() 569 direction /= vector_norm(direction) 570 # normal: unit eigenvector of M33.T corresponding to eigenvalue 0 571 w, V = numpy.linalg.eig(M33.T) 572 i = numpy.where(abs(numpy.real(w)) < 1e-8)[0] 574 # parallel projection 575 normal = numpy.real(V[:, i[0]]).squeeze() 576 normal /= vector_norm(normal) 577 return point, normal, direction, None, False 579 # orthogonal projection, where normal equals direction vector 580 return point, direction, None, None, False 582 # perspective projection 583 i = numpy.where(abs(numpy.real(w)) > 1e-8)[0] 586 "no eigenvector not corresponding to eigenvalue 0") 587 point = numpy.real(V[:, i[-1]]).squeeze() 590 perspective = M[:3, 3] / numpy.dot(point[:3], normal) 592 perspective -= normal 593 return point, normal, None, perspective, pseudo 596 def clip_matrix(left, right, bottom, top, near, far, perspective=False): 597 """Return matrix to obtain normalized device coordinates
from frustum.
599 The frustum bounds are axis-aligned along x (left, right),
600 y (bottom, top)
and z (near, far).
602 Normalized device coordinates are
in range [-1, 1]
if coordinates are
605 If perspective
is True the frustum
is a truncated pyramid with the
606 perspective point at origin
and direction along z axis, otherwise an
607 orthographic canonical view volume (a box).
609 Homogeneous coordinates transformed by the perspective clip matrix
610 need to be dehomogenized (divided by w coordinate).
612 >>> frustum = numpy.random.rand(6)
613 >>> frustum[1] += frustum[0]
614 >>> frustum[3] += frustum[2]
615 >>> frustum[5] += frustum[4]
617 >>> numpy.dot(M, [frustum[0], frustum[2], frustum[4], 1])
618 array([-1., -1., -1., 1.])
619 >>> numpy.dot(M, [frustum[1], frustum[3], frustum[5], 1])
620 array([ 1., 1., 1., 1.])
622 >>> v = numpy.dot(M, [frustum[0], frustum[2], frustum[4], 1])
624 array([-1., -1., -1., 1.])
625 >>> v = numpy.dot(M, [frustum[1], frustum[3], frustum[4], 1])
627 array([ 1., 1., -1., 1.])
630 if left >= right or bottom >= top or near >= far: 631 raise ValueError("invalid frustum") 634 raise ValueError("invalid frustum: near <= 0") 636 M = [[t/(left-right), 0.0, (right+left)/(right-left), 0.0], 637 [0.0, t/(bottom-top), (top+bottom)/(top-bottom), 0.0], 638 [0.0, 0.0, (far+near)/(near-far), t*far/(far-near)], 639 [0.0, 0.0, -1.0, 0.0]] 641 M = [[2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)], 642 [0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)], 643 [0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)], 644 [0.0, 0.0, 0.0, 1.0]] 645 return numpy.array(M) 648 def shear_matrix(angle, direction, point, normal): 649 """Return matrix to shear by angle along direction vector on shear plane.
651 The shear plane
is defined by a point
and normal vector. The direction
652 vector must be orthogonal to the plane
's normal vector. 654 A point P is transformed by the shear matrix into P
" such that 655 the vector P-P" is parallel to the direction vector and its extent is 656 given by the angle of P-P'-P", where P' is the orthogonal projection
657 of P onto the shear plane.
659 >>> angle = (random.random() - 0.5) * 4*math.pi
660 >>> direct = numpy.random.random(3) - 0.5
661 >>> point = numpy.random.random(3) - 0.5
662 >>> normal = numpy.cross(direct, numpy.random.random(3))
664 >>> numpy.allclose(1, numpy.linalg.det(S))
668 normal = unit_vector(normal[:3]) 669 direction = unit_vector(direction[:3]) 670 if abs(numpy.dot(normal, direction)) > 1e-6: 671 raise ValueError("direction and normal vectors are not orthogonal") 672 angle = math.tan(angle) 673 M = numpy.identity(4) 674 M[:3, :3] += angle * numpy.outer(direction, normal) 675 M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction 679 def shear_from_matrix(matrix): 680 """Return shear angle, direction
and plane
from shear matrix.
682 >>> angle = (random.random() - 0.5) * 4*math.pi
683 >>> direct = numpy.random.random(3) - 0.5
684 >>> point = numpy.random.random(3) - 0.5
685 >>> normal = numpy.cross(direct, numpy.random.random(3))
693 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 695 # normal: cross independent eigenvectors corresponding to the eigenvalue 1 696 w, V = numpy.linalg.eig(M33) 697 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-4)[0] 699 raise ValueError("no two linear independent eigenvectors found %s" % w) 700 V = numpy.real(V[:, i]).squeeze().T 702 for i0, i1 in ((0, 1), (0, 2), (1, 2)): 703 n = numpy.cross(V[i0], V[i1]) 709 # direction and angle 710 direction = numpy.dot(M33 - numpy.identity(3), normal) 711 angle = vector_norm(direction) 713 angle = math.atan(angle) 714 # point: eigenvector corresponding to eigenvalue 1 715 w, V = numpy.linalg.eig(M) 716 i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0] 718 raise ValueError("no eigenvector corresponding to eigenvalue 1") 719 point = numpy.real(V[:, i[-1]]).squeeze() 721 return angle, direction, point, normal 724 def decompose_matrix(matrix): 725 """Return sequence of transformations
from transformation matrix.
728 Non-degenerative homogeneous transformation matrix
731 scale : vector of 3 scaling factors
732 shear : list of shear factors
for x-y, x-z, y-z axes
733 angles : list of Euler angles about static x, y, z axes
734 translate : translation vector along x, y, z axes
735 perspective : perspective partition of matrix
737 Raise ValueError
if matrix
is of wrong type
or degenerative.
742 >>> numpy.allclose(T0, T1)
751 >>> numpy.allclose(R0, R1)
755 M = numpy.array(matrix, dtype=numpy.float64, copy=True).T 756 if abs(M[3, 3]) < _EPS: 757 raise ValueError("M[3, 3] is zero") 760 P[:, 3] = 0.0, 0.0, 0.0, 1.0 761 if not numpy.linalg.det(P): 762 raise ValueError("matrix is singular") 764 scale = numpy.zeros((3, )) 765 shear = [0.0, 0.0, 0.0] 766 angles = [0.0, 0.0, 0.0] 768 if any(abs(M[:3, 3]) > _EPS): 769 perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T)) 770 M[:, 3] = 0.0, 0.0, 0.0, 1.0 772 perspective = numpy.array([0.0, 0.0, 0.0, 1.0]) 774 translate = M[3, :3].copy() 777 row = M[:3, :3].copy() 778 scale[0] = vector_norm(row[0]) 780 shear[0] = numpy.dot(row[0], row[1]) 781 row[1] -= row[0] * shear[0] 782 scale[1] = vector_norm(row[1]) 785 shear[1] = numpy.dot(row[0], row[2]) 786 row[2] -= row[0] * shear[1] 787 shear[2] = numpy.dot(row[1], row[2]) 788 row[2] -= row[1] * shear[2] 789 scale[2] = vector_norm(row[2]) 791 shear[1:] /= scale[2] 793 if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0: 794 numpy.negative(scale, scale) 795 numpy.negative(row, row) 797 angles[1] = math.asin(-row[0, 2]) 798 if math.cos(angles[1]): 799 angles[0] = math.atan2(row[1, 2], row[2, 2]) 800 angles[2] = math.atan2(row[0, 1], row[0, 0]) 802 # angles[0] = math.atan2(row[1, 0], row[1, 1]) 803 angles[0] = math.atan2(-row[2, 1], row[1, 1]) 806 return scale, shear, angles, translate, perspective 809 def compose_matrix(scale=None, shear=None, angles=None, translate=None, 811 """Return transformation matrix
from sequence of transformations.
813 This
is the inverse of the decompose_matrix function.
815 Sequence of transformations:
816 scale : vector of 3 scaling factors
817 shear : list of shear factors
for x-y, x-z, y-z axes
818 angles : list of Euler angles about static x, y, z axes
819 translate : translation vector along x, y, z axes
820 perspective : perspective partition of matrix
822 >>> scale = numpy.random.random(3) - 0.5
823 >>> shear = numpy.random.random(3) - 0.5
824 >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
825 >>> trans = numpy.random.random(3) - 0.5
826 >>> persp = numpy.random.random(4) - 0.5
834 M = numpy.identity(4) 835 if perspective is not None: 836 P = numpy.identity(4) 837 P[3, :] = perspective[:4] 839 if translate is not None: 840 T = numpy.identity(4) 841 T[:3, 3] = translate[:3] 843 if angles is not None: 844 R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz') 846 if shear is not None: 847 Z = numpy.identity(4) 852 if scale is not None: 853 S = numpy.identity(4) 862 def orthogonalization_matrix(lengths, angles): 863 """Return orthogonalization matrix
for crystallographic cell coordinates.
865 Angles are expected
in degrees.
867 The de-orthogonalization matrix
is the inverse.
870 >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
873 >>> numpy.allclose(numpy.sum(O), 43.063229)
878 angles = numpy.radians(angles) 879 sina, sinb, _ = numpy.sin(angles) 880 cosa, cosb, cosg = numpy.cos(angles) 881 co = (cosa * cosb - cosg) / (sina * sinb) 883 [a*sinb*math.sqrt(1.0-co*co), 0.0, 0.0, 0.0], 884 [-a*sinb*co, b*sina, 0.0, 0.0], 885 [a*cosb, b*cosa, c, 0.0], 886 [0.0, 0.0, 0.0, 1.0]]) 889 def affine_matrix_from_points(v0, v1, shear=True, scale=True, usesvd=True): 890 """Return affine transform matrix to register two point sets.
892 v0
and v1 are shape (ndims, \*) arrays of at least ndims non-homogeneous
893 coordinates, where ndims
is the dimensionality of the coordinate space.
895 If shear
is False, a similarity transformation matrix
is returned.
896 If also scale
is False, a rigid/Euclidean transformation matrix
899 By default the algorithm by Hartley
and Zissermann [15]
is used.
900 If usesvd
is True, similarity
and Euclidean transformation matrices
901 are calculated by minimizing the weighted sum of squared deviations
902 (RMSD) according to the algorithm by Kabsch [8].
903 Otherwise,
and if ndims
is 3, the quaternion based algorithm by Horn [9]
904 is used, which
is slower when using this Python implementation.
906 The returned matrix performs rotation, translation
and uniform scaling
909 >>> v0 = [[0, 1031, 1031, 0], [0, 0, 1600, 1600]]
910 >>> v1 = [[675, 826, 826, 677], [55, 52, 281, 277]]
912 array([[ 0.14549, 0.00062, 675.50008],
913 [ 0.00048, 0.14094, 53.24971],
919 >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20
921 >>> v1 = numpy.dot(M, v0)
922 >>> v0[:3] += numpy.random.normal(0, 1e-8, 300).reshape(3, -1)
924 >>> numpy.allclose(v1, numpy.dot(M, v0))
930 v0 = numpy.array(v0, dtype=numpy.float64, copy=True) 931 v1 = numpy.array(v1, dtype=numpy.float64, copy=True) 934 if ndims < 2 or v0.shape[1] < ndims or v0.shape != v1.shape: 935 raise ValueError("input arrays are of wrong shape or type") 937 # move centroids to origin 938 t0 = -numpy.mean(v0, axis=1) 939 M0 = numpy.identity(ndims+1) 940 M0[:ndims, ndims] = t0 941 v0 += t0.reshape(ndims, 1) 942 t1 = -numpy.mean(v1, axis=1) 943 M1 = numpy.identity(ndims+1) 944 M1[:ndims, ndims] = t1 945 v1 += t1.reshape(ndims, 1) 948 # Affine transformation 949 A = numpy.concatenate((v0, v1), axis=0) 950 u, s, vh = numpy.linalg.svd(A.T) 953 C = vh[ndims:2*ndims] 954 t = numpy.dot(C, numpy.linalg.pinv(B)) 955 t = numpy.concatenate((t, numpy.zeros((ndims, 1))), axis=1) 956 M = numpy.vstack((t, ((0.0,)*ndims) + (1.0,))) 957 elif usesvd or ndims != 3: 958 # Rigid transformation via SVD of covariance matrix 959 u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T)) 960 # rotation matrix from SVD orthonormal bases 962 if numpy.linalg.det(R) < 0.0: 963 # R does not constitute right handed system 964 R -= numpy.outer(u[:, ndims-1], vh[ndims-1, :]*2.0) 966 # homogeneous transformation matrix 967 M = numpy.identity(ndims+1) 968 M[:ndims, :ndims] = R 970 # Rigid transformation matrix via quaternion 971 # compute symmetric matrix N 972 xx, yy, zz = numpy.sum(v0 * v1, axis=1) 973 xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1) 974 xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1) 975 N = [[xx+yy+zz, 0.0, 0.0, 0.0], 976 [yz-zy, xx-yy-zz, 0.0, 0.0], 977 [zx-xz, xy+yx, yy-xx-zz, 0.0], 978 [xy-yx, zx+xz, yz+zy, zz-xx-yy]] 979 # quaternion: eigenvector corresponding to most positive eigenvalue 980 w, V = numpy.linalg.eigh(N) 981 q = V[:, numpy.argmax(w)] 982 q /= vector_norm(q) # unit quaternion 983 # homogeneous transformation matrix 984 M = quaternion_matrix(q) 986 if scale and not shear: 987 # Affine transformation; scale is ratio of RMS deviations from centroid 990 M[:ndims, :ndims] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0)) 992 # move centroids back 993 M = numpy.dot(numpy.linalg.inv(M1), numpy.dot(M, M0)) 998 def superimposition_matrix(v0, v1, scale=False, usesvd=True): 999 """Return matrix to transform given 3D point set into second point set.
1001 v0
and v1 are shape (3, \*)
or (4, \*) arrays of at least 3 points.
1003 The parameters scale
and usesvd are explained
in the more general
1004 affine_matrix_from_points function.
1006 The returned matrix
is a similarity
or Euclidean transformation matrix.
1007 This function has a fast C implementation
in transformations.c.
1009 >>> v0 = numpy.random.rand(3, 10)
1011 >>> numpy.allclose(M, numpy.identity(4))
1014 >>> v0 = [[1,0,0], [0,1,0], [0,0,1], [1,1,1]]
1015 >>> v1 = numpy.dot(R, v0)
1017 >>> numpy.allclose(v1, numpy.dot(M, v0))
1019 >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20
1021 >>> v1 = numpy.dot(R, v0)
1023 >>> numpy.allclose(v1, numpy.dot(M, v0))
1028 >>> v1 = numpy.dot(M, v0)
1029 >>> v0[:3] += numpy.random.normal(0, 1e-9, 300).reshape(3, -1)
1031 >>> numpy.allclose(v1, numpy.dot(M, v0))
1034 >>> numpy.allclose(v1, numpy.dot(M, v0))
1036 >>> v = numpy.empty((4, 100, 3))
1039 >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
1043 v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3] 1044 v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3] 1045 return affine_matrix_from_points(v0, v1, shear=False, 1046 scale=scale, usesvd=usesvd) 1049 def euler_matrix(ai, aj, ak, axes='sxyz'): 1050 """Return homogeneous rotation matrix
from Euler angles
and axis sequence.
1052 ai, aj, ak : Euler
's roll, pitch and yaw angles 1053 axes : One of 24 axis sequences as string
or encoded tuple
1056 >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
1059 >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
1061 >>> ai, aj, ak = (4*math.pi) * (numpy.random.random(3) - 0.5)
1062 >>>
for axes
in _AXES2TUPLE.keys():
1064 >>>
for axes
in _TUPLE2AXES.keys():
1069 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes] 1070 except (AttributeError, KeyError): 1071 _TUPLE2AXES[axes] # validation 1072 firstaxis, parity, repetition, frame = axes 1075 j = _NEXT_AXIS[i+parity] 1076 k = _NEXT_AXIS[i-parity+1] 1081 ai, aj, ak = -ai, -aj, -ak 1083 si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak) 1084 ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak) 1085 cc, cs = ci*ck, ci*sk 1086 sc, ss = si*ck, si*sk 1088 M = numpy.identity(4) 1112 def euler_from_matrix(matrix, axes='sxyz'): 1113 """Return Euler angles
from rotation matrix
for specified axis sequence.
1115 axes : One of 24 axis sequences
as string
or encoded tuple
1117 Note that many Euler angle triplets can describe one matrix.
1122 >>> numpy.allclose(R0, R1)
1124 >>> angles = (4*math.pi) * (numpy.random.random(3) - 0.5)
1125 >>>
for axes
in _AXES2TUPLE.keys():
1128 ...
if not numpy.allclose(R0, R1): print(axes,
"failed")
1132 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] 1133 except (AttributeError, KeyError): 1134 _TUPLE2AXES[axes] # validation 1135 firstaxis, parity, repetition, frame = axes 1138 j = _NEXT_AXIS[i+parity] 1139 k = _NEXT_AXIS[i-parity+1] 1141 M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3] 1143 sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k]) 1145 ax = math.atan2(M[i, j], M[i, k]) 1146 ay = math.atan2(sy, M[i, i]) 1147 az = math.atan2(M[j, i], -M[k, i]) 1149 ax = math.atan2(-M[j, k], M[j, j]) 1150 ay = math.atan2(sy, M[i, i]) 1153 cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i]) 1155 ax = math.atan2(M[k, j], M[k, k]) 1156 ay = math.atan2(-M[k, i], cy) 1157 az = math.atan2(M[j, i], M[i, i]) 1159 ax = math.atan2(-M[j, k], M[j, j]) 1160 ay = math.atan2(-M[k, i], cy) 1164 ax, ay, az = -ax, -ay, -az 1170 def euler_from_quaternion(quaternion, axes='sxyz'): 1171 """Return Euler angles
from quaternion
for specified axis sequence.
1174 >>> numpy.allclose(angles, [0.123, 0, 0])
1178 return euler_from_matrix(quaternion_matrix(quaternion), axes) 1181 def quaternion_from_euler(ai, aj, ak, axes='sxyz'): 1182 """Return quaternion
from Euler angles
and axis sequence.
1184 ai, aj, ak : Euler
's roll, pitch and yaw angles 1185 axes : One of 24 axis sequences as string
or encoded tuple
1188 >>> numpy.allclose(q, [0.435953, 0.310622, -0.718287, 0.444435])
1193 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] 1194 except (AttributeError, KeyError): 1195 _TUPLE2AXES[axes] # validation 1196 firstaxis, parity, repetition, frame = axes 1199 j = _NEXT_AXIS[i+parity-1] + 1 1200 k = _NEXT_AXIS[i-parity] + 1 1221 q = numpy.empty((4, )) 1228 q[0] = cj*cc + sj*ss 1229 q[i] = cj*sc - sj*cs 1230 q[j] = cj*ss + sj*cc 1231 q[k] = cj*cs - sj*sc 1238 def quaternion_about_axis(angle, axis): 1239 """Return quaternion
for rotation about axis.
1242 >>> numpy.allclose(q, [0.99810947, 0.06146124, 0, 0])
1246 q = numpy.array([0.0, axis[0], axis[1], axis[2]]) 1247 qlen = vector_norm(q) 1249 q *= math.sin(angle/2.0) / qlen 1250 q[0] = math.cos(angle/2.0) 1254 def quaternion_matrix(quaternion): 1255 """Return homogeneous rotation matrix
from quaternion.
1261 >>> numpy.allclose(M, numpy.identity(4))
1264 >>> numpy.allclose(M, numpy.diag([1, -1, -1, 1]))
1268 q = numpy.array(quaternion, dtype=numpy.float64, copy=True) 1271 return numpy.identity(4) 1272 q *= math.sqrt(2.0 / n) 1273 q = numpy.outer(q, q) 1274 return numpy.array([ 1275 [1.0-q[2, 2]-q[3, 3], q[1, 2]-q[3, 0], q[1, 3]+q[2, 0], 0.0], 1276 [q[1, 2]+q[3, 0], 1.0-q[1, 1]-q[3, 3], q[2, 3]-q[1, 0], 0.0], 1277 [q[1, 3]-q[2, 0], q[2, 3]+q[1, 0], 1.0-q[1, 1]-q[2, 2], 0.0], 1278 [0.0, 0.0, 0.0, 1.0]]) 1281 def quaternion_from_matrix(matrix, isprecise=False): 1282 """Return quaternion
from rotation matrix.
1284 If isprecise
is True, the input matrix
is assumed to be a precise rotation
1285 matrix
and a faster algorithm
is used.
1288 >>> numpy.allclose(q, [1, 0, 0, 0])
1291 >>> numpy.allclose(q, [0, 1, 0, 0])
or numpy.allclose(q, [0, -1, 0, 0])
1295 >>> numpy.allclose(q, [0.9981095, 0.0164262, 0.0328524, 0.0492786])
1297 >>> R = [[-0.545, 0.797, 0.260, 0], [0.733, 0.603, -0.313, 0],
1298 ... [-0.407, 0.021, -0.913, 0], [0, 0, 0, 1]]
1300 >>> numpy.allclose(q, [0.19069, 0.43736, 0.87485, -0.083611])
1302 >>> R = [[0.395, 0.362, 0.843, 0], [-0.626, 0.796, -0.056, 0],
1303 ... [-0.677, -0.498, 0.529, 0], [0, 0, 0, 1]]
1305 >>> numpy.allclose(q, [0.82336615, -0.13610694, 0.46344705, -0.29792603])
1320 M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4] 1322 q = numpy.empty((4, )) 1326 q[3] = M[1, 0] - M[0, 1] 1327 q[2] = M[0, 2] - M[2, 0] 1328 q[1] = M[2, 1] - M[1, 2] 1331 if M[1, 1] > M[0, 0]: 1333 if M[2, 2] > M[i, i]: 1335 t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3] 1337 q[j] = M[i, j] + M[j, i] 1338 q[k] = M[k, i] + M[i, k] 1339 q[3] = M[k, j] - M[j, k] 1341 q *= 0.5 / math.sqrt(t * M[3, 3]) 1352 # symmetric matrix K 1353 K = numpy.array([[m00-m11-m22, 0.0, 0.0, 0.0], 1354 [m01+m10, m11-m00-m22, 0.0, 0.0], 1355 [m02+m20, m12+m21, m22-m00-m11, 0.0], 1356 [m21-m12, m02-m20, m10-m01, m00+m11+m22]]) 1358 # quaternion is eigenvector of K that corresponds to largest eigenvalue 1359 w, V = numpy.linalg.eigh(K) 1360 q = V[[3, 0, 1, 2], numpy.argmax(w)] 1362 numpy.negative(q, q) 1366 def quaternion_multiply(quaternion1, quaternion0): 1367 """Return multiplication of two quaternions.
1370 >>> numpy.allclose(q, [28, -44, -14, 48])
1374 w0, x0, y0, z0 = quaternion0 1375 w1, x1, y1, z1 = quaternion1 1376 return numpy.array([ 1377 -x1*x0 - y1*y0 - z1*z0 + w1*w0, 1378 x1*w0 + y1*z0 - z1*y0 + w1*x0, 1379 -x1*z0 + y1*w0 + z1*x0 + w1*y0, 1380 x1*y0 - y1*x0 + z1*w0 + w1*z0], dtype=numpy.float64) 1383 def quaternion_conjugate(quaternion): 1384 """Return conjugate of quaternion.
1388 >>> q1[0] == q0[0]
and all(q1[1:] == -q0[1:])
1392 q = numpy.array(quaternion, dtype=numpy.float64, copy=True) 1393 numpy.negative(q[1:], q[1:]) 1397 def quaternion_inverse(quaternion): 1398 """Return inverse of quaternion.
1406 q = numpy.array(quaternion, dtype=numpy.float64, copy=True) 1407 numpy.negative(q[1:], q[1:]) 1408 return q / numpy.dot(q, q) 1411 def quaternion_real(quaternion): 1412 """Return real part of quaternion.
1418 return float(quaternion[0]) 1421 def quaternion_imag(quaternion): 1422 """Return imaginary part of quaternion.
1425 array([ 0., 1., 2.])
1428 return numpy.array(quaternion[1:4], dtype=numpy.float64, copy=True) 1431 def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True): 1432 """Return spherical linear interpolation between two quaternions.
1437 >>> numpy.allclose(q, q0)
1440 >>> numpy.allclose(q, q1)
1443 >>> angle = math.acos(numpy.dot(q0, q))
1444 >>> numpy.allclose(2, math.acos(numpy.dot(q0, q1)) / angle)
or \
1445 numpy.allclose(2, math.acos(-numpy.dot(q0, q1)) / angle)
1449 q0 = unit_vector(quat0[:4]) 1450 q1 = unit_vector(quat1[:4]) 1453 elif fraction == 1.0: 1455 d = numpy.dot(q0, q1) 1456 if abs(abs(d) - 1.0) < _EPS: 1458 if shortestpath and d < 0.0: 1461 numpy.negative(q1, q1) 1462 angle = math.acos(d) + spin * math.pi 1463 if abs(angle) < _EPS: 1465 isin = 1.0 / math.sin(angle) 1466 q0 *= math.sin((1.0 - fraction) * angle) * isin 1467 q1 *= math.sin(fraction * angle) * isin 1472 def random_quaternion(rand=None): 1473 """Return uniform random unit quaternion.
1475 rand: array like
or None 1476 Three independent random variables that are uniformly distributed
1483 >>> len(q.shape), q.shape[0]==4
1488 rand = numpy.random.rand(3) 1490 assert len(rand) == 3 1491 r1 = numpy.sqrt(1.0 - rand[0]) 1492 r2 = numpy.sqrt(rand[0]) 1496 return numpy.array([numpy.cos(t2)*r2, numpy.sin(t1)*r1, 1497 numpy.cos(t1)*r1, numpy.sin(t2)*r2]) 1500 def random_rotation_matrix(rand=None): 1501 """Return uniform random rotation matrix.
1504 Three independent random variables that are uniformly distributed
1505 between 0
and 1
for each returned quaternion.
1508 >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
1512 return quaternion_matrix(random_quaternion(rand)) 1515 class Arcball(object): 1516 """Virtual Trackball Control.
1519 >>> ball =
Arcball(initial=numpy.identity(4))
1520 >>> ball.place([320, 320], 320)
1521 >>> ball.down([500, 250])
1522 >>> ball.drag([475, 275])
1523 >>> R = ball.matrix()
1524 >>> numpy.allclose(numpy.sum(R), 3.90583455)
1526 >>> ball =
Arcball(initial=[1, 0, 0, 0])
1527 >>> ball.place([320, 320], 320)
1528 >>> ball.setaxes([1, 1, 0], [-1, 1, 0])
1529 >>> ball.constrain =
True 1530 >>> ball.down([400, 200])
1531 >>> ball.drag([200, 400])
1532 >>> R = ball.matrix()
1533 >>> numpy.allclose(numpy.sum(R), 0.2055924)
1539 def __init__(self, initial=None): 1540 """Initialize virtual trackball control.
1542 initial : quaternion
or rotation matrix
1548 self._center = [0.0, 0.0] 1549 self._vdown = numpy.array([0.0, 0.0, 1.0]) 1550 self._constrain = False 1552 self._qdown = numpy.array([1.0, 0.0, 0.0, 0.0]) 1554 initial = numpy.array(initial, dtype=numpy.float64) 1555 if initial.shape == (4, 4): 1556 self._qdown = quaternion_from_matrix(initial) 1557 elif initial.shape == (4, ): 1558 initial /= vector_norm(initial) 1559 self._qdown = initial 1561 raise ValueError("initial not a quaternion or matrix") 1562 self._qnow = self._qpre = self._qdown 1564 def place(self, center, radius): 1565 """Place Arcball, e.g. when window size changes.
1567 center : sequence[2]
1568 Window coordinates of trackball center.
1570 Radius of trackball
in window coordinates.
1573 self._radius = float(radius) 1574 self._center[0] = center[0] 1575 self._center[1] = center[1] 1577 def setaxes(self, *axes): 1578 """Set axes to constrain rotations.
""" 1582 self._axes = [unit_vector(axis) for axis in axes] 1585 def constrain(self): 1586 """Return state of constrain to axis mode.
""" 1587 return self._constrain 1590 def constrain(self, value): 1591 """Set state of constrain to axis mode.
""" 1592 self._constrain = bool(value) 1594 def down(self, point): 1595 """Set initial cursor window coordinates
and pick constrain-axis.
""" 1596 self._vdown = arcball_map_to_sphere(point, self._center, self._radius) 1597 self._qdown = self._qpre = self._qnow 1598 if self._constrain and self._axes is not None: 1599 self._axis = arcball_nearest_axis(self._vdown, self._axes) 1600 self._vdown = arcball_constrain_to_axis(self._vdown, self._axis) 1604 def drag(self, point): 1605 """Update current cursor window coordinates.
""" 1606 vnow = arcball_map_to_sphere(point, self._center, self._radius) 1607 if self._axis is not None: 1608 vnow = arcball_constrain_to_axis(vnow, self._axis) 1609 self._qpre = self._qnow 1610 t = numpy.cross(self._vdown, vnow) 1611 if numpy.dot(t, t) < _EPS: 1612 self._qnow = self._qdown 1614 q = [numpy.dot(self._vdown, vnow), t[0], t[1], t[2]] 1615 self._qnow = quaternion_multiply(q, self._qdown) 1617 def next(self, acceleration=0.0): 1618 """Continue rotation
in direction of last drag.
""" 1619 q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False) 1620 self._qpre, self._qnow = self._qnow, q 1623 """Return homogeneous rotation matrix.
""" 1624 return quaternion_matrix(self._qnow) 1627 def arcball_map_to_sphere(point, center, radius): 1628 """Return unit sphere coordinates
from window coordinates.
""" 1629 v0 = (point[0] - center[0]) / radius 1630 v1 = (center[1] - point[1]) / radius 1633 # position outside of sphere 1635 return numpy.array([v0/n, v1/n, 0.0]) 1637 return numpy.array([v0, v1, math.sqrt(1.0 - n)]) 1640 def arcball_constrain_to_axis(point, axis): 1641 """Return sphere point perpendicular to axis.
""" 1642 v = numpy.array(point, dtype=numpy.float64, copy=True) 1643 a = numpy.array(axis, dtype=numpy.float64, copy=True) 1644 v -= a * numpy.dot(a, v) # on plane 1648 numpy.negative(v, v) 1652 return numpy.array([1.0, 0.0, 0.0]) 1653 return unit_vector([-a[1], a[0], 0.0]) 1656 def arcball_nearest_axis(point, axes): 1657 """Return axis, which arc
is nearest to point.
""" 1658 point = numpy.array(point, dtype=numpy.float64, copy=False) 1662 t = numpy.dot(arcball_constrain_to_axis(point, axis), point) 1669 # epsilon for testing whether a number is close to zero 1670 _EPS = numpy.finfo(float).eps * 4.0 1672 # axis sequences for Euler angles 1673 _NEXT_AXIS = [1, 2, 0, 1] 1675 # map axes strings to/from tuples of inner axis, parity, repetition, frame 1677 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0), 1678 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0), 1679 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0), 1680 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0), 1681 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1), 1682 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1), 1683 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1), 1684 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)} 1686 _TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items()) 1689 def vector_norm(data, axis=None, out=None): 1690 """Return length, i.e. Euclidean norm, of ndarray along axis.
1692 >>> v = numpy.random.random(3)
1694 >>> numpy.allclose(n, numpy.linalg.norm(v))
1696 >>> v = numpy.random.rand(6, 5, 3)
1698 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
1701 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
1703 >>> v = numpy.random.rand(5, 4, 3)
1704 >>> n = numpy.empty((5, 3))
1706 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
1714 data = numpy.array(data, dtype=numpy.float64, copy=True) 1717 return math.sqrt(numpy.dot(data, data)) 1719 out = numpy.atleast_1d(numpy.sum(data, axis=axis)) 1720 numpy.sqrt(out, out) 1724 numpy.sum(data, axis=axis, out=out) 1725 numpy.sqrt(out, out) 1728 def unit_vector(data, axis=None, out=None): 1729 """Return ndarray normalized by length, i.e. Euclidean norm, along axis.
1731 >>> v0 = numpy.random.random(3)
1733 >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
1735 >>> v0 = numpy.random.rand(5, 4, 3)
1737 >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
1738 >>> numpy.allclose(v1, v2)
1741 >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
1742 >>> numpy.allclose(v1, v2)
1744 >>> v1 = numpy.empty((5, 4, 3))
1746 >>> numpy.allclose(v1, v2)
1755 data = numpy.array(data, dtype=numpy.float64, copy=True) 1757 data /= math.sqrt(numpy.dot(data, data)) 1761 out[:] = numpy.array(data, copy=False) 1763 length = numpy.atleast_1d(numpy.sum(data*data, axis)) 1764 numpy.sqrt(length, length) 1765 if axis is not None: 1766 length = numpy.expand_dims(length, axis) 1772 def random_vector(size): 1773 """Return array of random doubles
in the half-open interval [0.0, 1.0).
1776 >>> numpy.all(v >= 0)
and numpy.all(v < 1)
1780 >>> numpy.any(v0 == v1)
1784 return numpy.random.random(size) 1787 def vector_product(v0, v1, axis=0): 1788 """Return vector perpendicular to vectors.
1791 >>> numpy.allclose(v, [0, 0, 6])
1793 >>> v0 = [[2, 0, 0, 2], [0, 2, 0, 2], [0, 0, 2, 2]]
1794 >>> v1 = [[3], [0], [0]]
1796 >>> numpy.allclose(v, [[0, 0, 0, 0], [0, 0, 6, 6], [0, -6, 0, -6]])
1798 >>> v0 = [[2, 0, 0], [2, 0, 0], [0, 2, 0], [2, 0, 0]]
1799 >>> v1 = [[0, 3, 0], [0, 0, 3], [0, 0, 3], [3, 3, 3]]
1801 >>> numpy.allclose(v, [[0, 0, 6], [0, -6, 0], [6, 0, 0], [0, -6, 6]])
1805 return numpy.cross(v0, v1, axis=axis) 1808 def angle_between_vectors(v0, v1, directed=True, axis=0): 1809 """Return angle between vectors.
1811 If directed
is False, the input vectors are interpreted
as undirected axes,
1812 i.e. the maximum angle
is pi/2.
1815 >>> numpy.allclose(a, math.pi)
1818 >>> numpy.allclose(a, 0)
1820 >>> v0 = [[2, 0, 0, 2], [0, 2, 0, 2], [0, 0, 2, 2]]
1821 >>> v1 = [[3], [0], [0]]
1823 >>> numpy.allclose(a, [0, 1.5708, 1.5708, 0.95532])
1825 >>> v0 = [[2, 0, 0], [2, 0, 0], [0, 2, 0], [2, 0, 0]]
1826 >>> v1 = [[0, 3, 0], [0, 0, 3], [0, 0, 3], [3, 3, 3]]
1828 >>> numpy.allclose(a, [1.5708, 1.5708, 1.5708, 0.95532])
1832 v0 = numpy.array(v0, dtype=numpy.float64, copy=False) 1833 v1 = numpy.array(v1, dtype=numpy.float64, copy=False) 1834 dot = numpy.sum(v0 * v1, axis=axis) 1835 dot /= vector_norm(v0, axis=axis) * vector_norm(v1, axis=axis) 1836 return numpy.arccos(dot if directed else numpy.fabs(dot)) 1839 def inverse_matrix(matrix): 1840 """Return inverse of square transformation matrix.
1844 >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
1846 >>>
for size
in range(1, 7):
1847 ... M0 = numpy.random.rand(size, size)
1849 ...
if not numpy.allclose(M1, numpy.linalg.inv(M0)): print(size)
1852 return numpy.linalg.inv(matrix) 1855 def concatenate_matrices(*matrices): 1856 """Return concatenation of series of transformation matrices.
1858 >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
1865 M = numpy.identity(4) 1871 def is_same_transform(matrix0, matrix1): 1872 """Return
True if two matrices perform same transformation.
1880 matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True) 1881 matrix0 /= matrix0[3, 3] 1882 matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True) 1883 matrix1 /= matrix1[3, 3] 1884 return numpy.allclose(matrix0, matrix1) 1887 def is_same_quaternion(q0, q1): 1888 """Return
True if two quaternions are equal.
""" 1889 q0 = numpy.array(q0) 1890 q1 = numpy.array(q1) 1891 return numpy.allclose(q0, q1) or numpy.allclose(q0, -q1) 1894 def _import_module(name, package=None, warn=False, prefix='_py_', ignore='_'): 1895 """Try
import all public attributes
from module into
global namespace.
1897 Existing attributes with name clashes are renamed with prefix.
1898 Attributes starting with underscore are ignored by default.
1900 Return
True on successful
import.
1904 from importlib import import_module 1907 module = import_module(name) 1909 module = import_module('.' + name, package=package) 1912 warnings.warn("failed to import module %s" % name) 1914 for attr in dir(module): 1915 if ignore and attr.startswith(ignore): 1918 if attr in globals(): 1919 globals()[prefix + attr] = globals()[attr] 1921 warnings.warn("no Python implementation of " + attr) 1922 globals()[attr] = getattr(module, attr) 1926 _import_module('_transformations') 1928 if __name__ == "__main__": 1930 import random # noqa: used in doctests 1931 numpy.set_printoptions(suppress=True, precision=5)