Repository: kornia/kornia

Issue:
`RandomMotionBlur` fails when `kernel_size` is a `Tuple[int, int]`
### Describe the bug

Using the `RandomMotionBlur` augmentation with a tuple for `kernel_size` throws a `TypeError`. This appears to happen because internally `apply_transform` is trying to convert `kernel_size` to an `int` while receiving a collection of numbers

### Reproduction steps

```python
import kornia as K
import torch

x = torch.rand(3,3,224,224)
tfm = K.augmentation.RandomMotionBlur((7,21), (15.,15.), (-1.,1.), p=1)

tfm(x)
```


### Expected behavior

Successfully perform the transform with a different kernel size every batch

### Environment

```shell
wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py
# For security purposes, please check the contents of collect_env.py before running it.
python collect_env.py
```
- PyTorch Version (e.g., 1.0): 1.10.1
- OS (e.g., Linux): macOS 12.0.1 (x86_64)
- How you installed PyTorch (`conda`, `pip`, source): pip
- Python version: 3.7.10
- CUDA/cuDNN version: NA
- GPU models and configuration: NA
- Any other relevant information: NA




### Additional context

Full stack trace:

```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-a535d4587048> in <module>
----> 1 tfm(x)

~/miniconda3/lib/python3.7/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
   1100         if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
   1101                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1102             return forward_call(*input, **kwargs)
   1103         # Do not call functions when jit is used
   1104         full_backward_hooks, non_full_backward_hooks = [], []

~/miniconda3/lib/python3.7/site-packages/kornia/augmentation/base.py in forward(self, input, params, return_transform)
    268 
    269         self._params = params
--> 270         output = self.apply_func(in_tensor, in_transform, self._params, return_transform)
    271         return _transform_output_shape(output, ori_shape) if self.keepdim else output
    272 

~/miniconda3/lib/python3.7/site-packages/kornia/augmentation/base.py in apply_func(self, in_tensor, in_transform, params, return_transform)
    227         elif torch.sum(to_apply) == len(to_apply):
    228             trans_matrix = self.compute_transformation(in_tensor, params)
--> 229             output = self.apply_transform(in_tensor, params, trans_matrix)
    230         else:
    231             output = in_tensor.clone()

~/miniconda3/lib/python3.7/site-packages/kornia/augmentation/augmentation.py in apply_transform(self, input, params, transform)
   1400         self, input: torch.Tensor, params: Dict[str, torch.Tensor], transform: Optional[torch.Tensor] = None
   1401     ) -> torch.Tensor:
-> 1402         kernel_size: int = cast(int, params["ksize_factor"].unique().item())
   1403         angle = params["angle_factor"]
   1404         direction = params["direction_factor"]

ValueError: only one element tensors can be converted to Python scalars
```


Discussion:
This actually seems to go more deeper :)
https://github.com/kornia/kornia/blob/master/kornia/filters/kernels_geometry.py#L10

Feel free to give it a shot. We can assist you if you face some issues while implementing /cc @shijianjian @twsl 
@edgarriba thanks for the quick response!

I'll be happy to take a crack at it. In my view, the simplest solution would be to keep the `kernel_size: int` as is in the inner functions and simply pick one of `batch_size` `ksize_factor` inside `RandomMotionBlur.apply_transform`. Something like:

```python

class RandomMotionBlur(IntensityAugmentationBase2D):
    ...

    def apply_transform(
        self, input: torch.Tensor, params: Dict[str, torch.Tensor], transform: Optional[torch.Tensor] = None
    ) -> torch.Tensor:
        # kernel_size: int = cast(int, params["ksize_factor"].unique().item())
        kernel_size: int = cast(int, random.choice(params["ksize_factor"]).item())
        angle = params["angle_factor"]
        direction = params["direction_factor"]
        return motion_blur(
            input,
            kernel_size,
            angle,
            direction,
            border_type=self.flags["border_type"].name.lower(),
            mode=self.flags["resample"].name.lower(),
        )
```

The other, more complicated route would be to make `get_motion_kernel2d` accept a `tuple[int,int] | int` as input, which would perhaps play out as follows:
* `motion_blur` also accepts a tuple for `kernel_size`
* `get_motion_kernel2d` also accepts a tuple for `kernel_size` -- internally it's setting the width and height of the kernel anyway
* `RandomMotionBlur.apply_transform` would need to pass a tuple to `get_motion_kernel2d`.
    - This is the part that gets tricky. I think we'd need to modify `...random_generator.MotionBlurGenerator` to produce tuples of random sizes instead of a list of ints, and in the `apply_transform` call, we can pick one of the sizes? 


Curious to hear your thoughts


We could start with 1) and later we expand to 2)/cc @shijianjian ?

Resolving patch:
diff --git a/kornia/augmentation/_2d/intensity/motion_blur.py b/kornia/augmentation/_2d/intensity/motion_blur.py
--- a/kornia/augmentation/_2d/intensity/motion_blur.py
+++ b/kornia/augmentation/_2d/intensity/motion_blur.py
@@ -1,4 +1,4 @@
-from typing import Dict, Optional, Tuple, Union, cast
+from typing import Dict, List, Optional, Tuple, Union, cast
 
 import torch
 
@@ -83,14 +83,14 @@ def __init__(
     def apply_transform(
         self, input: torch.Tensor, params: Dict[str, torch.Tensor], transform: Optional[torch.Tensor] = None
     ) -> torch.Tensor:
-        kernel_size: int = cast(int, params["ksize_factor"].unique().item())
-        angle = params["angle_factor"]
-        direction = params["direction_factor"]
+        # sample a kernel size
+        kernel_size_list: List[int] = params["ksize_factor"].tolist()
+        idx: int = cast(int, torch.randint(len(kernel_size_list), (1,)).item())
         return motion_blur(
             input,
-            kernel_size,
-            angle,
-            direction,
+            kernel_size=kernel_size_list[idx],
+            angle=params["angle_factor"],
+            direction=params["direction_factor"],
             border_type=self.flags["border_type"].name.lower(),
             mode=self.flags["resample"].name.lower(),
         )


Test patch:
diff --git a/test/augmentation/test_motionblur.py b/test/augmentation/test_motionblur.py
--- a/test/augmentation/test_motionblur.py
+++ b/test/augmentation/test_motionblur.py
@@ -21,12 +21,13 @@ def test_smoke(self, device):
         )
         assert str(f) == repr
 
+    @pytest.mark.parametrize("kernel_size", [(3, 5), (7, 21)])
     @pytest.mark.parametrize("same_on_batch", [True, False])
     @pytest.mark.parametrize("return_transform", [True, False])
     @pytest.mark.parametrize("p", [0.0, 1.0])
-    def test_random_motion_blur(self, same_on_batch, return_transform, p, device, dtype):
+    def test_random_motion_blur(self, kernel_size, same_on_batch, return_transform, p, device, dtype):
         f = RandomMotionBlur(
-            kernel_size=(3, 5),
+            kernel_size=kernel_size,
             angle=(10, 30),
             direction=0.5,
             same_on_batch=same_on_batch,