Source code for soccer_common.transformation
import numpy as np
import rospy
from geometry_msgs.msg import Pose, PoseStamped, PoseWithCovarianceStamped
from geometry_msgs.msg import Transform as GeometryMsgsTransform
from scipy.spatial.transform import Rotation as R
from scipy.spatial.transform import Slerp
[docs]class Transformation(np.ndarray):
"""
3D transformation or pose of an object represented as a 4x4 matrix, but can take in many formats
"""
def __new__(
cls,
position=(0.0, 0.0, 0.0),
quaternion=(0.0, 0.0, 0.0, 1.0),
rotation_matrix: np.array = None,
matrix: np.array = None,
euler: np.array = None,
pos_theta: np.array = None,
pose_theta_covariance_array: np.array = None,
pose: Pose = None,
pose_stamped: PoseStamped = None,
pose_with_covariance_stamped: PoseWithCovarianceStamped = None,
geometry_msgs_transform: GeometryMsgsTransform = None,
dh: np.array = None,
timestamp: rospy.Time = None,
*args,
**kwargs
):
"""
Constructor for the Transformation object
:param position: Position represented as (x, y, z) in meters
:param quaternion: Quaternions represented in (x, y, z, w)
:param rotation_matrix: 3x3 rotation matrix of the transform, can be used in conjunction with position
:param matrix: 4x4 transformation matrix, includes both position and rotation
:param euler: Orientation represented in euler format [yaw, pitch, roll]
:param pos_theta: 2D position and theta format (x, y, theta/yaw)
:param pose_theta_covariance_array: Covariance matrix for position and theta format etc diag([x_var, y_var, theta/yaw_var])
:param pose: Takes a geometry_msg.Pose object and converts it
:param pose_stamped: Takes a geometry_msg.PoseStamped object and converts it
:param pose_with_covariance_stamped: Takes a geometry_msg.PoseWithCovarianceStamped object and converts it
:type pose: :class:`Transformation`
:param dh: (d, theta, r, alpha)
See `DH <https://en.wikipedia.org/wiki/Field_of_view>`_
:param args: Additional arguments, passed down to the numpy array object
:param kwargs: Additional keyword arguments, passed down to the numpy array object
"""
cls = np.eye(4).view(cls)
cls.timestamp = timestamp
if matrix is not None:
cls.matrix = matrix
elif rotation_matrix is not None:
cls[0:3, 0:3] = rotation_matrix
cls.position = position
elif dh is not None:
a = dh[0]
alpha = dh[1]
d = dh[2]
theta = dh[3]
cls[0, 0] = np.cos(theta)
cls[0, 1] = -np.sin(theta) * np.cos(alpha)
cls[0, 2] = np.sin(theta) * np.sin(alpha)
cls[0, 3] = a * np.cos(theta)
cls[1, 0] = np.sin(theta)
cls[1, 1] = np.cos(theta) * np.cos(alpha)
cls[1, 2] = -np.cos(theta) * np.sin(alpha)
cls[1, 3] = a * np.sin(theta)
cls[2, 1] = np.sin(alpha)
cls[2, 2] = np.cos(alpha)
cls[2, 3] = d
elif euler is not None:
cls.orientation_euler = euler
cls.position = position
elif pos_theta is not None:
cls.pos_theta = pos_theta
if pose_theta_covariance_array is not None:
cls.pose_theta_covariance_array = pose_theta_covariance_array
elif pose is not None:
cls.pose = pose
elif geometry_msgs_transform is not None:
cls.geometry_msgs_transform = geometry_msgs_transform
elif pose_stamped is not None:
cls.pose = pose_stamped.pose
cls.timestamp = pose_stamped.header.stamp
elif pose_with_covariance_stamped is not None:
cls.pose = pose_with_covariance_stamped.pose.pose
cls.pose_covariance = pose_with_covariance_stamped.pose.covariance
cls.timestamp = pose_with_covariance_stamped.header.stamp
else:
cls.position = position
cls.quaternion = quaternion
return cls
@property
def matrix(self) -> np.ndarray:
"""
Representation of the transformation in quaternion in 4x4 transformation matrix
"""
return np.array(self)
@matrix.setter
def matrix(self, matrix: np.array):
self[0:4, 0:4] = matrix
@property
def position(self) -> np.ndarray:
"""
Representation of the position of the transformation in quaternion in form [x y z]
"""
return np.array(self[0:3, 3])
@position.setter
def position(self, position: [float]):
self[0:3, 3] = position
@property
def norm_squared(self) -> float:
position = self.position
return position[0] ** 2 + position[1] ** 2 + position[2] ** 2
@property
def quaternion(self) -> np.ndarray:
"""
Representation of the rotation of the transformation in quaternion in form [x y z w]
"""
r = R.from_matrix(self[0:3, 0:3])
return r.as_quat()
@quaternion.setter
def quaternion(self, quat: [float]):
r = R.from_quat(quat)
self[0:3, 0:3] = np.reshape(r.as_matrix(), [3, 3])
@property
def orientation_euler(self, orientation="ZYX") -> np.ndarray:
"""
Representation of the rotation of the transformation in euler coordinates [yaw, pitch, roll]
"""
e = R.from_matrix(self[0:3, 0:3])
return e.as_euler(orientation, degrees=False)
@orientation_euler.setter
def orientation_euler(self, euler_array, sequence="ZYX"):
"""
Representation of the rotation of the transformation in euler coordinates [yaw, pitch, roll]
"""
r = R.from_euler(seq=sequence, angles=euler_array, degrees=False)
self.quaternion = r.as_quat()
@property
def rotation_matrix(self) -> np.array:
"""
Representation of the rotation of the transformation in 3x3 rotation matrix
"""
return np.array(self[0:3, 0:3])
@rotation_matrix.setter
def rotation_matrix(self, rotation_matrix):
self[0:3, 0:3] = rotation_matrix
@property
def pos_theta(self):
"""
Representation of the transformation in the form [x, y, yaw]
"""
return np.array([self.position[0], self.position[1], self.orientation_euler[0]])
@pos_theta.setter
def pos_theta(self, pos_theta: [float]):
self.position = (pos_theta[0], pos_theta[1], 0.0)
self.orientation_euler = [pos_theta[2], 0, 0]
@property
def pose_theta_covariance_array(self) -> np.array:
return self.pose_covariance_array[(0, 1, 5), :][:, (0, 1, 5)]
@pose_theta_covariance_array.setter
def pose_theta_covariance_array(self, pose_theta_covariance_array):
full_matrix = np.insert(np.insert(pose_theta_covariance_array, [2, 2, 2], 0, axis=1), [2, 2, 2], 0, axis=0)
self.pose_covariance = full_matrix.flatten()
@property
def pose(self) -> Pose:
"""
Representation of the transformation in the ros Pose format
"""
position = self.position
quaternion = self.quaternion
p = Pose()
p.position.x = position[0]
p.position.y = position[1]
p.position.z = position[2]
p.orientation.x = quaternion[0]
p.orientation.y = quaternion[1]
p.orientation.z = quaternion[2]
p.orientation.w = quaternion[3]
return p
@pose.setter
def pose(self, pose: Pose):
self.position = [pose.position.x, pose.position.y, pose.position.z]
self.quaternion = [pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w]
@property
def geometry_msgs_transform(self) -> GeometryMsgsTransform:
"""
Representation of the transformation in the ros geometry_msgs Transform format
"""
position = self.position
quaternion = self.quaternion
p = GeometryMsgsTransform()
p.translation.x = position[0]
p.translation.y = position[1]
p.translation.z = position[2]
p.rotation.x = quaternion[0]
p.rotation.y = quaternion[1]
p.rotation.z = quaternion[2]
p.rotation.w = quaternion[3]
return p
@geometry_msgs_transform.setter
def geometry_msgs_transform(self, geometry_msgs_transform: GeometryMsgsTransform):
self.position = [geometry_msgs_transform.translation.x, geometry_msgs_transform.translation.y, geometry_msgs_transform.translation.z]
self.quaternion = [
geometry_msgs_transform.rotation.x,
geometry_msgs_transform.rotation.y,
geometry_msgs_transform.rotation.z,
geometry_msgs_transform.rotation.w,
]
@property
def pose_stamped(self) -> PoseStamped:
"""
The transformation represented in the PoseStamped format
"""
t = PoseStamped()
t.header.stamp = rospy.Time.now()
t.header.frame_id = "world"
t.pose = self.pose
return t
@property
def pose_covariance_array(self) -> np.array:
return np.reshape(self.pose_covariance, (6, 6))
@property
def pose_with_covariance_stamped(self) -> PoseWithCovarianceStamped:
"""
The transformation represented in the PoseStamped format
"""
p = PoseWithCovarianceStamped()
p.header.stamp = rospy.Time.now()
p.header.frame_id = "world"
p.pose.pose = self.pose
p.pose.covariance = list(self.pose_covariance)
return p
[docs] @staticmethod
def distance(t1, t2) -> float:
"""
Returns the distance between two transformations
:param t1: transformation 1
:type t1: Transformation
:param t2: transformation 2
:type t1: Transformation
:return: The distance in meters
"""
return np.linalg.norm(t1[0:3, 3] - t2[0:3, 3])
[docs] @staticmethod
def get_euler_from_quaternion(quaternion, seq="ZYX"):
"""
Get the quaternion representation of euler angle rotations
:param euler_array: array of 3 angles for rotation (yaw, pitch, roll)
:param sequence: order and type of rotation, see intrinsic vs extrinsic for capital and small case letter
:return: quaternion in the form of [x y z w]
"""
r = R.from_quat(quaternion)
return r.as_euler(seq=seq)
[docs] @staticmethod
def get_quaternion_from_axis_angle(vector, angle):
"""
Gives the quaternion representation of axis-angle rotation
:param vector: vector around which the rotation takes place
:param angle: angle by which is rotated around the vector
:return: quaternion in the form of [x y z w]
"""
r = R.from_rotvec(np.array(vector).reshape((1, 3)) * (angle / np.linalg.norm(vector)))
return r.as_quat()
[docs] @staticmethod
def get_axis_angle_from_quaternion(quaternion):
"""
Gives the axis-angle representation of rotation from a quaternion
:param quaternion: quaternion in the form of [x y z w]
:return: angle and vector
"""
r = R.from_quat(quaternion)
angle = np.linalg.norm(r.as_rotvec())
if angle:
vector = r.as_rotvec() / angle
else:
vector = [0, 0, 1]
return angle, vector
[docs] @staticmethod
def transformation_weighted_average(t_start, t_end, ratio):
"""
Interpolates between two transforms. Inclination is based on a ratio.
Ratio = 0, t_start shall be returned
Ratio = 1, t_end shall be returned
:param t_start: start H-transform
:param t_end: end H-transform
:param ratio: a number between 0 and 1
:return: weighted average H-transform
"""
average = Transformation()
delta_position = t_end.position - t_start.position
average.position = t_start.position + (delta_position * ratio)
rots = R.from_quat([t_start.quaternion, t_end.quaternion])
s = Slerp([0, 1], rots)
r_average = s([ratio])[0]
average.quaternion = r_average.as_quat()
return average