Home | History | Annotate | Download | only in test
      1 #!/usr/bin/env python
      2 
      3 # -*- coding: utf-8 -*-
      4 # transformations.py
      5 
      6 # Copyright (c) 2006, Christoph Gohlke
      7 # Copyright (c) 2006-2009, The Regents of the University of California
      8 # All rights reserved.
      9 #
     10 # Redistribution and use in source and binary forms, with or without
     11 # modification, are permitted provided that the following conditions are met:
     12 #
     13 # * Redistributions of source code must retain the above copyright
     14 #   notice, this list of conditions and the following disclaimer.
     15 # * Redistributions in binary form must reproduce the above copyright
     16 #   notice, this list of conditions and the following disclaimer in the
     17 #   documentation and/or other materials provided with the distribution.
     18 # * Neither the name of the copyright holders nor the names of any
     19 #   contributors may be used to endorse or promote products derived
     20 #   from this software without specific prior written permission.
     21 #
     22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     23 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     24 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     25 # ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     26 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     27 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     28 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     29 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     30 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     31 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     32 # POSSIBILITY OF SUCH DAMAGE.
     33 
     34 """Homogeneous Transformation Matrices and Quaternions.
     35 
     36 A library for calculating 4x4 matrices for translating, rotating, reflecting,
     37 scaling, shearing, projecting, orthogonalizing, and superimposing arrays of
     38 3D homogeneous coordinates as well as for converting between rotation matrices,
     39 Euler angles, and quaternions. Also includes an Arcball control object and
     40 functions to decompose transformation matrices.
     41 
     42 :Authors:
     43   `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`__,
     44   Laboratory for Fluorescence Dynamics, University of California, Irvine
     45 
     46 :Version: 20090418
     47 
     48 Requirements
     49 ------------
     50 
     51 * `Python 2.6 <http://www.python.org>`__
     52 * `Numpy 1.3 <http://numpy.scipy.org>`__
     53 * `transformations.c 20090418 <http://www.lfd.uci.edu/~gohlke/>`__
     54   (optional implementation of some functions in C)
     55 
     56 Notes
     57 -----
     58 
     59 Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using
     60 numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using
     61 numpy.dot(M, v) for shape (4, \*) "point of arrays", respectively
     62 numpy.dot(v, M.T) for shape (\*, 4) "array of points".
     63 
     64 Calculations are carried out with numpy.float64 precision.
     65 
     66 This Python implementation is not optimized for speed.
     67 
     68 Vector, point, quaternion, and matrix function arguments are expected to be
     69 "array like", i.e. tuple, list, or numpy arrays.
     70 
     71 Return types are numpy arrays unless specified otherwise.
     72 
     73 Angles are in radians unless specified otherwise.
     74 
     75 Quaternions ix+jy+kz+w are represented as [x, y, z, w].
     76 
     77 Use the transpose of transformation matrices for OpenGL glMultMatrixd().
     78 
     79 A triple of Euler angles can be applied/interpreted in 24 ways, which can
     80 be specified using a 4 character string or encoded 4-tuple:
     81 
     82   *Axes 4-string*: e.g. 'sxyz' or 'ryxy'
     83 
     84   - first character : rotations are applied to 's'tatic or 'r'otating frame
     85   - remaining characters : successive rotation axis 'x', 'y', or 'z'
     86 
     87   *Axes 4-tuple*: e.g. (0, 0, 0, 0) or (1, 1, 1, 1)
     88 
     89   - inner axis: code of axis ('x':0, 'y':1, 'z':2) of rightmost matrix.
     90   - parity : even (0) if inner axis 'x' is followed by 'y', 'y' is followed
     91     by 'z', or 'z' is followed by 'x'. Otherwise odd (1).
     92   - repetition : first and last axis are same (1) or different (0).
     93   - frame : rotations are applied to static (0) or rotating (1) frame.
     94 
     95 References
     96 ----------
     97 
     98 (1)  Matrices and transformations. Ronald Goldman.
     99      In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
    100 (2)  More matrices and transformations: shear and pseudo-perspective.
    101      Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
    102 (3)  Decomposing a matrix into simple transformations. Spencer Thomas.
    103      In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
    104 (4)  Recovering the data from the transformation matrix. Ronald Goldman.
    105      In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
    106 (5)  Euler angle conversion. Ken Shoemake.
    107      In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
    108 (6)  Arcball rotation control. Ken Shoemake.
    109      In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
    110 (7)  Representing attitude: Euler angles, unit quaternions, and rotation
    111      vectors. James Diebel. 2006.
    112 (8)  A discussion of the solution for the best rotation to relate two sets
    113      of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
    114 (9)  Closed-form solution of absolute orientation using unit quaternions.
    115      BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642.
    116 (10) Quaternions. Ken Shoemake.
    117      http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
    118 (11) From quaternion to matrix and back. JMP van Waveren. 2005.
    119      http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
    120 (12) Uniform random rotations. Ken Shoemake.
    121      In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
    122 
    123 
    124 Examples
    125 --------
    126 
    127 >>> alpha, beta, gamma = 0.123, -1.234, 2.345
    128 >>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
    129 >>> I = identity_matrix()
    130 >>> Rx = rotation_matrix(alpha, xaxis)
    131 >>> Ry = rotation_matrix(beta, yaxis)
    132 >>> Rz = rotation_matrix(gamma, zaxis)
    133 >>> R = concatenate_matrices(Rx, Ry, Rz)
    134 >>> euler = euler_from_matrix(R, 'rxyz')
    135 >>> numpy.allclose([alpha, beta, gamma], euler)
    136 True
    137 >>> Re = euler_matrix(alpha, beta, gamma, 'rxyz')
    138 >>> is_same_transform(R, Re)
    139 True
    140 >>> al, be, ga = euler_from_matrix(Re, 'rxyz')
    141 >>> is_same_transform(Re, euler_matrix(al, be, ga, 'rxyz'))
    142 True
    143 >>> qx = quaternion_about_axis(alpha, xaxis)
    144 >>> qy = quaternion_about_axis(beta, yaxis)
    145 >>> qz = quaternion_about_axis(gamma, zaxis)
    146 >>> q = quaternion_multiply(qx, qy)
    147 >>> q = quaternion_multiply(q, qz)
    148 >>> Rq = quaternion_matrix(q)
    149 >>> is_same_transform(R, Rq)
    150 True
    151 >>> S = scale_matrix(1.23, origin)
    152 >>> T = translation_matrix((1, 2, 3))
    153 >>> Z = shear_matrix(beta, xaxis, origin, zaxis)
    154 >>> R = random_rotation_matrix(numpy.random.rand(3))
    155 >>> M = concatenate_matrices(T, R, Z, S)
    156 >>> scale, shear, angles, trans, persp = decompose_matrix(M)
    157 >>> numpy.allclose(scale, 1.23)
    158 True
    159 >>> numpy.allclose(trans, (1, 2, 3))
    160 True
    161 >>> numpy.allclose(shear, (0, math.tan(beta), 0))
    162 True
    163 >>> is_same_transform(R, euler_matrix(axes='sxyz', *angles))
    164 True
    165 >>> M1 = compose_matrix(scale, shear, angles, trans, persp)
    166 >>> is_same_transform(M, M1)
    167 True
    168 
    169 """
    170 
    171 from __future__ import division
    172 
    173 import warnings
    174 import math
    175 
    176 import numpy
    177 
    178 # Documentation in HTML format can be generated with Epydoc
    179 __docformat__ = "restructuredtext en"
    180 
    181 
    182 def identity_matrix():
    183     """Return 4x4 identity/unit matrix.
    184 
    185     >>> I = identity_matrix()
    186     >>> numpy.allclose(I, numpy.dot(I, I))
    187     True
    188     >>> numpy.sum(I), numpy.trace(I)
    189     (4.0, 4.0)
    190     >>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64))
    191     True
    192 
    193     """
    194     return numpy.identity(4, dtype=numpy.float64)
    195 
    196 
    197 def translation_matrix(direction):
    198     """Return matrix to translate by direction vector.
    199 
    200     >>> v = numpy.random.random(3) - 0.5
    201     >>> numpy.allclose(v, translation_matrix(v)[:3, 3])
    202     True
    203 
    204     """
    205     M = numpy.identity(4)
    206     M[:3, 3] = direction[:3]
    207     return M
    208 
    209 
    210 def translation_from_matrix(matrix):
    211     """Return translation vector from translation matrix.
    212 
    213     >>> v0 = numpy.random.random(3) - 0.5
    214     >>> v1 = translation_from_matrix(translation_matrix(v0))
    215     >>> numpy.allclose(v0, v1)
    216     True
    217 
    218     """
    219     return numpy.array(matrix, copy=False)[:3, 3].copy()
    220 
    221 
    222 def reflection_matrix(point, normal):
    223     """Return matrix to mirror at plane defined by point and normal vector.
    224 
    225     >>> v0 = numpy.random.random(4) - 0.5
    226     >>> v0[3] = 1.0
    227     >>> v1 = numpy.random.random(3) - 0.5
    228     >>> R = reflection_matrix(v0, v1)
    229     >>> numpy.allclose(2., numpy.trace(R))
    230     True
    231     >>> numpy.allclose(v0, numpy.dot(R, v0))
    232     True
    233     >>> v2 = v0.copy()
    234     >>> v2[:3] += v1
    235     >>> v3 = v0.copy()
    236     >>> v2[:3] -= v1
    237     >>> numpy.allclose(v2, numpy.dot(R, v3))
    238     True
    239 
    240     """
    241     normal = unit_vector(normal[:3])
    242     M = numpy.identity(4)
    243     M[:3, :3] -= 2.0 * numpy.outer(normal, normal)
    244     M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal
    245     return M
    246 
    247 
    248 def reflection_from_matrix(matrix):
    249     """Return mirror plane point and normal vector from reflection matrix.
    250 
    251     >>> v0 = numpy.random.random(3) - 0.5
    252     >>> v1 = numpy.random.random(3) - 0.5
    253     >>> M0 = reflection_matrix(v0, v1)
    254     >>> point, normal = reflection_from_matrix(M0)
    255     >>> M1 = reflection_matrix(point, normal)
    256     >>> is_same_transform(M0, M1)
    257     True
    258 
    259     """
    260     M = numpy.array(matrix, dtype=numpy.float64, copy=False)
    261     # normal: unit eigenvector corresponding to eigenvalue -1
    262     l, V = numpy.linalg.eig(M[:3, :3])
    263     i = numpy.where(abs(numpy.real(l) + 1.0) < 1e-8)[0]
    264     if not len(i):
    265         raise ValueError("no unit eigenvector corresponding to eigenvalue -1")
    266     normal = numpy.real(V[:, i[0]]).squeeze()
    267     # point: any unit eigenvector corresponding to eigenvalue 1
    268     l, V = numpy.linalg.eig(M)
    269     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    270     if not len(i):
    271         raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
    272     point = numpy.real(V[:, i[-1]]).squeeze()
    273     point /= point[3]
    274     return point, normal
    275 
    276 
    277 def rotation_matrix(angle, direction, point=None):
    278     """Return matrix to rotate about axis defined by point and direction.
    279 
    280     >>> angle = (random.random() - 0.5) * (2*math.pi)
    281     >>> direc = numpy.random.random(3) - 0.5
    282     >>> point = numpy.random.random(3) - 0.5
    283     >>> R0 = rotation_matrix(angle, direc, point)
    284     >>> R1 = rotation_matrix(angle-2*math.pi, direc, point)
    285     >>> is_same_transform(R0, R1)
    286     True
    287     >>> R0 = rotation_matrix(angle, direc, point)
    288     >>> R1 = rotation_matrix(-angle, -direc, point)
    289     >>> is_same_transform(R0, R1)
    290     True
    291     >>> I = numpy.identity(4, numpy.float64)
    292     >>> numpy.allclose(I, rotation_matrix(math.pi*2, direc))
    293     True
    294     >>> numpy.allclose(2., numpy.trace(rotation_matrix(math.pi/2,
    295     ...                                                direc, point)))
    296     True
    297 
    298     """
    299     sina = math.sin(angle)
    300     cosa = math.cos(angle)
    301     direction = unit_vector(direction[:3])
    302     # rotation matrix around unit vector
    303     R = numpy.array(((cosa, 0.0,  0.0),
    304                      (0.0,  cosa, 0.0),
    305                      (0.0,  0.0,  cosa)), dtype=numpy.float64)
    306     R += numpy.outer(direction, direction) * (1.0 - cosa)
    307     direction *= sina
    308     R += numpy.array((( 0.0,         -direction[2],  direction[1]),
    309                       ( direction[2], 0.0,          -direction[0]),
    310                       (-direction[1], direction[0],  0.0)),
    311                      dtype=numpy.float64)
    312     M = numpy.identity(4)
    313     M[:3, :3] = R
    314     if point is not None:
    315         # rotation not around origin
    316         point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
    317         M[:3, 3] = point - numpy.dot(R, point)
    318     return M
    319 
    320 
    321 def rotation_from_matrix(matrix):
    322     """Return rotation angle and axis from rotation matrix.
    323 
    324     >>> angle = (random.random() - 0.5) * (2*math.pi)
    325     >>> direc = numpy.random.random(3) - 0.5
    326     >>> point = numpy.random.random(3) - 0.5
    327     >>> R0 = rotation_matrix(angle, direc, point)
    328     >>> angle, direc, point = rotation_from_matrix(R0)
    329     >>> R1 = rotation_matrix(angle, direc, point)
    330     >>> is_same_transform(R0, R1)
    331     True
    332 
    333     """
    334     R = numpy.array(matrix, dtype=numpy.float64, copy=False)
    335     R33 = R[:3, :3]
    336     # direction: unit eigenvector of R33 corresponding to eigenvalue of 1
    337     l, W = numpy.linalg.eig(R33.T)
    338     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    339     if not len(i):
    340         raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
    341     direction = numpy.real(W[:, i[-1]]).squeeze()
    342     # point: unit eigenvector of R33 corresponding to eigenvalue of 1
    343     l, Q = numpy.linalg.eig(R)
    344     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    345     if not len(i):
    346         raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
    347     point = numpy.real(Q[:, i[-1]]).squeeze()
    348     point /= point[3]
    349     # rotation angle depending on direction
    350     cosa = (numpy.trace(R33) - 1.0) / 2.0
    351     if abs(direction[2]) > 1e-8:
    352         sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2]
    353     elif abs(direction[1]) > 1e-8:
    354         sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1]
    355     else:
    356         sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0]
    357     angle = math.atan2(sina, cosa)
    358     return angle, direction, point
    359 
    360 
    361 def scale_matrix(factor, origin=None, direction=None):
    362     """Return matrix to scale by factor around origin in direction.
    363 
    364     Use factor -1 for point symmetry.
    365 
    366     >>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0
    367     >>> v[3] = 1.0
    368     >>> S = scale_matrix(-1.234)
    369     >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
    370     True
    371     >>> factor = random.random() * 10 - 5
    372     >>> origin = numpy.random.random(3) - 0.5
    373     >>> direct = numpy.random.random(3) - 0.5
    374     >>> S = scale_matrix(factor, origin)
    375     >>> S = scale_matrix(factor, origin, direct)
    376 
    377     """
    378     if direction is None:
    379         # uniform scaling
    380         M = numpy.array(((factor, 0.0,    0.0,    0.0),
    381                          (0.0,    factor, 0.0,    0.0),
    382                          (0.0,    0.0,    factor, 0.0),
    383                          (0.0,    0.0,    0.0,    1.0)), dtype=numpy.float64)
    384         if origin is not None:
    385             M[:3, 3] = origin[:3]
    386             M[:3, 3] *= 1.0 - factor
    387     else:
    388         # nonuniform scaling
    389         direction = unit_vector(direction[:3])
    390         factor = 1.0 - factor
    391         M = numpy.identity(4)
    392         M[:3, :3] -= factor * numpy.outer(direction, direction)
    393         if origin is not None:
    394             M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction
    395     return M
    396 
    397 
    398 def scale_from_matrix(matrix):
    399     """Return scaling factor, origin and direction from scaling matrix.
    400 
    401     >>> factor = random.random() * 10 - 5
    402     >>> origin = numpy.random.random(3) - 0.5
    403     >>> direct = numpy.random.random(3) - 0.5
    404     >>> S0 = scale_matrix(factor, origin)
    405     >>> factor, origin, direction = scale_from_matrix(S0)
    406     >>> S1 = scale_matrix(factor, origin, direction)
    407     >>> is_same_transform(S0, S1)
    408     True
    409     >>> S0 = scale_matrix(factor, origin, direct)
    410     >>> factor, origin, direction = scale_from_matrix(S0)
    411     >>> S1 = scale_matrix(factor, origin, direction)
    412     >>> is_same_transform(S0, S1)
    413     True
    414 
    415     """
    416     M = numpy.array(matrix, dtype=numpy.float64, copy=False)
    417     M33 = M[:3, :3]
    418     factor = numpy.trace(M33) - 2.0
    419     try:
    420         # direction: unit eigenvector corresponding to eigenvalue factor
    421         l, V = numpy.linalg.eig(M33)
    422         i = numpy.where(abs(numpy.real(l) - factor) < 1e-8)[0][0]
    423         direction = numpy.real(V[:, i]).squeeze()
    424         direction /= vector_norm(direction)
    425     except IndexError:
    426         # uniform scaling
    427         factor = (factor + 2.0) / 3.0
    428         direction = None
    429     # origin: any eigenvector corresponding to eigenvalue 1
    430     l, V = numpy.linalg.eig(M)
    431     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    432     if not len(i):
    433         raise ValueError("no eigenvector corresponding to eigenvalue 1")
    434     origin = numpy.real(V[:, i[-1]]).squeeze()
    435     origin /= origin[3]
    436     return factor, origin, direction
    437 
    438 
    439 def projection_matrix(point, normal, direction=None,
    440                       perspective=None, pseudo=False):
    441     """Return matrix to project onto plane defined by point and normal.
    442 
    443     Using either perspective point, projection direction, or none of both.
    444 
    445     If pseudo is True, perspective projections will preserve relative depth
    446     such that Perspective = dot(Orthogonal, PseudoPerspective).
    447 
    448     >>> P = projection_matrix((0, 0, 0), (1, 0, 0))
    449     >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
    450     True
    451     >>> point = numpy.random.random(3) - 0.5
    452     >>> normal = numpy.random.random(3) - 0.5
    453     >>> direct = numpy.random.random(3) - 0.5
    454     >>> persp = numpy.random.random(3) - 0.5
    455     >>> P0 = projection_matrix(point, normal)
    456     >>> P1 = projection_matrix(point, normal, direction=direct)
    457     >>> P2 = projection_matrix(point, normal, perspective=persp)
    458     >>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True)
    459     >>> is_same_transform(P2, numpy.dot(P0, P3))
    460     True
    461     >>> P = projection_matrix((3, 0, 0), (1, 1, 0), (1, 0, 0))
    462     >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0
    463     >>> v0[3] = 1.0
    464     >>> v1 = numpy.dot(P, v0)
    465     >>> numpy.allclose(v1[1], v0[1])
    466     True
    467     >>> numpy.allclose(v1[0], 3.0-v1[1])
    468     True
    469 
    470     """
    471     M = numpy.identity(4)
    472     point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
    473     normal = unit_vector(normal[:3])
    474     if perspective is not None:
    475         # perspective projection
    476         perspective = numpy.array(perspective[:3], dtype=numpy.float64,
    477                                   copy=False)
    478         M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal)
    479         M[:3, :3] -= numpy.outer(perspective, normal)
    480         if pseudo:
    481             # preserve relative depth
    482             M[:3, :3] -= numpy.outer(normal, normal)
    483             M[:3, 3] = numpy.dot(point, normal) * (perspective+normal)
    484         else:
    485             M[:3, 3] = numpy.dot(point, normal) * perspective
    486         M[3, :3] = -normal
    487         M[3, 3] = numpy.dot(perspective, normal)
    488     elif direction is not None:
    489         # parallel projection
    490         direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False)
    491         scale = numpy.dot(direction, normal)
    492         M[:3, :3] -= numpy.outer(direction, normal) / scale
    493         M[:3, 3] = direction * (numpy.dot(point, normal) / scale)
    494     else:
    495         # orthogonal projection
    496         M[:3, :3] -= numpy.outer(normal, normal)
    497         M[:3, 3] = numpy.dot(point, normal) * normal
    498     return M
    499 
    500 
    501 def projection_from_matrix(matrix, pseudo=False):
    502     """Return projection plane and perspective point from projection matrix.
    503 
    504     Return values are same as arguments for projection_matrix function:
    505     point, normal, direction, perspective, and pseudo.
    506 
    507     >>> point = numpy.random.random(3) - 0.5
    508     >>> normal = numpy.random.random(3) - 0.5
    509     >>> direct = numpy.random.random(3) - 0.5
    510     >>> persp = numpy.random.random(3) - 0.5
    511     >>> P0 = projection_matrix(point, normal)
    512     >>> result = projection_from_matrix(P0)
    513     >>> P1 = projection_matrix(*result)
    514     >>> is_same_transform(P0, P1)
    515     True
    516     >>> P0 = projection_matrix(point, normal, direct)
    517     >>> result = projection_from_matrix(P0)
    518     >>> P1 = projection_matrix(*result)
    519     >>> is_same_transform(P0, P1)
    520     True
    521     >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False)
    522     >>> result = projection_from_matrix(P0, pseudo=False)
    523     >>> P1 = projection_matrix(*result)
    524     >>> is_same_transform(P0, P1)
    525     True
    526     >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True)
    527     >>> result = projection_from_matrix(P0, pseudo=True)
    528     >>> P1 = projection_matrix(*result)
    529     >>> is_same_transform(P0, P1)
    530     True
    531 
    532     """
    533     M = numpy.array(matrix, dtype=numpy.float64, copy=False)
    534     M33 = M[:3, :3]
    535     l, V = numpy.linalg.eig(M)
    536     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    537     if not pseudo and len(i):
    538         # point: any eigenvector corresponding to eigenvalue 1
    539         point = numpy.real(V[:, i[-1]]).squeeze()
    540         point /= point[3]
    541         # direction: unit eigenvector corresponding to eigenvalue 0
    542         l, V = numpy.linalg.eig(M33)
    543         i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
    544         if not len(i):
    545             raise ValueError("no eigenvector corresponding to eigenvalue 0")
    546         direction = numpy.real(V[:, i[0]]).squeeze()
    547         direction /= vector_norm(direction)
    548         # normal: unit eigenvector of M33.T corresponding to eigenvalue 0
    549         l, V = numpy.linalg.eig(M33.T)
    550         i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
    551         if len(i):
    552             # parallel projection
    553             normal = numpy.real(V[:, i[0]]).squeeze()
    554             normal /= vector_norm(normal)
    555             return point, normal, direction, None, False
    556         else:
    557             # orthogonal projection, where normal equals direction vector
    558             return point, direction, None, None, False
    559     else:
    560         # perspective projection
    561         i = numpy.where(abs(numpy.real(l)) > 1e-8)[0]
    562         if not len(i):
    563             raise ValueError(
    564                 "no eigenvector not corresponding to eigenvalue 0")
    565         point = numpy.real(V[:, i[-1]]).squeeze()
    566         point /= point[3]
    567         normal = - M[3, :3]
    568         perspective = M[:3, 3] / numpy.dot(point[:3], normal)
    569         if pseudo:
    570             perspective -= normal
    571         return point, normal, None, perspective, pseudo
    572 
    573 
    574 def clip_matrix(left, right, bottom, top, near, far, perspective=False):
    575     """Return matrix to obtain normalized device coordinates from frustrum.
    576 
    577     The frustrum bounds are axis-aligned along x (left, right),
    578     y (bottom, top) and z (near, far).
    579 
    580     Normalized device coordinates are in range [-1, 1] if coordinates are
    581     inside the frustrum.
    582 
    583     If perspective is True the frustrum is a truncated pyramid with the
    584     perspective point at origin and direction along z axis, otherwise an
    585     orthographic canonical view volume (a box).
    586 
    587     Homogeneous coordinates transformed by the perspective clip matrix
    588     need to be dehomogenized (devided by w coordinate).
    589 
    590     >>> frustrum = numpy.random.rand(6)
    591     >>> frustrum[1] += frustrum[0]
    592     >>> frustrum[3] += frustrum[2]
    593     >>> frustrum[5] += frustrum[4]
    594     >>> M = clip_matrix(*frustrum, perspective=False)
    595     >>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
    596     array([-1., -1., -1.,  1.])
    597     >>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0])
    598     array([ 1.,  1.,  1.,  1.])
    599     >>> M = clip_matrix(*frustrum, perspective=True)
    600     >>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
    601     >>> v / v[3]
    602     array([-1., -1., -1.,  1.])
    603     >>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0])
    604     >>> v / v[3]
    605     array([ 1.,  1., -1.,  1.])
    606 
    607     """
    608     if left >= right or bottom >= top or near >= far:
    609         raise ValueError("invalid frustrum")
    610     if perspective:
    611         if near <= _EPS:
    612             raise ValueError("invalid frustrum: near <= 0")
    613         t = 2.0 * near
    614         M = ((-t/(right-left), 0.0, (right+left)/(right-left), 0.0),
    615              (0.0, -t/(top-bottom), (top+bottom)/(top-bottom), 0.0),
    616              (0.0, 0.0, -(far+near)/(far-near), t*far/(far-near)),
    617              (0.0, 0.0, -1.0, 0.0))
    618     else:
    619         M = ((2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)),
    620              (0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)),
    621              (0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)),
    622              (0.0, 0.0, 0.0, 1.0))
    623     return numpy.array(M, dtype=numpy.float64)
    624 
    625 
    626 def shear_matrix(angle, direction, point, normal):
    627     """Return matrix to shear by angle along direction vector on shear plane.
    628 
    629     The shear plane is defined by a point and normal vector. The direction
    630     vector must be orthogonal to the plane's normal vector.
    631 
    632     A point P is transformed by the shear matrix into P" such that
    633     the vector P-P" is parallel to the direction vector and its extent is
    634     given by the angle of P-P'-P", where P' is the orthogonal projection
    635     of P onto the shear plane.
    636 
    637     >>> angle = (random.random() - 0.5) * 4*math.pi
    638     >>> direct = numpy.random.random(3) - 0.5
    639     >>> point = numpy.random.random(3) - 0.5
    640     >>> normal = numpy.cross(direct, numpy.random.random(3))
    641     >>> S = shear_matrix(angle, direct, point, normal)
    642     >>> numpy.allclose(1.0, numpy.linalg.det(S))
    643     True
    644 
    645     """
    646     normal = unit_vector(normal[:3])
    647     direction = unit_vector(direction[:3])
    648     if abs(numpy.dot(normal, direction)) > 1e-6:
    649         raise ValueError("direction and normal vectors are not orthogonal")
    650     angle = math.tan(angle)
    651     M = numpy.identity(4)
    652     M[:3, :3] += angle * numpy.outer(direction, normal)
    653     M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction
    654     return M
    655 
    656 
    657 def shear_from_matrix(matrix):
    658     """Return shear angle, direction and plane from shear matrix.
    659 
    660     >>> angle = (random.random() - 0.5) * 4*math.pi
    661     >>> direct = numpy.random.random(3) - 0.5
    662     >>> point = numpy.random.random(3) - 0.5
    663     >>> normal = numpy.cross(direct, numpy.random.random(3))
    664     >>> S0 = shear_matrix(angle, direct, point, normal)
    665     >>> angle, direct, point, normal = shear_from_matrix(S0)
    666     >>> S1 = shear_matrix(angle, direct, point, normal)
    667     >>> is_same_transform(S0, S1)
    668     True
    669 
    670     """
    671     M = numpy.array(matrix, dtype=numpy.float64, copy=False)
    672     M33 = M[:3, :3]
    673     # normal: cross independent eigenvectors corresponding to the eigenvalue 1
    674     l, V = numpy.linalg.eig(M33)
    675     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-4)[0]
    676     if len(i) < 2:
    677         raise ValueError("No two linear independent eigenvectors found %s" % l)
    678     V = numpy.real(V[:, i]).squeeze().T
    679     lenorm = -1.0
    680     for i0, i1 in ((0, 1), (0, 2), (1, 2)):
    681         n = numpy.cross(V[i0], V[i1])
    682         l = vector_norm(n)
    683         if l > lenorm:
    684             lenorm = l
    685             normal = n
    686     normal /= lenorm
    687     # direction and angle
    688     direction = numpy.dot(M33 - numpy.identity(3), normal)
    689     angle = vector_norm(direction)
    690     direction /= angle
    691     angle = math.atan(angle)
    692     # point: eigenvector corresponding to eigenvalue 1
    693     l, V = numpy.linalg.eig(M)
    694     i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
    695     if not len(i):
    696         raise ValueError("no eigenvector corresponding to eigenvalue 1")
    697     point = numpy.real(V[:, i[-1]]).squeeze()
    698     point /= point[3]
    699     return angle, direction, point, normal
    700 
    701 
    702 def decompose_matrix(matrix):
    703     """Return sequence of transformations from transformation matrix.
    704 
    705     matrix : array_like
    706         Non-degenerative homogeneous transformation matrix
    707 
    708     Return tuple of:
    709         scale : vector of 3 scaling factors
    710         shear : list of shear factors for x-y, x-z, y-z axes
    711         angles : list of Euler angles about static x, y, z axes
    712         translate : translation vector along x, y, z axes
    713         perspective : perspective partition of matrix
    714 
    715     Raise ValueError if matrix is of wrong type or degenerative.
    716 
    717     >>> T0 = translation_matrix((1, 2, 3))
    718     >>> scale, shear, angles, trans, persp = decompose_matrix(T0)
    719     >>> T1 = translation_matrix(trans)
    720     >>> numpy.allclose(T0, T1)
    721     True
    722     >>> S = scale_matrix(0.123)
    723     >>> scale, shear, angles, trans, persp = decompose_matrix(S)
    724     >>> scale[0]
    725     0.123
    726     >>> R0 = euler_matrix(1, 2, 3)
    727     >>> scale, shear, angles, trans, persp = decompose_matrix(R0)
    728     >>> R1 = euler_matrix(*angles)
    729     >>> numpy.allclose(R0, R1)
    730     True
    731 
    732     """
    733     M = numpy.array(matrix, dtype=numpy.float64, copy=True).T
    734     if abs(M[3, 3]) < _EPS:
    735         raise ValueError("M[3, 3] is zero")
    736     M /= M[3, 3]
    737     P = M.copy()
    738     P[:, 3] = 0, 0, 0, 1
    739     if not numpy.linalg.det(P):
    740         raise ValueError("Matrix is singular")
    741 
    742     scale = numpy.zeros((3, ), dtype=numpy.float64)
    743     shear = [0, 0, 0]
    744     angles = [0, 0, 0]
    745 
    746     if any(abs(M[:3, 3]) > _EPS):
    747         perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T))
    748         M[:, 3] = 0, 0, 0, 1
    749     else:
    750         perspective = numpy.array((0, 0, 0, 1), dtype=numpy.float64)
    751 
    752     translate = M[3, :3].copy()
    753     M[3, :3] = 0
    754 
    755     row = M[:3, :3].copy()
    756     scale[0] = vector_norm(row[0])
    757     row[0] /= scale[0]
    758     shear[0] = numpy.dot(row[0], row[1])
    759     row[1] -= row[0] * shear[0]
    760     scale[1] = vector_norm(row[1])
    761     row[1] /= scale[1]
    762     shear[0] /= scale[1]
    763     shear[1] = numpy.dot(row[0], row[2])
    764     row[2] -= row[0] * shear[1]
    765     shear[2] = numpy.dot(row[1], row[2])
    766     row[2] -= row[1] * shear[2]
    767     scale[2] = vector_norm(row[2])
    768     row[2] /= scale[2]
    769     shear[1:] /= scale[2]
    770 
    771     if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0:
    772         scale *= -1
    773         row *= -1
    774 
    775     angles[1] = math.asin(-row[0, 2])
    776     if math.cos(angles[1]):
    777         angles[0] = math.atan2(row[1, 2], row[2, 2])
    778         angles[2] = math.atan2(row[0, 1], row[0, 0])
    779     else:
    780         #angles[0] = math.atan2(row[1, 0], row[1, 1])
    781         angles[0] = math.atan2(-row[2, 1], row[1, 1])
    782         angles[2] = 0.0
    783 
    784     return scale, shear, angles, translate, perspective
    785 
    786 
    787 def compose_matrix(scale=None, shear=None, angles=None, translate=None,
    788                    perspective=None):
    789     """Return transformation matrix from sequence of transformations.
    790 
    791     This is the inverse of the decompose_matrix function.
    792 
    793     Sequence of transformations:
    794         scale : vector of 3 scaling factors
    795         shear : list of shear factors for x-y, x-z, y-z axes
    796         angles : list of Euler angles about static x, y, z axes
    797         translate : translation vector along x, y, z axes
    798         perspective : perspective partition of matrix
    799 
    800     >>> scale = numpy.random.random(3) - 0.5
    801     >>> shear = numpy.random.random(3) - 0.5
    802     >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
    803     >>> trans = numpy.random.random(3) - 0.5
    804     >>> persp = numpy.random.random(4) - 0.5
    805     >>> M0 = compose_matrix(scale, shear, angles, trans, persp)
    806     >>> result = decompose_matrix(M0)
    807     >>> M1 = compose_matrix(*result)
    808     >>> is_same_transform(M0, M1)
    809     True
    810 
    811     """
    812     M = numpy.identity(4)
    813     if perspective is not None:
    814         P = numpy.identity(4)
    815         P[3, :] = perspective[:4]
    816         M = numpy.dot(M, P)
    817     if translate is not None:
    818         T = numpy.identity(4)
    819         T[:3, 3] = translate[:3]
    820         M = numpy.dot(M, T)
    821     if angles is not None:
    822         R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz')
    823         M = numpy.dot(M, R)
    824     if shear is not None:
    825         Z = numpy.identity(4)
    826         Z[1, 2] = shear[2]
    827         Z[0, 2] = shear[1]
    828         Z[0, 1] = shear[0]
    829         M = numpy.dot(M, Z)
    830     if scale is not None:
    831         S = numpy.identity(4)
    832         S[0, 0] = scale[0]
    833         S[1, 1] = scale[1]
    834         S[2, 2] = scale[2]
    835         M = numpy.dot(M, S)
    836     M /= M[3, 3]
    837     return M
    838 
    839 
    840 def orthogonalization_matrix(lengths, angles):
    841     """Return orthogonalization matrix for crystallographic cell coordinates.
    842 
    843     Angles are expected in degrees.
    844 
    845     The de-orthogonalization matrix is the inverse.
    846 
    847     >>> O = orthogonalization_matrix((10., 10., 10.), (90., 90., 90.))
    848     >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
    849     True
    850     >>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7])
    851     >>> numpy.allclose(numpy.sum(O), 43.063229)
    852     True
    853 
    854     """
    855     a, b, c = lengths
    856     angles = numpy.radians(angles)
    857     sina, sinb, _ = numpy.sin(angles)
    858     cosa, cosb, cosg = numpy.cos(angles)
    859     co = (cosa * cosb - cosg) / (sina * sinb)
    860     return numpy.array((
    861         ( a*sinb*math.sqrt(1.0-co*co),  0.0,    0.0, 0.0),
    862         (-a*sinb*co,                    b*sina, 0.0, 0.0),
    863         ( a*cosb,                       b*cosa, c,   0.0),
    864         ( 0.0,                          0.0,    0.0, 1.0)),
    865         dtype=numpy.float64)
    866 
    867 
    868 def superimposition_matrix(v0, v1, scaling=False, usesvd=True):
    869     """Return matrix to transform given vector set into second vector set.
    870 
    871     v0 and v1 are shape (3, \*) or (4, \*) arrays of at least 3 vectors.
    872 
    873     If usesvd is True, the weighted sum of squared deviations (RMSD) is
    874     minimized according to the algorithm by W. Kabsch [8]. Otherwise the
    875     quaternion based algorithm by B. Horn [9] is used (slower when using
    876     this Python implementation).
    877 
    878     The returned matrix performs rotation, translation and uniform scaling
    879     (if specified).
    880 
    881     >>> v0 = numpy.random.rand(3, 10)
    882     >>> M = superimposition_matrix(v0, v0)
    883     >>> numpy.allclose(M, numpy.identity(4))
    884     True
    885     >>> R = random_rotation_matrix(numpy.random.random(3))
    886     >>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1))
    887     >>> v1 = numpy.dot(R, v0)
    888     >>> M = superimposition_matrix(v0, v1)
    889     >>> numpy.allclose(v1, numpy.dot(M, v0))
    890     True
    891     >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0
    892     >>> v0[3] = 1.0
    893     >>> v1 = numpy.dot(R, v0)
    894     >>> M = superimposition_matrix(v0, v1)
    895     >>> numpy.allclose(v1, numpy.dot(M, v0))
    896     True
    897     >>> S = scale_matrix(random.random())
    898     >>> T = translation_matrix(numpy.random.random(3)-0.5)
    899     >>> M = concatenate_matrices(T, R, S)
    900     >>> v1 = numpy.dot(M, v0)
    901     >>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1)
    902     >>> M = superimposition_matrix(v0, v1, scaling=True)
    903     >>> numpy.allclose(v1, numpy.dot(M, v0))
    904     True
    905     >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
    906     >>> numpy.allclose(v1, numpy.dot(M, v0))
    907     True
    908     >>> v = numpy.empty((4, 100, 3), dtype=numpy.float64)
    909     >>> v[:, :, 0] = v0
    910     >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
    911     >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
    912     True
    913 
    914     """
    915     v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3]
    916     v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3]
    917 
    918     if v0.shape != v1.shape or v0.shape[1] < 3:
    919         raise ValueError("Vector sets are of wrong shape or type.")
    920 
    921     # move centroids to origin
    922     t0 = numpy.mean(v0, axis=1)
    923     t1 = numpy.mean(v1, axis=1)
    924     v0 = v0 - t0.reshape(3, 1)
    925     v1 = v1 - t1.reshape(3, 1)
    926 
    927     if usesvd:
    928         # Singular Value Decomposition of covariance matrix
    929         u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))
    930         # rotation matrix from SVD orthonormal bases
    931         R = numpy.dot(u, vh)
    932         if numpy.linalg.det(R) < 0.0:
    933             # R does not constitute right handed system
    934             R -= numpy.outer(u[:, 2], vh[2, :]*2.0)
    935             s[-1] *= -1.0
    936         # homogeneous transformation matrix
    937         M = numpy.identity(4)
    938         M[:3, :3] = R
    939     else:
    940         # compute symmetric matrix N
    941         xx, yy, zz = numpy.sum(v0 * v1, axis=1)
    942         xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1)
    943         xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1)
    944         N = ((xx+yy+zz, yz-zy,    zx-xz,    xy-yx),
    945              (yz-zy,    xx-yy-zz, xy+yx,    zx+xz),
    946              (zx-xz,    xy+yx,   -xx+yy-zz, yz+zy),
    947              (xy-yx,    zx+xz,    yz+zy,   -xx-yy+zz))
    948         # quaternion: eigenvector corresponding to most positive eigenvalue
    949         l, V = numpy.linalg.eig(N)
    950         q = V[:, numpy.argmax(l)]
    951         q /= vector_norm(q) # unit quaternion
    952         q = numpy.roll(q, -1) # move w component to end
    953         # homogeneous transformation matrix
    954         M = quaternion_matrix(q)
    955 
    956     # scale: ratio of rms deviations from centroid
    957     if scaling:
    958         v0 *= v0
    959         v1 *= v1
    960         M[:3, :3] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0))
    961 
    962     # translation
    963     M[:3, 3] = t1
    964     T = numpy.identity(4)
    965     T[:3, 3] = -t0
    966     M = numpy.dot(M, T)
    967     return M
    968 
    969 
    970 def euler_matrix(ai, aj, ak, axes='sxyz'):
    971     """Return homogeneous rotation matrix from Euler angles and axis sequence.
    972 
    973     ai, aj, ak : Euler's roll, pitch and yaw angles
    974     axes : One of 24 axis sequences as string or encoded tuple
    975 
    976     >>> R = euler_matrix(1, 2, 3, 'syxz')
    977     >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
    978     True
    979     >>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1))
    980     >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
    981     True
    982     >>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
    983     >>> for axes in _AXES2TUPLE.keys():
    984     ...    R = euler_matrix(ai, aj, ak, axes)
    985     >>> for axes in _TUPLE2AXES.keys():
    986     ...    R = euler_matrix(ai, aj, ak, axes)
    987 
    988     """
    989     try:
    990         firstaxis, parity, repetition, frame = _AXES2TUPLE[axes]
    991     except (AttributeError, KeyError):
    992         _ = _TUPLE2AXES[axes]
    993         firstaxis, parity, repetition, frame = axes
    994 
    995     i = firstaxis
    996     j = _NEXT_AXIS[i+parity]
    997     k = _NEXT_AXIS[i-parity+1]
    998 
    999     if frame:
   1000         ai, ak = ak, ai
   1001     if parity:
   1002         ai, aj, ak = -ai, -aj, -ak
   1003 
   1004     si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak)
   1005     ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak)
   1006     cc, cs = ci*ck, ci*sk
   1007     sc, ss = si*ck, si*sk
   1008 
   1009     M = numpy.identity(4)
   1010     if repetition:
   1011         M[i, i] = cj
   1012         M[i, j] = sj*si
   1013         M[i, k] = sj*ci
   1014         M[j, i] = sj*sk
   1015         M[j, j] = -cj*ss+cc
   1016         M[j, k] = -cj*cs-sc
   1017         M[k, i] = -sj*ck
   1018         M[k, j] = cj*sc+cs
   1019         M[k, k] = cj*cc-ss
   1020     else:
   1021         M[i, i] = cj*ck
   1022         M[i, j] = sj*sc-cs
   1023         M[i, k] = sj*cc+ss
   1024         M[j, i] = cj*sk
   1025         M[j, j] = sj*ss+cc
   1026         M[j, k] = sj*cs-sc
   1027         M[k, i] = -sj
   1028         M[k, j] = cj*si
   1029         M[k, k] = cj*ci
   1030     return M
   1031 
   1032 
   1033 def euler_from_matrix(matrix, axes='sxyz'):
   1034     """Return Euler angles from rotation matrix for specified axis sequence.
   1035 
   1036     axes : One of 24 axis sequences as string or encoded tuple
   1037 
   1038     Note that many Euler angle triplets can describe one matrix.
   1039 
   1040     >>> R0 = euler_matrix(1, 2, 3, 'syxz')
   1041     >>> al, be, ga = euler_from_matrix(R0, 'syxz')
   1042     >>> R1 = euler_matrix(al, be, ga, 'syxz')
   1043     >>> numpy.allclose(R0, R1)
   1044     True
   1045     >>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
   1046     >>> for axes in _AXES2TUPLE.keys():
   1047     ...    R0 = euler_matrix(axes=axes, *angles)
   1048     ...    R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes))
   1049     ...    if not numpy.allclose(R0, R1): print axes, "failed"
   1050 
   1051     """
   1052     try:
   1053         firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
   1054     except (AttributeError, KeyError):
   1055         _ = _TUPLE2AXES[axes]
   1056         firstaxis, parity, repetition, frame = axes
   1057 
   1058     i = firstaxis
   1059     j = _NEXT_AXIS[i+parity]
   1060     k = _NEXT_AXIS[i-parity+1]
   1061 
   1062     M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
   1063     if repetition:
   1064         sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k])
   1065         if sy > _EPS:
   1066             ax = math.atan2( M[i, j],  M[i, k])
   1067             ay = math.atan2( sy,       M[i, i])
   1068             az = math.atan2( M[j, i], -M[k, i])
   1069         else:
   1070             ax = math.atan2(-M[j, k],  M[j, j])
   1071             ay = math.atan2( sy,       M[i, i])
   1072             az = 0.0
   1073     else:
   1074         cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i])
   1075         if cy > _EPS:
   1076             ax = math.atan2( M[k, j],  M[k, k])
   1077             ay = math.atan2(-M[k, i],  cy)
   1078             az = math.atan2( M[j, i],  M[i, i])
   1079         else:
   1080             ax = math.atan2(-M[j, k],  M[j, j])
   1081             ay = math.atan2(-M[k, i],  cy)
   1082             az = 0.0
   1083 
   1084     if parity:
   1085         ax, ay, az = -ax, -ay, -az
   1086     if frame:
   1087         ax, az = az, ax
   1088     return ax, ay, az
   1089 
   1090 
   1091 def euler_from_quaternion(quaternion, axes='sxyz'):
   1092     """Return Euler angles from quaternion for specified axis sequence.
   1093 
   1094     >>> angles = euler_from_quaternion([0.06146124, 0, 0, 0.99810947])
   1095     >>> numpy.allclose(angles, [0.123, 0, 0])
   1096     True
   1097 
   1098     """
   1099     return euler_from_matrix(quaternion_matrix(quaternion), axes)
   1100 
   1101 
   1102 def quaternion_from_euler(ai, aj, ak, axes='sxyz'):
   1103     """Return quaternion from Euler angles and axis sequence.
   1104 
   1105     ai, aj, ak : Euler's roll, pitch and yaw angles
   1106     axes : One of 24 axis sequences as string or encoded tuple
   1107 
   1108     >>> q = quaternion_from_euler(1, 2, 3, 'ryxz')
   1109     >>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953])
   1110     True
   1111 
   1112     """
   1113     try:
   1114         firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
   1115     except (AttributeError, KeyError):
   1116         _ = _TUPLE2AXES[axes]
   1117         firstaxis, parity, repetition, frame = axes
   1118 
   1119     i = firstaxis
   1120     j = _NEXT_AXIS[i+parity]
   1121     k = _NEXT_AXIS[i-parity+1]
   1122 
   1123     if frame:
   1124         ai, ak = ak, ai
   1125     if parity:
   1126         aj = -aj
   1127 
   1128     ai /= 2.0
   1129     aj /= 2.0
   1130     ak /= 2.0
   1131     ci = math.cos(ai)
   1132     si = math.sin(ai)
   1133     cj = math.cos(aj)
   1134     sj = math.sin(aj)
   1135     ck = math.cos(ak)
   1136     sk = math.sin(ak)
   1137     cc = ci*ck
   1138     cs = ci*sk
   1139     sc = si*ck
   1140     ss = si*sk
   1141 
   1142     quaternion = numpy.empty((4, ), dtype=numpy.float64)
   1143     if repetition:
   1144         quaternion[i] = cj*(cs + sc)
   1145         quaternion[j] = sj*(cc + ss)
   1146         quaternion[k] = sj*(cs - sc)
   1147         quaternion[3] = cj*(cc - ss)
   1148     else:
   1149         quaternion[i] = cj*sc - sj*cs
   1150         quaternion[j] = cj*ss + sj*cc
   1151         quaternion[k] = cj*cs - sj*sc
   1152         quaternion[3] = cj*cc + sj*ss
   1153     if parity:
   1154         quaternion[j] *= -1
   1155 
   1156     return quaternion
   1157 
   1158 
   1159 def quaternion_about_axis(angle, axis):
   1160     """Return quaternion for rotation about axis.
   1161 
   1162     >>> q = quaternion_about_axis(0.123, (1, 0, 0))
   1163     >>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947])
   1164     True
   1165 
   1166     """
   1167     quaternion = numpy.zeros((4, ), dtype=numpy.float64)
   1168     quaternion[:3] = axis[:3]
   1169     qlen = vector_norm(quaternion)
   1170     if qlen > _EPS:
   1171         quaternion *= math.sin(angle/2.0) / qlen
   1172     quaternion[3] = math.cos(angle/2.0)
   1173     return quaternion
   1174 
   1175 
   1176 def quaternion_matrix(quaternion):
   1177     """Return homogeneous rotation matrix from quaternion.
   1178 
   1179     >>> R = quaternion_matrix([0.06146124, 0, 0, 0.99810947])
   1180     >>> numpy.allclose(R, rotation_matrix(0.123, (1, 0, 0)))
   1181     True
   1182 
   1183     """
   1184     q = numpy.array(quaternion[:4], dtype=numpy.float64, copy=True)
   1185     nq = numpy.dot(q, q)
   1186     if nq < _EPS:
   1187         return numpy.identity(4)
   1188     q *= math.sqrt(2.0 / nq)
   1189     q = numpy.outer(q, q)
   1190     return numpy.array((
   1191         (1.0-q[1, 1]-q[2, 2],     q[0, 1]-q[2, 3],     q[0, 2]+q[1, 3], 0.0),
   1192         (    q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2],     q[1, 2]-q[0, 3], 0.0),
   1193         (    q[0, 2]-q[1, 3],     q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0),
   1194         (                0.0,                 0.0,                 0.0, 1.0)
   1195         ), dtype=numpy.float64)
   1196 
   1197 
   1198 def quaternion_from_matrix(matrix):
   1199     """Return quaternion from rotation matrix.
   1200 
   1201     >>> R = rotation_matrix(0.123, (1, 2, 3))
   1202     >>> q = quaternion_from_matrix(R)
   1203     >>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095])
   1204     True
   1205 
   1206     """
   1207     q = numpy.empty((4, ), dtype=numpy.float64)
   1208     M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4]
   1209     t = numpy.trace(M)
   1210     if t > M[3, 3]:
   1211         q[3] = t
   1212         q[2] = M[1, 0] - M[0, 1]
   1213         q[1] = M[0, 2] - M[2, 0]
   1214         q[0] = M[2, 1] - M[1, 2]
   1215     else:
   1216         i, j, k = 0, 1, 2
   1217         if M[1, 1] > M[0, 0]:
   1218             i, j, k = 1, 2, 0
   1219         if M[2, 2] > M[i, i]:
   1220             i, j, k = 2, 0, 1
   1221         t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3]
   1222         q[i] = t
   1223         q[j] = M[i, j] + M[j, i]
   1224         q[k] = M[k, i] + M[i, k]
   1225         q[3] = M[k, j] - M[j, k]
   1226     q *= 0.5 / math.sqrt(t * M[3, 3])
   1227     return q
   1228 
   1229 
   1230 def quaternion_multiply(quaternion1, quaternion0):
   1231     """Return multiplication of two quaternions.
   1232 
   1233     >>> q = quaternion_multiply([1, -2, 3, 4], [-5, 6, 7, 8])
   1234     >>> numpy.allclose(q, [-44, -14, 48, 28])
   1235     True
   1236 
   1237     """
   1238     x0, y0, z0, w0 = quaternion0
   1239     x1, y1, z1, w1 = quaternion1
   1240     return numpy.array((
   1241          x1*w0 + y1*z0 - z1*y0 + w1*x0,
   1242         -x1*z0 + y1*w0 + z1*x0 + w1*y0,
   1243          x1*y0 - y1*x0 + z1*w0 + w1*z0,
   1244         -x1*x0 - y1*y0 - z1*z0 + w1*w0), dtype=numpy.float64)
   1245 
   1246 
   1247 def quaternion_conjugate(quaternion):
   1248     """Return conjugate of quaternion.
   1249 
   1250     >>> q0 = random_quaternion()
   1251     >>> q1 = quaternion_conjugate(q0)
   1252     >>> q1[3] == q0[3] and all(q1[:3] == -q0[:3])
   1253     True
   1254 
   1255     """
   1256     return numpy.array((-quaternion[0], -quaternion[1],
   1257                         -quaternion[2], quaternion[3]), dtype=numpy.float64)
   1258 
   1259 
   1260 def quaternion_inverse(quaternion):
   1261     """Return inverse of quaternion.
   1262 
   1263     >>> q0 = random_quaternion()
   1264     >>> q1 = quaternion_inverse(q0)
   1265     >>> numpy.allclose(quaternion_multiply(q0, q1), [0, 0, 0, 1])
   1266     True
   1267 
   1268     """
   1269     return quaternion_conjugate(quaternion) / numpy.dot(quaternion, quaternion)
   1270 
   1271 
   1272 def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True):
   1273     """Return spherical linear interpolation between two quaternions.
   1274 
   1275     >>> q0 = random_quaternion()
   1276     >>> q1 = random_quaternion()
   1277     >>> q = quaternion_slerp(q0, q1, 0.0)
   1278     >>> numpy.allclose(q, q0)
   1279     True
   1280     >>> q = quaternion_slerp(q0, q1, 1.0, 1)
   1281     >>> numpy.allclose(q, q1)
   1282     True
   1283     >>> q = quaternion_slerp(q0, q1, 0.5)
   1284     >>> angle = math.acos(numpy.dot(q0, q))
   1285     >>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle) or \
   1286         numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle)
   1287     True
   1288 
   1289     """
   1290     q0 = unit_vector(quat0[:4])
   1291     q1 = unit_vector(quat1[:4])
   1292     if fraction == 0.0:
   1293         return q0
   1294     elif fraction == 1.0:
   1295         return q1
   1296     d = numpy.dot(q0, q1)
   1297     if abs(abs(d) - 1.0) < _EPS:
   1298         return q0
   1299     if shortestpath and d < 0.0:
   1300         # invert rotation
   1301         d = -d
   1302         q1 *= -1.0
   1303     angle = math.acos(d) + spin * math.pi
   1304     if abs(angle) < _EPS:
   1305         return q0
   1306     isin = 1.0 / math.sin(angle)
   1307     q0 *= math.sin((1.0 - fraction) * angle) * isin
   1308     q1 *= math.sin(fraction * angle) * isin
   1309     q0 += q1
   1310     return q0
   1311 
   1312 
   1313 def random_quaternion(rand=None):
   1314     """Return uniform random unit quaternion.
   1315 
   1316     rand: array like or None
   1317         Three independent random variables that are uniformly distributed
   1318         between 0 and 1.
   1319 
   1320     >>> q = random_quaternion()
   1321     >>> numpy.allclose(1.0, vector_norm(q))
   1322     True
   1323     >>> q = random_quaternion(numpy.random.random(3))
   1324     >>> q.shape
   1325     (4,)
   1326 
   1327     """
   1328     if rand is None:
   1329         rand = numpy.random.rand(3)
   1330     else:
   1331         assert len(rand) == 3
   1332     r1 = numpy.sqrt(1.0 - rand[0])
   1333     r2 = numpy.sqrt(rand[0])
   1334     pi2 = math.pi * 2.0
   1335     t1 = pi2 * rand[1]
   1336     t2 = pi2 * rand[2]
   1337     return numpy.array((numpy.sin(t1)*r1,
   1338                         numpy.cos(t1)*r1,
   1339                         numpy.sin(t2)*r2,
   1340                         numpy.cos(t2)*r2), dtype=numpy.float64)
   1341 
   1342 
   1343 def random_rotation_matrix(rand=None):
   1344     """Return uniform random rotation matrix.
   1345 
   1346     rnd: array like
   1347         Three independent random variables that are uniformly distributed
   1348         between 0 and 1 for each returned quaternion.
   1349 
   1350     >>> R = random_rotation_matrix()
   1351     >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
   1352     True
   1353 
   1354     """
   1355     return quaternion_matrix(random_quaternion(rand))
   1356 
   1357 
   1358 class Arcball(object):
   1359     """Virtual Trackball Control.
   1360 
   1361     >>> ball = Arcball()
   1362     >>> ball = Arcball(initial=numpy.identity(4))
   1363     >>> ball.place([320, 320], 320)
   1364     >>> ball.down([500, 250])
   1365     >>> ball.drag([475, 275])
   1366     >>> R = ball.matrix()
   1367     >>> numpy.allclose(numpy.sum(R), 3.90583455)
   1368     True
   1369     >>> ball = Arcball(initial=[0, 0, 0, 1])
   1370     >>> ball.place([320, 320], 320)
   1371     >>> ball.setaxes([1,1,0], [-1, 1, 0])
   1372     >>> ball.setconstrain(True)
   1373     >>> ball.down([400, 200])
   1374     >>> ball.drag([200, 400])
   1375     >>> R = ball.matrix()
   1376     >>> numpy.allclose(numpy.sum(R), 0.2055924)
   1377     True
   1378     >>> ball.next()
   1379 
   1380     """
   1381 
   1382     def __init__(self, initial=None):
   1383         """Initialize virtual trackball control.
   1384 
   1385         initial : quaternion or rotation matrix
   1386 
   1387         """
   1388         self._axis = None
   1389         self._axes = None
   1390         self._radius = 1.0
   1391         self._center = [0.0, 0.0]
   1392         self._vdown = numpy.array([0, 0, 1], dtype=numpy.float64)
   1393         self._constrain = False
   1394 
   1395         if initial is None:
   1396             self._qdown = numpy.array([0, 0, 0, 1], dtype=numpy.float64)
   1397         else:
   1398             initial = numpy.array(initial, dtype=numpy.float64)
   1399             if initial.shape == (4, 4):
   1400                 self._qdown = quaternion_from_matrix(initial)
   1401             elif initial.shape == (4, ):
   1402                 initial /= vector_norm(initial)
   1403                 self._qdown = initial
   1404             else:
   1405                 raise ValueError("initial not a quaternion or matrix.")
   1406 
   1407         self._qnow = self._qpre = self._qdown
   1408 
   1409     def place(self, center, radius):
   1410         """Place Arcball, e.g. when window size changes.
   1411 
   1412         center : sequence[2]
   1413             Window coordinates of trackball center.
   1414         radius : float
   1415             Radius of trackball in window coordinates.
   1416 
   1417         """
   1418         self._radius = float(radius)
   1419         self._center[0] = center[0]
   1420         self._center[1] = center[1]
   1421 
   1422     def setaxes(self, *axes):
   1423         """Set axes to constrain rotations."""
   1424         if axes is None:
   1425             self._axes = None
   1426         else:
   1427             self._axes = [unit_vector(axis) for axis in axes]
   1428 
   1429     def setconstrain(self, constrain):
   1430         """Set state of constrain to axis mode."""
   1431         self._constrain = constrain == True
   1432 
   1433     def getconstrain(self):
   1434         """Return state of constrain to axis mode."""
   1435         return self._constrain
   1436 
   1437     def down(self, point):
   1438         """Set initial cursor window coordinates and pick constrain-axis."""
   1439         self._vdown = arcball_map_to_sphere(point, self._center, self._radius)
   1440         self._qdown = self._qpre = self._qnow
   1441 
   1442         if self._constrain and self._axes is not None:
   1443             self._axis = arcball_nearest_axis(self._vdown, self._axes)
   1444             self._vdown = arcball_constrain_to_axis(self._vdown, self._axis)
   1445         else:
   1446             self._axis = None
   1447 
   1448     def drag(self, point):
   1449         """Update current cursor window coordinates."""
   1450         vnow = arcball_map_to_sphere(point, self._center, self._radius)
   1451 
   1452         if self._axis is not None:
   1453             vnow = arcball_constrain_to_axis(vnow, self._axis)
   1454 
   1455         self._qpre = self._qnow
   1456 
   1457         t = numpy.cross(self._vdown, vnow)
   1458         if numpy.dot(t, t) < _EPS:
   1459             self._qnow = self._qdown
   1460         else:
   1461             q = [t[0], t[1], t[2], numpy.dot(self._vdown, vnow)]
   1462             self._qnow = quaternion_multiply(q, self._qdown)
   1463 
   1464     def next(self, acceleration=0.0):
   1465         """Continue rotation in direction of last drag."""
   1466         q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False)
   1467         self._qpre, self._qnow = self._qnow, q
   1468 
   1469     def matrix(self):
   1470         """Return homogeneous rotation matrix."""
   1471         return quaternion_matrix(self._qnow)
   1472 
   1473 
   1474 def arcball_map_to_sphere(point, center, radius):
   1475     """Return unit sphere coordinates from window coordinates."""
   1476     v = numpy.array(((point[0] - center[0]) / radius,
   1477                      (center[1] - point[1]) / radius,
   1478                      0.0), dtype=numpy.float64)
   1479     n = v[0]*v[0] + v[1]*v[1]
   1480     if n > 1.0:
   1481         v /= math.sqrt(n) # position outside of sphere
   1482     else:
   1483         v[2] = math.sqrt(1.0 - n)
   1484     return v
   1485 
   1486 
   1487 def arcball_constrain_to_axis(point, axis):
   1488     """Return sphere point perpendicular to axis."""
   1489     v = numpy.array(point, dtype=numpy.float64, copy=True)
   1490     a = numpy.array(axis, dtype=numpy.float64, copy=True)
   1491     v -= a * numpy.dot(a, v) # on plane
   1492     n = vector_norm(v)
   1493     if n > _EPS:
   1494         if v[2] < 0.0:
   1495             v *= -1.0
   1496         v /= n
   1497         return v
   1498     if a[2] == 1.0:
   1499         return numpy.array([1, 0, 0], dtype=numpy.float64)
   1500     return unit_vector([-a[1], a[0], 0])
   1501 
   1502 
   1503 def arcball_nearest_axis(point, axes):
   1504     """Return axis, which arc is nearest to point."""
   1505     point = numpy.array(point, dtype=numpy.float64, copy=False)
   1506     nearest = None
   1507     mx = -1.0
   1508     for axis in axes:
   1509         t = numpy.dot(arcball_constrain_to_axis(point, axis), point)
   1510         if t > mx:
   1511             nearest = axis
   1512             mx = t
   1513     return nearest
   1514 
   1515 
   1516 # epsilon for testing whether a number is close to zero
   1517 _EPS = numpy.finfo(float).eps * 4.0
   1518 
   1519 # axis sequences for Euler angles
   1520 _NEXT_AXIS = [1, 2, 0, 1]
   1521 
   1522 # map axes strings to/from tuples of inner axis, parity, repetition, frame
   1523 _AXES2TUPLE = {
   1524     'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0),
   1525     'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0),
   1526     'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0),
   1527     'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0),
   1528     'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1),
   1529     'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1),
   1530     'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1),
   1531     'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)}
   1532 
   1533 _TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items())
   1534 
   1535 # helper functions
   1536 
   1537 def vector_norm(data, axis=None, out=None):
   1538     """Return length, i.e. eucledian norm, of ndarray along axis.
   1539 
   1540     >>> v = numpy.random.random(3)
   1541     >>> n = vector_norm(v)
   1542     >>> numpy.allclose(n, numpy.linalg.norm(v))
   1543     True
   1544     >>> v = numpy.random.rand(6, 5, 3)
   1545     >>> n = vector_norm(v, axis=-1)
   1546     >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
   1547     True
   1548     >>> n = vector_norm(v, axis=1)
   1549     >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
   1550     True
   1551     >>> v = numpy.random.rand(5, 4, 3)
   1552     >>> n = numpy.empty((5, 3), dtype=numpy.float64)
   1553     >>> vector_norm(v, axis=1, out=n)
   1554     >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
   1555     True
   1556     >>> vector_norm([])
   1557     0.0
   1558     >>> vector_norm([1.0])
   1559     1.0
   1560 
   1561     """
   1562     data = numpy.array(data, dtype=numpy.float64, copy=True)
   1563     if out is None:
   1564         if data.ndim == 1:
   1565             return math.sqrt(numpy.dot(data, data))
   1566         data *= data
   1567         out = numpy.atleast_1d(numpy.sum(data, axis=axis))
   1568         numpy.sqrt(out, out)
   1569         return out
   1570     else:
   1571         data *= data
   1572         numpy.sum(data, axis=axis, out=out)
   1573         numpy.sqrt(out, out)
   1574 
   1575 
   1576 def unit_vector(data, axis=None, out=None):
   1577     """Return ndarray normalized by length, i.e. eucledian norm, along axis.
   1578 
   1579     >>> v0 = numpy.random.random(3)
   1580     >>> v1 = unit_vector(v0)
   1581     >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
   1582     True
   1583     >>> v0 = numpy.random.rand(5, 4, 3)
   1584     >>> v1 = unit_vector(v0, axis=-1)
   1585     >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
   1586     >>> numpy.allclose(v1, v2)
   1587     True
   1588     >>> v1 = unit_vector(v0, axis=1)
   1589     >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
   1590     >>> numpy.allclose(v1, v2)
   1591     True
   1592     >>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64)
   1593     >>> unit_vector(v0, axis=1, out=v1)
   1594     >>> numpy.allclose(v1, v2)
   1595     True
   1596     >>> list(unit_vector([]))
   1597     []
   1598     >>> list(unit_vector([1.0]))
   1599     [1.0]
   1600 
   1601     """
   1602     if out is None:
   1603         data = numpy.array(data, dtype=numpy.float64, copy=True)
   1604         if data.ndim == 1:
   1605             data /= math.sqrt(numpy.dot(data, data))
   1606             return data
   1607     else:
   1608         if out is not data:
   1609             out[:] = numpy.array(data, copy=False)
   1610         data = out
   1611     length = numpy.atleast_1d(numpy.sum(data*data, axis))
   1612     numpy.sqrt(length, length)
   1613     if axis is not None:
   1614         length = numpy.expand_dims(length, axis)
   1615     data /= length
   1616     if out is None:
   1617         return data
   1618 
   1619 
   1620 def random_vector(size):
   1621     """Return array of random doubles in the half-open interval [0.0, 1.0).
   1622 
   1623     >>> v = random_vector(10000)
   1624     >>> numpy.all(v >= 0.0) and numpy.all(v < 1.0)
   1625     True
   1626     >>> v0 = random_vector(10)
   1627     >>> v1 = random_vector(10)
   1628     >>> numpy.any(v0 == v1)
   1629     False
   1630 
   1631     """
   1632     return numpy.random.random(size)
   1633 
   1634 
   1635 def inverse_matrix(matrix):
   1636     """Return inverse of square transformation matrix.
   1637 
   1638     >>> M0 = random_rotation_matrix()
   1639     >>> M1 = inverse_matrix(M0.T)
   1640     >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
   1641     True
   1642     >>> for size in range(1, 7):
   1643     ...     M0 = numpy.random.rand(size, size)
   1644     ...     M1 = inverse_matrix(M0)
   1645     ...     if not numpy.allclose(M1, numpy.linalg.inv(M0)): print size
   1646 
   1647     """
   1648     return numpy.linalg.inv(matrix)
   1649 
   1650 
   1651 def concatenate_matrices(*matrices):
   1652     """Return concatenation of series of transformation matrices.
   1653 
   1654     >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
   1655     >>> numpy.allclose(M, concatenate_matrices(M))
   1656     True
   1657     >>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T))
   1658     True
   1659 
   1660     """
   1661     M = numpy.identity(4)
   1662     for i in matrices:
   1663         M = numpy.dot(M, i)
   1664     return M
   1665 
   1666 
   1667 def is_same_transform(matrix0, matrix1):
   1668     """Return True if two matrices perform same transformation.
   1669 
   1670     >>> is_same_transform(numpy.identity(4), numpy.identity(4))
   1671     True
   1672     >>> is_same_transform(numpy.identity(4), random_rotation_matrix())
   1673     False
   1674 
   1675     """
   1676     matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True)
   1677     matrix0 /= matrix0[3, 3]
   1678     matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True)
   1679     matrix1 /= matrix1[3, 3]
   1680     return numpy.allclose(matrix0, matrix1)
   1681 
   1682 
   1683 def _import_module(module_name, warn=True, prefix='_py_', ignore='_'):
   1684     """Try import all public attributes from module into global namespace.
   1685 
   1686     Existing attributes with name clashes are renamed with prefix.
   1687     Attributes starting with underscore are ignored by default.
   1688 
   1689     Return True on successful import.
   1690 
   1691     """
   1692     try:
   1693         module = __import__(module_name)
   1694     except ImportError:
   1695         if warn:
   1696             warnings.warn("Failed to import module " + module_name)
   1697     else:
   1698         for attr in dir(module):
   1699             if ignore and attr.startswith(ignore):
   1700                 continue
   1701             if prefix:
   1702                 if attr in globals():
   1703                     globals()[prefix + attr] = globals()[attr]
   1704                 elif warn:
   1705                     warnings.warn("No Python implementation of " + attr)
   1706             globals()[attr] = getattr(module, attr)
   1707         return True
   1708