Skip to content

Euler angle

beignet.apply_euler_angle

apply_euler_angle(input, rotation, axes, degrees=False, inverse=False)

Rotates vectors in three-dimensional space using Euler angles.

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

Vectors in three-dimensional space with the shape \((\ldots \times 3)\). Euler angles and vectors must conform to PyTorch broadcasting rules.

required
rotation Tensor

Euler angles with the shape \((\ldots \times 3)\), specifying the rotation in three-dimensional space.

required
axes str

Specifies the sequence of axes for the rotations, using one to three characters from the set \({X, Y, Z}\) for intrinsic rotations, or \({x, y, z}\) for extrinsic rotations. Mixing extrinsic and intrinsic rotations raises a ValueError.

required
degrees bool

Indicates whether the Euler angles are provided in degrees. If False, angles are assumed to be in radians. Default, False.

False
inverse bool

If True, applies the inverse rotation using the Euler angles to the input vectors. Default, False.

False

Returns:

Name Type Description
output Tensor

A tensor of the same shape as input, containing the rotated vectors.

Source code in src/beignet/_apply_euler_angle.py
 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
def apply_euler_angle(
    input: Tensor,
    rotation: Tensor,
    axes: str,
    degrees: bool = False,
    inverse: bool = False,
) -> Tensor:
    r"""
    Rotates vectors in three-dimensional space using Euler angles.

    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
        Vectors in three-dimensional space with the shape $(\ldots \times 3)$.
        Euler angles and vectors must conform to PyTorch broadcasting rules.

    rotation : Tensor
        Euler angles with the shape $(\ldots \times 3)$, specifying the
        rotation in three-dimensional space.

    axes : str
        Specifies the sequence of axes for the rotations, using one to three
        characters from the set ${X, Y, Z}$ for intrinsic rotations, or
        ${x, y, z}$ for extrinsic rotations. Mixing extrinsic and intrinsic
        rotations raises a `ValueError`.

    degrees : bool, optional
        Indicates whether the Euler angles are provided in degrees. If `False`,
        angles are assumed to be in radians. Default, `False`.

    inverse : bool, optional
        If `True`, applies the inverse rotation using the Euler angles to the
        input vectors. Default, `False`.

    Returns
    -------
    output : Tensor
        A tensor of the same shape as `input`, containing the rotated vectors.
    """
    return apply_rotation_matrix(
        input,
        euler_angle_to_rotation_matrix(
            rotation,
            axes,
            degrees,
        ),
        inverse,
    )

beignet.compose_euler_angle

compose_euler_angle(input, other, axes, degrees=False)

Compose rotation quaternions.

Parameters:

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

Euler angles.

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

Euler angles.

required
axes str

Axes. One to three characters belonging to the set :math:\{X, Y, Z\} for intrinsic rotations, or :math:\{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)

Composed Euler angles.

Source code in src/beignet/_compose_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
def compose_euler_angle(
    input: Tensor,
    other: Tensor,
    axes: str,
    degrees: bool | None = False,
) -> Tensor:
    r"""
    Compose rotation quaternions.

    Parameters
    ----------
    input : Tensor, shape=(..., 3)
        Euler angles.

    other : Tensor, shape=(..., 3)
        Euler angles.

    axes : str
        Axes. One to three characters belonging to the set :math:`\{X, Y, Z\}`
        for intrinsic rotations, or :math:`\{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)
        Composed Euler angles.
    """
    return quaternion_to_euler_angle(
        compose_quaternion(
            euler_angle_to_quaternion(
                input,
                axes,
                degrees,
            ),
            euler_angle_to_quaternion(
                other,
                axes,
                degrees,
            ),
        ),
        axes,
        degrees,
    )

beignet.euler_angle_identity

euler_angle_identity(size, axes, degrees=False, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

Identity Euler angles.

Parameters:

Name Type Description Default
size int

Output size.

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
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_euler_angles (Tensor, shape(size, 3))

Identity Euler angles.

Source code in src/beignet/_euler_angle_identity.py
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
def euler_angle_identity(
    size: int,
    axes: str,
    degrees: 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 Euler angles.

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

    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`.

    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_euler_angles : Tensor, shape (size, 3)
        Identity Euler angles.
    """
    return quaternion_to_euler_angle(
        quaternion_identity(
            size,
            out=out,
            dtype=dtype,
            layout=layout,
            device=device,
            requires_grad=requires_grad,
        ),
        axes,
        degrees,
    )

beignet.euler_angle_magnitude

euler_angle_magnitude(input, axes, degrees=False)

Euler angle magnitudes.

Parameters:

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

Euler angles.

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
euler_angle_magnitudes (Tensor, shape(...))

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

Source code in src/beignet/_euler_angle_magnitude.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
def euler_angle_magnitude(
    input: Tensor,
    axes: str,
    degrees: bool | None = False,
) -> Tensor:
    r"""
    Euler angle magnitudes.

    Parameters
    ----------
    input : Tensor, shape (..., 3)
        Euler angles.

    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
    -------
    euler_angle_magnitudes: Tensor, shape (...)
        Angles in radians. Magnitudes will be in the range :math:`[0, \pi]`.
    """
    return quaternion_magnitude(
        euler_angle_to_quaternion(
            input,
            axes,
            degrees,
        ),
    )

beignet.euler_angle_mean

euler_angle_mean(input, weight=None, axes=None, degrees=False)

Euler angle mean.

Parameters:

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

Euler angles.

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

Relative importance of rotation quaternions.

None
axes str

Axes. One to three characters belonging to the set :math:\{X, Y, Z\} for intrinsic rotations, or :math:\{x, y, z\} for extrinsic rotations. Extrinsic and intrinsic rotations cannot be mixed.

None
degrees bool

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

False

Returns:

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

Euler angle mean.

Source code in src/beignet/_euler_angle_mean.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
def euler_angle_mean(
    input: Tensor,
    weight: Tensor | None = None,
    axes: str | None = None,
    degrees: bool | None = False,
) -> Tensor:
    r"""
    Euler angle mean.

    Parameters
    ----------
    input : Tensor, shape=(..., 3)
        Euler angles.

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

    axes : str
        Axes. One to three characters belonging to the set :math:`\{X, Y, Z\}`
        for intrinsic rotations, or :math:`\{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 angle mean.
    """
    return quaternion_to_euler_angle(
        quaternion_mean(
            euler_angle_to_quaternion(
                input,
                axes,
                degrees,
            ),
            weight,
        ),
        axes,
        degrees,
    )

beignet.euler_angle_to_quaternion

euler_angle_to_quaternion(input, axes, degrees=False, canonical=False)

Convert Euler angles to rotation quaternions.

Parameters:

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

Euler angles.

required
axes str

Axes. One to three characters belonging to the set :math:\{X, Y, Z\} for intrinsic rotations, or :math:\{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
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)

Rotation quaternions.

Source code in src/beignet/_euler_angle_to_quaternion.py
  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
 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def euler_angle_to_quaternion(
    input: Tensor,
    axes: str,
    degrees: bool = False,
    canonical: bool | None = False,
) -> Tensor:
    r"""
    Convert Euler angles to rotation quaternions.

    Parameters
    ----------
    input : Tensor, shape=(..., 3)
        Euler angles.

    axes : str
        Axes. One to three characters belonging to the set :math:`\{X, Y, Z\}`
        for intrinsic rotations, or :math:`\{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`.

    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)
        Rotation quaternions.
    """
    intrinsic = re.match(r"^[XYZ]{1,3}$", axes) is not None

    if degrees:
        input = torch.deg2rad(input)

    if len(axes) == 1:
        if input.ndim == 0:
            input = input.reshape([1, 1])
        elif input.ndim == 1:
            input = input[:, None]
        elif input.ndim == 2 and input.shape[-1] != 1:
            raise ValueError
        elif input.ndim > 2:
            raise ValueError
    else:
        if input.ndim not in [1, 2] or input.shape[-1] != len(axes):
            raise ValueError

        if input.ndim == 1:
            input = input[None, :]

    if input.ndim != 2 or input.shape[-1] != len(axes):
        raise ValueError

    output = torch.zeros(
        [input.shape[0], 4],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    match axes.lower()[0]:
        case "x":
            k = 0
        case "y":
            k = 1
        case "z":
            k = 2
        case _:
            raise ValueError

    for j in range(input[:, 0].shape[0]):
        output[j, 3] = torch.cos(input[:, 0][j] / 2)
        output[j, k] = torch.sin(input[:, 0][j] / 2)

    z = output

    c = torch.empty(
        [3],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    for j in range(1, len(axes.lower())):
        y = torch.zeros(
            [input.shape[0], 4],
            dtype=input.dtype,
            layout=input.layout,
            device=input.device,
        )

        r = torch.empty(
            [max(y.shape[0], z.shape[0]), 4],
            dtype=input.dtype,
            layout=input.layout,
            device=input.device,
        )

        match axes.lower()[j]:
            case "x":
                p = 0
            case "y":
                p = 1
            case "z":
                p = 2
            case _:
                raise ValueError

        for k in range(input[:, j].shape[0]):
            y[k, 3] = torch.cos(input[:, j][k] / 2)
            y[k, p] = torch.sin(input[:, j][k] / 2)

        if intrinsic:
            for k in range(max(y.shape[0], z.shape[0])):
                c[0] = z[k, 1] * y[k, 2] - z[k, 2] * y[k, 1]
                c[1] = z[k, 2] * y[k, 0] - z[k, 0] * y[k, 2]
                c[2] = z[k, 0] * y[k, 1] - z[k, 1] * y[k, 0]

                t = z[k, 0]
                u = z[k, 1]
                v = z[k, 2]
                w = z[k, 3]

                r[k, 0] = w * y[k, 0] + y[k, 3] * t + c[0]
                r[k, 1] = w * y[k, 1] + y[k, 3] * u + c[1]
                r[k, 2] = w * y[k, 2] + y[k, 3] * v + c[2]
                r[k, 3] = w * y[k, 3] - t * y[k, 0] - u * y[k, 1] - v * y[k, 2]

            z = r
        else:
            for k in range(max(y.shape[0], z.shape[0])):
                c[0] = y[k, 1] * z[k, 2] - y[k, 2] * z[k, 1]
                c[1] = y[k, 2] * z[k, 0] - y[k, 0] * z[k, 2]
                c[2] = y[k, 0] * z[k, 1] - y[k, 1] * z[k, 0]

                t = z[k, 0]
                u = z[k, 1]
                v = z[k, 2]
                w = z[k, 3]

                r[k, 0] = y[k, 3] * t + w * y[k, 0] + c[0]
                r[k, 1] = y[k, 3] * u + w * y[k, 1] + c[1]
                r[k, 2] = y[k, 3] * v + w * y[k, 2] + c[2]
                r[k, 3] = y[k, 3] * w - y[k, 0] * t - y[k, 1] * u - y[k, 2] * v

            z = r

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

            if d == 0 and (a == 0 & (b == 0 & c < 0 | b < 0) | a < 0) | d < 0:
                z[j] = -z[j]

    return z

beignet.euler_angle_to_rotation_matrix

euler_angle_to_rotation_matrix(input, axes, degrees=False)

Convert Euler angles to rotation matrices.

Parameters:

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

Euler angles.

required
axes str

Axes. One to three characters belonging to the set :math:\{X, Y, Z\} for intrinsic rotations, or :math:\{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, 3)

Rotation matrices.

Source code in src/beignet/_euler_angle_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
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
def euler_angle_to_rotation_matrix(
    input: Tensor,
    axes: str,
    degrees: bool = False,
) -> Tensor:
    r"""
    Convert Euler angles to rotation matrices.

    Parameters
    ----------
    input : Tensor, shape=(..., 3)
        Euler angles.

    axes : str
        Axes. One to three characters belonging to the set :math:`\{X, Y, Z\}`
        for intrinsic rotations, or :math:`\{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, 3)
        Rotation matrices.
    """
    if degrees:
        input = torch.deg2rad(input)

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

    for j, axis in enumerate(axes):
        a = torch.cos(input[..., j])
        b = torch.sin(input[..., j])

        p = torch.full_like(a, 1.0)
        q = torch.full_like(a, 0.0)

        match axis.lower():
            case "x":
                x = [
                    torch.stack([+p, +q, +q], dim=-1),
                    torch.stack([+q, +a, -b], dim=-1),
                    torch.stack([+q, +b, +a], dim=-1),
                ]
            case "y":
                x = [
                    torch.stack([+a, +q, +b], dim=-1),
                    torch.stack([+q, +p, +q], dim=-1),
                    torch.stack([-b, +q, +a], dim=-1),
                ]
            case "z":
                x = [
                    torch.stack([+a, -b, +q], dim=-1),
                    torch.stack([+b, +a, +q], dim=-1),
                    torch.stack([+q, +q, +p], dim=-1),
                ]
            case _:
                raise ValueError

        x = torch.stack(x, dim=-2)

        if j == 0:
            output = x
        else:
            if axes.islower():
                output = x @ output
            else:
                output = output @ x

    return output

beignet.euler_angle_to_rotation_vector

euler_angle_to_rotation_vector(input, axes, degrees=False)

Convert Euler angles to rotation vectors.

Parameters:

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

Euler angles.

required
axes str

Axes. One to three characters belonging to the set :math:\{X, Y, Z\} for intrinsic rotations, or :math:\{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 and returned rotation vector magnitudes are in degrees. Default, False.

False

Returns:

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

Rotation vectors.

Source code in src/beignet/_euler_angle_to_rotation_vector.py
  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
 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def euler_angle_to_rotation_vector(
    input: Tensor,
    axes: str,
    degrees: bool = False,
) -> Tensor:
    r"""
    Convert Euler angles to rotation vectors.

    Parameters
    ----------
    input : Tensor, shape=(..., 3)
        Euler angles.

    axes : str
        Axes. One to three characters belonging to the set :math:`\{X, Y, Z\}`
        for intrinsic rotations, or :math:`\{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 and returned
        rotation vector magnitudes are in degrees. Default, `False`.

    Returns
    -------
    output : Tensor, shape=(..., 3)
        Rotation vectors.
    """
    num_axes = len(axes)

    if num_axes < 1 or num_axes > 3:
        raise ValueError

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

    if not (intrinsic or extrinsic):
        raise ValueError

    if any(axes[i] == axes[i + 1] for i in range(num_axes - 1)):
        raise ValueError

    if degrees:
        input = torch.deg2rad(input)

    if len(axes.lower()) == 1:
        match input.ndim:
            case 0:
                input = torch.reshape(input, [1, 1])
            case 1:
                input = input[:, None]
            case 2 if input.shape[-1] != 1:
                raise ValueError
            case _:
                raise ValueError

    else:
        if input.ndim not in [1, 2] or input.shape[-1] != len(axes.lower()):
            raise ValueError

        if input.ndim == 1:
            input = input[None, :]

    if input.ndim != 2 or input.shape[-1] != len(axes.lower()):
        raise ValueError

    x = torch.zeros(
        [input.shape[0], 4],
        dtype=input.dtype,
        layout=input.layout,
        device=input.device,
    )

    match axes.lower()[0]:
        case "x":
            m = 0
        case "y":
            m = 1
        case "z":
            m = 2
        case _:
            raise ValueError

    for j in range(input[:, 0].shape[0]):
        x[j, 3] = torch.cos(input[:, 0][j] / 2)
        x[j, m] = torch.sin(input[:, 0][j] / 2)

    for j in range(1, len(axes)):
        y = torch.zeros(
            [input.shape[0], 4],
            dtype=input.dtype,
            layout=input.layout,
            device=input.device,
        )

        z = torch.empty(
            [max(y.shape[0], x.shape[0]), 4],
            dtype=input.dtype,
            layout=input.layout,
            device=input.device,
        )

        match axes.lower()[j]:
            case "x":
                m = 0
            case "y":
                m = 1
            case "z":
                m = 2
            case _:
                raise ValueError

        for k in range(input[:, j].shape[0]):
            y[k, 3] = torch.cos(input[:, j][k] / 2)
            y[k, m] = torch.sin(input[:, j][k] / 2)

        if intrinsic:
            if x.shape[0] == 1:
                for k in range(max(x.shape[0], y.shape[0])):
                    q = y[k, 1]
                    r = y[k, 2]
                    s = y[k, 3]
                    p = y[k, 0]

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

                    z[k, 0] = w * p + s * t + u * r - v * q
                    z[k, 1] = w * q + s * u + v * p - t * r
                    z[k, 2] = w * r + s * v + t * q - u * p
                    z[k, 3] = w * s - t * p - u * q - v * r
            elif y.shape[0] == 1:
                for k in range(max(x.shape[0], y.shape[0])):
                    p = y[0, 0]
                    q = y[0, 1]
                    r = y[0, 2]
                    s = y[0, 3]

                    t = x[k, 0]
                    u = x[k, 1]
                    v = x[k, 2]
                    w = x[k, 3]

                    z[k, 0] = w * p + s * t + u * r - v * q
                    z[k, 1] = w * q + s * u + v * p - t * r
                    z[k, 2] = w * r + s * v + t * q - u * p
                    z[k, 3] = w * s - t * p - u * q - v * r
            else:
                for k in range(max(x.shape[0], y.shape[0])):
                    p = y[k, 0]
                    q = y[k, 1]
                    r = y[k, 2]
                    s = y[k, 3]

                    t = x[k, 0]
                    u = x[k, 1]
                    v = x[k, 2]
                    w = x[k, 3]

                    z[k, 0] = w * p + s * t + u * r - v * q
                    z[k, 1] = w * q + s * u + v * p - t * r
                    z[k, 2] = w * r + s * v + t * q - u * p
                    z[k, 3] = w * s - t * p - u * q - v * r

            x = z
        else:
            if y.shape[0] == 1:
                for k in range(max(y.shape[0], x.shape[0])):
                    p = y[0, 0]
                    q = y[0, 1]
                    r = y[0, 2]
                    s = y[0, 3]

                    t = x[k, 0]
                    u = x[k, 1]
                    v = x[k, 2]
                    w = x[k, 3]

                    z[k, 0] = s * t + w * p + q * v - r * u
                    z[k, 1] = s * u + w * q + r * t - p * v
                    z[k, 2] = s * v + w * r + p * u - q * t
                    z[k, 3] = s * w - p * t - q * u - r * v
            elif x.shape[0] == 1:
                for k in range(max(y.shape[0], x.shape[0])):
                    t = x[0, 0]
                    u = x[0, 1]
                    v = x[0, 2]
                    w = x[0, 3]

                    p = y[k, 0]
                    q = y[k, 1]
                    r = y[k, 2]
                    s = y[k, 3]

                    z[k, 0] = s * t + w * p + q * v - r * u
                    z[k, 1] = s * u + w * q + r * t - p * v
                    z[k, 2] = s * v + w * r + p * u - q * t
                    z[k, 3] = s * w - p * t - q * u - r * v
            else:
                for k in range(max(y.shape[0], x.shape[0])):
                    p = y[k, 0]
                    q = y[k, 1]
                    r = y[k, 2]
                    s = y[k, 3]

                    t = x[k, 0]
                    u = x[k, 1]
                    v = x[k, 2]
                    w = x[k, 3]

                    z[k, 0] = s * t + w * p + q * v - r * u
                    z[k, 1] = s * u + w * q + r * t - p * v
                    z[k, 2] = s * v + w * r + p * u - q * t
                    z[k, 3] = s * w - p * t - q * u - r * v

            x = z

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

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

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

        y = 2.0 * torch.atan2(torch.sqrt(a**2.0 + b**2.0 + c**2.0), d**1.0)

        if y < 0.001:
            y = 2.0 + y**2.0 / 12.0 + 7.0 * y**2.0 * y**2.0 / 2880.0
        else:
            y = y / torch.sin(y / 2.0)

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

    if degrees:
        output = torch.rad2deg(output)

    return output

beignet.invert_euler_angle

invert_euler_angle(input, axes, degrees=False)

Invert Euler angles.

Parameters:

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

Euler angles.

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
inverted_euler_angles (Tensor, shape(..., 3))

Inverted Euler angles.

Source code in src/beignet/_invert_euler_angle.py
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
def invert_euler_angle(
    input: Tensor,
    axes: str,
    degrees: bool | None = False,
) -> Tensor:
    r"""
    Invert Euler angles.

    Parameters
    ----------
    input : Tensor, shape (..., 3)
        Euler angles.

    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
    -------
    inverted_euler_angles : Tensor, shape (..., 3)
        Inverted Euler angles.
    """
    return quaternion_to_euler_angle(
        invert_quaternion(
            euler_angle_to_quaternion(
                input,
                axes,
                degrees,
            ),
        ),
        axes,
        degrees,
    )

beignet.random_euler_angle

random_euler_angle(size, axes, degrees=False, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False)

Generate random Euler angles.

Parameters:

Name Type Description Default
size int

Output size.

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
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_euler_angles (Tensor, shape(..., 3))

Random 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/_random_euler_angle.py
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
def random_euler_angle(
    size: int,
    axes: str,
    degrees: bool | None = 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 Euler angles.

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

    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`.

    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_euler_angles : Tensor, shape (..., 3)
        Random 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)
    """
    return quaternion_to_euler_angle(
        random_quaternion(
            size,
            generator=generator,
            out=out,
            dtype=dtype,
            layout=layout,
            device=device,
            requires_grad=requires_grad,
            pin_memory=pin_memory,
        ),
        axes,
        degrees,
    )