import numpy as np from numpy import dot, inner, array, linalg from common.transformations.coordinates import LocalCoord ''' Vectorized functions that transform between rotation matrices, euler angles and quaternions. All support lists, array or array of arrays as inputs. Supports both x2y and y_from_x format (y_from_x preferred!). ''' def euler2quat(eulers): eulers = array(eulers) if len(eulers.shape) > 1: output_shape = (-1,4) else: output_shape = (4,) eulers = np.atleast_2d(eulers) gamma, theta, psi = eulers[:,0], eulers[:,1], eulers[:,2] q0 = np.cos(gamma / 2) * np.cos(theta / 2) * np.cos(psi / 2) + \ np.sin(gamma / 2) * np.sin(theta / 2) * np.sin(psi / 2) q1 = np.sin(gamma / 2) * np.cos(theta / 2) * np.cos(psi / 2) - \ np.cos(gamma / 2) * np.sin(theta / 2) * np.sin(psi / 2) q2 = np.cos(gamma / 2) * np.sin(theta / 2) * np.cos(psi / 2) + \ np.sin(gamma / 2) * np.cos(theta / 2) * np.sin(psi / 2) q3 = np.cos(gamma / 2) * np.cos(theta / 2) * np.sin(psi / 2) - \ np.sin(gamma / 2) * np.sin(theta / 2) * np.cos(psi / 2) quats = array([q0, q1, q2, q3]).T for i in range(len(quats)): if quats[i,0] < 0: quats[i] = -quats[i] return quats.reshape(output_shape) def quat2euler(quats): quats = array(quats) if len(quats.shape) > 1: output_shape = (-1,3) else: output_shape = (3,) quats = np.atleast_2d(quats) q0, q1, q2, q3 = quats[:,0], quats[:,1], quats[:,2], quats[:,3] gamma = np.arctan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1**2 + q2**2)) theta = np.arcsin(2 * (q0 * q2 - q3 * q1)) psi = np.arctan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2**2 + q3**2)) eulers = array([gamma, theta, psi]).T return eulers.reshape(output_shape) def quat2rot(quats): quats = array(quats) input_shape = quats.shape quats = np.atleast_2d(quats) Rs = np.zeros((quats.shape[0], 3, 3)) q0 = quats[:, 0] q1 = quats[:, 1] q2 = quats[:, 2] q3 = quats[:, 3] Rs[:, 0, 0] = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3 Rs[:, 0, 1] = 2 * (q1 * q2 - q0 * q3) Rs[:, 0, 2] = 2 * (q0 * q2 + q1 * q3) Rs[:, 1, 0] = 2 * (q1 * q2 + q0 * q3) Rs[:, 1, 1] = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3 Rs[:, 1, 2] = 2 * (q2 * q3 - q0 * q1) Rs[:, 2, 0] = 2 * (q1 * q3 - q0 * q2) Rs[:, 2, 1] = 2 * (q0 * q1 + q2 * q3) Rs[:, 2, 2] = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3 if len(input_shape) < 2: return Rs[0] else: return Rs def rot2quat(rots): input_shape = rots.shape if len(input_shape) < 3: rots = array([rots]) K3 = np.empty((len(rots), 4, 4)) K3[:, 0, 0] = (rots[:, 0, 0] - rots[:, 1, 1] - rots[:, 2, 2]) / 3.0 K3[:, 0, 1] = (rots[:, 1, 0] + rots[:, 0, 1]) / 3.0 K3[:, 0, 2] = (rots[:, 2, 0] + rots[:, 0, 2]) / 3.0 K3[:, 0, 3] = (rots[:, 1, 2] - rots[:, 2, 1]) / 3.0 K3[:, 1, 0] = K3[:, 0, 1] K3[:, 1, 1] = (rots[:, 1, 1] - rots[:, 0, 0] - rots[:, 2, 2]) / 3.0 K3[:, 1, 2] = (rots[:, 2, 1] + rots[:, 1, 2]) / 3.0 K3[:, 1, 3] = (rots[:, 2, 0] - rots[:, 0, 2]) / 3.0 K3[:, 2, 0] = K3[:, 0, 2] K3[:, 2, 1] = K3[:, 1, 2] K3[:, 2, 2] = (rots[:, 2, 2] - rots[:, 0, 0] - rots[:, 1, 1]) / 3.0 K3[:, 2, 3] = (rots[:, 0, 1] - rots[:, 1, 0]) / 3.0 K3[:, 3, 0] = K3[:, 0, 3] K3[:, 3, 1] = K3[:, 1, 3] K3[:, 3, 2] = K3[:, 2, 3] K3[:, 3, 3] = (rots[:, 0, 0] + rots[:, 1, 1] + rots[:, 2, 2]) / 3.0 q = np.empty((len(rots), 4)) for i in range(len(rots)): _, eigvecs = linalg.eigh(K3[i].T) eigvecs = eigvecs[:,3:] q[i, 0] = eigvecs[-1] q[i, 1:] = -eigvecs[:-1].flatten() if q[i, 0] < 0: q[i] = -q[i] if len(input_shape) < 3: return q[0] else: return q def euler2rot(eulers): return rotations_from_quats(euler2quat(eulers)) def rot2euler(rots): return quat2euler(quats_from_rotations(rots)) quats_from_rotations = rot2quat quat_from_rot = rot2quat rotations_from_quats = quat2rot rot_from_quat = quat2rot rot_from_quat = quat2rot euler_from_rot = rot2euler euler_from_quat = quat2euler rot_from_euler = euler2rot quat_from_euler = euler2quat ''' Random helpers below ''' def quat_product(q, r): t = np.zeros(4) t[0] = r[0] * q[0] - r[1] * q[1] - r[2] * q[2] - r[3] * q[3] t[1] = r[0] * q[1] + r[1] * q[0] - r[2] * q[3] + r[3] * q[2] t[2] = r[0] * q[2] + r[1] * q[3] + r[2] * q[0] - r[3] * q[1] t[3] = r[0] * q[3] - r[1] * q[2] + r[2] * q[1] + r[3] * q[0] return t def rot_matrix(roll, pitch, yaw): cr, sr = np.cos(roll), np.sin(roll) cp, sp = np.cos(pitch), np.sin(pitch) cy, sy = np.cos(yaw), np.sin(yaw) rr = array([[1,0,0],[0, cr,-sr],[0, sr, cr]]) rp = array([[cp,0,sp],[0, 1,0],[-sp, 0, cp]]) ry = array([[cy,-sy,0],[sy, cy,0],[0, 0, 1]]) return ry.dot(rp.dot(rr)) def rot(axis, angle): # Rotates around an arbitrary axis ret_1 = (1 - np.cos(angle)) * array([[axis[0]**2, axis[0] * axis[1], axis[0] * axis[2]], [ axis[1] * axis[0], axis[1]**2, axis[1] * axis[2] ], [axis[2] * axis[0], axis[2] * axis[1], axis[2]**2]]) ret_2 = np.cos(angle) * np.eye(3) ret_3 = np.sin(angle) * array([[0, -axis[2], axis[1]], [axis[2], 0, -axis[0]], [-axis[1], axis[0], 0]]) return ret_1 + ret_2 + ret_3 def ecef_euler_from_ned(ned_ecef_init, ned_pose): ''' Got it from here: Using Rotations to Build Aerospace Coordinate Systems -Don Koks ''' converter = LocalCoord.from_ecef(ned_ecef_init) x0 = converter.ned2ecef([1, 0, 0]) - converter.ned2ecef([0, 0, 0]) y0 = converter.ned2ecef([0, 1, 0]) - converter.ned2ecef([0, 0, 0]) z0 = converter.ned2ecef([0, 0, 1]) - converter.ned2ecef([0, 0, 0]) x1 = rot(z0, ned_pose[2]).dot(x0) y1 = rot(z0, ned_pose[2]).dot(y0) z1 = rot(z0, ned_pose[2]).dot(z0) x2 = rot(y1, ned_pose[1]).dot(x1) y2 = rot(y1, ned_pose[1]).dot(y1) z2 = rot(y1, ned_pose[1]).dot(z1) x3 = rot(x2, ned_pose[0]).dot(x2) y3 = rot(x2, ned_pose[0]).dot(y2) #z3 = rot(x2, ned_pose[0]).dot(z2) x0 = array([1, 0, 0]) y0 = array([0, 1, 0]) z0 = array([0, 0, 1]) psi = np.arctan2(inner(x3, y0), inner(x3, x0)) theta = np.arctan2(-inner(x3, z0), np.sqrt(inner(x3, x0)**2 + inner(x3, y0)**2)) y2 = rot(z0, psi).dot(y0) z2 = rot(y2, theta).dot(z0) phi = np.arctan2(inner(y3, z2), inner(y3, y2)) ret = array([phi, theta, psi]) return ret def ned_euler_from_ecef(ned_ecef_init, ecef_poses): ''' Got the math from here: Using Rotations to Build Aerospace Coordinate Systems -Don Koks Also accepts array of ecef_poses and array of ned_ecef_inits. Where each row is a pose and an ecef_init. ''' ned_ecef_init = array(ned_ecef_init) ecef_poses = array(ecef_poses) output_shape = ecef_poses.shape ned_ecef_init = np.atleast_2d(ned_ecef_init) if ned_ecef_init.shape[0] == 1: ned_ecef_init = np.tile(ned_ecef_init[0], (output_shape[0], 1)) ecef_poses = np.atleast_2d(ecef_poses) ned_poses = np.zeros(ecef_poses.shape) for i, ecef_pose in enumerate(ecef_poses): converter = LocalCoord.from_ecef(ned_ecef_init[i]) x0 = array([1, 0, 0]) y0 = array([0, 1, 0]) z0 = array([0, 0, 1]) x1 = rot(z0, ecef_pose[2]).dot(x0) y1 = rot(z0, ecef_pose[2]).dot(y0) z1 = rot(z0, ecef_pose[2]).dot(z0) x2 = rot(y1, ecef_pose[1]).dot(x1) y2 = rot(y1, ecef_pose[1]).dot(y1) z2 = rot(y1, ecef_pose[1]).dot(z1) x3 = rot(x2, ecef_pose[0]).dot(x2) y3 = rot(x2, ecef_pose[0]).dot(y2) #z3 = rot(x2, ecef_pose[0]).dot(z2) x0 = converter.ned2ecef([1, 0, 0]) - converter.ned2ecef([0, 0, 0]) y0 = converter.ned2ecef([0, 1, 0]) - converter.ned2ecef([0, 0, 0]) z0 = converter.ned2ecef([0, 0, 1]) - converter.ned2ecef([0, 0, 0]) psi = np.arctan2(inner(x3, y0), inner(x3, x0)) theta = np.arctan2(-inner(x3, z0), np.sqrt(inner(x3, x0)**2 + inner(x3, y0)**2)) y2 = rot(z0, psi).dot(y0) z2 = rot(y2, theta).dot(z0) phi = np.arctan2(inner(y3, z2), inner(y3, y2)) ned_poses[i] = array([phi, theta, psi]) return ned_poses.reshape(output_shape) def ecef2car(car_ecef, psi, theta, points_ecef, ned_converter): """ TODO: add roll rotation Converts an array of points in ecef coordinates into x-forward, y-left, z-up coordinates Parameters ---------- psi: yaw, radian theta: pitch, radian Returns ------- [x, y, z] coordinates in car frame """ # input is an array of points in ecef cocrdinates # output is an array of points in car's coordinate (x-front, y-left, z-up) # convert points to NED points_ned = [] for p in points_ecef: points_ned.append(ned_converter.ecef2ned_matrix.dot(array(p) - car_ecef)) points_ned = np.vstack(points_ned).T # n, e, d -> x, y, z # Calculate relative postions and rotate wrt to heading and pitch of car invert_R = array([[1., 0., 0.], [0., -1., 0.], [0., 0., -1.]]) c, s = np.cos(psi), np.sin(psi) yaw_R = array([[c, s, 0.], [-s, c, 0.], [0., 0., 1.]]) c, s = np.cos(theta), np.sin(theta) pitch_R = array([[c, 0., -s], [0., 1., 0.], [s, 0., c]]) return dot(pitch_R, dot(yaw_R, dot(invert_R, points_ned)))