Skip to content

Commit

Permalink
Added unsharp mask filtering (kornia#1004)
Browse files Browse the repository at this point in the history
* Added functionality for unsharp mask

* Added functionality for Unsharp Mask

* Implemented the changes to Unsharp Mask functionality

* fix linter and jit test

* add unsharp to docs

* update python version in gh actions format

Co-authored-by: AnimeshMaheshwari22 <animesh.m2202@gmail.com>
Co-authored-by: AnimeshMaheshwari22 <45392539+AnimeshMaheshwari22@users.noreply.github.com>
  • Loading branch information
3 people committed May 13, 2021
1 parent f725260 commit d57a444
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 3 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/format.yml
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: 3.6
python-version: 3.7
- name: Install dependencies
shell: bash -l {0}
run: |
Expand All @@ -40,7 +40,7 @@ jobs:
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: 3.6
python-version: 3.7
- name: Install dependencies
shell: bash -l {0}
run: |
Expand All @@ -62,7 +62,7 @@ jobs:
uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
python-version: 3.6
python-version: 3.7
- name: Install dependencies
shell: bash -l {0}
run: |
Expand Down
2 changes: 2 additions & 0 deletions docs/source/filters.rst
Expand Up @@ -14,6 +14,7 @@ Blurring
.. autofunction:: median_blur
.. autofunction:: gaussian_blur2d
.. autofunction:: motion_blur
.. autofunction:: unsharp_mask

Kernels
-------
Expand Down Expand Up @@ -46,3 +47,4 @@ Module
.. autoclass:: SpatialGradient
.. autoclass:: SpatialGradient3d
.. autoclass:: MotionBlur
.. autoclass:: UnsharpMask
1 change: 1 addition & 0 deletions kornia/__init__.py
Expand Up @@ -90,6 +90,7 @@
motion_blur,
filter2D,
filter3D,
unsharp_mask,
)
from kornia.losses import (
ssim,
Expand Down
3 changes: 3 additions & 0 deletions kornia/filters/__init__.py
Expand Up @@ -7,6 +7,7 @@
from .median import MedianBlur, median_blur
from .motion import MotionBlur, MotionBlur3D, motion_blur, motion_blur3d
from .filter import filter2D, filter3D
from .unsharp import unsharp_mask, UnsharpMask
from .kernels import (
gaussian,
laplacian_1d,
Expand Down Expand Up @@ -39,6 +40,7 @@
"gaussian_blur2d",
"get_motion_kernel2d",
"laplacian",
"unsharp_mask",
"sobel",
"spatial_gradient",
"box_blur",
Expand All @@ -61,4 +63,5 @@
"MotionBlur3D",
"SpatialGradient3d",
"spatial_gradient3d",
"UnsharpMask",
]
74 changes: 74 additions & 0 deletions kornia/filters/unsharp.py
@@ -0,0 +1,74 @@
from typing import Tuple

import torch
import torch.nn as nn

import kornia
from kornia.filters import gaussian_blur2d


def unsharp_mask(
input: torch.Tensor,
kernel_size: Tuple[int, int],
sigma: Tuple[float, float],
border_type: str = 'reflect') -> torch.Tensor:
r"""Creates an operator that blurs a tensor using the existing Gaussian filter available with the Kornia library.
Arguments:
input (torch.Tensor): the input tensor with shape :math:`(B,C,H,W)`.
kernel_size (Tuple[int, int]): the size of the kernel.
sigma (Tuple[float, float]): the standard deviation of the kernel.
border_type (str): the padding mode to be applied before convolving.
The expected modes are: ``'constant'``, ``'reflect'``,
``'replicate'`` or ``'circular'``. Default: ``'reflect'``.
Returns:
torch.Tensor: the blurred tensor with shape :math:`(B,C,H,W)`.
Examples:
>>> input = torch.rand(2, 4, 5, 5)
>>> output = unsharp_mask(input, (3, 3), (1.5, 1.5))
>>> output.shape
torch.Size([2, 4, 5, 5])
"""
data_blur: torch.Tensor = gaussian_blur2d(input, kernel_size, sigma)
data_sharpened: torch.Tensor = input + (input - data_blur)
return data_sharpened


class UnsharpMask(nn.Module):
r"""Creates an operator that sharpens image using the existing Gaussian filter available with the Kornia library..
Arguments:
kernel_size (Tuple[int, int]): the size of the kernel.
sigma (Tuple[float, float]): the standard deviation of the kernel.
border_type (str): the padding mode to be applied before convolving.
The expected modes are: ``'constant'``, ``'reflect'``,
``'replicate'`` or ``'circular'``. Default: ``'reflect'``.
Returns:
Tensor: the sharpened tensor with shape :math:`(B,C,H,W)`.
Shape:
- Input: :math:`(B, C, H, W)`
- Output: :math:`(B, C, H, W)`
Examples:
>>> input = torch.rand(2, 4, 5, 5)
>>> sharpen = UnsharpMask((3, 3), (1.5, 1.5))
>>> output = sharpen(input)
>>> output.shape
torch.Size([2, 4, 5, 5])
"""

def __init__(self, kernel_size: Tuple[int, int],
sigma: Tuple[float, float],
border_type: str = 'reflect') -> None:
super(UnsharpMask, self).__init__()
self.kernel_size: Tuple[int, int] = kernel_size
self.sigma: Tuple[float, float] = sigma
self.border_type = border_type

def forward(self, input: torch.Tensor) -> torch.Tensor:
return unsharp_mask(input, self.kernel_size, self.sigma, self.border_type)
1 change: 1 addition & 0 deletions kornia/utils/image.py
Expand Up @@ -151,6 +151,7 @@ class ImageToTensor(nn.Module):
keepdim (bool): If ``False`` unsqueeze the input image to match the shape
:math:`(B, H, W, C)`. Default: ``True``
"""

def __init__(self, keepdim: bool = False):
super().__init__()
self.keepdim = keepdim
Expand Down
59 changes: 59 additions & 0 deletions test/filters/test_unsharp_mask.py
@@ -0,0 +1,59 @@
import pytest

import kornia
import kornia.testing as utils # test utils

import torch
from torch.autograd import gradcheck
from torch.testing import assert_allclose


class Testunsharp:
@pytest.mark.parametrize("batch_shape", [(1, 4, 8, 15), (2, 3, 11, 7)])
def test_cardinality(self, batch_shape, device, dtype):
kernel_size = (5, 7)
sigma = (1.5, 2.1)

input = torch.rand(batch_shape, device=device, dtype=dtype)
actual = kornia.filters.unsharp_mask(input, kernel_size, sigma, "replicate")
assert actual.shape == batch_shape

def test_noncontiguous(self, device, dtype):
batch_size = 3
input = torch.rand(3, 5, 5, device=device, dtype=dtype).expand(batch_size, -1, -1, -1)

kernel_size = (3, 3)
sigma = (1.5, 2.1)
actual = kornia.filters.unsharp_mask(input, kernel_size, sigma, "replicate")
assert_allclose(actual, actual)

def test_gradcheck(self, device, dtype):
# test parameters
batch_shape = (1, 3, 5, 5)
kernel_size = (3, 3)
sigma = (1.5, 2.1)

# evaluate function gradient
input = torch.rand(batch_shape, device=device, dtype=dtype)
input = utils.tensor_to_gradcheck_var(input) # to var
assert gradcheck(
kornia.filters.unsharp_mask,
(input, kernel_size, sigma, "replicate"),
raise_exception=True,
)

def test_jit(self, device, dtype):
op = kornia.filters.unsharp_mask
op_script = torch.jit.script(op)
params = [(3, 3), (1.5, 1.5)]

img = torch.ones(1, 3, 5, 5, device=device, dtype=dtype)
assert_allclose(op(img, *params), op_script(img, *params))

def test_module(self, device, dtype):
params = [(3, 3), (1.5, 1.5)]
op = kornia.filters.unsharp_mask
op_module = kornia.filters.UnsharpMask(*params)

img = torch.ones(1, 3, 5, 5, device=device, dtype=dtype)
assert_allclose(op(img, *params), op_module(img))

0 comments on commit d57a444

Please sign in to comment.