{"slug": "euler-angles-to-rotation-matrices-and-vice-versa", "title": "Euler angles to rotation matrices and vice versa", "summary": "This Python module provides utility functions for converting between 4x4 homogeneous transformation matrices and Euler angles, supporting all six rotation orders ('xyz', 'xzy', 'yxz', 'yzx', 'zxy', 'zyx') for body-axis rotations. The two primary functions are `rotation()`, which generates a rotation matrix from three angle values, and `inverse_rotation()`, which extracts two possible sets of Euler angles from a pure rotation matrix. The module also includes a Jacobian function for computing the derivative of the rotation with respect to the angle parameters.", "body_md": "\"\"\"Euler-angle to matrix and matrix to Euler-angle utility routines\n\nThis module provides utility routines to convert between 4x4 homogeneous\ntransformation matrices and Euler angles (and vice versa). It supports \nall six rotation orders ('xyz','xzy','yxz','yzx','zxy','zyx') for rotations\naround body axes.\n\nTwo primary functions are provided:\n\n- rotation( theta, order='xyz' ): given input rotation angles in radians\n    and order of rotations, return the corresponding 4x4 rotation matrix\n\n- inverse_rotation( M, order='xyz' ): given a matrix with a **pure** \n    rotation in the upper-left 3x3 submatrix, return the two sets of \n    Euler angles that reproduce the rotation matrix (see Notes)\n\nThe transformation matrices occupy the upper-left 3x3 submatrix of the\n4x4 output. The transformations are suitable for column-vector homogeneous\npoints, e.g.:\n\n    res = rotation((theta_x,theta_y,theta_z), order='zyx')@np.array((x,y,z,1))\n\ntransforms the point [x,y,z] by rotating first around the z axis, then\naround the y axis and finally around the x axis. Note that the order of the \ntheta argument is always (theta_x, theta_y, theta_z) regardless of the order\nargument.\n\nAn additional function is provided to compute the Jacobian of the rotation\nw.r.t. the angle parameters. It returns a 4x4x3 ndarray, where the third\naxis indexes the angles. E.g.:\n\n    p = (*np.random.uniform(size=3),1)\n    theta = np.random.uniform(-np.pi,np.pi,3)\n\n    w = euler.rotation( theta, order='xzy' )@p\n    J = euler.rotation_jacobian( theta, order='xzy' )\n    dwdthetax = J[:,:,0]@p\n    dwdthetay = J[:,:,1]@p\n    dwdthetaz = J[:,:,2]@p\n\nArguments:\n\n- theta (3-element sequence): rotation angles in radians, packed as \n    (theta_x, theta_y, theta_z) regardless of the order parameter\n\n- order (one of 'xyz', 'xzy', 'yxz', 'yzx', 'zxy', 'zyx') indicating\n    the order in which rotations are applied to points, read \n    left-to-right ('yzx' rotates by y first, then z and finally x)\n\n- M (at least 3x3 ndarray): matrix from which to extract angles,\n    must be a pure rotation in a right-handed coordinate system \n\nRaises:\n\n- ValueError if matrices input to inverse rotation do not have\n    determinant equal to 1 or have M[:3,:3].T@M[:3,:3] == I to \n    a high tolerance\n\nNotes:\n\n- Two sets of angles are returned by inverse_rotation, both reproduce the\nthe rotation matrix. First set has the 'middle' angle in range [-pi/2,pi/2]\nand remaining axes in [-pi,pi], second result subtracts the middle angle \nfrom pi and recomputes the remaining angles.  Applications should pick \nthe appropriate return value based on either legal range of angles or \nproximity to known values.\n\n- The order of axes in the 'order' argument and the order in which \nmatrices are multiplied is reversed, e.g. order 'xyz' results in:\n\n    res = Rz(theta_z)*Ry(theta_y)*Rx(theta_x)@np.array(x,y,z,1)\n\nThe order argument consequently specifies the order that rotations are\napplied, rather than the order of multiplication.\n\"\"\"\nimport numpy as np\n\ndef rotation( theta, order='xyz' ):\n    \"\"\"Return the 4x4 homogeneous transform for a given triplet of angles in radians and rotation order\n    \n    See module docs for more info\n    \"\"\"\n    if order == 'xyz':\n        return rotation_xyz( theta )\n    elif order == 'xzy':\n        return rotation_xzy( theta )\n    elif order == 'yxz':\n        return rotation_yxz( theta )\n    elif order == 'yzx':\n        return rotation_yzx( theta )\n    elif order == 'zxy':\n        return rotation_zxy( theta )\n    elif order == 'zyx':\n        return rotation_zyx( theta )\n    raise ValueError('Invalid rotation order {}'.format(order) )\n\ndef inverse_rotation( M, order='xyz' ):\n    \"\"\"Return two sets of Euler angles in radians that reproduce a given (at least) 3x3 rotation matrix according to the target rotation order\n\n    See module docs for more info\n    \"\"\"\n    if order == 'xyz':\n        return inverse_rotation_xyz( M )\n    elif order == 'xzy':\n        return inverse_rotation_xzy( M )\n    elif order == 'yxz':\n        return inverse_rotation_yxz( M )\n    elif order == 'yzx':\n        return inverse_rotation_yzx( M )\n    elif order == 'zxy':\n        return inverse_rotation_zxy( M )\n    elif order == 'zyx':\n        return inverse_rotation_zyx( M )\n    raise ValueError('Invalid rotation order {}'.format(order) )\n\ndef rotation_jacobian( theta, order='xyz' ):\n    \"\"\"Returns the 4x4x3 Jacobian of the rotation by the given angles with specified order\n\n    See module docs for more info\n    \"\"\"\n    if order == 'xyz':\n        return rotation_jacobian_xyz( theta )\n    elif order == 'xzy':\n        return rotation_jacobian_xzy( theta )\n    elif order == 'yxz':\n        return rotation_jacobian_yxz( theta )\n    elif order == 'yzx':\n        return rotation_jacobian_yzx( theta )\n    elif order == 'zxy':\n        return rotation_jacobian_zxy( theta )\n    elif order == 'zyx':\n        return rotation_jacobian_zyx( theta )\n\ndef rotation_xyz( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [cy*cz, -cx*sz + cz*sx*sy, cx*cz*sy + sx*sz, 0],\n        [cy*sz,  cx*cz + sx*sy*sz, cx*sy*sz - cz*sx, 0],\n        [  -sy,             cy*sx,            cx*cy, 0],\n        [    0,                 0,                0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_xyz( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[2,0]) > thresh:        \n        sy = -np.sign(M[2,0])\n        y0 = sy*np.pi/2\n\n        # arbitrarily set z=0\n        z0 = 0 # so sz=0, cz=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        x0 = np.arctan2( M[0,1]/sy, M[0,2]/sy )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        y0 = np.arcsin( -M[2,0] )\n        y1 = np.pi - y0\n        c0 = np.cos(y0)\n        c1 = np.cos(y1)\n\n        x0 = np.arctan2( M[2,1]/c0, M[2,2]/c0 )\n        x1 = np.arctan2( M[2,1]/c1, M[2,2]/c1 )\n\n        z0 = np.arctan2( M[1,0]/c0, M[0,0]/c0 )\n        z1 = np.arctan2( M[1,0]/c1, M[0,0]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef rotation_xzy( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [ cy*cz, -cx*cy*sz + sx*sy,  cx*sy + cy*sx*sz, 0],\n        [    sz,             cx*cz,            -cz*sx, 0],\n        [-cz*sy,  cx*sy*sz + cy*sx,  cx*cy - sx*sy*sz, 0],\n        [0, 0, 0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_xzy( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[1,0]) > thresh:        \n        sz = np.sign(M[1,0])\n        z0 = sz*np.pi/2\n\n        # arbitrarily set z=0\n        y0 = 0 # so sy=0, cy=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        x0 = np.arctan2( M[0,2]/sz, -M[0,1]/sz )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        z0 = np.arcsin( M[1,0] )\n        z1 = np.pi - z0\n        c0 = np.cos(z0)\n        c1 = np.cos(z1)\n\n        x0 = np.arctan2( -M[1,2]/c0, M[1,1]/c0 )\n        x1 = np.arctan2( -M[1,2]/c1, M[1,1]/c1 )\n\n        y0 = np.arctan2( -M[2,0]/c0, M[0,0]/c0 )\n        y1 = np.arctan2( -M[2,0]/c1, M[0,0]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef rotation_yxz( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [ cy*cz - sx*sy*sz, -cx*sz,  cy*sx*sz + cz*sy, 0],\n        [ cy*sz + cz*sx*sy,  cx*cz, -cy*cz*sx + sy*sz, 0],\n        [           -cx*sy,     sx,             cx*cy, 0],\n        [                0,      0,                 0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_yxz( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[2,1]) > thresh:        \n        sx = np.sign(M[2,1])\n        x0 = sx*np.pi/2\n\n        # arbitrarily set z=0\n        z0 = 0 # so sz=0, cz=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        y0 = np.arctan2( M[1,0]/sx, -M[1,2]/sx )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        x0 = np.arcsin( M[2,1] )\n        x1 = np.pi - x0\n        c0 = np.cos(x0)\n        c1 = np.cos(x1)\n\n        y0 = np.arctan2( -M[2,0]/c0, M[2,2]/c0 )\n        y1 = np.arctan2( -M[2,0]/c1, M[2,2]/c1 )\n\n        z0 = np.arctan2( -M[0,1]/c0, M[1,1]/c0 )\n        z1 = np.arctan2( -M[0,1]/c1, M[1,1]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef rotation_yzx( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [             cy*cz,   -sz,            cz*sy, 0],\n        [  cx*cy*sz + sx*sy, cx*cz, cx*sy*sz - cy*sx, 0],\n        [ -cx*sy + cy*sx*sz, cz*sx, cx*cy + sx*sy*sz, 0],\n        [                 0,     0,                0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_yzx( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[0,1]) > thresh:        \n        sz = np.sign(-M[0,1])\n        z0 = sz*np.pi/2\n\n        # arbitrarily set z=0\n        x0 = 0 # so sx=0, cx=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        y0 = np.arctan2( M[1,2]/sz, M[1,0]/sz )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        z0 = np.arcsin( -M[0,1] )\n        z1 = np.pi - z0\n        c0 = np.cos(z0)\n        c1 = np.cos(z1)\n\n        y0 = np.arctan2( M[0,2]/c0, M[0,0]/c0 )\n        y1 = np.arctan2( M[0,2]/c1, M[0,0]/c1 )\n\n        x0 = np.arctan2( M[2,1]/c0, M[1,1]/c0 )\n        x1 = np.arctan2( M[2,1]/c1, M[1,1]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef rotation_zxy( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [ cy*cz + sx*sy*sz, -cy*sz + cz*sx*sy,  cx*sy, 0],\n        [            cx*sz,             cx*cz,    -sx, 0],\n        [ cy*sx*sz - cz*sy,  cy*cz*sx + sy*sz,  cx*cy, 0],\n        [                0,                 0,      0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_zxy( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[1,2]) > thresh:        \n        sx = np.sign(-M[1,2])\n        x0 = sx*np.pi/2\n\n        # arbitrarily set z=0\n        y0 = 0 # so sy=0, cy=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        z0 = np.arctan2( M[2,0]/sx, M[2,1]/sx )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        x0 = np.arcsin( -M[1,2] )\n        x1 = np.pi - x0\n        c0 = np.cos(x0)\n        c1 = np.cos(x1)\n\n        y0 = np.arctan2( M[0,2]/c0, M[2,2]/c0 )\n        y1 = np.arctan2( M[0,2]/c1, M[2,2]/c1 )\n\n        z0 = np.arctan2( M[1,0]/c0, M[1,1]/c0 )\n        z1 = np.arctan2( M[1,0]/c1, M[1,1]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef rotation_zyx( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n    return np.array([\n        [             cy*cz,            -cy*sz,     sy, 0],\n        [  cx*sz + cz*sx*sy,  cx*cz - sx*sy*sz, -cy*sx, 0],\n        [ -cx*cz*sy + sx*sz,  cx*sy*sz + cz*sx,  cx*cy, 0],\n        [                 0,                 0,      0, 1]\n    ],dtype=float)\n\ndef inverse_rotation_zyx( M, thresh=0.9999999 ):\n    __check_rotation_matrix( M )\n    if np.abs(M[0,2]) > thresh:        \n        sy = np.sign(M[0,2])\n        y0 = sy*np.pi/2\n\n        # arbitrarily set z=0\n        x0 = 0 # so sx=0, cx=1\n\n        # compute x = arctan2( M[0,1]/sy, M[02]/sy )\n        z0 = np.arctan2( M[2,1]/sy, -M[2,0]/sy )\n        return np.array((x0,y0,z0)), np.array((x0,y0,z0))\n    else:\n        y0 = np.arcsin( M[0,2] )\n        y1 = np.pi - y0\n        c0 = np.cos(y0)\n        c1 = np.cos(y1)\n\n        x0 = np.arctan2( -M[1,2]/c0, M[2,2]/c0 )\n        x1 = np.arctan2( -M[1,2]/c1, M[2,2]/c1 )\n\n        z0 = np.arctan2( -M[0,1]/c0, M[0,0]/c0 )\n        z1 = np.arctan2( -M[0,1]/c1, M[0,0]/c1 )\n        return np.array((x0,y0,z0)), np.array((x1,y1,z1))\n\ndef __check_rotation_matrix( M, orth_thresh=1e-5, det_thresh=1e-5 ):\n    if np.linalg.norm( M[:3,:3].T@M[:3,:3] - np.eye(3) ) > orth_thresh:\n        raise ValueError('Input matrix is not a pure rotation')\n    if np.abs(np.linalg.det(M[:3,:3])-1.0) > det_thresh:\n        raise ValueError('Input matrix is not a pure rotation')\n\ndef rotation_jacobian_xyz( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [     0, -dcx*sz + cz*dsx*sy, dcx*cz*sy + dsx*sz, 0],\n            [     0,  dcx*cz + dsx*sy*sz, dcx*sy*sz - cz*dsx, 0],\n            [     0,              cy*dsx,             dcx*cy, 0],\n            [     0,                   0,                  0, 0]\n        ],[\n            [dcy*cz,           cz*sx*dsy,          cx*cz*dsy, 0],\n            [dcy*sz,           sx*dsy*sz,          cx*dsy*sz, 0],\n            [  -dsy,              dcy*sx,             cx*dcy, 0],\n            [     0,                   0,                  0, 0]\n        ],[\n            [cy*dcz, -cx*dsz + dcz*sx*sy, cx*dcz*sy + sx*dsz, 0],\n            [cy*dsz,  cx*dcz + sx*sy*dsz, cx*sy*dsz - dcz*sx, 0],\n            [     0,                   0,                  0, 0],\n            [     0,                   0,                  0, 0]\n        ]\n    ]), (1,2,0) )\n\ndef rotation_jacobian_xzy( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [     0, -dcx*cy*sz + dsx*sy,   dcx*sy + cy*dsx*sz, 0],\n            [     0,              dcx*cz,              -cz*dsx, 0],\n            [     0,  dcx*sy*sz + cy*dsx,   dcx*cy - dsx*sy*sz, 0],\n            [     0,                   0,                    0, 0]\n        ],[\n            [ dcy*cz, -cx*dcy*sz + sx*dsy,  cx*dsy + dcy*sx*sz, 0],\n            [      0,                   0,                   0, 0],\n            [-cz*dsy,  cx*dsy*sz + dcy*sx,  cx*dcy - sx*dsy*sz, 0],\n            [      0,                   0,                   0, 0]\n        ],[\n            [ cy*dcz,          -cx*cy*dsz,           cy*sx*dsz, 0],\n            [    dsz,              cx*dcz,             -dcz*sx, 0],\n            [-dcz*sy,           cx*sy*dsz,          -sx*sy*dsz, 0],\n            [      0,                   0,                   0, 0]\n        ]\n    ]), (1,2,0) )\n\ndef rotation_jacobian_yxz( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [         -dsx*sy*sz, -dcx*sz,           cy*dsx*sz, 0],\n            [          cz*dsx*sy,  dcx*cz,          -cy*cz*dsx, 0],\n            [            -dcx*sy,     dsx,              dcx*cy, 0],\n            [                  0,       0,                   0, 0]\n        ],[\n            [ dcy*cz - sx*dsy*sz,       0,  dcy*sx*sz + cz*dsy, 0],\n            [ dcy*sz + cz*sx*dsy,       0, -dcy*cz*sx + dsy*sz, 0],\n            [            -cx*dsy,       0,              cx*dcy, 0],\n            [                  0,       0,                   0, 0]\n        ],[\n            [ cy*dcz - sx*sy*dsz, -cx*dsz,  cy*sx*dsz + dcz*sy, 0],\n            [ cy*dsz + dcz*sx*sy,  cx*dcz, -cy*dcz*sx + sy*dsz, 0],\n            [                  0,       0,                   0, 0],\n            [                  0,       0,                   0, 0]\n        ]\n    ]), (1,2,0) )\n\ndef rotation_jacobian_yzx( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [                   0,      0,                  0, 0],\n            [  dcx*cy*sz + dsx*sy, dcx*cz, dcx*sy*sz - cy*dsx, 0],\n            [ -dcx*sy + cy*dsx*sz, cz*dsx, dcx*cy + dsx*sy*sz, 0],\n            [                   0,      0,                  0, 0]\n        ],[\n            [              dcy*cz,      0,             cz*dsy, 0],\n            [  cx*dcy*sz + sx*dsy,      0, cx*dsy*sz - dcy*sx, 0],\n            [ -cx*dsy + dcy*sx*sz,      0, cx*dcy + sx*dsy*sz, 0],\n            [                   0,      0,                  0, 0]\n        ],[\n            [             cy*dcz,   -dsz,              dcz*sy, 0],\n            [          cx*cy*dsz, cx*dcz,           cx*sy*dsz, 0],\n            [          cy*sx*dsz, dcz*sx,           sx*sy*dsz, 0],\n            [                  0,      0,                   0, 0]\n        ]\n    ]), (1,2,0) )\n\ndef rotation_jacobian_zxy( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [          dsx*sy*sz,           cz*dsx*sy,  dcx*sy, 0],\n            [             dcx*sz,              dcx*cz,    -dsx, 0],\n            [          cy*dsx*sz,           cy*cz*dsx,  dcx*cy, 0],\n            [                  0,                   0,       0, 0]\n        ],[\n            [ dcy*cz + sx*dsy*sz, -dcy*sz + cz*sx*dsy,  cx*dsy, 0],\n            [                  0,                   0,       0, 0],\n            [ dcy*sx*sz - cz*dsy,  dcy*cz*sx + dsy*sz,  cx*dcy, 0],\n            [                  0,                   0,       0, 0]\n        ],[\n            [ cy*dcz + sx*sy*dsz, -cy*dsz + dcz*sx*sy,       0, 0],\n            [             cx*dsz,             cx*dcz,        0, 0],\n            [ cy*sx*dsz - dcz*sy,  cy*dcz*sx + sy*dsz,       0, 0],\n            [                  0,                   0,       0, 0]\n        ]\n    ]), (1,2,0) )\n\ndef rotation_jacobian_zyx( theta ):\n    cx,cy,cz = np.cos(theta)\n    sx,sy,sz = np.sin(theta)\n\n    dcx,dsx = (-sx,cx)\n    dcy,dsy = (-sy,cy)\n    dcz,dsz = (-sz,cz)\n\n    return np.transpose( np.array([\n        [\n            [                   0,                   0,       0, 0],\n            [  dcx*sz + cz*dsx*sy,  dcx*cz - dsx*sy*sz, -cy*dsx, 0],\n            [ -dcx*cz*sy + dsx*sz,  dcx*sy*sz + cz*dsx,  dcx*cy, 0],\n            [                   0,                   0,       0, 0]\n        ],[\n            [              dcy*cz,             -dcy*sz,     dsy, 0],\n            [           cz*sx*dsy,          -sx*dsy*sz, -dcy*sx, 0],\n            [          -cx*cz*dsy,           cx*dsy*sz,  cx*dcy, 0],\n            [                   0,                   0,       0, 0]\n        ],[\n            [              cy*dcz,             -cy*dsz,       0, 0],\n            [  cx*dsz + dcz*sx*sy,  cx*dcz - sx*sy*dsz,       0, 0],\n            [ -cx*dcz*sy + sx*dsz,  cx*sy*dsz + dcz*sx,       0, 0],\n            [                   0,                   0,       0, 0]\n        ]\n    ]), (1,2,0) )\n", "url": "https://wpnews.pro/news/euler-angles-to-rotation-matrices-and-vice-versa", "canonical_source": "https://gist.github.com/jamesgregson/67eb5509af0d8b372f25146d5e3c5149", "published_at": "2019-09-05 01:19:45+00:00", "updated_at": "2026-05-22 14:44:09.868291+00:00", "lang": "en", "topics": ["robotics"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/euler-angles-to-rotation-matrices-and-vice-versa", "markdown": "https://wpnews.pro/news/euler-angles-to-rotation-matrices-and-vice-versa.md", "text": "https://wpnews.pro/news/euler-angles-to-rotation-matrices-and-vice-versa.txt", "jsonld": "https://wpnews.pro/news/euler-angles-to-rotation-matrices-and-vice-versa.jsonld"}}