Files
wehub-resource-sync 3a2c66702c
Tests on CPU (scheduled) / check-skip (push) Has been cancelled
Tests on CPU (scheduled) / pre-tests (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-ubuntu (float32) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-ubuntu (float64) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float64, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.11, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float64, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.12, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.13, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-windows (3.13, float64, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.11, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.11, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.12, float32, 2.5.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.12, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / tests-cpu-mac (3.13, float32, 2.9.1) (push) Has been cancelled
Tests on CPU (scheduled) / coverage (push) Has been cancelled
Tests on CPU (scheduled) / typing (push) Has been cancelled
Tests on CPU (scheduled) / tutorials (push) Has been cancelled
Tests on CPU (scheduled) / docs (push) Has been cancelled
Lint / TOML Format (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:27 +08:00

1370 lines
64 KiB
Python

# LICENSE HEADER MANAGED BY add-license-header
#
# Copyright 2018 Kornia Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import sys
from functools import partial
import numpy as np
import pytest
import torch
import kornia
from kornia.core._compat import torch_version
from kornia.core.ops import eye_like
from kornia.geometry.conversions import (
ARKitQTVecs_to_ColmapQTVecs,
Rt_to_matrix4x4,
axis_angle_to_rotation_matrix,
camtoworld_graphics_to_vision_4x4,
camtoworld_graphics_to_vision_Rt,
camtoworld_to_worldtocam_Rt,
camtoworld_vision_to_graphics_4x4,
camtoworld_vision_to_graphics_Rt,
euler_from_quaternion,
matrix4x4_to_Rt,
quaternion_from_euler,
worldtocam_to_camtoworld_Rt,
)
from kornia.geometry.quaternion import Quaternion
from testing.base import BaseTester, assert_close
@pytest.fixture()
def atol(device, dtype):
"""Lower tolerance for cuda-float16 only."""
if "cuda" in device.type and dtype == torch.float16:
return 1.0e-3
return 1.0e-4
@pytest.fixture()
def rtol(device, dtype):
"""Lower tolerance for cuda-float16 only."""
if "cuda" in device.type and dtype == torch.float16:
return 1.0e-3
return 1.0e-4
class TestAngleAxisToQuaternion(BaseTester):
# based on:
# https://github.com/ceres-solver/ceres-solver/blob/master/internal/ceres/rotation_test.cc#L271
def test_smoke(self, device, dtype):
axis_angle = torch.zeros(3, dtype=dtype, device=device)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
assert quaternion.shape == (4,)
@pytest.mark.parametrize("batch_size", (1, 3, 8))
def test_smoke_batch(self, batch_size, device, dtype):
axis_angle = torch.zeros(batch_size, 3, device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
assert quaternion.shape == (batch_size, 4)
def test_zero_angle(self, device, dtype, atol, rtol):
axis_angle = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_small_angle_x(self, device, dtype, atol, rtol):
theta = 1.0e-2
axis_angle = torch.tensor((theta, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((np.cos(theta / 2.0), np.sin(theta / 2.0), 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_small_angle_y(self, device, dtype, atol, rtol):
theta = 1.0e-2
axis_angle = torch.tensor((0.0, theta, 0.0), device=device, dtype=dtype)
expected = torch.tensor((np.cos(theta / 2.0), 0.0, np.sin(theta / 2.0), 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_small_angle_z(self, device, dtype, atol, rtol):
theta = 1.0e-2
axis_angle = torch.tensor((0.0, 0.0, theta), device=device, dtype=dtype)
expected = torch.tensor((np.cos(theta / 2.0), 0.0, 0.0, np.sin(theta / 2.0)), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_x_rotation(self, device, dtype, atol, rtol):
half_sqrt2 = 0.5 * np.sqrt(2.0)
axis_angle = torch.tensor((kornia.pi / 2.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((half_sqrt2, half_sqrt2, 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_y_rotation(self, device, dtype, atol, rtol):
half_sqrt2 = 0.5 * np.sqrt(2.0)
axis_angle = torch.tensor((0.0, kornia.pi / 2.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((half_sqrt2, 0.0, half_sqrt2, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_z_rotation(self, device, dtype, atol, rtol):
half_sqrt2 = 0.5 * np.sqrt(2.0)
axis_angle = torch.tensor((0.0, 0.0, kornia.pi / 2.0), device=device, dtype=dtype)
expected = torch.tensor((half_sqrt2, 0.0, 0.0, half_sqrt2), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.axis_angle_to_quaternion(axis_angle)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
dtype = torch.float64
eps = torch.finfo(dtype).eps
axis_angle = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype) + eps
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.axis_angle_to_quaternion), (axis_angle,))
class TestQuaternionToAngleAxis(BaseTester):
def test_smoke(self, device, dtype):
quaternion = torch.zeros(4, device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
assert axis_angle.shape == (3,)
@pytest.mark.parametrize("batch_size", (1, 3, 8))
def test_smoke_batch(self, batch_size, device, dtype):
quaternion = torch.zeros(batch_size, 4, device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
assert axis_angle.shape == (batch_size, 3)
def test_unit_quaternion(self, device, dtype, atol, rtol):
quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_x_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((kornia.pi, 0.0, 0.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_y_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, kornia.pi, 0.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_z_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((np.sqrt(3.0) / 2.0, 0.0, 0.0, 0.5), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, kornia.pi / 3.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_small_angle_x(self, device, dtype, atol, rtol):
theta = 1.0e-2
quaternion = torch.tensor((np.cos(theta / 2.0), np.sin(theta / 2.0), 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((theta, 0.0, 0.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_small_angle_y(self, device, dtype, atol, rtol):
theta = 1.0e-2
quaternion = torch.tensor((np.cos(theta / 2), 0.0, np.sin(theta / 2), 0.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, theta, 0.0), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_small_angle_z(self, device, dtype, atol, rtol):
theta = 1.0e-2
quaternion = torch.tensor((np.cos(theta / 2), 0.0, 0.0, np.sin(theta / 2)), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, theta), device=device, dtype=dtype)
axis_angle = kornia.geometry.conversions.quaternion_to_axis_angle(quaternion)
self.assert_close(axis_angle, expected, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
dtype = torch.float64
eps = torch.finfo(dtype).eps
quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype) + eps
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.quaternion_to_axis_angle), (quaternion,))
class TestRotationMatrixToQuaternion(BaseTester):
@pytest.mark.parametrize("batch_size", (1, 3, 8))
def test_smoke_batch(self, batch_size, device, dtype):
matrix = torch.zeros(batch_size, 3, 3, device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix)
assert quaternion.shape == (batch_size, 4)
def test_identity(self, device, dtype, atol, rtol):
matrix = torch.tensor(((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)), device=device, dtype=dtype)
expected = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_rot_x_45(self, device, dtype, atol, rtol):
matrix = torch.tensor(((1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, 1.0, 0.0)), device=device, dtype=dtype)
pi_half2 = torch.cos(kornia.pi / 4.0).to(device=device, dtype=dtype)
expected = torch.tensor((pi_half2, pi_half2, 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix)
self.assert_close(quaternion, expected, atol=atol, rtol=rtol)
def test_back_and_forth(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
matrix = torch.tensor(((1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, 1.0, 0.0)), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix, eps=eps)
matrix_hat = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(matrix, matrix_hat, atol=atol, rtol=rtol)
def test_corner_case(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
matrix = torch.tensor(
(
(-0.7799533010, -0.5432914495, 0.3106555045),
(0.0492402576, -0.5481169224, -0.8349509239),
(0.6238971353, -0.6359263659, 0.4542570710),
),
device=device,
dtype=dtype,
)
quaternion_true = torch.tensor(
(0.177614107728004, 0.280136495828629, -0.440902262926102, 0.834015488624573), device=device, dtype=dtype
)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix, eps=eps)
torch.set_printoptions(precision=10)
self.assert_close(quaternion_true, quaternion, atol=atol, rtol=rtol)
def test_cond1_180_rot_x(self, device, dtype, atol, rtol):
# 180° rotation around X: trace < 0, m00 > m11 and m00 > m22 → activates cond_1 branch.
# R_x(π) = diag(1, -1, -1); expected quaternion (w,x,y,z) = (0, 1, 0, 0).
eps = torch.finfo(dtype).eps
matrix = torch.tensor(((1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
expected = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix, eps=eps)
self.assert_close(quaternion.abs(), expected.abs(), atol=atol, rtol=rtol)
# Round-trip: convert back and verify the rotation matrix is recovered.
mat_back = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(mat_back, matrix, atol=atol, rtol=rtol)
def test_cond2_180_rot_y(self, device, dtype, atol, rtol):
# 180° rotation around Y: trace < 0, m11 > m22 and m00 not dominant → activates cond_2 branch.
# R_y(π) = diag(-1, 1, -1); expected quaternion (w,x,y,z) = (0, 0, 1, 0).
eps = torch.finfo(dtype).eps
matrix = torch.tensor(((-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(matrix, eps=eps)
self.assert_close(quaternion.abs(), expected.abs(), atol=atol, rtol=rtol)
mat_back = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(mat_back, matrix, atol=atol, rtol=rtol)
def test_all_four_branches_in_batch(self, device, dtype, atol, rtol):
# Batch of 4 rotation matrices that each activate a different internal branch.
# Verify consistency via round-trip: R → q → R must recover the original rotation.
eps = torch.finfo(dtype).eps
identity = torch.eye(3, device=device, dtype=dtype) # trace > 0 → trace_positive_cond
rot_x_180 = torch.tensor(((1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
rot_y_180 = torch.tensor(((-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
rot_z_180 = torch.tensor(((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, 1.0)), device=device, dtype=dtype)
batch = torch.stack([identity, rot_x_180, rot_y_180, rot_z_180]) # (4, 3, 3)
quaternions = kornia.geometry.conversions.rotation_matrix_to_quaternion(batch, eps=eps)
mats_back = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternions)
self.assert_close(mats_back, batch, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
dtype = torch.float64
eps = torch.finfo(dtype).eps
matrix = torch.eye(3, device=device, dtype=dtype)
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.rotation_matrix_to_quaternion, eps=eps), (matrix,))
def test_dynamo(self, device, dtype, torch_optimizer):
quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
op = kornia.geometry.conversions.quaternion_log_to_exp
op_optimized = torch_optimizer(op)
actual = op_optimized(quaternion)
expected = op(quaternion)
self.assert_close(actual, expected)
class TestQuaternionToRotationMatrix(BaseTester):
@pytest.mark.parametrize("batch_dims", ((), (1,), (3,), (8,), (1, 1), (5, 6)))
def test_smoke_batch(self, batch_dims, device, dtype):
quaternion = torch.zeros(*batch_dims, 4, device=device, dtype=dtype)
matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
assert matrix.shape == (*batch_dims, 3, 3)
def test_unit_quaternion(self, device, dtype, atol, rtol):
quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor(((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)), device=device, dtype=dtype)
matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(matrix, expected, atol=atol, rtol=rtol)
def test_x_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor(((1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(matrix, expected, atol=atol, rtol=rtol)
def test_y_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor(((-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0)), device=device, dtype=dtype)
matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(matrix, expected, atol=atol, rtol=rtol)
def test_z_rotation(self, device, dtype, atol, rtol):
quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
expected = torch.tensor(((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, 1.0)), device=device, dtype=dtype)
matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion)
self.assert_close(matrix, expected, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=torch.float64)
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.quaternion_to_rotation_matrix), (quaternion,))
def test_dynamo(self, device, dtype, torch_optimizer):
quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
op = kornia.geometry.conversions.quaternion_to_rotation_matrix
op_optimized = torch_optimizer(op)
actual = op_optimized(quaternion)
expected = op(quaternion)
self.assert_close(actual, expected)
class TestQuaternionLogToExp(BaseTester):
@pytest.mark.parametrize("batch_size", (1, 3, 8))
def test_smoke_batch(self, batch_size, device, dtype):
quaternion_log = torch.zeros(batch_size, 3, device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log)
assert quaternion_exp.shape == (batch_size, 4)
def test_unit_quaternion(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_log = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
self.assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_x(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
one = torch.tensor(1.0, device=device, dtype=dtype)
quaternion_log = torch.tensor((1.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((torch.cos(one), torch.sin(one), 0.0, 0.0), device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
self.assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_y(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
one = torch.tensor(1.0, device=device, dtype=dtype)
quaternion_log = torch.tensor((0.0, 1.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((torch.cos(one), 0.0, torch.sin(one), 0.0), device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
self.assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_z(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
one = torch.tensor(1.0, device=device, dtype=dtype)
quaternion_log = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
expected = torch.tensor((torch.cos(one), 0.0, 0.0, torch.sin(one)), device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
self.assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)
def test_back_and_forth(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_log = torch.tensor((1.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
quaternion_log_hat = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
self.assert_close(quaternion_log, quaternion_log_hat, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
dtype = torch.float64
eps = torch.finfo(dtype).eps
quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.quaternion_log_to_exp, eps=eps), (quaternion,))
def test_dynamo(self, device, dtype, torch_optimizer):
quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
op = kornia.geometry.conversions.quaternion_log_to_exp
op_optimized = torch_optimizer(op)
actual = op_optimized(quaternion)
expected = op(quaternion)
self.assert_close(actual, expected)
class TestQuaternionExpToLog(BaseTester):
@pytest.mark.parametrize("batch_size", (1, 3, 8))
def test_smoke_batch(self, batch_size, device, dtype):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.zeros(batch_size, 4, device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
assert quaternion_log.shape == (batch_size, 3)
def test_unit_quaternion(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
self.assert_close(quaternion_log, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_x(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((kornia.pi / 2.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
self.assert_close(quaternion_log, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_y(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, kornia.pi / 2.0, 0.0), device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
self.assert_close(quaternion_log, expected, atol=atol, rtol=rtol)
def test_pi_quaternion_z(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
expected = torch.tensor((0.0, 0.0, kornia.pi / 2.0), device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
self.assert_close(quaternion_log, expected, atol=atol, rtol=rtol)
def test_back_and_forth(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
quaternion_exp = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(quaternion_exp, eps=eps)
quaternion_exp_hat = kornia.geometry.conversions.quaternion_log_to_exp(quaternion_log, eps=eps)
self.assert_close(quaternion_exp, quaternion_exp_hat, atol=atol, rtol=rtol)
def test_gradcheck(self, device):
dtype = torch.float64
eps = torch.finfo(dtype).eps
quaternion = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
# evaluate function gradient
self.gradcheck(partial(kornia.geometry.conversions.quaternion_exp_to_log, eps=eps), (quaternion,))
def test_dynamo(self, device, dtype, torch_optimizer):
quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
op = kornia.geometry.conversions.quaternion_exp_to_log
op_optimized = torch_optimizer(op)
actual = op_optimized(quaternion)
expected = op(quaternion)
self.assert_close(actual, expected)
class TestAngleAxisToRotationMatrix(BaseTester):
@pytest.mark.parametrize("batch_size", (1, 2, 5))
def test_rand_axis_angle_gradcheck(self, batch_size, device, atol, rtol):
dtype = torch.float64
# generate input data
axis_angle = torch.rand(batch_size, 3, device=device, dtype=dtype)
eye_batch = eye_like(3, axis_angle)
# apply transform
rotation_matrix = kornia.geometry.conversions.axis_angle_to_rotation_matrix(axis_angle)
rotation_matrix_eye = torch.matmul(rotation_matrix, rotation_matrix.transpose(-2, -1))
self.assert_close(rotation_matrix_eye, eye_batch, atol=atol, rtol=rtol)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.axis_angle_to_rotation_matrix, (axis_angle,))
def test_axis_angle_to_rotation_matrix(self, device, dtype, atol, rtol):
rmat_1 = torch.tensor(
(
(-0.30382753, -0.95095137, -0.05814062),
(-0.71581715, 0.26812278, -0.64476041),
(0.62872461, -0.15427791, -0.76217038),
),
device=device,
dtype=dtype,
)
rvec_1 = torch.tensor((1.50485376, -2.10737739, 0.7214174), device=device, dtype=dtype)
rmat_2 = torch.tensor(
(
(0.6027768, -0.79275544, -0.09054801),
(-0.67915707, -0.56931658, 0.46327563),
(-0.41881476, -0.21775548, -0.88157628),
),
device=device,
dtype=dtype,
)
rvec_2 = torch.tensor((-2.44916812, 1.18053411, 0.4085298), device=device, dtype=dtype)
rmat = torch.stack((rmat_2, rmat_1), dim=0)
rvec = torch.stack((rvec_2, rvec_1), dim=0)
self.assert_close(kornia.geometry.conversions.axis_angle_to_rotation_matrix(rvec), rmat, atol=atol, rtol=rtol)
class TestRotationMatrixToAngleAxis(BaseTester):
@pytest.mark.parametrize("batch_size", (1, 2, 5))
def test_rand_quaternion_gradcheck(self, batch_size, device, dtype, atol, rtol):
# generate input data
quaternion = torch.rand(batch_size, 4, device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.normalize_quaternion(quaternion + 1e-6)
rotation_matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion=quaternion)
eye_batch = eye_like(3, rotation_matrix)
rotation_matrix_eye = torch.matmul(rotation_matrix, rotation_matrix.transpose(-2, -1))
# This didn't pass with atol=0.001, rtol=0.001 for float16 Cuda 11.2 GeForce 1080 Ti
self.assert_close(rotation_matrix_eye, eye_batch, atol=atol * 10.0, rtol=rtol * 10.0)
@pytest.mark.parametrize("batch_size", [4])
def test_gradcheck(self, batch_size, device):
dtype = torch.float64
quaternion = torch.rand(batch_size, 4, device=device, dtype=dtype)
quaternion = kornia.geometry.conversions.normalize_quaternion(quaternion + 1e-6)
rotation_matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion=quaternion)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.rotation_matrix_to_axis_angle, (rotation_matrix,))
def test_rotation_matrix_to_axis_angle(self, device, dtype, atol, rtol):
rmat_1 = torch.tensor(
(
(-0.30382753, -0.95095137, -0.05814062),
(-0.71581715, 0.26812278, -0.64476041),
(0.62872461, -0.15427791, -0.76217038),
),
device=device,
dtype=dtype,
)
rvec_1 = torch.tensor((1.50485376, -2.10737739, 0.7214174), device=device, dtype=dtype)
rmat_2 = torch.tensor(
(
(0.6027768, -0.79275544, -0.09054801),
(-0.67915707, -0.56931658, 0.46327563),
(-0.41881476, -0.21775548, -0.88157628),
),
device=device,
dtype=dtype,
)
rvec_2 = torch.tensor((-2.44916812, 1.18053411, 0.4085298), device=device, dtype=dtype)
rmat = torch.stack((rmat_2, rmat_1), dim=0)
rvec = torch.stack((rvec_2, rvec_1), dim=0)
self.assert_close(kornia.geometry.conversions.rotation_matrix_to_axis_angle(rmat), rvec, atol=atol, rtol=rtol)
class TestRadDegConversions(BaseTester):
def test_pi(self):
self.assert_close(kornia.constants.pi.item(), 3.141592)
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_rad2deg(self, batch_shape, device, dtype):
# generate input data
x_rad = kornia.constants.pi * torch.rand(batch_shape, device=device, dtype=dtype)
# convert radians/degrees
x_deg = kornia.geometry.conversions.rad2deg(x_rad)
x_deg_to_rad = kornia.geometry.conversions.deg2rad(x_deg)
# compute error
self.assert_close(x_rad, x_deg_to_rad)
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_rad2deg_gradcheck(self, batch_shape, device):
dtype = torch.float64
x_rad = torch.rand(batch_shape, device=device, dtype=dtype)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.rad2deg, (x_rad,))
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_deg2rad(self, batch_shape, device, dtype, atol, rtol):
# generate input data
x_deg = 180.0 * torch.rand(batch_shape, device=device, dtype=dtype)
# convert radians/degrees
x_rad = kornia.geometry.conversions.deg2rad(x_deg)
x_rad_to_deg = kornia.geometry.conversions.rad2deg(x_rad)
self.assert_close(x_deg, x_rad_to_deg, atol=atol, rtol=rtol)
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_deg2rad_gradcheck(self, batch_shape, device):
x_deg = 180.0 * torch.rand(batch_shape, device=device, dtype=torch.float64)
self.gradcheck(kornia.geometry.conversions.deg2rad, (x_deg,))
class TestPolCartConversions(BaseTester):
def test_smoke(self, device, dtype):
x = torch.ones(1, 1, 1, 1, device=device, dtype=dtype)
assert kornia.geometry.conversions.pol2cart(x, x) is not None
assert kornia.geometry.conversions.cart2pol(x, x) is not None
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_pol2cart(self, batch_shape, device, dtype):
# generate input data
rho = torch.rand(batch_shape, dtype=dtype)
phi = kornia.constants.pi * torch.rand(batch_shape, dtype=dtype)
rho = rho.to(device)
phi = phi.to(device)
# convert pol/cart
x_pol2cart, y_pol2cart = kornia.geometry.conversions.pol2cart(rho, phi)
rho_pol2cart, phi_pol2cart = kornia.geometry.conversions.cart2pol(x_pol2cart, y_pol2cart, 0)
self.assert_close(rho, rho_pol2cart)
self.assert_close(phi, phi_pol2cart)
@pytest.mark.parametrize("batch_shape", [(2, 3)])
def test_gradcheck(self, batch_shape, device):
rho = torch.rand(batch_shape, dtype=torch.float64, device=device)
phi = kornia.constants.pi * torch.rand(batch_shape, dtype=torch.float64, device=device)
self.gradcheck(kornia.geometry.conversions.pol2cart, (rho, phi))
self.gradcheck(kornia.geometry.conversions.cart2pol, (rho, phi))
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_cart2pol(self, batch_shape, device, dtype):
# generate input data
x = torch.rand(batch_shape, dtype=dtype)
y = torch.rand(batch_shape, dtype=dtype)
x = x.to(device)
y = y.to(device)
# convert cart/pol
rho_cart2pol, phi_cart2pol = kornia.geometry.conversions.cart2pol(x, y, 0)
x_cart2pol, y_cart2pol = kornia.geometry.conversions.pol2cart(rho_cart2pol, phi_cart2pol)
self.assert_close(x, x_cart2pol)
self.assert_close(y, y_cart2pol)
class TestConvertPointsToHomogeneous(BaseTester):
def test_convert_points(self, device, dtype):
# generate input data
points_h = torch.tensor(
[[1.0, 2.0, 1.0], [0.0, 1.0, 2.0], [2.0, 1.0, 0.0], [-1.0, -2.0, -1.0], [0.0, 1.0, -2.0]],
device=device,
dtype=dtype,
)
expected = torch.tensor(
[
[1.0, 2.0, 1.0, 1.0],
[0.0, 1.0, 2.0, 1.0],
[2.0, 1.0, 0.0, 1.0],
[-1.0, -2.0, -1.0, 1.0],
[0.0, 1.0, -2.0, 1.0],
],
device=device,
dtype=dtype,
)
# to euclidean
points = kornia.geometry.conversions.convert_points_to_homogeneous(points_h)
self.assert_close(points, expected, atol=1e-4, rtol=1e-4)
def test_convert_points_batch(self, device, dtype):
# generate input data
points_h = torch.tensor([[[2.0, 1.0, 0.0]], [[0.0, 1.0, 2.0]], [[0.0, 1.0, -2.0]]], device=device, dtype=dtype)
expected = torch.tensor(
[[[2.0, 1.0, 0.0, 1.0]], [[0.0, 1.0, 2.0, 1.0]], [[0.0, 1.0, -2.0, 1.0]]], device=device, dtype=dtype
)
# to euclidean
points = kornia.geometry.conversions.convert_points_to_homogeneous(points_h)
self.assert_close(points, expected, atol=1e-4, rtol=1e-4)
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_gradcheck(self, batch_shape, device):
points_h = torch.rand(batch_shape, device=device, dtype=torch.float64)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.convert_points_to_homogeneous, (points_h,))
def test_dynamo(self, device, dtype, torch_optimizer):
points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
op = kornia.geometry.conversions.convert_points_to_homogeneous
op_optimized = torch_optimizer(op)
actual = op_optimized(points_h)
expected = op(points_h)
self.assert_close(actual, expected)
class TestConvertAtoH(BaseTester):
def test_convert_points(self, device, dtype):
# generate input data
A = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], device=device, dtype=dtype).view(1, 2, 3)
expected = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], device=device, dtype=dtype).view(
1, 3, 3
)
# to euclidean
H = kornia.geometry.conversions.convert_affinematrix_to_homography(A)
self.assert_close(H, expected)
@pytest.mark.parametrize("batch_shape", [(10, 2, 3), (16, 2, 3)])
def test_gradcheck(self, batch_shape, device):
points_h = torch.rand(batch_shape, device=device, dtype=torch.float64)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.convert_affinematrix_to_homography, (points_h,))
def test_dynamo(self, device, dtype, torch_optimizer):
points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
op = kornia.geometry.conversions.convert_affinematrix_to_homography
op_optimized = torch_optimizer(op)
actual = op_optimized(points_h)
expected = op(points_h)
self.assert_close(actual, expected)
class TestConvertPointsFromHomogeneous(BaseTester):
@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_cardinality(self, device, dtype, batch_shape):
points_h = torch.rand(batch_shape, device=device, dtype=dtype)
points = kornia.geometry.conversions.convert_points_from_homogeneous(points_h)
assert points.shape == points.shape[:-1] + (2,)
def test_points(self, device, dtype):
# generate input data
points_h = torch.tensor(
[[1.0, 2.0, 1.0], [0.0, 1.0, 2.0], [2.0, 1.0, 0.0], [-1.0, -2.0, -1.0], [0.0, 1.0, -2.0]],
device=device,
dtype=dtype,
)
expected = torch.tensor(
[[1.0, 2.0], [0.0, 0.5], [2.0, 1.0], [1.0, 2.0], [0.0, -0.5]], device=device, dtype=dtype
)
# to euclidean
points = kornia.geometry.conversions.convert_points_from_homogeneous(points_h)
self.assert_close(points, expected, atol=1e-4, rtol=1e-4)
def test_points_batch(self, device, dtype):
# generate input data
points_h = torch.tensor([[[2.0, 1.0, 0.0]], [[0.0, 1.0, 2.0]], [[0.0, 1.0, -2.0]]], device=device, dtype=dtype)
expected = torch.tensor([[[2.0, 1.0]], [[0.0, 0.5]], [[0.0, -0.5]]], device=device, dtype=dtype)
# to euclidean
points = kornia.geometry.conversions.convert_points_from_homogeneous(points_h)
self.assert_close(points, expected, atol=1e-4, rtol=1e-4)
def test_gradcheck(self, device):
points_h = torch.ones(1, 10, 3, device=device, dtype=torch.float64)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.convert_points_from_homogeneous, (points_h,))
def test_gradcheck_zvec_zeros(self, device):
# generate input data
points_h = torch.tensor([[1.0, 2.0, 0.0], [0.0, 1.0, 0.1], [2.0, 1.0, 0.1]], device=device, dtype=torch.float64)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.convert_points_from_homogeneous, (points_h,), eps=1e-8)
def test_dynamo(self, device, dtype, torch_optimizer):
points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
op = kornia.geometry.conversions.convert_points_from_homogeneous
op_optimized = torch_optimizer(op)
actual = op_optimized(points_h)
expected = op(points_h)
self.assert_close(actual, expected)
class TestNormalizePixelCoordinates(BaseTester):
def test_tensor_bhw2(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(
dtype=dtype
)
expected = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
grid_norm = kornia.geometry.conversions.normalize_pixel_coordinates(grid, height, width, eps=eps)
self.assert_close(grid_norm, expected, atol=atol, rtol=rtol)
def test_list(self, device, dtype, atol, rtol):
eps = torch.finfo(dtype).eps
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(
dtype=dtype
)
grid = grid.contiguous().view(-1, 2)
expected = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
expected = expected.contiguous().view(-1, 2)
grid_norm = kornia.geometry.conversions.normalize_pixel_coordinates(grid, height, width, eps=eps)
self.assert_close(grid_norm, expected, atol=atol, rtol=rtol)
def test_dynamo(self, device, dtype, torch_optimizer):
if device == torch.device("cpu"):
pytest.skip("NormalizePixelCoordinates not working on CPU with dynamo!")
op = kornia.geometry.conversions.normalize_pixel_coordinates
op_optimized = torch_optimizer(op)
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
actual = op_optimized(grid, height, width)
expected = op(grid, height, width)
self.assert_close(actual, expected)
class TestDenormalizePixelCoordinates(BaseTester):
def test_tensor_bhw2(self, device, dtype):
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
expected = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(
dtype=dtype
)
grid_norm = kornia.geometry.conversions.denormalize_pixel_coordinates(grid, height, width)
self.assert_close(grid_norm, expected, atol=1e-4, rtol=1e-4)
def test_list(self, device, dtype):
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
grid = grid.contiguous().view(-1, 2)
expected = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(
dtype=dtype
)
expected = expected.contiguous().view(-1, 2)
grid_norm = kornia.geometry.conversions.denormalize_pixel_coordinates(grid, height, width)
self.assert_close(grid_norm, expected, atol=1e-4, rtol=1e-4)
def test_dynamo(self, device, dtype, torch_optimizer):
if device == torch.device("cpu"):
pytest.xfail("DenormalizePixelCoordinates not working on CPU with dynamo!")
op = kornia.geometry.conversions.denormalize_pixel_coordinates
op_optimized = torch_optimizer(op)
height, width = 3, 4
grid = kornia.geometry.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(
dtype=dtype
)
actual = op_optimized(grid, height, width)
expected = op(grid, height, width)
self.assert_close(actual, expected)
class TestProjectPoints(BaseTester):
def test_smoke(self, device, dtype):
point_3d = torch.zeros(1, 3, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
point_2d = kornia.geometry.camera.project_points(point_3d, camera_matrix)
assert point_2d.shape == (1, 2)
def test_smoke_batch(self, device, dtype):
point_3d = torch.zeros(2, 3, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, -1, -1)
point_2d = kornia.geometry.camera.project_points(point_3d, camera_matrix)
assert point_2d.shape == (2, 2)
def test_smoke_batch_multi(self, device, dtype):
point_3d = torch.zeros(2, 4, 3, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, 4, -1, -1)
point_2d = kornia.geometry.camera.project_points(point_3d, camera_matrix)
assert point_2d.shape == (2, 4, 2)
def test_project_and_unproject(self, device, dtype):
point_3d = torch.tensor([[10.0, 2.0, 30.0]], device=device, dtype=dtype)
depth = point_3d[..., -1:]
camera_matrix = torch.tensor(
[[[2746.0, 0.0, 991.0], [0.0, 2748.0, 619.0], [0.0, 0.0, 1.0]]], device=device, dtype=dtype
)
point_2d = kornia.geometry.camera.project_points(point_3d, camera_matrix)
point_3d_hat = kornia.geometry.camera.unproject_points(point_2d, depth, camera_matrix)
self.assert_close(point_3d, point_3d_hat, atol=1e-4, rtol=1e-4)
def test_gradcheck(self, device):
# TODO: point [0, 0, 0] crashes
points_3d = torch.ones(1, 3, device=device, dtype=torch.float64)
camera_matrix = torch.eye(3, device=device, dtype=torch.float64).expand(1, -1, -1)
# evaluate function gradient
self.gradcheck(kornia.geometry.camera.project_points, (points_3d, camera_matrix))
def test_dynamo(self, device, dtype, torch_optimizer):
points_3d = torch.zeros(1, 3, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
op = kornia.geometry.camera.project_points
op_optimized = torch_optimizer(op)
actual = op_optimized(points_3d, camera_matrix)
expected = op(points_3d, camera_matrix)
self.assert_close(actual, expected)
class TestDenormalizePointsWithIntrinsics(BaseTester):
def test_smoke(self, device, dtype):
points_2d = torch.zeros(1, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
points_norm = kornia.geometry.conversions.denormalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (1, 2)
def test_smoke_batch(self, device, dtype):
points_2d = torch.zeros(2, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, -1, -1)
points_norm = kornia.geometry.conversions.denormalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (2, 2)
def test_smoke_batch_n(self, device, dtype):
points_2d = torch.zeros(2, 9, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, -1, -1)
points_norm = kornia.geometry.conversions.denormalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (2, 9, 2)
def test_toy(self, device, dtype):
point_2d = torch.tensor([[1.0, 1.0]], device=device, dtype=dtype)
camera_matrix = torch.tensor(
[[64.0, 0.0, 128.0], [0.0, 64.0, 128.0], [0.0, 0.0, 1.0]], device=device, dtype=dtype
)
op = kornia.geometry.conversions.denormalize_points_with_intrinsics
expected = torch.tensor([[192.0, 192.0]], device=device, dtype=dtype)
self.assert_close(op(point_2d, camera_matrix), expected, atol=1e-4, rtol=1e-4)
def test_gradcheck(self, device):
points_2d = torch.zeros(1, 2, device=device, dtype=torch.float64)
camera_matrix = torch.eye(3, device=device, dtype=torch.float64).expand(1, -1, -1)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.denormalize_points_with_intrinsics, (points_2d, camera_matrix))
def test_dynamo(self, device, dtype, torch_optimizer):
points_2d = torch.zeros(1, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
op = kornia.geometry.conversions.denormalize_points_with_intrinsics
op_optimized = torch_optimizer(op)
actual = op_optimized(points_2d, camera_matrix)
expected = op(points_2d, camera_matrix)
self.assert_close(actual, expected)
class TestNormalizePointsWithIntrinsics(BaseTester):
def test_smoke(self, device, dtype):
points_2d = torch.zeros(1, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
points_norm = kornia.geometry.conversions.normalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (1, 2)
def test_smoke_batch(self, device, dtype):
points_2d = torch.zeros(2, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, -1, -1)
points_norm = kornia.geometry.conversions.normalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (2, 2)
def test_smoke_batch_n(self, device, dtype):
points_2d = torch.zeros(2, 10, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(2, -1, -1)
points_norm = kornia.geometry.conversions.normalize_points_with_intrinsics(points_2d, camera_matrix)
assert points_norm.shape == (2, 10, 2)
def test_norm_unnorm(self, device, dtype):
point_2d = torch.tensor([[128.0, 128.0]], device=device, dtype=dtype)
camera_matrix = torch.tensor(
[[64.0, 0.0, 128.0], [0.0, 64.0, 128.0], [0.0, 0.0, 1.0]], device=device, dtype=dtype
)
op = kornia.geometry.conversions.normalize_points_with_intrinsics
back = kornia.geometry.conversions.denormalize_points_with_intrinsics
point_2d_norm = op(point_2d, camera_matrix)
point_2d_hat = back(point_2d_norm, camera_matrix)
self.assert_close(point_2d, point_2d_hat, atol=1e-4, rtol=1e-4)
def test_toy(self, device, dtype):
point_2d = torch.tensor([[192.0, 192.0]], device=device, dtype=dtype)
camera_matrix = torch.tensor(
[[64.0, 0.0, 128.0], [0.0, 64.0, 128.0], [0.0, 0.0, 1.0]], device=device, dtype=dtype
)
op = kornia.geometry.conversions.normalize_points_with_intrinsics
out = op(point_2d, camera_matrix)
expected = torch.tensor([[1.0, 1.0]], device=device, dtype=dtype)
self.assert_close(out, expected, atol=1e-4, rtol=1e-4)
def test_gradcheck(self, device):
points_2d = torch.zeros(1, 2, device=device, dtype=torch.float64)
camera_matrix = torch.eye(3, device=device, dtype=torch.float64).expand(1, -1, -1)
# evaluate function gradient
self.gradcheck(kornia.geometry.conversions.normalize_points_with_intrinsics, (points_2d, camera_matrix))
def test_dynamo(self, device, dtype, torch_optimizer):
points_2d = torch.zeros(1, 2, device=device, dtype=dtype)
camera_matrix = torch.eye(3, device=device, dtype=dtype).expand(1, -1, -1)
op = kornia.geometry.conversions.normalize_points_with_intrinsics
op_optimized = torch_optimizer(op)
actual = op_optimized(points_2d, camera_matrix)
expected = op(points_2d, camera_matrix)
self.assert_close(actual, expected)
class TestRt2Extrinsics(BaseTester):
@pytest.mark.parametrize("batch_size", [1, 2, 3])
def test_everything(self, batch_size, device, dtype):
# generate input data
R = torch.rand(batch_size, 3, 3, dtype=dtype, device=device)
t = torch.rand(batch_size, 3, 1, dtype=dtype, device=device)
Rt = Rt_to_matrix4x4(R, t)
assert Rt.shape == (batch_size, 4, 4)
R2, t2 = matrix4x4_to_Rt(Rt)
assert R2.shape == (batch_size, 3, 3)
assert t2.shape == (batch_size, 3, 1)
self.assert_close(R, R2, rtol=1e-4, atol=1e-5)
self.assert_close(t, t2, rtol=1e-4, atol=1e-5)
@pytest.mark.parametrize("batch_size", [5])
def test_gradcheck(self, batch_size, device):
R = torch.rand(batch_size, 3, 3, dtype=torch.float64, device=device)
t = torch.rand(batch_size, 3, 1, dtype=torch.float64, device=device)
self.gradcheck(kornia.geometry.conversions.Rt_to_matrix4x4, (R, t))
class TestCamtoworldGraphicsToVision(BaseTester):
@pytest.mark.parametrize("batch_size", [1, 2, 3])
def test_everything(self, batch_size, device, dtype):
# generate input data
t_vis = torch.tensor([2, 3, 4], device=device, dtype=dtype).view(1, 3, 1).repeat(batch_size, 1, 1)
angles = torch.tensor([0, kornia.pi / 2.0, 0.0], device=device, dtype=dtype)[None]
R_vis = kornia.geometry.axis_angle_to_rotation_matrix(angles).repeat(batch_size, 1, 1)
K_vis = Rt_to_matrix4x4(R_vis, t_vis)
K_graf = camtoworld_vision_to_graphics_4x4(K_vis)
expected = torch.tensor(
[[0, 0, -1, 2], [0, -1, 0, 3], [-1, 0, 0, 4], [0, 0, 0, 1]], device=device, dtype=dtype
)[None].repeat(batch_size, 1, 1)
self.assert_close(K_graf, expected, rtol=1e-4, atol=1e-5)
R_graf, t_graf = camtoworld_vision_to_graphics_Rt(R_vis, t_vis)
expected_R = torch.tensor([[0, 0, -1], [0, -1, 0], [-1, 0, 0]], device=device, dtype=dtype)[None].repeat(
batch_size, 1, 1
)
expected_t = torch.tensor([2, 3, 4], device=device, dtype=dtype).reshape(1, 3, 1).repeat(batch_size, 1, 1)
self.assert_close(t_graf, expected_t, rtol=1e-4, atol=1e-5)
self.assert_close(R_graf, expected_R, rtol=1e-4, atol=1e-5)
Kvis_back = camtoworld_graphics_to_vision_4x4(K_graf)
self.assert_close(Kvis_back, K_vis, rtol=1e-4, atol=1e-5)
R_vis_back, t_vis_back = camtoworld_graphics_to_vision_Rt(R_graf, t_graf)
self.assert_close(R_vis_back, R_vis, rtol=1e-4, atol=1e-5)
self.assert_close(t_vis_back, t_vis, rtol=1e-4, atol=1e-5)
@pytest.mark.parametrize("batch_size", [4])
def test_gradcheck(self, batch_size, device):
t_vis = torch.tensor([2, 3, 4], device=device, dtype=torch.float64).view(1, 3, 1).repeat(batch_size, 1, 1)
angles = torch.tensor([0, kornia.pi / 2.0, 0.0], device=device, dtype=torch.float64)[None]
R_vis = kornia.geometry.axis_angle_to_rotation_matrix(angles).repeat(batch_size, 1, 1)
K_vis = Rt_to_matrix4x4(R_vis, t_vis)
self.gradcheck(camtoworld_graphics_to_vision_4x4, (K_vis,))
self.gradcheck(camtoworld_vision_to_graphics_4x4, (K_vis,))
class TestCamtoworldRtToPoseRt(BaseTester):
@pytest.mark.parametrize("batch_size", [1, 2, 3])
def test_everything(self, batch_size, device, dtype):
# generate input data
t = torch.tensor([2, 3, 4], device=device, dtype=dtype).view(1, 3, 1).repeat(batch_size, 1, 1)
angles = torch.tensor([0, kornia.pi / 2.0, 0.0], device=device, dtype=dtype)[None]
R = kornia.geometry.axis_angle_to_rotation_matrix(angles).repeat(batch_size, 1, 1)
Rp, tp = camtoworld_to_worldtocam_Rt(R, t)
expected_Rp = torch.tensor([[0, 0, -1], [0, 1, 0], [1, 0, 0]], device=device, dtype=dtype)[None].repeat(
batch_size, 1, 1
)
expected_tp = torch.tensor([4, -3, -2], device=device, dtype=dtype).view(1, 3, 1).repeat(batch_size, 1, 1)
self.assert_close(Rp, expected_Rp, rtol=1e-4, atol=1e-5)
self.assert_close(tp, expected_tp, rtol=1e-4, atol=1e-5)
Rback, tback = worldtocam_to_camtoworld_Rt(Rp, tp)
self.assert_close(Rback, R, rtol=1e-4, atol=1e-5)
self.assert_close(tback, t, rtol=1e-4, atol=1e-5)
@pytest.mark.parametrize("batch_size", [4])
def test_gradcheck(self, batch_size, device):
t = torch.tensor([2, 3, 4], device=device, dtype=torch.float64).view(1, 3, 1).repeat(batch_size, 1, 1)
angles = torch.tensor([0, kornia.pi / 2.0, 0.0], device=device, dtype=torch.float64)[None]
R = kornia.geometry.axis_angle_to_rotation_matrix(angles).repeat(batch_size, 1, 1)
self.gradcheck(camtoworld_to_worldtocam_Rt, (R, t))
self.gradcheck(worldtocam_to_camtoworld_Rt, (R, t))
class TestCARKitToColmap(BaseTester):
def test_everything(self, device, dtype):
# generate input data
t = torch.tensor([1, 0, 0], device=device, dtype=dtype).view(1, 3, 1)
ang_deg = torch.tensor([45, 60.0, 0.0], device=device, dtype=dtype)[None]
ang_rad = kornia.geometry.conversions.deg2rad(ang_deg)
qvec = kornia.geometry.axis_angle_to_quaternion(ang_rad)
q_colmap, t_colmap = ARKitQTVecs_to_ColmapQTVecs(qvec, t)
angles_colmap = kornia.geometry.conversions.quaternion_to_axis_angle(q_colmap)
angles_colmap = kornia.geometry.conversions.rad2deg(angles_colmap)
expected_angles = torch.tensor([[116.8870620728, 0.0, -71.7524719238]], device=device, dtype=dtype)
expected_t = torch.tensor([[[-0.5256], [0.3558], [0.7727]]], device=device, dtype=dtype)
self.assert_close(angles_colmap, expected_angles, rtol=1e-4, atol=1e-5)
self.assert_close(t_colmap, expected_t, rtol=1e-4, atol=1e-5)
class TestEulerFromQuaternion(BaseTester):
def test_smoke(self, device, dtype):
q = Quaternion.random(batch_size=1)
q = q.to(device, dtype)
roll, pitch, yaw = euler_from_quaternion(q.w, q.x, q.y, q.z)
assert roll.shape == pitch.shape
assert pitch.shape == yaw.shape
@pytest.mark.parametrize("batch_size", ((1, 3, 4)))
def test_cardinality(self, device, dtype, batch_size):
q = Quaternion.random(batch_size=batch_size)
q = q.to(device, dtype)
roll, pitch, yaw = euler_from_quaternion(q.w, q.x, q.y, q.z)
assert roll.shape[0] == batch_size
assert pitch.shape[0] == batch_size
assert yaw.shape[0] == batch_size
def test_exception(self, device, dtype):
q = Quaternion.random(batch_size=2)
q = q.to(device, dtype)
with pytest.raises(Exception):
euler_from_quaternion(q.w, torch.rand(1), q.y, q.z)
def test_gradcheck(self, device):
q = Quaternion.random(batch_size=1).to(device, torch.float64)
self.gradcheck(euler_from_quaternion, (q.w, q.x, q.y, q.z))
@pytest.mark.skipif(
torch_version() in {"2.0.1", "2.1.2", "2.2.2", "2.3.1"} and sys.version_info.minor == 8,
reason="Not working on 2.0",
)
def test_dynamo(self, device, dtype, torch_optimizer):
q = Quaternion.random(batch_size=1)
q = q.to(device, dtype)
op = euler_from_quaternion
op_optimized = torch_optimizer(op)
self.assert_close(op(q.w, q.x, q.y, q.z), op_optimized(q.w, q.x, q.y, q.z))
def test_forth_and_back(self, device, dtype):
q = Quaternion.random(batch_size=2)
q = q.to(device, dtype)
roll, pitch, yaw = euler_from_quaternion(q.w, q.x, q.y, q.z)
qw, qx, qy, qz = quaternion_from_euler(roll, pitch, yaw)
# TODO: check hwo to prevent getting inverted angles sometimes
self.assert_close(q.w.abs(), qw.abs())
self.assert_close(q.x.abs(), qx.abs())
self.assert_close(q.y.abs(), qy.abs())
self.assert_close(q.z.abs(), qz.abs())
class TestQuaternionFromEuler(BaseTester):
def test_smoke(self, device, dtype):
roll, pitch, yaw = torch.rand(3, device=device, dtype=dtype)
qw, qx, qy, qz = quaternion_from_euler(roll, pitch, yaw)
assert qw.shape == qx.shape
assert qx.shape == qy.shape
assert qy.shape == qz.shape
@pytest.mark.parametrize("batch_size", ((1, 3, 4)))
def test_cardinality(self, device, dtype, batch_size):
roll, pitch, yaw = torch.rand(3, batch_size, device=device, dtype=dtype)
qw, qx, qy, qz = quaternion_from_euler(roll, pitch, yaw)
assert qw.shape[0] == batch_size
assert qx.shape[0] == batch_size
assert qy.shape[0] == batch_size
assert qz.shape[0] == batch_size
def test_exception(self, device, dtype):
_, pitch, yaw = torch.rand(3, 2, device=device, dtype=dtype)
with pytest.raises(Exception):
quaternion_from_euler(torch.rand(1), pitch, yaw)
def test_gradcheck(self, device):
roll, pitch, yaw = torch.rand(3, 2, device=device, dtype=torch.float64, requires_grad=True)
self.gradcheck(quaternion_from_euler, (roll, pitch, yaw))
def test_dynamo(self, device, dtype, torch_optimizer):
roll, pitch, yaw = torch.rand(3, 2, device=device, dtype=dtype)
op = quaternion_from_euler
op_optimized = torch_optimizer(op)
actual = op_optimized(roll, pitch, yaw)
expected = op(roll, pitch, yaw)
self.assert_close(actual[0], expected[0])
self.assert_close(actual[1], expected[1])
self.assert_close(actual[2], expected[2])
def test_forth_and_back(self, device, dtype):
roll, pitch, yaw = torch.rand(3, 2, device=device, dtype=dtype)
qw, qx, qy, qz = quaternion_from_euler(roll, pitch, yaw)
roll_new, pitch_new, yaw_new = euler_from_quaternion(qw, qx, qy, qz)
self.assert_close(roll, roll_new)
self.assert_close(pitch, pitch_new)
self.assert_close(yaw, yaw_new)
def test_values(self, device, dtype):
# num_samples = 5
# data = 2 * torch.rand(3, num_samples, device=device, dtype=dtype) - 1
# roll, pitch, yaw = torch.pi * data
roll = torch.tensor(
[2.6518599987, 0.0612506270, 1.2417907715, 2.8829660416, -1.9961174726], device=device, dtype=dtype
)
pitch = torch.tensor(
[2.3267219067, -2.7309591770, -1.4011553526, -2.1962766647, 2.1454355717], device=device, dtype=dtype
)
yaw = torch.tensor(
[-0.8856627345, 0.2605336905, 0.4579202533, -1.3095731735, 0.6096843481], device=device, dtype=dtype
)
euler_expected = torch.tensor(
[
[-0.4897327125, 0.8148705959, 2.2559301853],
[-3.0803420544, -0.4106334746, -2.8810589314],
[1.2417914867, -1.4011553526, 0.4579201937],
[-0.2586266696, -0.9453159571, 1.8320195675],
[1.1454752684, 0.9961569905, -2.5319085121],
],
device=device,
dtype=dtype,
)
qw, qx, qy, qz = quaternion_from_euler(roll, pitch, yaw)
euler = euler_from_quaternion(qw, qx, qy, qz)
euler = torch.stack(euler, -1)
self.assert_close(euler, euler_expected, 1e-4, 1e-4)
# this test is passing: pip install transforms3d
# import transforms3d as tf3
# out = [tf3.euler.euler2quat(roll[i], pitch[i], yaw[i]) for i in range(num_samples)]
# out = torch.tensor(out, device=device, dtype=dtype)
# self.assert_close(torch.stack((qw, qx, qy, qz), -1), out)
# out = [tf3.euler.quat2euler((qw[i], qx[i], qy[i], qz[i])) for i in range(num_samples)]
# out = torch.tensor(out, device=device, dtype=dtype)
@pytest.mark.parametrize("batch_size", (None, 1, 2, 5))
def test_vector_to_skew_symmetric_matrix(batch_size, device, dtype):
if batch_size is None:
vector = torch.rand(3, device=device, dtype=dtype)
else:
vector = torch.rand((batch_size, 3), device=device, dtype=dtype)
skew_symmetric_matrix = kornia.geometry.conversions.vector_to_skew_symmetric_matrix(vector)
assert skew_symmetric_matrix.shape[-1] == 3
assert skew_symmetric_matrix.shape[-2] == 3
z = torch.zeros_like(vector[..., 0])
assert_close(skew_symmetric_matrix[..., 0, 0], z)
assert_close(skew_symmetric_matrix[..., 1, 1], z)
assert_close(skew_symmetric_matrix[..., 2, 2], z)
assert_close(skew_symmetric_matrix[..., 0, 1], -vector[..., 2])
assert_close(skew_symmetric_matrix[..., 1, 0], vector[..., 2])
assert_close(skew_symmetric_matrix[..., 0, 2], vector[..., 1])
assert_close(skew_symmetric_matrix[..., 2, 0], -vector[..., 1])
assert_close(skew_symmetric_matrix[..., 1, 2], -vector[..., 0])
assert_close(skew_symmetric_matrix[..., 2, 1], vector[..., 0])
class TestAxisAngleToRotationMatrix:
def test_identity_rotation(self):
aa = torch.zeros(1, 3, dtype=torch.float64, requires_grad=True)
R = axis_angle_to_rotation_matrix(aa)
Id = torch.eye(3, dtype=torch.float64).unsqueeze(0)
assert torch.allclose(R, Id, atol=1e-6)
def test_90deg_x_axis(self):
aa = torch.tensor([[torch.pi / 2, 0.0, 0.0]], dtype=torch.float64)
R = axis_angle_to_rotation_matrix(aa).squeeze(0)
expected = torch.tensor(
[
[1.0, 0.0, 0.0],
[0.0, 0.0, -1.0],
[0.0, 1.0, 0.0],
],
dtype=torch.float64,
)
assert torch.allclose(R, expected, atol=1e-6)
def test_180deg_y_axis(self):
aa = torch.tensor([[0.0, torch.pi, 0.0]], dtype=torch.float64)
R = axis_angle_to_rotation_matrix(aa).squeeze(0)
expected = torch.tensor(
[
[-1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, -1.0],
],
dtype=torch.float64,
)
assert torch.allclose(R, expected, atol=1e-6)
def test_batched_input(self):
aa = torch.tensor(
[
[0.0, 0.0, 0.0],
[torch.pi / 2, 0.0, 0.0],
[0.0, torch.pi, 0.0],
],
dtype=torch.float64,
)
R = axis_angle_to_rotation_matrix(aa)
assert R.shape == (3, 3, 3)