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:
ducha-aiki: Looks great! Could you please add tests as well?

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 45fd9e5c..c7a59654 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,7 @@ repos:
       - id: pyproject-fmt
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.1.6
+    rev: v0.1.7
     hooks:
       - id: ruff
         args: [--fix, --exit-non-zero-on-fix]
diff --git a/kornia/contrib/extract_patches.py b/kornia/contrib/extract_patches.py
index b16eaf05..7552a3d9 100644
--- a/kornia/contrib/extract_patches.py
+++ b/kornia/contrib/extract_patches.py
@@ -80,10 +80,6 @@ class ExtractTensorPatches(Module):
         - a ``tuple`` of two ints -- in which case, the first `int` is used for
           the height dimension, and the second `int` for the width dimension.
 
-    :attr:`padding` can also be a ``tuple`` of four ints -- in which case, the
-    first two ints are for the height dimension while the last two ints are for
-    the width dimension.
-
     Args:
         window_size: the size of the sliding window and the output patch size.
         stride: stride of the sliding window.
@@ -155,10 +151,6 @@ class CombineTensorPatches(Module):
         - a ``tuple`` of two ints -- in which case, the first `int` is used for
           the height dimension, and the second `int` for the width dimension.
 
-    :attr:`unpadding` can also be a ``tuple`` of four ints -- in which case, the
-    first two ints are for the height dimension while the last two ints are for
-    the width dimension.
-
     Args:
         patches: patched tensor.
         original_size: the size of the original tensor and the output patch size.
diff --git a/kornia/feature/loftr/utils/fine_matching.py b/kornia/feature/loftr/utils/fine_matching.py
index c134ae8e..a6638e66 100644
--- a/kornia/feature/loftr/utils/fine_matching.py
+++ b/kornia/feature/loftr/utils/fine_matching.py
@@ -47,7 +47,7 @@ class FineMatching(Module):
             )
             return
 
-        feat_f0_picked = feat_f0_picked = feat_f0[:, WW // 2, :]
+        feat_f0_picked = feat_f0[:, WW // 2, :]
         sim_matrix = torch.einsum("mc,mrc->mr", feat_f0_picked, feat_f1)
         softmax_temp = 1.0 / C**0.5
         heatmap = torch.softmax(softmax_temp * sim_matrix, dim=1).view(-1, W, W)
diff --git a/kornia/geometry/quaternion.py b/kornia/geometry/quaternion.py
index 62806e67..3d4d190c 100644
--- a/kornia/geometry/quaternion.py
+++ b/kornia/geometry/quaternion.py
@@ -9,7 +9,9 @@ from kornia.core import Device, Dtype, Module, Parameter, Tensor, concatenate, r
 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 @@ class Quaternion(Module):
         """
         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.
diff --git a/kornia/nerf/samplers.py b/kornia/nerf/samplers.py
index b27c459f..720b2942 100755
--- a/kornia/nerf/samplers.py
+++ b/kornia/nerf/samplers.py
@@ -213,7 +213,7 @@ class RaySampler:
 
     @staticmethod
     def _build_num_ray_dict_of_points2d(
-        points2d_as_flat_tensors: Dict[int, Points2D_FlatTensors]
+        points2d_as_flat_tensors: Dict[int, Points2D_FlatTensors],
     ) -> Dict[int, Points2D]:
         r"""Builds a dictionary of ray pixel points, by total number of rays as key. The dictionary groups rays by
         the total amount of rays, which allows the case of casting different number of rays from each scene camera.


Test patch:
diff --git a/test/geometry/test_quaternion.py b/test/geometry/test_quaternion.py
index 1ba9a654..8bcd713f 100644
--- a/test/geometry/test_quaternion.py
+++ b/test/geometry/test_quaternion.py
@@ -221,3 +221,38 @@ class TestQuaternion:
                 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)