from functools import partial

import numpy as np
import pytest
import torch
from torch.autograd import gradcheck

import kornia
from kornia.geometry.conversions import QuaternionCoeffOrder
from kornia.testing import assert_close, create_eye_batch, tensor_to_gradcheck_var


@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


# based on:
# https://github.com/ceres-solver/ceres-solver/blob/master/internal/ceres/rotation_test.cc#L271


class TestAngleAxisToQuaternion:
    def test_smoke_xyzw(self, device, dtype):
        angle_axis = torch.zeros(3, dtype=dtype, device=device)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert quaternion.shape == (4,)

    def test_smoke(self, device, dtype):
        angle_axis = torch.zeros(3, dtype=dtype, device=device)
        quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert quaternion.shape == (4,)

    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        angle_axis = torch.zeros(batch_size, 3, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert quaternion.shape == (batch_size, 4)

    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch(self, batch_size, device, dtype):
        angle_axis = torch.zeros(batch_size, 3, device=device, dtype=dtype)
        quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert quaternion.shape == (batch_size, 4)

    def test_zero_angle_xyzw(self, device, dtype, atol, rtol):
        angle_axis = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_zero_angle(self, device, dtype, atol, rtol):
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_x_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = torch.tensor((theta, 0.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((np.sin(theta / 2.0), 0.0, 0.0, np.cos(theta / 2.0)), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_x(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_y_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = torch.tensor((0.0, theta, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, np.sin(theta / 2.0), 0.0, np.cos(theta / 2.0)), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_y(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_z_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = torch.tensor((0.0, 0.0, theta), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, np.sin(theta / 2.0), np.cos(theta / 2.0)), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_small_angle_z(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_x_rotation_xyzw(self, device, dtype, atol, rtol):
        half_sqrt2 = 0.5 * np.sqrt(2.0)
        angle_axis = torch.tensor((kornia.pi / 2.0, 0.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((half_sqrt2, 0.0, 0.0, half_sqrt2), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        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)
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_y_rotation_xyzw(self, device, dtype, atol, rtol):
        half_sqrt2 = 0.5 * np.sqrt(2.0)
        angle_axis = torch.tensor((0.0, kornia.pi / 2.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, half_sqrt2, 0.0, half_sqrt2), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        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)
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_z_rotation_xyzw(self, device, dtype, atol, rtol):
        half_sqrt2 = 0.5 * np.sqrt(2.0)
        angle_axis = torch.tensor((0.0, 0.0, kornia.pi / 2.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, half_sqrt2, half_sqrt2), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.angle_axis_to_quaternion(
                angle_axis, order=QuaternionCoeffOrder.XYZW
            )
        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)
        angle_axis = 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.angle_axis_to_quaternion(angle_axis, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        eps = torch.finfo(dtype).eps
        angle_axis = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype) + eps
        angle_axis = tensor_to_gradcheck_var(angle_axis)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(kornia.geometry.conversions.angle_axis_to_quaternion, order=QuaternionCoeffOrder.XYZW),
                (angle_axis,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        eps = torch.finfo(dtype).eps
        angle_axis = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype) + eps
        angle_axis = tensor_to_gradcheck_var(angle_axis)
        # evaluate function gradient
        assert gradcheck(
            partial(kornia.geometry.conversions.angle_axis_to_quaternion, order=QuaternionCoeffOrder.WXYZ),
            (angle_axis,),
            raise_exception=True,
        )


class TestQuaternionToAngleAxis:
    def test_smoke_xyzw(self, device, dtype):
        quaternion = torch.zeros(4, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert angle_axis.shape == (3,)

    def test_smoke(self, device, dtype):
        quaternion = torch.zeros(4, device=device, dtype=dtype)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert angle_axis.shape == (3,)

    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        quaternion = torch.zeros(batch_size, 4, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert angle_axis.shape == (batch_size, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert angle_axis.shape == (batch_size, 3)

    def test_unit_quaternion_xyzw(self, device, dtype, atol, rtol):
        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_x_rotation_xyzw(self, device, dtype, atol, rtol):
        quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((kornia.pi, 0.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_y_rotation_xyzw(self, device, dtype, atol, rtol):
        quaternion = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, kornia.pi, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_z_rotation_xyzw(self, device, dtype, atol, rtol):
        quaternion = torch.tensor((0.0, 0.0, 0.5, np.sqrt(3.0) / 2.0), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, kornia.pi / 3.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_small_angle_x_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        quaternion = torch.tensor((np.sin(theta / 2.0), 0.0, 0.0, np.cos(theta / 2.0)), device=device, dtype=dtype)
        expected = torch.tensor((theta, 0.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_small_angle_y_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        quaternion = torch.tensor((0.0, np.sin(theta / 2), 0.0, np.cos(theta / 2)), device=device, dtype=dtype)
        expected = torch.tensor((0.0, theta, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_small_angle_z_xyzw(self, device, dtype, atol, rtol):
        theta = 1.0e-2
        quaternion = torch.tensor((0.0, 0.0, np.sin(theta / 2), np.cos(theta / 2)), device=device, dtype=dtype)
        expected = torch.tensor((0.0, 0.0, theta), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(angle_axis, 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)
        angle_axis = kornia.geometry.conversions.quaternion_to_angle_axis(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert_close(angle_axis, expected, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype) + eps
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(kornia.geometry.conversions.quaternion_to_angle_axis, order=QuaternionCoeffOrder.XYZW),
                (quaternion,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype) + eps
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        assert gradcheck(
            partial(kornia.geometry.conversions.quaternion_to_angle_axis, order=QuaternionCoeffOrder.WXYZ),
            (quaternion,),
            raise_exception=True,
        )


class TestRotationMatrixToQuaternion:
    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        matrix = torch.zeros(batch_size, 3, 3, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(
                matrix, order=QuaternionCoeffOrder.XYZW
            )
        assert quaternion.shape == (batch_size, 4)

    @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, order=QuaternionCoeffOrder.WXYZ)
        assert quaternion.shape == (batch_size, 4)

    def test_identity_xyzw(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((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(
                matrix, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_rot_x_45_xyzw(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, 0.0, 0.0, pi_half2), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(
                matrix, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(quaternion, expected, atol=atol, rtol=rtol)

    def test_back_and_forth_xyzw(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)
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(
                matrix, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
            matrix_hat = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(matrix, matrix_hat, 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, order=QuaternionCoeffOrder.WXYZ
        )
        matrix_hat = kornia.geometry.conversions.quaternion_to_rotation_matrix(
            quaternion, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(matrix, matrix_hat, atol=atol, rtol=rtol)

    def test_corner_case_xyzw(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.280136495828629, -0.440902262926102, 0.834015488624573, 0.177614107728004), device=device, dtype=dtype
        )
        with pytest.warns(UserWarning):
            quaternion = kornia.geometry.conversions.rotation_matrix_to_quaternion(
                matrix, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        torch.set_printoptions(precision=10)
        assert_close(quaternion_true, quaternion, 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, order=QuaternionCoeffOrder.WXYZ
        )
        torch.set_printoptions(precision=10)
        assert_close(quaternion_true, quaternion, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        eps = torch.finfo(dtype).eps
        matrix = torch.eye(3, device=device, dtype=dtype)
        matrix = tensor_to_gradcheck_var(matrix)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(
                    kornia.geometry.conversions.rotation_matrix_to_quaternion, eps=eps, order=QuaternionCoeffOrder.XYZW
                ),
                (matrix,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        eps = torch.finfo(dtype).eps
        matrix = torch.eye(3, device=device, dtype=dtype)
        matrix = tensor_to_gradcheck_var(matrix)
        # evaluate function gradient
        assert gradcheck(
            partial(
                kornia.geometry.conversions.rotation_matrix_to_quaternion, eps=eps, order=QuaternionCoeffOrder.WXYZ
            ),
            (matrix,),
            raise_exception=True,
        )

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit_xyzw(self, device, dtype):
        op = kornia.geometry.conversions.quaternion_log_to_exp
        op_script = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            actual = op_script(quaternion)
        with pytest.warns(UserWarning):
            expected = op(quaternion)
        assert_close(actual, expected)

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit(self, device, dtype):
        eps = torch.finfo(dtype).eps
        op = kornia.geometry.conversions.quaternion_log_to_exp
        op_script = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        actual = op_script(quaternion, eps=eps, order=QuaternionCoeffOrder.WXYZ)
        expected = op(quaternion, eps=eps, order=QuaternionCoeffOrder.WXYZ)
        assert_close(actual, expected)


class TestQuaternionToRotationMatrix:
    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        quaternion = torch.zeros(batch_size, 4, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert matrix.shape == (batch_size, 3, 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)
        matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(quaternion, order=QuaternionCoeffOrder.WXYZ)
        assert matrix.shape == (batch_size, 3, 3)

    def test_unit_quaternion_xyzw(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)
        with pytest.warns(UserWarning):
            matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(matrix, expected, atol=atol, rtol=rtol)

    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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(matrix, expected, atol=atol, rtol=rtol)

    def test_x_rotation_xyzw(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)
        with pytest.warns(UserWarning):
            matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(matrix, expected, atol=atol, rtol=rtol)

    def test_y_rotation_xyzw(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)

        with pytest.warns(UserWarning):
            matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(matrix, expected, atol=atol, rtol=rtol)

    def test_z_rotation_xyzw(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)
        with pytest.warns(UserWarning):
            matrix = kornia.geometry.conversions.quaternion_to_rotation_matrix(
                quaternion, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ)
        assert_close(matrix, expected, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(kornia.geometry.conversions.quaternion_to_rotation_matrix, order=QuaternionCoeffOrder.XYZW),
                (quaternion,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        assert gradcheck(
            partial(kornia.geometry.conversions.quaternion_to_rotation_matrix, order=QuaternionCoeffOrder.WXYZ),
            (quaternion,),
            raise_exception=True,
        )

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit_xyzw(self, device, dtype):
        op = kornia.geometry.conversions.quaternion_to_rotation_matrix
        op_jit = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            assert_close(op(quaternion), op_jit(quaternion))

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.quaternion_to_rotation_matrix
        op_jit = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        assert_close(
            op(quaternion, order=QuaternionCoeffOrder.WXYZ), op_jit(quaternion, order=QuaternionCoeffOrder.WXYZ)
        )


class TestQuaternionLogToExp:
    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        quaternion_log = torch.zeros(batch_size, 3, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, order=QuaternionCoeffOrder.XYZW
            )
        assert quaternion_exp.shape == (batch_size, 4)

    @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, order=QuaternionCoeffOrder.WXYZ
        )
        assert quaternion_exp.shape == (batch_size, 4)

    def test_unit_quaternion_xyzw(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((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)

    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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_x_xyzw(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.sin(one), 0.0, 0.0, torch.cos(one)), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_y_xyzw(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((0.0, torch.sin(one), 0.0, torch.cos(one)), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_z_xyzw(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((0.0, 0.0, torch.sin(one), torch.cos(one)), device=device, dtype=dtype)

        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_exp, expected, atol=atol, rtol=rtol)

    def test_back_and_forth_xyzw(self, device, dtype, atol, rtol):
        eps = torch.finfo(dtype).eps
        quaternion_log = torch.tensor((1.0, 0.0, 0.0), device=device, dtype=dtype)

        with pytest.warns(UserWarning):
            quaternion_exp = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        with pytest.warns(UserWarning):
            quaternion_log_hat = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion_log, quaternion_log_hat, 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, order=QuaternionCoeffOrder.WXYZ
        )
        quaternion_log_hat = kornia.geometry.conversions.quaternion_exp_to_log(
            quaternion_exp, eps=eps, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_log, quaternion_log_hat, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(kornia.geometry.conversions.quaternion_log_to_exp, eps=eps, order=QuaternionCoeffOrder.XYZW),
                (quaternion,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        assert gradcheck(
            partial(kornia.geometry.conversions.quaternion_log_to_exp, eps=eps, order=QuaternionCoeffOrder.WXYZ),
            (quaternion,),
            raise_exception=True,
        )

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit_xyzw(self, device, dtype):
        op = kornia.geometry.conversions.quaternion_log_to_exp
        op_jit = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            assert_close(
                op(quaternion, order=QuaternionCoeffOrder.XYZW), op_jit(quaternion, order=QuaternionCoeffOrder.XYZW)
            )

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.quaternion_log_to_exp
        op_jit = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0), device=device, dtype=dtype)
        assert_close(
            op(quaternion, order=QuaternionCoeffOrder.WXYZ), op_jit(quaternion, order=QuaternionCoeffOrder.WXYZ)
        )


class TestQuaternionExpToLog:
    @pytest.mark.parametrize("batch_size", (1, 3, 8))
    def test_smoke_batch_xyzw(self, batch_size, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion_exp = torch.zeros(batch_size, 4, device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        assert quaternion_log.shape == (batch_size, 3)

    @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, order=QuaternionCoeffOrder.WXYZ
        )
        assert quaternion_log.shape == (batch_size, 3)

    def test_unit_quaternion_xyzw(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, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion_log, expected, atol=atol, rtol=rtol)

    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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_log, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_x_xyzw(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((kornia.pi / 2.0, 0.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_log, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_y_xyzw(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((0.0, kornia.pi / 2.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_log, expected, atol=atol, rtol=rtol)

    def test_pi_quaternion_z_xyzw(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, 0.0, kornia.pi / 2.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        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, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_log, expected, atol=atol, rtol=rtol)

    def test_back_and_forth_xyzw(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)

        with pytest.warns(UserWarning):
            quaternion_log = kornia.geometry.conversions.quaternion_exp_to_log(
                quaternion_exp, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        with pytest.warns(UserWarning):
            quaternion_exp_hat = kornia.geometry.conversions.quaternion_log_to_exp(
                quaternion_log, eps=eps, order=QuaternionCoeffOrder.XYZW
            )
        assert_close(quaternion_exp, quaternion_exp_hat, 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, order=QuaternionCoeffOrder.WXYZ
        )
        quaternion_exp_hat = kornia.geometry.conversions.quaternion_log_to_exp(
            quaternion_log, eps=eps, order=QuaternionCoeffOrder.WXYZ
        )
        assert_close(quaternion_exp, quaternion_exp_hat, atol=atol, rtol=rtol)

    def test_gradcheck_xyzw(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((1.0, 0.0, 0.0, 0.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        with pytest.warns(UserWarning):
            assert gradcheck(
                partial(kornia.geometry.conversions.quaternion_exp_to_log, eps=eps, order=QuaternionCoeffOrder.XYZW),
                (quaternion,),
                raise_exception=True,
            )

    def test_gradcheck(self, device, dtype):
        eps = torch.finfo(dtype).eps
        quaternion = torch.tensor((0.0, 1.0, 0.0, 0.0), device=device, dtype=dtype)
        quaternion = tensor_to_gradcheck_var(quaternion)
        # evaluate function gradient
        assert gradcheck(
            partial(kornia.geometry.conversions.quaternion_exp_to_log, eps=eps, order=QuaternionCoeffOrder.WXYZ),
            (quaternion,),
            raise_exception=True,
        )

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit_xyzw(self, device, dtype, atol, rtol):
        op = kornia.geometry.conversions.quaternion_exp_to_log
        op_jit = torch.jit.script(op)
        quaternion = torch.tensor((0.0, 0.0, 1.0, 0.0), device=device, dtype=dtype)
        with pytest.warns(UserWarning):
            assert_close(op(quaternion), op_jit(quaternion), atol=atol, rtol=rtol)

    @pytest.mark.skipif(torch.__version__.startswith('1.6'), reason='JIT Enum not handled.')
    def test_jit(self, device, dtype, atol, rtol):
        op = kornia.geometry.conversions.quaternion_exp_to_log
        op_script = torch.jit.script(op)

        quaternion = torch.tensor((0.0, 0.0, 0.0, 1.0), device=device, dtype=dtype)
        actual = op_script(quaternion, eps=torch.finfo(dtype).eps, order=QuaternionCoeffOrder.WXYZ)
        expected = op(quaternion, eps=torch.finfo(dtype).eps, order=QuaternionCoeffOrder.WXYZ)
        assert_close(actual, expected, atol=atol, rtol=rtol)


class TestAngleAxisToRotationMatrix:
    @pytest.mark.parametrize("batch_size", (1, 2, 5))
    def test_rand_angle_axis_gradcheck(self, batch_size, device, dtype, atol, rtol):
        # generate input data
        angle_axis = torch.rand(batch_size, 3, device=device, dtype=dtype)
        eye_batch = create_eye_batch(batch_size, 3, device=device, dtype=dtype)

        # apply transform
        rotation_matrix = kornia.geometry.conversions.angle_axis_to_rotation_matrix(angle_axis)

        rotation_matrix_eye = torch.matmul(rotation_matrix, rotation_matrix.transpose(-2, -1))
        assert_close(rotation_matrix_eye, eye_batch, atol=atol, rtol=rtol)

        # evaluate function gradient
        angle_axis = tensor_to_gradcheck_var(angle_axis)  # to var
        assert gradcheck(kornia.geometry.conversions.angle_axis_to_rotation_matrix, (angle_axis,), raise_exception=True)

    def test_angle_axis_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)

        assert_close(kornia.geometry.conversions.angle_axis_to_rotation_matrix(rvec), rmat, atol=atol, rtol=rtol)


class TestRotationMatrixToAngleAxis:
    @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, order=QuaternionCoeffOrder.WXYZ
        )

        eye_batch = create_eye_batch(batch_size, 3, device=device, dtype=dtype)
        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
        assert_close(rotation_matrix_eye, eye_batch, atol=atol * 10.0, rtol=rtol * 10.0)

        # evaluate function gradient
        rotation_matrix = tensor_to_gradcheck_var(rotation_matrix)  # to var
        assert gradcheck(
            kornia.geometry.conversions.rotation_matrix_to_angle_axis, (rotation_matrix,), raise_exception=True
        )

    def test_rotation_matrix_to_angle_axis(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)

        assert_close(kornia.geometry.conversions.rotation_matrix_to_angle_axis(rmat), rvec, atol=atol, rtol=rtol)


def test_pi():
    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(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
    assert_close(x_rad, x_deg_to_rad)

    # evaluate function gradient
    assert gradcheck(kornia.geometry.conversions.rad2deg, (tensor_to_gradcheck_var(x_rad),), raise_exception=True)


@pytest.mark.parametrize("batch_shape", [(2, 3), (1, 2, 3), (2, 3, 3), (5, 5, 3)])
def test_deg2rad(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)

    assert_close(x_deg, x_rad_to_deg, atol=atol, rtol=rtol)

    assert gradcheck(kornia.geometry.conversions.deg2rad, (tensor_to_gradcheck_var(x_deg),), raise_exception=True)


class TestPolCartConversions:
    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)

        assert_close(rho, rho_pol2cart)
        assert_close(phi, phi_pol2cart)

        assert gradcheck(
            kornia.geometry.conversions.pol2cart,
            (tensor_to_gradcheck_var(rho), tensor_to_gradcheck_var(phi)),
            raise_exception=True,
        )

    @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)

        assert_close(x, x_cart2pol)
        assert_close(y, y_cart2pol)

        assert gradcheck(
            kornia.geometry.conversions.cart2pol,
            (tensor_to_gradcheck_var(x), tensor_to_gradcheck_var(y)),
            raise_exception=True,
        )


class TestConvertPointsToHomogeneous:
    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)
        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)
        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, dtype):
        points_h = torch.rand(batch_shape, device=device, dtype=dtype)

        # evaluate function gradient
        points_h = tensor_to_gradcheck_var(points_h)  # to var
        assert gradcheck(kornia.geometry.conversions.convert_points_to_homogeneous, (points_h,), raise_exception=True)

    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.convert_points_to_homogeneous
        op_jit = torch.jit.script(op)
        points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
        assert_close(op(points_h), op_jit(points_h))


class TestConvertAtoH:
    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)
        assert_close(H, expected)

    @pytest.mark.parametrize("batch_shape", [(10, 2, 3), (16, 2, 3)])
    def test_gradcheck(self, batch_shape, device, dtype):
        points_h = torch.rand(batch_shape, device=device, dtype=dtype)

        # evaluate function gradient
        points_h = tensor_to_gradcheck_var(points_h)  # to var
        assert gradcheck(
            kornia.geometry.conversions.convert_affinematrix_to_homography, (points_h,), raise_exception=True
        )

    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.convert_affinematrix_to_homography
        op_jit = torch.jit.script(op)
        points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
        assert_close(op(points_h), op_jit(points_h))


class TestConvertPointsFromHomogeneous:
    @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)
        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)
        assert_close(points, expected, atol=1e-4, rtol=1e-4)

    def test_gradcheck(self, device, dtype):
        points_h = torch.ones(1, 10, 3, device=device, dtype=dtype)

        # evaluate function gradient
        points_h = tensor_to_gradcheck_var(points_h)  # to var
        assert gradcheck(kornia.geometry.conversions.convert_points_from_homogeneous, (points_h,), raise_exception=True)

    @pytest.mark.skip("RuntimeError: Jacobian mismatch for output 0 with respect to input 0,")
    def test_gradcheck_zvec_zeros(self, device, dtype):
        # 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=dtype)

        # evaluate function gradient
        points_h = tensor_to_gradcheck_var(points_h)  # to var
        assert gradcheck(kornia.geometry.conversions.convert_points_from_homogeneous, (points_h,), raise_exception=True)

    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.convert_points_from_homogeneous
        op_jit = torch.jit.script(op)
        points_h = torch.zeros(1, 2, 3, device=device, dtype=dtype)
        assert_close(op(points_h), op_jit(points_h))


class TestNormalizePixelCoordinates:
    def test_tensor_bhw2(self, device, dtype, atol, rtol):
        eps = torch.finfo(dtype).eps
        height, width = 3, 4
        grid = kornia.utils.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(dtype=dtype)

        expected = kornia.utils.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)

        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.utils.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(dtype=dtype)
        grid = grid.contiguous().view(-1, 2)

        expected = kornia.utils.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)

        assert_close(grid_norm, expected, atol=atol, rtol=rtol)

    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.normalize_pixel_coordinates
        op_script = torch.jit.script(op)

        height, width = 3, 4
        grid = kornia.utils.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(dtype=dtype)

        actual = op_script(grid, height, width)
        expected = op(grid, height, width)
        assert_close(actual, expected)


class TestDenormalizePixelCoordinates:
    def test_tensor_bhw2(self, device, dtype):
        height, width = 3, 4
        grid = kornia.utils.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(dtype=dtype)

        expected = kornia.utils.create_meshgrid(height, width, normalized_coordinates=False, device=device).to(
            dtype=dtype
        )

        grid_norm = kornia.geometry.conversions.denormalize_pixel_coordinates(grid, height, width)

        assert_close(grid_norm, expected, atol=1e-4, rtol=1e-4)

    def test_list(self, device, dtype):
        height, width = 3, 4
        grid = kornia.utils.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(dtype=dtype)
        grid = grid.contiguous().view(-1, 2)

        expected = kornia.utils.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)

        assert_close(grid_norm, expected, atol=1e-4, rtol=1e-4)

    def test_jit(self, device, dtype):
        op = kornia.geometry.conversions.denormalize_pixel_coordinates
        op_script = torch.jit.script(op)

        height, width = 3, 4
        grid = kornia.utils.create_meshgrid(height, width, normalized_coordinates=True, device=device).to(dtype=dtype)

        actual = op_script(grid, height, width)
        expected = op(grid, height, width)

        assert_close(actual, expected)
