Repository: kornia/kornia

Issue:
Add from / to euler angle in Quaterion
## 🚀 Feature

We would like a direct interface for the [Quaternion](https://github.com/kornia/kornia/blob/02de36a3dbe6fe7361c9a5cea78974413f09f67d/kornia/geometry/quaternion.py#L19) class to go from / to Euler angles implemented in
https://github.com/kornia/kornia/blob/02de36a3dbe6fe7361c9a5cea78974413f09f67d/kornia/geometry/conversions.py#L739

```python
quat = Quaternion.from_euler(roll, pitch, yaw)
euler: Tensor = quat.to_euler()

```

______________________________________________________________________

#### Consider also to contribute to Kornia universe projects :)

<sub>

- [**Tutorials**](https://github.com/kornia/tutorials): our repository containing the tutorials.

</sub>



Discussion:
@edgarriba can I take this?

Resolving patch:
diff --git a/kornia/geometry/quaternion.py b/kornia/geometry/quaternion.py
--- a/kornia/geometry/quaternion.py
+++ b/kornia/geometry/quaternion.py
@@ -9,7 +9,9 @@
 from kornia.core.check import KORNIA_CHECK_TYPE
 from kornia.geometry.conversions import (
     axis_angle_to_quaternion,
+    euler_from_quaternion,
     normalize_quaternion,
+    quaternion_from_euler,
     quaternion_to_rotation_matrix,
     rotation_matrix_to_quaternion,
 )
@@ -258,6 +260,44 @@ def from_matrix(cls, matrix: Tensor) -> "Quaternion":
         """
         return cls(rotation_matrix_to_quaternion(matrix))
 
+    @classmethod
+    def from_euler(cls, roll: Tensor, pitch: Tensor, yaw: Tensor) -> "Quaternion":
+        """Create a quaternion from euler angles.
+
+        Args:
+            roll: the roll euler angle.
+            pitch: the pitch euler angle.
+            yaw: the yaw euler angle.
+
+        Example:
+            >>> roll, pitch, yaw = tensor(0), tensor(1), tensor(0)
+            >>> q = Quaternion.from_euler(roll, pitch, yaw)
+            >>> q.data
+            Parameter containing:
+            tensor([0.8776, 0.0000, 0.4794, 0.0000], requires_grad=True)
+        """
+        w, x, y, z = quaternion_from_euler(roll=roll, pitch=pitch, yaw=yaw)
+        q = stack((w, x, y, z), -1)
+        return cls(q)
+
+    def to_euler(self) -> Tuple[Tensor, Tensor, Tensor]:
+        """Create a quaternion from euler angles.
+
+        Args:
+            matrix: the rotation matrix to convert of shape :math:`(B, 3, 3)`.
+
+        Example:
+            >>> q = Quaternion(tensor([2., 0., 1., 1.]))
+            >>> roll, pitch, yaw = q.to_euler()
+            >>> roll
+            tensor(2.0344, grad_fn=<Atan2Backward0>)
+            >>> pitch
+            tensor(1.5708, grad_fn=<AsinBackward0>)
+            >>> yaw
+            tensor(2.2143, grad_fn=<Atan2Backward0>)
+        """
+        return euler_from_quaternion(self.w, self.x, self.y, self.z)
+
     @classmethod
     def from_axis_angle(cls, axis_angle: Tensor) -> "Quaternion":
         """Create a quaternion from axis-angle representation.


Test patch:
diff --git a/test/geometry/test_quaternion.py b/test/geometry/test_quaternion.py
--- a/test/geometry/test_quaternion.py
+++ b/test/geometry/test_quaternion.py
@@ -221,3 +221,38 @@ def test_slerp(self, device, dtype, batch_size):
                 q3 = q1.slerp(q2, t)
                 q4 = Quaternion.from_axis_angle(axis * t * 3.14159)
                 self.assert_close(q3, q4)
+
+    def test_from_to_euler_values(self, device, dtype):
+        # num_samples = 5
+        # data = 2 * torch.rand(3, num_samples, device=device, dtype=dtype) - 1
+        # roll, pitch, yaw = torch.pi * data
+        roll = torch.tensor(
+            [2.6518599987, 0.0612506270, 1.2417907715, 2.8829660416, -1.9961174726, 0], device=device, dtype=dtype
+        )
+
+        pitch = torch.tensor(
+            [2.3267219067, -2.7309591770, -1.4011553526, -2.1962766647, 2.1454355717, 0], device=device, dtype=dtype
+        )
+
+        yaw = torch.tensor(
+            [-0.8856627345, 0.2605336905, 0.4579202533, -1.3095731735, 0.6096843481, 0], device=device, dtype=dtype
+        )
+
+        euler_expected = torch.tensor(
+            [
+                [-0.4897327125, 0.8148705959, 2.2559301853],
+                [-3.0803420544, -0.4106334746, -2.8810589314],
+                [1.2417914867, -1.4011553526, 0.4579201937],
+                [-0.2586266696, -0.9453159571, 1.8320195675],
+                [1.1454752684, 0.9961569905, -2.5319085121],
+                [0, 0, 0],
+            ],
+            device=device,
+            dtype=dtype,
+        )
+
+        q = Quaternion.from_euler(roll, pitch, yaw)
+        euler = q.to_euler()
+        euler = torch.stack(euler, -1)
+
+        self.assert_close(euler, euler_expected, 1e-4, 1e-4)