Spaces:
Runtime error
Runtime error
| import functools | |
| import torch | |
| import torch.nn.functional as F | |
| ########################Implementations of the functions in the PyTorch3D######################## | |
| def quaternion_to_matrix(quaternions): | |
| r, i, j, k = torch.unbind(quaternions, -1) | |
| two_s = 2.0 / (quaternions * quaternions).sum(-1) | |
| o = torch.stack( | |
| ( | |
| 1 - two_s * (j * j + k * k), | |
| two_s * (i * j - k * r), | |
| two_s * (i * k + j * r), | |
| two_s * (i * j + k * r), | |
| 1 - two_s * (i * i + k * k), | |
| two_s * (j * k - i * r), | |
| two_s * (i * k - j * r), | |
| two_s * (j * k + i * r), | |
| 1 - two_s * (i * i + j * j), | |
| ), | |
| -1, | |
| ) | |
| return o.reshape(quaternions.shape[:-1] + (3, 3)) | |
| def _copysign(a, b): | |
| signs_differ = (a < 0) != (b < 0) | |
| return torch.where(signs_differ, -a, a) | |
| def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor: | |
| ret = torch.zeros_like(x) | |
| positive_mask = x > 0 | |
| ret[positive_mask] = torch.sqrt(x[positive_mask]) | |
| return ret | |
| def matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor: | |
| if matrix.size(-1) != 3 or matrix.size(-2) != 3: | |
| raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.") | |
| batch_dim = matrix.shape[:-2] | |
| m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind( | |
| matrix.reshape(*batch_dim, 9), dim=-1 | |
| ) | |
| q_abs = _sqrt_positive_part( | |
| torch.stack( | |
| [ | |
| 1.0 + m00 + m11 + m22, | |
| 1.0 + m00 - m11 - m22, | |
| 1.0 - m00 + m11 - m22, | |
| 1.0 - m00 - m11 + m22, | |
| ], | |
| dim=-1, | |
| ) | |
| ) | |
| quat_by_rijk = torch.stack( | |
| [ | |
| torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1), | |
| torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1), | |
| torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1), | |
| torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1), | |
| ], | |
| dim=-2, | |
| ) | |
| quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(q_abs.new_tensor(0.1))) | |
| return quat_candidates[ | |
| F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, : | |
| ].reshape(*batch_dim, 4) | |
| def _axis_angle_rotation(axis: str, angle): | |
| cos = torch.cos(angle) | |
| sin = torch.sin(angle) | |
| one = torch.ones_like(angle) | |
| zero = torch.zeros_like(angle) | |
| if axis == "X": | |
| R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos) | |
| if axis == "Y": | |
| R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos) | |
| if axis == "Z": | |
| R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one) | |
| return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3)) | |
| def euler_angles_to_matrix(euler_angles, convention: str): | |
| if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3: | |
| raise ValueError("Invalid input euler angles.") | |
| if len(convention) != 3: | |
| raise ValueError("Convention must have 3 letters.") | |
| if convention[1] in (convention[0], convention[2]): | |
| raise ValueError(f"Invalid convention {convention}.") | |
| for letter in convention: | |
| if letter not in ("X", "Y", "Z"): | |
| raise ValueError(f"Invalid letter {letter} in convention string.") | |
| matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1)) | |
| return functools.reduce(torch.matmul, matrices) | |
| def _angle_from_tan( | |
| axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool | |
| ): | |
| i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis] | |
| if horizontal: | |
| i2, i1 = i1, i2 | |
| even = (axis + other_axis) in ["XY", "YZ", "ZX"] | |
| if horizontal == even: | |
| return torch.atan2(data[..., i1], data[..., i2]) | |
| if tait_bryan: | |
| return torch.atan2(-data[..., i2], data[..., i1]) | |
| return torch.atan2(data[..., i2], -data[..., i1]) | |
| def _index_from_letter(letter: str): | |
| if letter == "X": | |
| return 0 | |
| if letter == "Y": | |
| return 1 | |
| if letter == "Z": | |
| return 2 | |
| def matrix_to_euler_angles(matrix, convention: str): | |
| if len(convention) != 3: | |
| raise ValueError("Convention must have 3 letters.") | |
| if convention[1] in (convention[0], convention[2]): | |
| raise ValueError(f"Invalid convention {convention}.") | |
| for letter in convention: | |
| if letter not in ("X", "Y", "Z"): | |
| raise ValueError(f"Invalid letter {letter} in convention string.") | |
| if matrix.size(-1) != 3 or matrix.size(-2) != 3: | |
| raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.") | |
| i0 = _index_from_letter(convention[0]) | |
| i2 = _index_from_letter(convention[2]) | |
| tait_bryan = i0 != i2 | |
| if tait_bryan: | |
| central_angle = torch.asin( | |
| matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0) | |
| ) | |
| else: | |
| central_angle = torch.acos(matrix[..., i0, i0]) | |
| o = ( | |
| _angle_from_tan( | |
| convention[0], convention[1], matrix[..., i2], False, tait_bryan | |
| ), | |
| central_angle, | |
| _angle_from_tan( | |
| convention[2], convention[1], matrix[..., i0, :], True, tait_bryan | |
| ), | |
| ) | |
| return torch.stack(o, -1) | |
| def standardize_quaternion(quaternions): | |
| return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions) | |
| def quaternion_raw_multiply(a, b): | |
| aw, ax, ay, az = torch.unbind(a, -1) | |
| bw, bx, by, bz = torch.unbind(b, -1) | |
| ow = aw * bw - ax * bx - ay * by - az * bz | |
| ox = aw * bx + ax * bw + ay * bz - az * by | |
| oy = aw * by - ax * bz + ay * bw + az * bx | |
| oz = aw * bz + ax * by - ay * bx + az * bw | |
| return torch.stack((ow, ox, oy, oz), -1) | |
| def quaternion_multiply(a, b): | |
| ab = quaternion_raw_multiply(a, b) | |
| return standardize_quaternion(ab) | |
| def quaternion_invert(quaternion): | |
| return quaternion * quaternion.new_tensor([1, -1, -1, -1]) | |
| def quaternion_apply(quaternion, point): | |
| if point.size(-1) != 3: | |
| raise ValueError(f"Points are not in 3D, f{point.shape}.") | |
| real_parts = point.new_zeros(point.shape[:-1] + (1,)) | |
| point_as_quaternion = torch.cat((real_parts, point), -1) | |
| out = quaternion_raw_multiply( | |
| quaternion_raw_multiply(quaternion, point_as_quaternion), | |
| quaternion_invert(quaternion), | |
| ) | |
| return out[..., 1:] | |
| def axis_angle_to_matrix(axis_angle): | |
| return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle)) | |
| def matrix_to_axis_angle(matrix): | |
| return quaternion_to_axis_angle(matrix_to_quaternion(matrix)) | |
| def axis_angle_to_quaternion(axis_angle): | |
| angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True) | |
| half_angles = 0.5 * angles | |
| eps = 1e-6 | |
| small_angles = angles.abs() < eps | |
| sin_half_angles_over_angles = torch.empty_like(angles) | |
| sin_half_angles_over_angles[~small_angles] = ( | |
| torch.sin(half_angles[~small_angles]) / angles[~small_angles] | |
| ) | |
| # for x small, sin(x/2) is about x/2 - (x/2)^3/6 | |
| # so sin(x/2)/x is about 1/2 - (x*x)/48 | |
| sin_half_angles_over_angles[small_angles] = ( | |
| 0.5 - (angles[small_angles] * angles[small_angles]) / 48 | |
| ) | |
| quaternions = torch.cat( | |
| [torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1 | |
| ) | |
| return quaternions | |
| def quaternion_to_axis_angle(quaternions): | |
| norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True) | |
| half_angles = torch.atan2(norms, quaternions[..., :1]) | |
| angles = 2 * half_angles | |
| eps = 1e-6 | |
| small_angles = angles.abs() < eps | |
| sin_half_angles_over_angles = torch.empty_like(angles) | |
| sin_half_angles_over_angles[~small_angles] = ( | |
| torch.sin(half_angles[~small_angles]) / angles[~small_angles] | |
| ) | |
| # for x small, sin(x/2) is about x/2 - (x/2)^3/6 | |
| # so sin(x/2)/x is about 1/2 - (x*x)/48 | |
| sin_half_angles_over_angles[small_angles] = ( | |
| 0.5 - (angles[small_angles] * angles[small_angles]) / 48 | |
| ) | |
| return quaternions[..., 1:] / sin_half_angles_over_angles | |
| def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor: | |
| a1, a2 = d6[..., :3], d6[..., 3:] | |
| b1 = F.normalize(a1, dim=-1) | |
| b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1 | |
| b2 = F.normalize(b2, dim=-1) | |
| b3 = torch.cross(b1, b2, dim=-1) | |
| return torch.stack((b1, b2, b3), dim=-2) | |
| def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor: | |
| return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6) | |
| import numpy as np | |
| def rotation_6d_to_matrix_np(d6: np.ndarray) -> np.ndarray: | |
| a1, a2 = d6[..., :3], d6[..., 3:] | |
| b1 = a1 / np.linalg.norm(a1, axis=-1, keepdims=True) | |
| b2 = a2 - np.sum(b1 * a2, axis=-1, keepdims=True) * b1 | |
| b2 = b2 / np.linalg.norm(b2, axis=-1, keepdims=True) | |
| b3 = np.cross(b1, b2, axis=-1) | |
| return np.stack((b1, b2, b3), axis=-2) | |
| def matrix_to_rotation_6d_np(matrix: np.ndarray) -> np.ndarray: | |
| return matrix[..., :2, :].reshape(*matrix.shape[:-2], 6) | |
| ########################Implementations of the functions in the PyTorch3D######################## | |
| from einops import rearrange | |
| def transform_points(x, mat): | |
| shape = x.shape | |
| x = rearrange(x, 'b t (j c) -> b (t j) c', c=3) # B x N x 3 | |
| x = torch.einsum('bpc,bck->bpk', mat[:, :3, :3], x.permute(0, 2, 1)) # B x 3 x N N x B x 3 | |
| x = x.permute(2, 0, 1) + mat[:, :3, 3] | |
| x = x.permute(1, 0, 2) | |
| x = x.reshape(shape) | |
| return x | |
| def transform_points_numpy(x, mat): | |
| shape = x.shape | |
| x = x.reshape(shape[0], -1, 3) # b x (t*j) x c | |
| x = np.einsum('bpc,bck->bpk', mat[:, :3, :3], np.transpose(x, (0, 2, 1))) | |
| x = np.transpose(x, (2, 0, 1)) + mat[:, :3, 3] | |
| x = np.transpose(x, (1, 0, 2)) | |
| x = x.reshape(shape) | |
| return x | |
| def zup_to_yup(coord): | |
| if len(coord.shape) > 1: | |
| coord = coord[..., [0, 2, 1]] | |
| coord[..., 2] *= -1 | |
| else: | |
| coord = coord[[0, 2, 1]] | |
| coord[2] *= -1 | |
| return coord | |
| def rigid_transform_3D(A, B, scale=False): | |
| assert len(A) == len(B) | |
| N = A.shape[0] # total points | |
| centroid_A = np.mean(A, axis=0) | |
| centroid_B = np.mean(B, axis=0) | |
| # center the points | |
| AA = A - np.tile(centroid_A, (N, 1)) | |
| BB = B - np.tile(centroid_B, (N, 1)) | |
| if scale: | |
| H = np.transpose(BB) * AA / N | |
| else: | |
| H = np.transpose(BB) * AA | |
| U, S, Vt = np.linalg.svd(H) | |
| R = Vt.T * U.T | |
| # special reflection case | |
| if np.linalg.det(R) < 0: | |
| Vt[2, :] *= -1 | |
| R = Vt.T * U.T | |
| if scale: | |
| varA = np.var(A, axis=0).sum() | |
| c = 1 / (1 / varA * np.sum(S)) # scale factor | |
| t = -R * (centroid_B.T * c) + centroid_A.T | |
| else: | |
| c = 1 | |
| t = -R * centroid_B.T + centroid_A.T | |
| return c, R, t | |
| ##################joints blending###################### | |
| def slerp(q0: torch.Tensor, q1: torch.Tensor, t: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Spherical linear interpolation between two quaternions. | |
| Args: | |
| q0: (..., 4) tensor of quaternions | |
| q1: (..., 4) tensor of quaternions | |
| t: (..., 1) tensor of interpolation coefficients | |
| Returns: | |
| (..., 4) tensor of quaternions | |
| """ | |
| cos_half_theta = torch.sum(q0 * q1, dim=-1) | |
| neg_mask = cos_half_theta < 0 | |
| q1 = q1.clone() | |
| q1[neg_mask] = -q1[neg_mask] | |
| cos_half_theta = torch.abs(cos_half_theta) | |
| cos_half_theta = torch.unsqueeze(cos_half_theta, dim=-1) | |
| half_theta = torch.acos(cos_half_theta) | |
| sin_half_theta = torch.sqrt(1.0 - cos_half_theta * cos_half_theta) | |
| ratioA = torch.sin((1 - t) * half_theta) / sin_half_theta | |
| ratioB = torch.sin(t * half_theta) / sin_half_theta | |
| new_q = ratioA * q0 + ratioB * q1 | |
| new_q = torch.where(torch.abs(sin_half_theta) < 0.001, 0.5 * q0 + 0.5 * q1, new_q) | |
| new_q = torch.where(torch.abs(cos_half_theta) >= 1, q0, new_q) | |
| return new_q | |
| def blend_joint_rot_batch(body_pose_1, body_pose_2, t): | |
| """ | |
| Blend two batches of joint rotations using spherical linear interpolation. | |
| Args: | |
| body_pose_1: (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations | |
| body_pose_2: (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations | |
| t: (batch_size, 1, num_joints, 1) tensor of interpolation coefficients | |
| Returns: | |
| (batch_size, sequence_length, num_joints, 3) tensor of axis-angle rotations | |
| """ | |
| shape = body_pose_1.shape | |
| if len(shape) == 3: | |
| body_pose_1 = body_pose_1.reshape(shape[0], shape[1], -1, 3) | |
| body_pose_2 = body_pose_2.reshape(shape[0], shape[1], -1, 3) | |
| ret = quaternion_to_axis_angle( | |
| slerp(axis_angle_to_quaternion(body_pose_1), axis_angle_to_quaternion(body_pose_2), t) | |
| ) | |
| if len(shape) == 3: | |
| ret = ret.reshape(shape) | |
| return ret |