Spaces:
Sleeping
Sleeping
# ---------------------------------------------------------------------------------------------- | |
# METRO (https://github.com/microsoft/MeshTransformer) | |
# Copyright (c) Microsoft Corporation. All Rights Reserved [see https://github.com/microsoft/MeshTransformer/blob/main/LICENSE for details] | |
# Licensed under the MIT license. | |
# ---------------------------------------------------------------------------------------------- | |
""" | |
Useful geometric operations, e.g. Orthographic projection and a differentiable Rodrigues formula | |
Parts of the code are taken from https://github.com/MandyMo/pytorch_HMR | |
""" | |
import torch | |
import torch.nn.functional as F | |
def rodrigues(theta): | |
"""Convert axis-angle representation to rotation matrix. | |
Args: | |
theta: size = [B, 3] | |
Returns: | |
Rotation matrix corresponding to the quaternion -- size = [B, 3, 3] | |
""" | |
l1norm = torch.norm(theta + 1e-8, p = 2, dim = 1) | |
angle = torch.unsqueeze(l1norm, -1) | |
normalized = torch.div(theta, angle) | |
angle = angle * 0.5 | |
v_cos = torch.cos(angle) | |
v_sin = torch.sin(angle) | |
quat = torch.cat([v_cos, v_sin * normalized], dim = 1) | |
return quat2mat(quat) | |
def quat2mat(quat): | |
"""Convert quaternion coefficients to rotation matrix. | |
Args: | |
quat: size = [B, 4] 4 <===>(w, x, y, z) | |
Returns: | |
Rotation matrix corresponding to the quaternion -- size = [B, 3, 3] | |
""" | |
norm_quat = quat | |
norm_quat = norm_quat/norm_quat.norm(p=2, dim=1, keepdim=True) | |
w, x, y, z = norm_quat[:,0], norm_quat[:,1], norm_quat[:,2], norm_quat[:,3] | |
B = quat.size(0) | |
w2, x2, y2, z2 = w.pow(2), x.pow(2), y.pow(2), z.pow(2) | |
wx, wy, wz = w*x, w*y, w*z | |
xy, xz, yz = x*y, x*z, y*z | |
rotMat = torch.stack([w2 + x2 - y2 - z2, 2*xy - 2*wz, 2*wy + 2*xz, | |
2*wz + 2*xy, w2 - x2 + y2 - z2, 2*yz - 2*wx, | |
2*xz - 2*wy, 2*wx + 2*yz, w2 - x2 - y2 + z2], dim=1).view(B, 3, 3) | |
return rotMat | |
def orthographic_projection(X, camera): | |
"""Perform orthographic projection of 3D points X using the camera parameters | |
Args: | |
X: size = [B, N, 3] | |
camera: size = [B, 3] | |
Returns: | |
Projected 2D points -- size = [B, N, 2] | |
""" | |
camera = camera.view(-1, 1, 3) | |
X_trans = X[:, :, :2] + camera[:, :, 1:] | |
shape = X_trans.shape | |
X_2d = (camera[:, :, 0] * X_trans.view(shape[0], -1)).view(shape) | |
return X_2d | |
def orthographic_projection_reshape(X, camera): | |
"""Perform orthographic projection of 3D points X using the camera parameters | |
Args: | |
X: size = [B, N, 3] | |
camera: size = [B, 3] | |
Returns: | |
Projected 2D points -- size = [B, N, 2] | |
""" | |
camera = camera.reshape(-1, 1, 3) | |
X_trans = X[:, :, :2] + camera[:, :, 1:] | |
shape = X_trans.shape | |
X_2d = (camera[:, :, 0] * X_trans.reshape(shape[0], -1)).reshape(shape) | |
return X_2d | |
def orthographic_projection_reshape(X, camera): | |
"""Perform orthographic projection of 3D points X using the camera parameters | |
Args: | |
X: size = [B, N, 3] | |
camera: size = [B, 3] | |
Returns: | |
Projected 2D points -- size = [B, N, 2] | |
""" | |
camera = camera.reshape(-1, 1, 3) | |
X_trans = X[:, :, :2] + camera[:, :, 1:] | |
shape = X_trans.shape | |
X_2d = (camera[:, :, 0] * X_trans.reshape(shape[0], -1)).reshape(shape) | |
return X_2d | |
def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor: | |
""" | |
Return a tensor where each element has the absolute value taken from the, | |
corresponding element of a, with sign taken from the corresponding | |
element of b. This is like the standard copysign floating-point operation, | |
but is not careful about negative 0 and NaN. | |
Args: | |
a: source tensor. | |
b: tensor whose signs will be used, of the same shape as a. | |
Returns: | |
Tensor of the same shape as a with the signs of b. | |
""" | |
signs_differ = (a < 0) != (b < 0) | |
return torch.where(signs_differ, -a, a) | |
def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor: | |
""" | |
Returns torch.sqrt(torch.max(0, x)) | |
but with a zero subgradient where x is 0. | |
""" | |
ret = torch.zeros_like(x) | |
positive_mask = x > 0 | |
ret[positive_mask] = torch.sqrt(x[positive_mask]) | |
return ret | |
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor: | |
""" | |
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix | |
using Gram--Schmidt orthogonalization per Section B of [1]. | |
Args: | |
d6: 6D rotation representation, of size (*, 6) | |
Returns: | |
batch of rotation matrices of size (*, 3, 3) | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
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: | |
""" | |
Converts rotation matrices to 6D rotation representation by Zhou et al. [1] | |
by dropping the last row. Note that 6D representation is not unique. | |
Args: | |
matrix: batch of rotation matrices of size (*, 3, 3) | |
Returns: | |
6D rotation representation, of size (*, 6) | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
batch_dim = matrix.size()[:-2] | |
return matrix[..., :2, :].clone().reshape(batch_dim + (6,)) | |
def axis_angle_to_quaternion(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to quaternions. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
quaternions with real part first, as tensor of shape (..., 4). | |
""" | |
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True) | |
half_angles = angles * 0.5 | |
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: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as quaternions to axis/angle. | |
Args: | |
quaternions: quaternions with real part first, | |
as tensor of shape (..., 4). | |
Returns: | |
Rotations given as a vector in axis angle form, as a tensor | |
of shape (..., 3), where the magnitude is the angle | |
turned anticlockwise in radians around the vector's | |
direction. | |
""" | |
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 quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as quaternions to rotation matrices. | |
Args: | |
quaternions: quaternions with real part first, | |
as tensor of shape (..., 4). | |
Returns: | |
Rotation matrices as tensor of shape (..., 3, 3). | |
""" | |
r, i, j, k = torch.unbind(quaternions, -1) | |
# pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`. | |
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 matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as rotation matrices to quaternions. | |
Args: | |
matrix: Rotation matrices as tensor of shape (..., 3, 3). | |
Returns: | |
quaternions with real part first, as tensor of shape (..., 4). | |
""" | |
if matrix.size(-1) != 3 or matrix.size(-2) != 3: | |
raise ValueError(f"Invalid rotation matrix shape {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, | |
) | |
) | |
# we produce the desired quaternion multiplied by each of r, i, j, k | |
quat_by_rijk = torch.stack( | |
[ | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1), | |
], | |
dim=-2, | |
) | |
# We floor here at 0.1 but the exact level is not important; if q_abs is small, | |
# the candidate won't be picked. | |
flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device) | |
quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr)) | |
# if not for numerical problems, quat_candidates[i] should be same (up to a sign), | |
# forall i; we pick the best-conditioned one (with the largest denominator) | |
return quat_candidates[ | |
F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, : | |
].reshape(batch_dim + (4,)) | |
def axis_angle_to_matrix(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to rotation matrices. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
Rotation matrices as tensor of shape (..., 3, 3). | |
""" | |
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle)) | |
def matrix_to_axis_angle(matrix: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as rotation matrices to axis/angle. | |
Args: | |
matrix: Rotation matrices as tensor of shape (..., 3, 3). | |
Returns: | |
Rotations given as a vector in axis angle form, as a tensor | |
of shape (..., 3), where the magnitude is the angle | |
turned anticlockwise in radians around the vector's | |
direction. | |
""" | |
return quaternion_to_axis_angle(matrix_to_quaternion(matrix)) | |
def axis_angle_to_rotation_6d(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to rotation matrices. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
6D rotation representation, of size (*, 6) | |
""" | |
return matrix_to_rotation_6d(axis_angle_to_matrix(axis_angle)) | |
def rotation_6d_to_axis_angle(d6): | |
""" | |
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix | |
using Gram--Schmidt orthogonalization per Section B of [1]. | |
Args: | |
d6: 6D rotation representation, of size (*, 6) | |
Returns: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
return matrix_to_axis_angle(rotation_6d_to_matrix(d6)) | |
def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor: | |
""" | |
Return a tensor where each element has the absolute value taken from the, | |
corresponding element of a, with sign taken from the corresponding | |
element of b. This is like the standard copysign floating-point operation, | |
but is not careful about negative 0 and NaN. | |
Args: | |
a: source tensor. | |
b: tensor whose signs will be used, of the same shape as a. | |
Returns: | |
Tensor of the same shape as a with the signs of b. | |
""" | |
signs_differ = (a < 0) != (b < 0) | |
return torch.where(signs_differ, -a, a) | |
def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor: | |
""" | |
Returns torch.sqrt(torch.max(0, x)) | |
but with a zero subgradient where x is 0. | |
""" | |
ret = torch.zeros_like(x) | |
positive_mask = x > 0 | |
ret[positive_mask] = torch.sqrt(x[positive_mask]) | |
return ret | |
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor: | |
""" | |
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix | |
using Gram--Schmidt orthogonalization per Section B of [1]. | |
Args: | |
d6: 6D rotation representation, of size (*, 6) | |
Returns: | |
batch of rotation matrices of size (*, 3, 3) | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
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: | |
""" | |
Converts rotation matrices to 6D rotation representation by Zhou et al. [1] | |
by dropping the last row. Note that 6D representation is not unique. | |
Args: | |
matrix: batch of rotation matrices of size (*, 3, 3) | |
Returns: | |
6D rotation representation, of size (*, 6) | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
batch_dim = matrix.size()[:-2] | |
return matrix[..., :2, :].clone().reshape(batch_dim + (6,)) | |
def axis_angle_to_quaternion(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to quaternions. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
quaternions with real part first, as tensor of shape (..., 4). | |
""" | |
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True) | |
half_angles = angles * 0.5 | |
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: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as quaternions to axis/angle. | |
Args: | |
quaternions: quaternions with real part first, | |
as tensor of shape (..., 4). | |
Returns: | |
Rotations given as a vector in axis angle form, as a tensor | |
of shape (..., 3), where the magnitude is the angle | |
turned anticlockwise in radians around the vector's | |
direction. | |
""" | |
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 quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as quaternions to rotation matrices. | |
Args: | |
quaternions: quaternions with real part first, | |
as tensor of shape (..., 4). | |
Returns: | |
Rotation matrices as tensor of shape (..., 3, 3). | |
""" | |
r, i, j, k = torch.unbind(quaternions, -1) | |
# pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`. | |
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 matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as rotation matrices to quaternions. | |
Args: | |
matrix: Rotation matrices as tensor of shape (..., 3, 3). | |
Returns: | |
quaternions with real part first, as tensor of shape (..., 4). | |
""" | |
if matrix.size(-1) != 3 or matrix.size(-2) != 3: | |
raise ValueError(f"Invalid rotation matrix shape {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, | |
) | |
) | |
# we produce the desired quaternion multiplied by each of r, i, j, k | |
quat_by_rijk = torch.stack( | |
[ | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1), | |
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and | |
# `int`. | |
torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1), | |
], | |
dim=-2, | |
) | |
# We floor here at 0.1 but the exact level is not important; if q_abs is small, | |
# the candidate won't be picked. | |
flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device) | |
quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr)) | |
# if not for numerical problems, quat_candidates[i] should be same (up to a sign), | |
# forall i; we pick the best-conditioned one (with the largest denominator) | |
return quat_candidates[ | |
F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, : | |
].reshape(batch_dim + (4,)) | |
def axis_angle_to_matrix(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to rotation matrices. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
Rotation matrices as tensor of shape (..., 3, 3). | |
""" | |
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle)) | |
def matrix_to_axis_angle(matrix: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as rotation matrices to axis/angle. | |
Args: | |
matrix: Rotation matrices as tensor of shape (..., 3, 3). | |
Returns: | |
Rotations given as a vector in axis angle form, as a tensor | |
of shape (..., 3), where the magnitude is the angle | |
turned anticlockwise in radians around the vector's | |
direction. | |
""" | |
return quaternion_to_axis_angle(matrix_to_quaternion(matrix)) | |
def axis_angle_to_rotation_6d(axis_angle: torch.Tensor) -> torch.Tensor: | |
""" | |
Convert rotations given as axis/angle to rotation matrices. | |
Args: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
Returns: | |
6D rotation representation, of size (*, 6) | |
""" | |
return matrix_to_rotation_6d(axis_angle_to_matrix(axis_angle)) | |
def rotation_6d_to_axis_angle(d6): | |
""" | |
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix | |
using Gram--Schmidt orthogonalization per Section B of [1]. | |
Args: | |
d6: 6D rotation representation, of size (*, 6) | |
Returns: | |
axis_angle: Rotations given as a vector in axis angle form, | |
as a tensor of shape (..., 3), where the magnitude is | |
the angle turned anticlockwise in radians around the | |
vector's direction. | |
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H. | |
On the Continuity of Rotation Representations in Neural Networks. | |
IEEE Conference on Computer Vision and Pattern Recognition, 2019. | |
Retrieved from http://arxiv.org/abs/1812.07035 | |
""" | |
return matrix_to_axis_angle(rotation_6d_to_matrix(d6)) |