32 """Homogeneous Transformation Matrices and Quaternions. 34 A library for calculating 4x4 matrices for translating, rotating, reflecting, 35 scaling, shearing, projecting, orthogonalizing, and superimposing arrays of 36 3D homogeneous coordinates as well as for converting between rotation matrices, 37 Euler angles, and quaternions. Also includes an Arcball control object and 38 functions to decompose transformation matrices. 41 `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`__, 42 Laboratory for Fluorescence Dynamics, University of California, Irvine 49 * `Python 2.6 <http://www.python.org>`__ 50 * `Numpy 1.3 <http://numpy.scipy.org>`__ 51 * `transformations.c 20090418 <http://www.lfd.uci.edu/~gohlke/>`__ 52 (optional implementation of some functions in C) 57 Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using 58 numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using 59 numpy.dot(M, v) for shape (4, \*) "point of arrays", respectively 60 numpy.dot(v, M.T) for shape (\*, 4) "array of points". 62 Calculations are carried out with numpy.float64 precision. 64 This Python implementation is not optimized for speed. 66 Vector, point, quaternion, and matrix function arguments are expected to be 67 "array like", i.e. tuple, list, or numpy arrays. 69 Return types are numpy arrays unless specified otherwise. 71 Angles are in radians unless specified otherwise. 73 Quaternions ix+jy+kz+w are represented as [x, y, z, w]. 75 Use the transpose of transformation matrices for OpenGL glMultMatrixd(). 77 A triple of Euler angles can be applied/interpreted in 24 ways, which can 78 be specified using a 4 character string or encoded 4-tuple: 80 *Axes 4-string*: e.g. 'sxyz' or 'ryxy' 82 - first character : rotations are applied to 's'tatic or 'r'otating frame 83 - remaining characters : successive rotation axis 'x',
'y',
or 'z' 85 *Axes 4-tuple*: e.g. (0, 0, 0, 0)
or (1, 1, 1, 1)
87 - inner axis: code of axis (
'x':0,
'y':1,
'z':2) of rightmost matrix.
88 - parity : even (0)
if inner axis
'x' is followed by
'y',
'y' is followed
89 by
'z',
or 'z' is followed by
'x'. Otherwise odd (1).
90 - repetition : first
and last axis are same (1)
or different (0).
91 - frame : rotations are applied to static (0)
or rotating (1) frame.
96 (1) Matrices
and transformations. Ronald Goldman.
97 In
"Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
98 (2) More matrices
and transformations: shear
and pseudo-perspective.
99 Ronald Goldman. In
"Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
100 (3) Decomposing a matrix into simple transformations. Spencer Thomas.
101 In
"Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
102 (4) Recovering the data
from the transformation matrix. Ronald Goldman.
103 In
"Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
104 (5) Euler angle conversion. Ken Shoemake.
105 In
"Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
106 (6) Arcball rotation control. Ken Shoemake.
107 In
"Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
108 (7) Representing attitude: Euler angles, unit quaternions,
and rotation
109 vectors. James Diebel. 2006.
110 (8) A discussion of the solution
for the best rotation to relate two sets
111 of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
112 (9) Closed-form solution of absolute orientation using unit quaternions.
113 BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642.
114 (10) Quaternions. Ken Shoemake.
115 http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
116 (11) From quaternion to matrix
and back. JMP van Waveren. 2005.
117 http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
118 (12) Uniform random rotations. Ken Shoemake.
119 In
"Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
125 >>> alpha, beta, gamma = 0.123, -1.234, 2.345
126 >>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
133 >>> numpy.allclose([alpha, beta, gamma], euler)
155 >>> numpy.allclose(scale, 1.23)
157 >>> numpy.allclose(trans, (1, 2, 3))
159 >>> numpy.allclose(shear, (0, math.tan(beta), 0))
169 from __future__ import division 176 # Documentation in HTML format can be generated with Epydoc 177 __docformat__ = "restructuredtext en" 180 def identity_matrix(): 181 """Return 4x4 identity/unit matrix.
184 >>> numpy.allclose(I, numpy.dot(I, I))
186 >>> numpy.sum(I), numpy.trace(I)
188 >>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64))
192 return numpy.identity(4, dtype=numpy.float64) 195 def translation_matrix(direction): 196 """Return matrix to translate by direction vector.
198 >>> v = numpy.random.random(3) - 0.5
203 M = numpy.identity(4) 204 M[:3, 3] = direction[:3] 208 def translation_from_matrix(matrix): 209 """Return translation vector
from translation matrix.
211 >>> v0 = numpy.random.random(3) - 0.5
213 >>> numpy.allclose(v0, v1)
217 return numpy.array(matrix, copy=False)[:3, 3].copy() 220 def reflection_matrix(point, normal): 221 """Return matrix to mirror at plane defined by point
and normal vector.
223 >>> v0 = numpy.random.random(4) - 0.5
225 >>> v1 = numpy.random.random(3) - 0.5
227 >>> numpy.allclose(2., numpy.trace(R))
229 >>> numpy.allclose(v0, numpy.dot(R, v0))
235 >>> numpy.allclose(v2, numpy.dot(R, v3))
239 normal = unit_vector(normal[:3]) 240 M = numpy.identity(4) 241 M[:3, :3] -= 2.0 * numpy.outer(normal, normal) 242 M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal 246 def reflection_from_matrix(matrix): 247 """Return mirror plane point
and normal vector
from reflection matrix.
249 >>> v0 = numpy.random.random(3) - 0.5
250 >>> v1 = numpy.random.random(3) - 0.5
258 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 259 # normal: unit eigenvector corresponding to eigenvalue -1 260 l, V = numpy.linalg.eig(M[:3, :3]) 261 i = numpy.where(abs(numpy.real(l) + 1.0) < 1e-8)[0] 263 raise ValueError("no unit eigenvector corresponding to eigenvalue -1") 264 normal = numpy.real(V[:, i[0]]).squeeze() 265 # point: any unit eigenvector corresponding to eigenvalue 1 266 l, V = numpy.linalg.eig(M) 267 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 269 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 270 point = numpy.real(V[:, i[-1]]).squeeze() 275 def rotation_matrix(angle, direction, point=None): 276 """Return matrix to rotate about axis defined by point
and direction.
278 >>> angle = (random.random() - 0.5) * (2*math.pi)
279 >>> direc = numpy.random.random(3) - 0.5
280 >>> point = numpy.random.random(3) - 0.5
289 >>> I = numpy.identity(4, numpy.float64)
297 sina = math.sin(angle) 298 cosa = math.cos(angle) 299 direction = unit_vector(direction[:3]) 300 # rotation matrix around unit vector 301 R = numpy.array(((cosa, 0.0, 0.0), 303 (0.0, 0.0, cosa)), dtype=numpy.float64) 304 R += numpy.outer(direction, direction) * (1.0 - cosa) 306 R += numpy.array((( 0.0, -direction[2], direction[1]), 307 ( direction[2], 0.0, -direction[0]), 308 (-direction[1], direction[0], 0.0)), 310 M = numpy.identity(4) 312 if point is not None: 313 # rotation not around origin 314 point = numpy.array(point[:3], dtype=numpy.float64, copy=False) 315 M[:3, 3] = point - numpy.dot(R, point) 319 def rotation_from_matrix(matrix): 320 """Return rotation angle
and axis
from rotation matrix.
322 >>> angle = (random.random() - 0.5) * (2*math.pi)
323 >>> direc = numpy.random.random(3) - 0.5
324 >>> point = numpy.random.random(3) - 0.5
332 R = numpy.array(matrix, dtype=numpy.float64, copy=False) 334 # direction: unit eigenvector of R33 corresponding to eigenvalue of 1 335 l, W = numpy.linalg.eig(R33.T) 336 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 338 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 339 direction = numpy.real(W[:, i[-1]]).squeeze() 340 # point: unit eigenvector of R33 corresponding to eigenvalue of 1 341 l, Q = numpy.linalg.eig(R) 342 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 344 raise ValueError("no unit eigenvector corresponding to eigenvalue 1") 345 point = numpy.real(Q[:, i[-1]]).squeeze() 347 # rotation angle depending on direction 348 cosa = (numpy.trace(R33) - 1.0) / 2.0 349 if abs(direction[2]) > 1e-8: 350 sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2] 351 elif abs(direction[1]) > 1e-8: 352 sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1] 354 sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0] 355 angle = math.atan2(sina, cosa) 356 return angle, direction, point 359 def scale_matrix(factor, origin=None, direction=None): 360 """Return matrix to scale by factor around origin
in direction.
362 Use factor -1
for point symmetry.
364 >>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0
367 >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
369 >>> factor = random.random() * 10 - 5
370 >>> origin = numpy.random.random(3) - 0.5
371 >>> direct = numpy.random.random(3) - 0.5
376 if direction is None: 378 M = numpy.array(((factor, 0.0, 0.0, 0.0), 379 (0.0, factor, 0.0, 0.0), 380 (0.0, 0.0, factor, 0.0), 381 (0.0, 0.0, 0.0, 1.0)), dtype=numpy.float64) 382 if origin is not None: 383 M[:3, 3] = origin[:3] 384 M[:3, 3] *= 1.0 - factor 387 direction = unit_vector(direction[:3]) 388 factor = 1.0 - factor 389 M = numpy.identity(4) 390 M[:3, :3] -= factor * numpy.outer(direction, direction) 391 if origin is not None: 392 M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction 396 def scale_from_matrix(matrix): 397 """Return scaling factor, origin
and direction
from scaling matrix.
399 >>> factor = random.random() * 10 - 5
400 >>> origin = numpy.random.random(3) - 0.5
401 >>> direct = numpy.random.random(3) - 0.5
414 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 416 factor = numpy.trace(M33) - 2.0 418 # direction: unit eigenvector corresponding to eigenvalue factor 419 l, V = numpy.linalg.eig(M33) 420 i = numpy.where(abs(numpy.real(l) - factor) < 1e-8)[0][0] 421 direction = numpy.real(V[:, i]).squeeze() 422 direction /= vector_norm(direction) 425 factor = (factor + 2.0) / 3.0 427 # origin: any eigenvector corresponding to eigenvalue 1 428 l, V = numpy.linalg.eig(M) 429 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 431 raise ValueError("no eigenvector corresponding to eigenvalue 1") 432 origin = numpy.real(V[:, i[-1]]).squeeze() 434 return factor, origin, direction 437 def projection_matrix(point, normal, direction=None, 438 perspective=None, pseudo=False): 439 """Return matrix to project onto plane defined by point
and normal.
441 Using either perspective point, projection direction,
or none of both.
443 If pseudo
is True, perspective projections will preserve relative depth
444 such that Perspective = dot(Orthogonal, PseudoPerspective).
447 >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
449 >>> point = numpy.random.random(3) - 0.5
450 >>> normal = numpy.random.random(3) - 0.5
451 >>> direct = numpy.random.random(3) - 0.5
452 >>> persp = numpy.random.random(3) - 0.5
460 >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0
462 >>> v1 = numpy.dot(P, v0)
463 >>> numpy.allclose(v1[1], v0[1])
465 >>> numpy.allclose(v1[0], 3.0-v1[1])
469 M = numpy.identity(4) 470 point = numpy.array(point[:3], dtype=numpy.float64, copy=False) 471 normal = unit_vector(normal[:3]) 472 if perspective is not None: 473 # perspective projection 474 perspective = numpy.array(perspective[:3], dtype=numpy.float64, 476 M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal) 477 M[:3, :3] -= numpy.outer(perspective, normal) 479 # preserve relative depth 480 M[:3, :3] -= numpy.outer(normal, normal) 481 M[:3, 3] = numpy.dot(point, normal) * (perspective+normal) 483 M[:3, 3] = numpy.dot(point, normal) * perspective 485 M[3, 3] = numpy.dot(perspective, normal) 486 elif direction is not None: 487 # parallel projection 488 direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False) 489 scale = numpy.dot(direction, normal) 490 M[:3, :3] -= numpy.outer(direction, normal) / scale 491 M[:3, 3] = direction * (numpy.dot(point, normal) / scale) 493 # orthogonal projection 494 M[:3, :3] -= numpy.outer(normal, normal) 495 M[:3, 3] = numpy.dot(point, normal) * normal 499 def projection_from_matrix(matrix, pseudo=False): 500 """Return projection plane
and perspective point
from projection matrix.
502 Return values are same
as arguments
for projection_matrix function:
503 point, normal, direction, perspective,
and pseudo.
505 >>> point = numpy.random.random(3) - 0.5
506 >>> normal = numpy.random.random(3) - 0.5
507 >>> direct = numpy.random.random(3) - 0.5
508 >>> persp = numpy.random.random(3) - 0.5
531 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 533 l, V = numpy.linalg.eig(M) 534 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 535 if not pseudo and len(i): 536 # point: any eigenvector corresponding to eigenvalue 1 537 point = numpy.real(V[:, i[-1]]).squeeze() 539 # direction: unit eigenvector corresponding to eigenvalue 0 540 l, V = numpy.linalg.eig(M33) 541 i = numpy.where(abs(numpy.real(l)) < 1e-8)[0] 543 raise ValueError("no eigenvector corresponding to eigenvalue 0") 544 direction = numpy.real(V[:, i[0]]).squeeze() 545 direction /= vector_norm(direction) 546 # normal: unit eigenvector of M33.T corresponding to eigenvalue 0 547 l, V = numpy.linalg.eig(M33.T) 548 i = numpy.where(abs(numpy.real(l)) < 1e-8)[0] 550 # parallel projection 551 normal = numpy.real(V[:, i[0]]).squeeze() 552 normal /= vector_norm(normal) 553 return point, normal, direction, None, False 555 # orthogonal projection, where normal equals direction vector 556 return point, direction, None, None, False 558 # perspective projection 559 i = numpy.where(abs(numpy.real(l)) > 1e-8)[0] 562 "no eigenvector not corresponding to eigenvalue 0") 563 point = numpy.real(V[:, i[-1]]).squeeze() 566 perspective = M[:3, 3] / numpy.dot(point[:3], normal) 568 perspective -= normal 569 return point, normal, None, perspective, pseudo 572 def clip_matrix(left, right, bottom, top, near, far, perspective=False): 573 """Return matrix to obtain normalized device coordinates
from frustrum.
575 The frustrum bounds are axis-aligned along x (left, right),
576 y (bottom, top)
and z (near, far).
578 Normalized device coordinates are
in range [-1, 1]
if coordinates are
581 If perspective
is True the frustrum
is a truncated pyramid with the
582 perspective point at origin
and direction along z axis, otherwise an
583 orthographic canonical view volume (a box).
585 Homogeneous coordinates transformed by the perspective clip matrix
586 need to be dehomogenized (devided by w coordinate).
588 >>> frustrum = numpy.random.rand(6)
589 >>> frustrum[1] += frustrum[0]
590 >>> frustrum[3] += frustrum[2]
591 >>> frustrum[5] += frustrum[4]
593 >>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
594 array([-1., -1., -1., 1.])
595 >>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0])
596 array([ 1., 1., 1., 1.])
598 >>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
600 array([-1., -1., -1., 1.])
601 >>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0])
603 array([ 1., 1., -1., 1.])
606 if left >= right or bottom >= top or near >= far: 607 raise ValueError("invalid frustrum") 610 raise ValueError("invalid frustrum: near <= 0") 612 M = ((-t/(right-left), 0.0, (right+left)/(right-left), 0.0), 613 (0.0, -t/(top-bottom), (top+bottom)/(top-bottom), 0.0), 614 (0.0, 0.0, -(far+near)/(far-near), t*far/(far-near)), 615 (0.0, 0.0, -1.0, 0.0)) 617 M = ((2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)), 618 (0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)), 619 (0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)), 620 (0.0, 0.0, 0.0, 1.0)) 621 return numpy.array(M, dtype=numpy.float64) 624 def shear_matrix(angle, direction, point, normal): 625 """Return matrix to shear by angle along direction vector on shear plane.
627 The shear plane
is defined by a point
and normal vector. The direction
628 vector must be orthogonal to the plane
's normal vector. 630 A point P is transformed by the shear matrix into P
" such that 631 the vector P-P" is parallel to the direction vector and its extent is 632 given by the angle of P-P'-P", where P' is the orthogonal projection
633 of P onto the shear plane.
635 >>> angle = (random.random() - 0.5) * 4*math.pi
636 >>> direct = numpy.random.random(3) - 0.5
637 >>> point = numpy.random.random(3) - 0.5
638 >>> normal = numpy.cross(direct, numpy.random.random(3))
640 >>> numpy.allclose(1.0, numpy.linalg.det(S))
644 normal = unit_vector(normal[:3]) 645 direction = unit_vector(direction[:3]) 646 if abs(numpy.dot(normal, direction)) > 1e-6: 647 raise ValueError("direction and normal vectors are not orthogonal") 648 angle = math.tan(angle) 649 M = numpy.identity(4) 650 M[:3, :3] += angle * numpy.outer(direction, normal) 651 M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction 655 def shear_from_matrix(matrix): 656 """Return shear angle, direction
and plane
from shear matrix.
658 >>> angle = (random.random() - 0.5) * 4*math.pi
659 >>> direct = numpy.random.random(3) - 0.5
660 >>> point = numpy.random.random(3) - 0.5
661 >>> normal = numpy.cross(direct, numpy.random.random(3))
669 M = numpy.array(matrix, dtype=numpy.float64, copy=False) 671 # normal: cross independent eigenvectors corresponding to the eigenvalue 1 672 l, V = numpy.linalg.eig(M33) 673 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-4)[0] 675 raise ValueError("No two linear independent eigenvectors found {}".format(l)) 676 V = numpy.real(V[:, i]).squeeze().T 678 for i0, i1 in ((0, 1), (0, 2), (1, 2)): 679 n = numpy.cross(V[i0], V[i1]) 685 # direction and angle 686 direction = numpy.dot(M33 - numpy.identity(3), normal) 687 angle = vector_norm(direction) 689 angle = math.atan(angle) 690 # point: eigenvector corresponding to eigenvalue 1 691 l, V = numpy.linalg.eig(M) 692 i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] 694 raise ValueError("no eigenvector corresponding to eigenvalue 1") 695 point = numpy.real(V[:, i[-1]]).squeeze() 697 return angle, direction, point, normal 700 def decompose_matrix(matrix): 701 """Return sequence of transformations
from transformation matrix.
704 Non-degenerative homogeneous transformation matrix
707 scale : vector of 3 scaling factors
708 shear : list of shear factors
for x-y, x-z, y-z axes
709 angles : list of Euler angles about static x, y, z axes
710 translate : translation vector along x, y, z axes
711 perspective : perspective partition of matrix
713 Raise ValueError
if matrix
is of wrong type
or degenerative.
718 >>> numpy.allclose(T0, T1)
727 >>> numpy.allclose(R0, R1)
731 M = numpy.array(matrix, dtype=numpy.float64, copy=True).T 732 if abs(M[3, 3]) < _EPS: 733 raise ValueError("M[3, 3] is zero") 737 if not numpy.linalg.det(P): 738 raise ValueError("Matrix is singular") 740 scale = numpy.zeros((3, ), dtype=numpy.float64) 744 if any(abs(M[:3, 3]) > _EPS): 745 perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T)) 748 perspective = numpy.array((0, 0, 0, 1), dtype=numpy.float64) 750 translate = M[3, :3].copy() 753 row = M[:3, :3].copy() 754 scale[0] = vector_norm(row[0]) 756 shear[0] = numpy.dot(row[0], row[1]) 757 row[1] -= row[0] * shear[0] 758 scale[1] = vector_norm(row[1]) 761 shear[1] = numpy.dot(row[0], row[2]) 762 row[2] -= row[0] * shear[1] 763 shear[2] = numpy.dot(row[1], row[2]) 764 row[2] -= row[1] * shear[2] 765 scale[2] = vector_norm(row[2]) 767 shear[1:] /= scale[2] 769 if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0: 773 angles[1] = math.asin(-row[0, 2]) 774 if math.cos(angles[1]): 775 angles[0] = math.atan2(row[1, 2], row[2, 2]) 776 angles[2] = math.atan2(row[0, 1], row[0, 0]) 778 #angles[0] = math.atan2(row[1, 0], row[1, 1]) 779 angles[0] = math.atan2(-row[2, 1], row[1, 1]) 782 return scale, shear, angles, translate, perspective 785 def compose_matrix(scale=None, shear=None, angles=None, translate=None, 787 """Return transformation matrix
from sequence of transformations.
789 This
is the inverse of the decompose_matrix function.
791 Sequence of transformations:
792 scale : vector of 3 scaling factors
793 shear : list of shear factors
for x-y, x-z, y-z axes
794 angles : list of Euler angles about static x, y, z axes
795 translate : translation vector along x, y, z axes
796 perspective : perspective partition of matrix
798 >>> scale = numpy.random.random(3) - 0.5
799 >>> shear = numpy.random.random(3) - 0.5
800 >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
801 >>> trans = numpy.random.random(3) - 0.5
802 >>> persp = numpy.random.random(4) - 0.5
810 M = numpy.identity(4) 811 if perspective is not None: 812 P = numpy.identity(4) 813 P[3, :] = perspective[:4] 815 if translate is not None: 816 T = numpy.identity(4) 817 T[:3, 3] = translate[:3] 819 if angles is not None: 820 R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz') 822 if shear is not None: 823 Z = numpy.identity(4) 828 if scale is not None: 829 S = numpy.identity(4) 838 def orthogonalization_matrix(lengths, angles): 839 """Return orthogonalization matrix
for crystallographic cell coordinates.
841 Angles are expected
in degrees.
843 The de-orthogonalization matrix
is the inverse.
846 >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
849 >>> numpy.allclose(numpy.sum(O), 43.063229)
854 angles = numpy.radians(angles) 855 sina, sinb, _ = numpy.sin(angles) 856 cosa, cosb, cosg = numpy.cos(angles) 857 co = (cosa * cosb - cosg) / (sina * sinb) 859 ( a*sinb*math.sqrt(1.0-co*co), 0.0, 0.0, 0.0), 860 (-a*sinb*co, b*sina, 0.0, 0.0), 861 ( a*cosb, b*cosa, c, 0.0), 862 ( 0.0, 0.0, 0.0, 1.0)), 866 def superimposition_matrix(v0, v1, scaling=False, usesvd=True): 867 """Return matrix to transform given vector set into second vector set.
869 v0
and v1 are shape (3, \*)
or (4, \*) arrays of at least 3 vectors.
871 If usesvd
is True, the weighted sum of squared deviations (RMSD)
is 872 minimized according to the algorithm by W. Kabsch [8]. Otherwise the
873 quaternion based algorithm by B. Horn [9]
is used (slower when using
874 this Python implementation).
876 The returned matrix performs rotation, translation
and uniform scaling
879 >>> v0 = numpy.random.rand(3, 10)
881 >>> numpy.allclose(M, numpy.identity(4))
884 >>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1))
885 >>> v1 = numpy.dot(R, v0)
887 >>> numpy.allclose(v1, numpy.dot(M, v0))
889 >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0
891 >>> v1 = numpy.dot(R, v0)
893 >>> numpy.allclose(v1, numpy.dot(M, v0))
898 >>> v1 = numpy.dot(M, v0)
899 >>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1)
901 >>> numpy.allclose(v1, numpy.dot(M, v0))
904 >>> numpy.allclose(v1, numpy.dot(M, v0))
906 >>> v = numpy.empty((4, 100, 3), dtype=numpy.float64)
909 >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
913 v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3] 914 v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3] 916 if v0.shape != v1.shape or v0.shape[1] < 3: 917 raise ValueError("Vector sets are of wrong shape or type.") 919 # move centroids to origin 920 t0 = numpy.mean(v0, axis=1) 921 t1 = numpy.mean(v1, axis=1) 922 v0 = v0 - t0.reshape(3, 1) 923 v1 = v1 - t1.reshape(3, 1) 926 # Singular Value Decomposition of covariance matrix 927 u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T)) 928 # rotation matrix from SVD orthonormal bases 930 if numpy.linalg.det(R) < 0.0: 931 # R does not constitute right handed system 932 R -= numpy.outer(u[:, 2], vh[2, :]*2.0) 934 # homogeneous transformation matrix 935 M = numpy.identity(4) 938 # compute symmetric matrix N 939 xx, yy, zz = numpy.sum(v0 * v1, axis=1) 940 xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1) 941 xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1) 942 N = ((xx+yy+zz, yz-zy, zx-xz, xy-yx), 943 (yz-zy, xx-yy-zz, xy+yx, zx+xz), 944 (zx-xz, xy+yx, -xx+yy-zz, yz+zy), 945 (xy-yx, zx+xz, yz+zy, -xx-yy+zz)) 946 # quaternion: eigenvector corresponding to most positive eigenvalue 947 l, V = numpy.linalg.eig(N) 948 q = V[:, numpy.argmax(l)] 949 q /= vector_norm(q) # unit quaternion 950 q = numpy.roll(q, -1) # move w component to end 951 # homogeneous transformation matrix 952 M = quaternion_matrix(q) 954 # scale: ratio of rms deviations from centroid 958 M[:3, :3] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0)) 962 T = numpy.identity(4) 968 def euler_matrix(ai, aj, ak, axes='sxyz'): 969 """Return homogeneous rotation matrix
from Euler angles
and axis sequence.
971 ai, aj, ak : Euler
's roll, pitch and yaw angles 972 axes : One of 24 axis sequences as string
or encoded tuple
975 >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
978 >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
980 >>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
981 >>>
for axes
in _AXES2TUPLE.keys():
983 >>>
for axes
in _TUPLE2AXES.keys():
988 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes] 989 except (AttributeError, KeyError): 990 _ = _TUPLE2AXES[axes] 991 firstaxis, parity, repetition, frame = axes 994 j = _NEXT_AXIS[i+parity] 995 k = _NEXT_AXIS[i-parity+1] 1000 ai, aj, ak = -ai, -aj, -ak 1002 si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak) 1003 ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak) 1004 cc, cs = ci*ck, ci*sk 1005 sc, ss = si*ck, si*sk 1007 M = numpy.identity(4) 1031 def euler_from_matrix(matrix, axes='sxyz'): 1032 """Return Euler angles
from rotation matrix
for specified axis sequence.
1034 axes : One of 24 axis sequences
as string
or encoded tuple
1036 Note that many Euler angle triplets can describe one matrix.
1041 >>> numpy.allclose(R0, R1)
1043 >>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
1044 >>>
for axes
in _AXES2TUPLE.keys():
1047 ...
if not numpy.allclose(R0, R1):
print axes,
"failed" 1051 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] 1052 except (AttributeError, KeyError): 1053 _ = _TUPLE2AXES[axes] 1054 firstaxis, parity, repetition, frame = axes 1057 j = _NEXT_AXIS[i+parity] 1058 k = _NEXT_AXIS[i-parity+1] 1060 M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3] 1062 sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k]) 1064 ax = math.atan2( M[i, j], M[i, k]) 1065 ay = math.atan2( sy, M[i, i]) 1066 az = math.atan2( M[j, i], -M[k, i]) 1068 ax = math.atan2(-M[j, k], M[j, j]) 1069 ay = math.atan2( sy, M[i, i]) 1072 cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i]) 1074 ax = math.atan2( M[k, j], M[k, k]) 1075 ay = math.atan2(-M[k, i], cy) 1076 az = math.atan2( M[j, i], M[i, i]) 1078 ax = math.atan2(-M[j, k], M[j, j]) 1079 ay = math.atan2(-M[k, i], cy) 1083 ax, ay, az = -ax, -ay, -az 1089 def euler_from_quaternion(quaternion, axes='sxyz'): 1090 """Return Euler angles
from quaternion
for specified axis sequence.
1093 >>> numpy.allclose(angles, [0.123, 0, 0])
1097 return euler_from_matrix(quaternion_matrix(quaternion), axes) 1100 def quaternion_from_euler(ai, aj, ak, axes='sxyz'): 1101 """Return quaternion
from Euler angles
and axis sequence.
1103 ai, aj, ak : Euler
's roll, pitch and yaw angles 1104 axes : One of 24 axis sequences as string
or encoded tuple
1107 >>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953])
1112 firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] 1113 except (AttributeError, KeyError): 1114 _ = _TUPLE2AXES[axes] 1115 firstaxis, parity, repetition, frame = axes 1118 j = _NEXT_AXIS[i+parity] 1119 k = _NEXT_AXIS[i-parity+1] 1140 quaternion = numpy.empty((4, ), dtype=numpy.float64) 1142 quaternion[i] = cj*(cs + sc) 1143 quaternion[j] = sj*(cc + ss) 1144 quaternion[k] = sj*(cs - sc) 1145 quaternion[3] = cj*(cc - ss) 1147 quaternion[i] = cj*sc - sj*cs 1148 quaternion[j] = cj*ss + sj*cc 1149 quaternion[k] = cj*cs - sj*sc 1150 quaternion[3] = cj*cc + sj*ss 1157 def quaternion_about_axis(angle, axis): 1158 """Return quaternion
for rotation about axis.
1161 >>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947])
1165 quaternion = numpy.zeros((4, ), dtype=numpy.float64) 1166 quaternion[:3] = axis[:3] 1167 qlen = vector_norm(quaternion) 1169 quaternion *= math.sin(angle/2.0) / qlen 1170 quaternion[3] = math.cos(angle/2.0) 1174 def quaternion_matrix(quaternion): 1175 """Return homogeneous rotation matrix
from quaternion.
1182 q = numpy.array(quaternion[:4], dtype=numpy.float64, copy=True) 1183 nq = numpy.dot(q, q) 1185 return numpy.identity(4) 1186 q *= math.sqrt(2.0 / nq) 1187 q = numpy.outer(q, q) 1188 return numpy.array(( 1189 (1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0), 1190 ( q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0), 1191 ( q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0), 1192 ( 0.0, 0.0, 0.0, 1.0) 1193 ), dtype=numpy.float64) 1196 def quaternion_from_matrix(matrix): 1197 """Return quaternion
from rotation matrix.
1201 >>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095])
1205 q = numpy.empty((4, ), dtype=numpy.float64) 1206 M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4] 1210 q[2] = M[1, 0] - M[0, 1] 1211 q[1] = M[0, 2] - M[2, 0] 1212 q[0] = M[2, 1] - M[1, 2] 1215 if M[1, 1] > M[0, 0]: 1217 if M[2, 2] > M[i, i]: 1219 t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3] 1221 q[j] = M[i, j] + M[j, i] 1222 q[k] = M[k, i] + M[i, k] 1223 q[3] = M[k, j] - M[j, k] 1224 q *= 0.5 / math.sqrt(t * M[3, 3]) 1228 def quaternion_multiply(quaternion1, quaternion0): 1229 """Return multiplication of two quaternions.
1232 >>> numpy.allclose(q, [-44, -14, 48, 28])
1236 x0, y0, z0, w0 = quaternion0 1237 x1, y1, z1, w1 = quaternion1 1238 return numpy.array(( 1239 x1*w0 + y1*z0 - z1*y0 + w1*x0, 1240 -x1*z0 + y1*w0 + z1*x0 + w1*y0, 1241 x1*y0 - y1*x0 + z1*w0 + w1*z0, 1242 -x1*x0 - y1*y0 - z1*z0 + w1*w0), dtype=numpy.float64) 1245 def quaternion_conjugate(quaternion): 1246 """Return conjugate of quaternion.
1250 >>> q1[3] == q0[3]
and all(q1[:3] == -q0[:3])
1254 return numpy.array((-quaternion[0], -quaternion[1], 1255 -quaternion[2], quaternion[3]), dtype=numpy.float64) 1258 def quaternion_inverse(quaternion): 1259 """Return inverse of quaternion.
1267 return quaternion_conjugate(quaternion) / numpy.dot(quaternion, quaternion) 1270 def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True): 1271 """Return spherical linear interpolation between two quaternions.
1276 >>> numpy.allclose(q, q0)
1279 >>> numpy.allclose(q, q1)
1282 >>> angle = math.acos(numpy.dot(q0, q))
1283 >>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle)
or \
1284 numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle)
1288 q0 = unit_vector(quat0[:4]) 1289 q1 = unit_vector(quat1[:4]) 1292 elif fraction == 1.0: 1294 d = numpy.dot(q0, q1) 1295 if abs(abs(d) - 1.0) < _EPS: 1297 if shortestpath and d < 0.0: 1301 angle = math.acos(d) + spin * math.pi 1302 if abs(angle) < _EPS: 1304 isin = 1.0 / math.sin(angle) 1305 q0 *= math.sin((1.0 - fraction) * angle) * isin 1306 q1 *= math.sin(fraction * angle) * isin 1311 def random_quaternion(rand=None): 1312 """Return uniform random unit quaternion.
1314 rand: array like
or None 1315 Three independent random variables that are uniformly distributed
1327 rand = numpy.random.rand(3) 1329 assert len(rand) == 3 1330 r1 = numpy.sqrt(1.0 - rand[0]) 1331 r2 = numpy.sqrt(rand[0]) 1335 return numpy.array((numpy.sin(t1)*r1, 1338 numpy.cos(t2)*r2), dtype=numpy.float64) 1341 def random_rotation_matrix(rand=None): 1342 """Return uniform random rotation matrix.
1345 Three independent random variables that are uniformly distributed
1346 between 0
and 1
for each returned quaternion.
1349 >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
1353 return quaternion_matrix(random_quaternion(rand)) 1356 class Arcball(object): 1357 """Virtual Trackball Control.
1360 >>> ball =
Arcball(initial=numpy.identity(4))
1361 >>> ball.place([320, 320], 320)
1362 >>> ball.down([500, 250])
1363 >>> ball.drag([475, 275])
1364 >>> R = ball.matrix()
1365 >>> numpy.allclose(numpy.sum(R), 3.90583455)
1367 >>> ball =
Arcball(initial=[0, 0, 0, 1])
1368 >>> ball.place([320, 320], 320)
1369 >>> ball.setaxes([1,1,0], [-1, 1, 0])
1370 >>> ball.setconstrain(
True)
1371 >>> ball.down([400, 200])
1372 >>> ball.drag([200, 400])
1373 >>> R = ball.matrix()
1374 >>> numpy.allclose(numpy.sum(R), 0.2055924)
1380 def __init__(self, initial=None): 1381 """Initialize virtual trackball control.
1383 initial : quaternion
or rotation matrix
1389 self._center = [0.0, 0.0] 1390 self._vdown = numpy.array([0, 0, 1], dtype=numpy.float64) 1391 self._constrain = False 1394 self._qdown = numpy.array([0, 0, 0, 1], dtype=numpy.float64) 1396 initial = numpy.array(initial, dtype=numpy.float64) 1397 if initial.shape == (4, 4): 1398 self._qdown = quaternion_from_matrix(initial) 1399 elif initial.shape == (4, ): 1400 initial /= vector_norm(initial) 1401 self._qdown = initial 1403 raise ValueError("initial not a quaternion or matrix.") 1405 self._qnow = self._qpre = self._qdown 1407 def place(self, center, radius): 1408 """Place Arcball, e.g. when window size changes.
1410 center : sequence[2]
1411 Window coordinates of trackball center.
1413 Radius of trackball
in window coordinates.
1416 self._radius = float(radius) 1417 self._center[0] = center[0] 1418 self._center[1] = center[1] 1420 def setaxes(self, *axes): 1421 """Set axes to constrain rotations.
""" 1425 self._axes = [unit_vector(axis) for axis in axes] 1427 def setconstrain(self, constrain): 1428 """Set state of constrain to axis mode.
""" 1429 self._constrain = constrain == True 1431 def getconstrain(self): 1432 """Return state of constrain to axis mode.
""" 1433 return self._constrain 1435 def down(self, point): 1436 """Set initial cursor window coordinates
and pick constrain-axis.
""" 1437 self._vdown = arcball_map_to_sphere(point, self._center, self._radius) 1438 self._qdown = self._qpre = self._qnow 1440 if self._constrain and self._axes is not None: 1441 self._axis = arcball_nearest_axis(self._vdown, self._axes) 1442 self._vdown = arcball_constrain_to_axis(self._vdown, self._axis) 1446 def drag(self, point): 1447 """Update current cursor window coordinates.
""" 1448 vnow = arcball_map_to_sphere(point, self._center, self._radius) 1450 if self._axis is not None: 1451 vnow = arcball_constrain_to_axis(vnow, self._axis) 1453 self._qpre = self._qnow 1455 t = numpy.cross(self._vdown, vnow) 1456 if numpy.dot(t, t) < _EPS: 1457 self._qnow = self._qdown 1459 q = [t[0], t[1], t[2], numpy.dot(self._vdown, vnow)] 1460 self._qnow = quaternion_multiply(q, self._qdown) 1462 def next(self, acceleration=0.0): 1463 """Continue rotation
in direction of last drag.
""" 1464 q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False) 1465 self._qpre, self._qnow = self._qnow, q 1468 """Return homogeneous rotation matrix.
""" 1469 return quaternion_matrix(self._qnow) 1472 def arcball_map_to_sphere(point, center, radius): 1473 """Return unit sphere coordinates
from window coordinates.
""" 1474 v = numpy.array(((point[0] - center[0]) / radius, 1475 (center[1] - point[1]) / radius, 1476 0.0), dtype=numpy.float64) 1477 n = v[0]*v[0] + v[1]*v[1] 1479 v /= math.sqrt(n) # position outside of sphere 1481 v[2] = math.sqrt(1.0 - n) 1485 def arcball_constrain_to_axis(point, axis): 1486 """Return sphere point perpendicular to axis.
""" 1487 v = numpy.array(point, dtype=numpy.float64, copy=True) 1488 a = numpy.array(axis, dtype=numpy.float64, copy=True) 1489 v -= a * numpy.dot(a, v) # on plane 1497 return numpy.array([1, 0, 0], dtype=numpy.float64) 1498 return unit_vector([-a[1], a[0], 0]) 1501 def arcball_nearest_axis(point, axes): 1502 """Return axis, which arc
is nearest to point.
""" 1503 point = numpy.array(point, dtype=numpy.float64, copy=False) 1507 t = numpy.dot(arcball_constrain_to_axis(point, axis), point) 1514 # epsilon for testing whether a number is close to zero 1515 _EPS = numpy.finfo(float).eps * 4.0 1517 # axis sequences for Euler angles 1518 _NEXT_AXIS = [1, 2, 0, 1] 1520 # map axes strings to/from tuples of inner axis, parity, repetition, frame 1522 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0), 1523 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0), 1524 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0), 1525 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0), 1526 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1), 1527 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1), 1528 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1), 1529 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)} 1531 _TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items()) 1535 def vector_norm(data, axis=None, out=None): 1536 """Return length, i.e. eucledian norm, of ndarray along axis.
1538 >>> v = numpy.random.random(3)
1540 >>> numpy.allclose(n, numpy.linalg.norm(v))
1542 >>> v = numpy.random.rand(6, 5, 3)
1544 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
1547 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
1549 >>> v = numpy.random.rand(5, 4, 3)
1550 >>> n = numpy.empty((5, 3), dtype=numpy.float64)
1552 >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
1560 data = numpy.array(data, dtype=numpy.float64, copy=True) 1563 return math.sqrt(numpy.dot(data, data)) 1565 out = numpy.atleast_1d(numpy.sum(data, axis=axis)) 1566 numpy.sqrt(out, out) 1570 numpy.sum(data, axis=axis, out=out) 1571 numpy.sqrt(out, out) 1574 def unit_vector(data, axis=None, out=None): 1575 """Return ndarray normalized by length, i.e. eucledian norm, along axis.
1577 >>> v0 = numpy.random.random(3)
1579 >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
1581 >>> v0 = numpy.random.rand(5, 4, 3)
1583 >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
1584 >>> numpy.allclose(v1, v2)
1587 >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
1588 >>> numpy.allclose(v1, v2)
1590 >>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64)
1592 >>> numpy.allclose(v1, v2)
1601 data = numpy.array(data, dtype=numpy.float64, copy=True) 1603 data /= math.sqrt(numpy.dot(data, data)) 1607 out[:] = numpy.array(data, copy=False) 1609 length = numpy.atleast_1d(numpy.sum(data*data, axis)) 1610 numpy.sqrt(length, length) 1611 if axis is not None: 1612 length = numpy.expand_dims(length, axis) 1618 def random_vector(size): 1619 """Return array of random doubles
in the half-open interval [0.0, 1.0).
1622 >>> numpy.all(v >= 0.0)
and numpy.all(v < 1.0)
1626 >>> numpy.any(v0 == v1)
1630 return numpy.random.random(size) 1633 def inverse_matrix(matrix): 1634 """Return inverse of square transformation matrix.
1638 >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
1640 >>>
for size
in range(1, 7):
1641 ... M0 = numpy.random.rand(size, size)
1643 ...
if not numpy.allclose(M1, numpy.linalg.inv(M0)):
print size
1646 return numpy.linalg.inv(matrix) 1649 def concatenate_matrices(*matrices): 1650 """Return concatenation of series of transformation matrices.
1652 >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
1659 M = numpy.identity(4) 1665 def is_same_transform(matrix0, matrix1): 1666 """Return
True if two matrices perform same transformation.
1674 matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True) 1675 matrix0 /= matrix0[3, 3] 1676 matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True) 1677 matrix1 /= matrix1[3, 3] 1678 return numpy.allclose(matrix0, matrix1) 1681 def _import_module(module_name, warn=True, prefix='_py_', ignore='_'): 1682 """Try
import all public attributes
from module into
global namespace.
1684 Existing attributes with name clashes are renamed with prefix.
1685 Attributes starting with underscore are ignored by default.
1687 Return
True on successful
import.
1691 module = __import__(module_name) 1694 warnings.warn("Failed to import module " + module_name) 1696 for attr in dir(module): 1697 if ignore and attr.startswith(ignore): 1700 if attr in globals(): 1701 globals()[prefix + attr] = globals()[attr] 1703 warnings.warn("No Python implementation of " + attr) 1704 globals()[attr] = getattr(module, attr)