# LICENSE HEADER MANAGED BY add-license-header # # Copyright 2018 Kornia Team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import pytest import torch from kornia.morphology import dilation from testing.base import BaseTester, assert_close from testing.parametrized_tester import parametrized_test @parametrized_test( smoke_inputs=lambda device, dtype: ( torch.rand(1, 3, 4, 4, device=device, dtype=dtype), torch.ones((3, 3), device=device, dtype=dtype), ), cardinality_tests=[ { "inputs": lambda device, dtype: ( torch.ones((1, 3, 4, 4), device=device, dtype=dtype), torch.ones((3, 3), device=device, dtype=dtype), ), "expected_shape": torch.Size([1, 3, 4, 4]), }, { "inputs": lambda device, dtype: ( torch.ones((2, 3, 2, 4), device=device, dtype=dtype), torch.ones((3, 3), device=device, dtype=dtype), ), "expected_shape": torch.Size([2, 3, 2, 4]), }, { "inputs": lambda device, dtype: ( torch.ones((3, 3, 4, 1), device=device, dtype=dtype), torch.ones((3, 3), device=device, dtype=dtype), ), "expected_shape": torch.Size([3, 3, 4, 1]), }, { "inputs": lambda device, dtype: ( torch.ones((3, 2, 5, 5), device=device, dtype=dtype), torch.ones((3, 3), device=device, dtype=dtype), ), "expected_shape": torch.Size([3, 2, 5, 5]), }, ], gradcheck_inputs=lambda device: ( torch.rand(2, 3, 4, 4, requires_grad=True, device=device, dtype=torch.float64), torch.rand(3, 3, requires_grad=True, device=device, dtype=torch.float64), ), ) class TestDilate(BaseTester): def setup_method(self) -> None: self.func = dilation def test_kernel(self, device, dtype): tensor = torch.tensor([[0.5, 1.0, 0.3], [0.7, 0.3, 0.8], [0.4, 0.9, 0.2]], device=device, dtype=dtype)[ None, None, :, : ] kernel = torch.tensor([[0.0, 1.0, 0.0], [1.0, 1.0, 1.0], [0.0, 1.0, 0.0]], device=device, dtype=dtype) expected = torch.tensor([[1.0, 1.0, 1.0], [0.7, 1.0, 0.8], [0.9, 0.9, 0.9]], device=device, dtype=dtype)[ None, None, :, : ] assert_close(dilation(tensor, kernel, engine="unfold"), expected, atol=1e-4, rtol=1e-4) assert_close(dilation(tensor, kernel, engine="convolution"), expected, atol=1e-3, rtol=1e-3) def test_structural_element(self, device, dtype): tensor = torch.tensor([[0.5, 1.0, 0.3], [0.7, 0.3, 0.8], [0.4, 0.9, 0.2]], device=device, dtype=dtype)[ None, None, :, : ] structural_element = torch.tensor( [[-1.0, 0.0, -1.0], [0.0, 0.0, 0.0], [-1.0, 0.0, -1.0]], device=device, dtype=dtype ) expected = torch.tensor([[1.0, 1.0, 1.0], [0.7, 1.0, 0.8], [0.9, 0.9, 0.9]], device=device, dtype=dtype)[ None, None, :, : ] assert_close( dilation( tensor, torch.ones_like(structural_element), structuring_element=structural_element, engine="unfold" ), expected, atol=1e-3, rtol=1e-3, ) assert_close( dilation( tensor, torch.ones_like(structural_element), structuring_element=structural_element, engine="convolution", ), expected, atol=1e-3, rtol=1e-3, ) def test_flip(self, device, dtype): tensor = torch.tensor([[0.5, 1.0, 0.3], [0.7, 0.3, 0.8], [0.4, 0.9, 0.2]], device=device, dtype=dtype)[ None, None, :, : ] kernel = torch.tensor([[0.0, 1.0, 1.0], [0.0, 1.0, 1.0], [0.0, 1.0, 1.0]], device=device, dtype=dtype) expected = torch.tensor([[0.7, 1.0, 1.0], [0.7, 1.0, 1.0], [0.7, 0.9, 0.9]], device=device, dtype=dtype)[ None, None, :, : ] assert_close(dilation(tensor, kernel), expected, atol=1e-3, rtol=1e-3) def test_exception(self, device, dtype): tensor = torch.ones(1, 1, 3, 4, device=device, dtype=dtype) kernel = torch.ones(3, 3, device=device, dtype=dtype) with pytest.raises(TypeError): assert dilation([0.0], kernel) with pytest.raises(TypeError): assert dilation(tensor, [0.0]) with pytest.raises(ValueError): test = torch.ones(2, 3, 4, device=device, dtype=dtype) assert dilation(test, kernel) with pytest.raises(ValueError): test = torch.ones(2, 3, 4, device=device, dtype=dtype) assert dilation(tensor, test) with pytest.raises(NotImplementedError, match="unknown"): dilation(tensor, kernel, engine="invalid_engine") def test_custom_origin(self, device, dtype): # Custom origin shifts the structuring element anchor point tensor = torch.zeros(1, 1, 5, 5, device=device, dtype=dtype) tensor[0, 0, 2, 2] = 1.0 # single hot pixel in center kernel = torch.ones(3, 3, device=device, dtype=dtype) # Default origin (center): dilation spreads symmetrically out_default = dilation(tensor, kernel) # Custom origin (top-left corner): effect shifts out_custom = dilation(tensor, kernel, origin=[0, 0]) # Results should differ when the anchor point changes assert not torch.equal(out_default, out_custom) @pytest.mark.jit() def test_jit(self, device, dtype): op = dilation op_script = torch.jit.script(op) tensor = torch.rand(1, 2, 7, 7, device=device, dtype=dtype) kernel = torch.ones(3, 3, device=device, dtype=dtype) actual = op_script(tensor, kernel) expected = op(tensor, kernel) assert_close(actual, expected)