Skip to content

Quaternion

beignet.apply_quaternion

apply_quaternion(input, rotation, inverse=False)

Rotates vectors in three-dimensional space using rotation quaternions.

Note

This function interprets the rotation of the original frame to the final frame as either a projection, where it maps the components of vectors from the final frame to the original frame, or as a physical rotation, integrating the vectors into the original frame during the rotation process. Consequently, the vector components are maintained in the original frame’s perspective both before and after the rotation.

Parameters:

Name Type Description Default
input (Tensor, shape(..., 3))

Each vector represents a vector in three-dimensional space. The number of rotation quaternions and number of vectors must follow standard broadcasting rules: either one of them equals unity or they both equal each other.

required
rotation (Tensor, shape(..., 4))

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
inverse bool

If True the inverse of the rotation quaternions are applied to the input vectors. Default, False.

False

Returns:

Name Type Description
output (Tensor, shape(..., 3))

Rotated vectors.

Source code in src/beignet/_apply_quaternion.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def apply_quaternion(
    input: Tensor,
    rotation: Tensor,
    inverse: bool | None = False,
) -> Tensor:
    r"""
    Rotates vectors in three-dimensional space using rotation quaternions.

    Note
    ----
    This function interprets the rotation of the original frame to the final
    frame as either a projection, where it maps the components of vectors from
    the final frame to the original frame, or as a physical rotation,
    integrating the vectors into the original frame during the rotation
    process. Consequently, the vector components are maintained in the original
    frame’s perspective both before and after the rotation.

    Parameters
    ----------
    input : Tensor, shape (..., 3)
        Each vector represents a vector in three-dimensional space. The number
        of rotation quaternions and number of vectors must follow standard
        broadcasting rules: either one of them equals unity or they both equal
        each other.

    rotation : Tensor, shape (..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    inverse : bool, optional
        If `True` the inverse of the rotation quaternions are applied to the
        input vectors. Default, `False`.

    Returns
    -------
    output : Tensor, shape (..., 3)
        Rotated vectors.
    """
    return apply_rotation_matrix(
        input,
        quaternion_to_rotation_matrix(
            rotation,
        ),
        inverse,
    )

beignet.compose_quaternion

compose_quaternion(input, other, canonical=False)

Compose rotation quaternions.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
other Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
canonical bool

Whether to map the redundant double cover of rotation space to a unique canonical single cover. If True, then the rotation quaternion is chosen from :math:{q, -q} such that the :math:w term is positive. If the :math:w term is :math:0, then the rotation quaternion is chosen such that the first non-zero term of the :math:x, :math:y, and :math:z terms is positive.

False

Returns:

Name Type Description
output Tensor, shape=(..., 4)

Composed rotation quaternions.

Source code in src/beignet/_compose_quaternion.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def compose_quaternion(
    input: Tensor,
    other: Tensor,
    canonical: bool = False,
) -> Tensor:
    r"""
    Compose rotation quaternions.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    other : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    canonical : bool, optional
        Whether to map the redundant double cover of rotation space to a unique
        canonical single cover. If `True`, then the rotation quaternion is
        chosen from :math:`{q, -q}` such that the :math:`w` term is positive.
        If the :math:`w` term is :math:`0`, then the rotation quaternion is
        chosen such that the first non-zero term of the :math:`x`, :math:`y`,
        and :math:`z` terms is positive.

    Returns
    -------
    output : Tensor, shape=(..., 4)
        Composed rotation quaternions.
    """
    output = torch.empty(
        [max(input.shape[0], other.shape[0]), 4],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    for j in range(max(input.shape[0], other.shape[0])):
        a = input[j, 0]
        b = input[j, 1]
        c = input[j, 2]
        d = input[j, 3]

        p = other[j, 0]
        q = other[j, 1]
        r = other[j, 2]
        s = other[j, 3]

        t = output[j, 0]
        u = output[j, 1]
        v = output[j, 2]
        w = output[j, 3]

        output[j, 0] = d * p + s * a + b * r - c * q
        output[j, 1] = d * q + s * b + c * p - a * r
        output[j, 2] = d * r + s * c + a * q - b * p
        output[j, 3] = d * s - a * p - b * q - c * r

        x = torch.sqrt(t**2.0 + u**2.0 + v**2.0 + w**2.0)

        if x == 0.0:
            output[j] = torch.nan

        output[j] = output[j] / x

        if canonical:
            if w == 0 and (t == 0 and (u == 0 and v < 0 or u < 0) or t < 0) or w < 0:
                output[j] = -output[j]

    return output

beignet.invert_quaternion

invert_quaternion(input, canonical=False)

Invert rotation quaternions.

Parameters:

Name Type Description Default
input (Tensor, shape(..., 4))

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
canonical bool

Whether to map the redundant double cover of rotation space to a unique canonical single cover. If True, then the rotation quaternion is chosen from :math:{q, -q} such that the :math:w term is positive. If the :math:w term is :math:0, then the rotation quaternion is chosen such that the first non-zero term of the :math:x, :math:y, and :math:z terms is positive.

False

Returns:

Name Type Description
inverted_quaternions (Tensor, shape(..., 4))

Inverted rotation quaternions.

Source code in src/beignet/_invert_quaternion.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def invert_quaternion(
    input: Tensor,
    canonical: bool = False,
) -> Tensor:
    r"""
    Invert rotation quaternions.

    Parameters
    ----------
    input : Tensor, shape (..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    canonical : bool, optional
        Whether to map the redundant double cover of rotation space to a unique
        canonical single cover. If `True`, then the rotation quaternion is
        chosen from :math:`{q, -q}` such that the :math:`w` term is positive.
        If the :math:`w` term is :math:`0`, then the rotation quaternion is
        chosen such that the first non-zero term of the :math:`x`, :math:`y`,
        and :math:`z` terms is positive.

    Returns
    -------
    inverted_quaternions : Tensor, shape (..., 4)
        Inverted rotation quaternions.
    """
    input[:, :3] = -input[:, :3]

    return input

beignet.quaternion_identity

quaternion_identity(size, canonical=False, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

Identity rotation quaternions.

Parameters:

Name Type Description Default
size int

Output size.

required
canonical bool

Whether to map the redundant double cover of rotation space to a unique canonical single cover. If True, then the rotation quaternion is chosen from :math:{q, -q} such that the :math:w term is positive. If the :math:w term is :math:0, then the rotation quaternion is chosen such that the first non-zero term of the :math:x, :math:y, and :math:z terms is positive.

False
out Tensor

Output tensor. Default, None.

None
dtype dtype

Type of the returned tensor. Default, global default.

None
layout layout

Layout of the returned tensor. Default, torch.strided.

strided
device device

Device of the returned tensor. Default, current device for the default tensor type.

None
requires_grad bool

Whether autograd records operations on the returned tensor. Default, False.

False

Returns:

Name Type Description
identity_quaternions (Tensor, shape(size, 4))

Identity rotation quaternions.

Source code in src/beignet/_quaternion_identity.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def quaternion_identity(
    size: int,
    canonical: bool | None = False,
    *,
    out: Tensor | None = None,
    dtype: torch.dtype | None = None,
    layout: torch.layout | None = torch.strided,
    device: torch.device | None = None,
    requires_grad: bool | None = False,
) -> Tensor:
    r"""
    Identity rotation quaternions.

    Parameters
    ----------
    size : int
        Output size.

    canonical : bool, optional
        Whether to map the redundant double cover of rotation space to a unique
        canonical single cover. If `True`, then the rotation quaternion is
        chosen from :math:`{q, -q}` such that the :math:`w` term is positive.
        If the :math:`w` term is :math:`0`, then the rotation quaternion is
        chosen such that the first non-zero term of the :math:`x`, :math:`y`,
        and :math:`z` terms is positive.

    out : Tensor, optional
        Output tensor. Default, `None`.

    dtype : torch.dtype, optional
        Type of the returned tensor. Default, global default.

    layout : torch.layout, optional
        Layout of the returned tensor. Default, `torch.strided`.

    device : torch.device, optional
        Device of the returned tensor. Default, current device for the default
        tensor type.

    requires_grad : bool, optional
        Whether autograd records operations on the returned tensor. Default,
        `False`.

    Returns
    -------
    identity_quaternions : Tensor, shape (size, 4)
        Identity rotation quaternions.
    """
    rotation = torch.zeros(
        [size, 4],
        out=out,
        dtype=dtype,
        layout=layout,
        device=device,
        requires_grad=requires_grad,
    )

    rotation[:, 3] = 1.0

    return rotation

beignet.quaternion_magnitude

quaternion_magnitude(input, canonical=False)

Rotation quaternion magnitudes.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions.

required

Returns:

Name Type Description
output Tensor, shape=(...)

Angles in radians. Magnitudes will be in the range :math:[0, \pi].

Source code in src/beignet/_quaternion_magnitude.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def quaternion_magnitude(input: Tensor, canonical=False) -> Tensor:
    r"""
    Rotation quaternion magnitudes.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions.

    Returns
    -------
    output : Tensor, shape=(...)
        Angles in radians. Magnitudes will be in the range :math:`[0, \pi]`.
    """
    output = torch.empty(
        input.shape[0],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
        requires_grad=input.requires_grad,
    )

    for j in range(input.shape[0]):
        a = input[j, 0]
        b = input[j, 1]
        c = input[j, 2]
        d = input[j, 3]

        x = torch.atan2(torch.sqrt(a**2 + b**2 + c**2), torch.abs(d))

        output[j] = x * 2.0

    return output

beignet.quaternion_mean

quaternion_mean(input, weight=None)

Mean rotation quaternions.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
weight Tensor, shape=(..., 4)

Relative importance of rotation quaternions.

None

Returns:

Name Type Description
output Tensor, shape=(..., 4)

Rotation quaternions mean.

Source code in src/beignet/_quaternion_mean.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def quaternion_mean(
    input: Tensor,
    weight: Tensor | None = None,
) -> Tensor:
    r"""
    Mean rotation quaternions.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    weight : Tensor, shape=(..., 4), optional
        Relative importance of rotation quaternions.

    Returns
    -------
    output : Tensor, shape=(..., 4)
        Rotation quaternions mean.
    """
    if weight is None:
        weight = torch.ones(input.shape[0])

    _, output = torch.linalg.eigh((input.T * weight) @ input)

    output = output[:, -1]

    output = torch.unsqueeze(output, dim=0)

    return output

beignet.quaternion_slerp

quaternion_slerp(input, time, rotation)

Interpolate between two or more points on a sphere.

Unlike linear interpolation, which can result in changes in speed when interpolating between orientations or positions on a sphere, spherical linear interpolation ensures that the interpolation occurs at a constant rate and follows the shortest path on the surface of the sphere. The process is useful for rotations and orientation interpolation in three-dimensional spaces, smoothly transitioning between different orientations.

Mathematically, spherical linear interpolation interpolates between two points on a sphere using a parameter \(t\), where \(t = 0\) represents the start point and \(t = n\) represents the end point. For two rotation quaternions \(q_{1}\) and \(q_{2}\) representing the start and end orientations:

\[\text{slerp}(q_{1}, q_{2}; t) = q_{1}\frac{\sin((1 - t)\theta)}{\sin(\theta)} + q_{2}\frac{\sin(t\theta)}{\sin(\theta)}\]

where \(\theta\) is the angle between \(q_{1}\) and \(q_{2}\), and is computed using the dot product of \(q_{1}\) and \(q_{2}\). This formula ensures that the interpolation moves along the shortest path on the four-dimensional sphere of rotation quaternions, resulting in a smooth and constant-speed rotation.

Parameters:

Name Type Description Default
input (Tensor, shape(..., N))

Times.

required
time (Tensor, shape(..., N))

Times of the known rotations. At least 2 times must be specified.

required
rotation (Tensor, shape(..., N, 4))

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
Source code in src/beignet/_quaternion_slerp.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def quaternion_slerp(
    input: Tensor,
    time: Tensor,
    rotation: Tensor,
) -> Tensor:
    r"""
    Interpolate between two or more points on a sphere.

    Unlike linear interpolation, which can result in changes in speed when
    interpolating between orientations or positions on a sphere, spherical
    linear interpolation ensures that the interpolation occurs at a constant
    rate and follows the shortest path on the surface of the sphere.
    The process is useful for rotations and orientation interpolation in
    three-dimensional spaces, smoothly transitioning between different
    orientations.

    Mathematically, spherical linear interpolation interpolates between two
    points on a sphere using a parameter $t$, where $t = 0$ represents the
    start point and $t = n$ represents the end point. For two rotation
    quaternions $q_{1}$ and $q_{2}$ representing the start and end
    orientations:

    $$\text{slerp}(q_{1}, q_{2}; t) = q_{1}\frac{\sin((1 - t)\theta)}{\sin(\theta)} + q_{2}\frac{\sin(t\theta)}{\sin(\theta)}$$

    where $\theta$ is the angle between $q_{1}$ and $q_{2}$, and is computed
    using the dot product of $q_{1}$ and $q_{2}$. This formula ensures that the
    interpolation moves along the shortest path on the four-dimensional sphere
    of rotation quaternions, resulting in a smooth and constant-speed rotation.

    Parameters
    ----------
    input : Tensor, shape (..., N)
        Times.

    time : Tensor, shape (..., N)
        Times of the known rotations. At least 2 times must be specified.

    rotation : Tensor, shape (..., N, 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.
    """  # noqa: E501
    if time.shape[-1] != rotation.shape[-2]:
        raise ValueError

    output = torch.empty(
        [*input.shape, 4],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    for index, t in enumerate(input):
        b = torch.min(torch.nonzero(torch.greater_equal(time, t)))

        if b > 0:
            a = b - 1
        else:
            a = b

        if time[b] == t or b == a:
            output[index] = rotation[b]

            continue

        p, q = time[a], time[b]

        r = (t - p) / (q - p)

        t = rotation[a]
        u = rotation[b]

        v = torch.dot(t, u)

        if v < 0.0:
            u = -u
            v = -v

        if v > 0.9995:
            z = (1.0 - r) * t + r * u
        else:
            x = torch.sqrt(1.0 - v**2.0)

            y = torch.atan2(x, v)

            z = t * torch.sin((1.0 - r) * y) / x + u * torch.sin(r * y) / x

        output[index] = z / torch.linalg.norm(z)

    return output

beignet.quaternion_to_euler_angle

quaternion_to_euler_angle(input, axes, degrees=False)

Convert rotation quaternions to Euler angles.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
axes str

Axes. 1-3 characters belonging to the set {‘X’, ‘Y’, ‘Z’} for intrinsic rotations, or {‘x’, ‘y’, ‘z’} for extrinsic rotations. Extrinsic and intrinsic rotations cannot be mixed.

required
degrees bool

If True, Euler angles are assumed to be in degrees. Default, False.

False

Returns:

Name Type Description
output Tensor, shape=(..., 3)

Euler angles. The returned Euler angles are in the range:

* First angle: :math:`(-180, 180]` degrees (inclusive)
* Second angle:
    * :math:`[-90, 90]` degrees if all axes are different
      (e.g., :math:`xyz`)
    * :math:`[0, 180]` degrees if first and third axes are the same
      (e.g., :math:`zxz`)
* Third angle: :math:`[-180, 180]` degrees (inclusive)
Source code in src/beignet/_quaternion_to_euler_angle.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def quaternion_to_euler_angle(
    input: Tensor,
    axes: str,
    degrees: bool = False,
) -> Tensor:
    r"""
    Convert rotation quaternions to Euler angles.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    axes : str
        Axes. 1-3 characters belonging to the set {‘X’, ‘Y’, ‘Z’} for intrinsic
        rotations, or {‘x’, ‘y’, ‘z’} for extrinsic rotations. Extrinsic and
        intrinsic rotations cannot be mixed.

    degrees : bool, optional
        If `True`, Euler angles are assumed to be in degrees. Default, `False`.

    Returns
    -------
    output : Tensor, shape=(..., 3)
        Euler angles. The returned Euler angles are in the range:

            * First angle: :math:`(-180, 180]` degrees (inclusive)
            * Second angle:
                * :math:`[-90, 90]` degrees if all axes are different
                  (e.g., :math:`xyz`)
                * :math:`[0, 180]` degrees if first and third axes are the same
                  (e.g., :math:`zxz`)
            * Third angle: :math:`[-180, 180]` degrees (inclusive)
    """
    epsilon = torch.finfo(input.dtype).eps

    output = torch.empty(
        [input.shape[0], 3],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    extrinsic = re.match(r"^[xyz]{1,3}$", axes) is not None

    axes = axes.lower()

    if not extrinsic:
        axes = axes[::-1]

    match axes[0]:
        case "x":
            p = 0
        case "y":
            p = 1
        case "z":
            p = 2
        case _:
            raise ValueError

    match axes[1]:
        case "x":
            q = 0
        case "y":
            q = 1
        case "z":
            q = 2
        case _:
            raise ValueError

    match axes[2]:
        case "x":
            r = 0
        case "y":
            r = 1
        case "z":
            r = 2
        case _:
            raise ValueError

    if p == r:
        r = 3 - p - q

    s = (p - q) * (q - r) * (r - p) // 2

    for j in range(input.shape[0]):
        if p == r:
            t = input[j, 3]
            u = input[j, p]
            v = input[j, q]
            w = input[j, r] * s
        else:
            t = input[j, 3] - input[j, q]
            u = input[j, p] + input[j, r] * s
            v = input[j, q] + input[j, 3]
            w = input[j, r] * s - input[j, p]

        if extrinsic:
            a = 0
            c = 2
        else:
            a = 2
            c = 0

        output[j, 1] = 2.0 * torch.atan2(torch.hypot(v, w), torch.hypot(t, u))

        match output[j, 1]:
            case _ if abs(output[j, 1]) < epsilon:
                output[j, 0] = 2.0 * torch.atan2(u, t)
                output[j, 2] = 0.0
            case _ if abs(output[j, 1] - math.pi) < epsilon:
                if extrinsic:
                    output[j, 0] = 2.0 * -torch.atan2(w, v)
                else:
                    output[j, 0] = 2.0 * +torch.atan2(w, v)

                output[j, 2] = 0.0
            case _:
                output[j, a] = torch.atan2(u, t) - torch.atan2(w, v)
                output[j, c] = torch.atan2(u, t) + torch.atan2(w, v)

        if not p == r:
            output[j, 1] = output[j, 1] - math.pi / 2.0
            output[j, c] = output[j, c] * s

        for k in range(3):
            if output[j, k] <= -math.pi:
                output[j, k] = output[j, k] + math.tau

            if output[j, k] >= +math.pi:
                output[j, k] = output[j, k] - math.tau

    if degrees:
        output = torch.rad2deg(output)

    return output

beignet.quaternion_to_rotation_matrix

quaternion_to_rotation_matrix(input)

Convert rotation quaternions to rotation matrices.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required

Returns:

Name Type Description
output Tensor, shape=(..., 3, 3)

Rotation matrices.

Source code in src/beignet/_quaternion_to_rotation_matrix.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def quaternion_to_rotation_matrix(input: Tensor) -> Tensor:
    r"""
    Convert rotation quaternions to rotation matrices.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    Returns
    -------
    output : Tensor, shape=(..., 3, 3)
        Rotation matrices.
    """
    output = torch.empty(
        [input.shape[0], 3, 3],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    for j in range(input.shape[0]):
        a = input[j, 0]
        b = input[j, 1]
        c = input[j, 2]
        d = input[j, 3]

        output[j, 0, 0] = +(a**2.0) - b**2.0 - c**2.0 + d**2.0
        output[j, 1, 1] = -(a**2.0) + b**2.0 - c**2.0 + d**2.0
        output[j, 2, 2] = -(a**2.0) - b**2.0 + c**2.0 + d**2.0

        output[j, 0, 1] = 2.0 * (a * b) - 2.0 * (c * d)
        output[j, 0, 2] = 2.0 * (a * c) + 2.0 * (b * d)
        output[j, 1, 0] = 2.0 * (a * b) + 2.0 * (c * d)
        output[j, 1, 2] = 2.0 * (b * c) - 2.0 * (a * d)
        output[j, 2, 0] = 2.0 * (a * c) - 2.0 * (b * d)
        output[j, 2, 1] = 2.0 * (b * c) + 2.0 * (a * d)

    return output

beignet.quaternion_to_rotation_vector

quaternion_to_rotation_vector(input, degrees=False)

Convert rotation quaternions to rotation vectors.

Parameters:

Name Type Description Default
input Tensor, shape=(..., 4)

Rotation quaternions. Rotation quaternions are normalized to unit norm.

required
degrees bool
False

Returns:

Name Type Description
output Tensor, shape=(..., 3)

Rotation vectors.

Source code in src/beignet/_quaternion_to_rotation_vector.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def quaternion_to_rotation_vector(
    input: Tensor,
    degrees: bool | None = False,
) -> Tensor:
    r"""
    Convert rotation quaternions to rotation vectors.

    Parameters
    ----------
    input : Tensor, shape=(..., 4)
        Rotation quaternions. Rotation quaternions are normalized to unit norm.

    degrees : bool, optional

    Returns
    -------
    output : Tensor, shape=(..., 3)
        Rotation vectors.
    """
    output = torch.empty(
        [input.shape[0], 3],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    for j in range(input.shape[0]):
        a = input[j, 0]
        b = input[j, 1]
        c = input[j, 2]
        d = input[j, 3]

        if d == 0 and (a == 0 and (b == 0 and c < 0 or b < 0) or a < 0) or d < 0:
            input[j] = -input[j]

        t = input[j, 0] ** 2.0
        u = input[j, 1] ** 2.0
        v = input[j, 2] ** 2.0
        w = input[j, 3] ** 1.0

        y = 2.0 * torch.atan2(torch.sqrt(t + u + v), w)

        if y < 0.001:
            y = 2.0 + y**2.0 / 12 + 7 * y**2.0 * y**2.0 / 2880
        else:
            y = y / torch.sin(y / 2.0)

        output[j] = input[j, :-1] * y

    if degrees:
        output = torch.rad2deg(output)

    return output

beignet.random_quaternion

random_quaternion(size, canonical=False, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False)

Generate random rotation quaternions.

Parameters:

Name Type Description Default
size int

Output size.

required
canonical bool

Whether to map the redundant double cover of rotation space to a unique canonical single cover. If True, then the rotation quaternion is chosen from :math:{q, -q} such that the :math:w term is positive. If the :math:w term is :math:0, then the rotation quaternion is chosen such that the first non-zero term of the :math:x, :math:y, and :math:z terms is positive.

False
generator Generator

Psuedo-random number generator. Default, None.

None
out Tensor

Output tensor. Default, None.

None
dtype dtype

Type of the returned tensor. Default, global default.

None
layout layout

Layout of the returned tensor. Default, torch.strided.

strided
device device

Device of the returned tensor. Default, current device for the default tensor type.

None
requires_grad bool

Whether autograd records operations on the returned tensor. Default, False.

False
pin_memory bool

If True, returned tensor is allocated in pinned memory. Default, False.

False

Returns:

Name Type Description
random_quaternions (Tensor, shape(..., 4))

Random rotation quaternions.

Source code in src/beignet/_random_quaternion.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def random_quaternion(
    size: int,
    canonical: bool = False,
    *,
    generator: Generator | None = None,
    out: Tensor | None = None,
    dtype: torch.dtype | None = None,
    layout: torch.layout | None = torch.strided,
    device: torch.device | None = None,
    requires_grad: bool | None = False,
    pin_memory: bool | None = False,
) -> Tensor:
    r"""
    Generate random rotation quaternions.

    Parameters
    ----------
    size : int
        Output size.

    canonical : bool, optional
        Whether to map the redundant double cover of rotation space to a unique
        canonical single cover. If `True`, then the rotation quaternion is
        chosen from :math:`{q, -q}` such that the :math:`w` term is positive.
        If the :math:`w` term is :math:`0`, then the rotation quaternion is
        chosen such that the first non-zero term of the :math:`x`, :math:`y`,
        and :math:`z` terms is positive.

    generator : torch.Generator, optional
        Psuedo-random number generator. Default, `None`.

    out : Tensor, optional
        Output tensor. Default, `None`.

    dtype : torch.dtype, optional
        Type of the returned tensor. Default, global default.

    layout : torch.layout, optional
        Layout of the returned tensor. Default, `torch.strided`.

    device : torch.device, optional
        Device of the returned tensor. Default, current device for the default
        tensor type.

    requires_grad : bool, optional
        Whether autograd records operations on the returned tensor. Default,
        `False`.

    pin_memory : bool, optional
        If `True`, returned tensor is allocated in pinned memory. Default,
        `False`.

    Returns
    -------
    random_quaternions : Tensor, shape (..., 4)
        Random rotation quaternions.
    """
    quaternions = torch.rand(
        [size, 4],
        generator=generator,
        out=out,
        dtype=dtype,
        layout=layout,
        device=device,
        requires_grad=requires_grad,
        pin_memory=pin_memory,
    )

    if canonical:
        for index in range(quaternions.size(0)):
            if (
                (quaternions[index][3] < 0)
                or (quaternions[index][3] == 0 and quaternions[index][0] < 0)
                or (
                    quaternions[index][3] == 0
                    and quaternions[index][0] == 0
                    and quaternions[index][1] < 0
                )
                or (
                    quaternions[index][3] == 0
                    and quaternions[index][0] == 0
                    and quaternions[index][1] == 0
                    and quaternions[index][2] < 0
                )
            ):
                quaternions[index][0] *= -1.0
                quaternions[index][1] *= -1.0
                quaternions[index][2] *= -1.0
                quaternions[index][3] *= -1.0

    return quaternions