From 4f0ec27ed29ed6230f0bf42c7aa7470f1a551392 Mon Sep 17 00:00:00 2001 From: Nicolas Hug Date: Tue, 26 Sep 2023 04:55:23 -0700 Subject: [PATCH] [fbsync] port tests for F.gaussian_blur GaussianBlur (#7935) Reviewed By: matteobettini Differential Revision: D49600788 fbshipit-source-id: 8afacb9e7dd6eba19f2212d8efa260f733bcb473 --- test/test_transforms_v2_consistency.py | 12 --- test/test_transforms_v2_functional.py | 58 -------------- test/test_transforms_v2_refactored.py | 105 ++++++++++++++++++++++++- test/transforms_v2_dispatcher_infos.py | 12 --- test/transforms_v2_kernel_infos.py | 37 --------- 5 files changed, 104 insertions(+), 120 deletions(-) diff --git a/test/test_transforms_v2_consistency.py b/test/test_transforms_v2_consistency.py index d2d5ff0c50a..3ad88ec1e51 100644 --- a/test/test_transforms_v2_consistency.py +++ b/test/test_transforms_v2_consistency.py @@ -269,17 +269,6 @@ def __init__( ], closeness_kwargs={"atol": 1e-5, "rtol": 1e-5}, ), - ConsistencyConfig( - v2_transforms.GaussianBlur, - legacy_transforms.GaussianBlur, - [ - ArgsKwargs(kernel_size=3), - ArgsKwargs(kernel_size=(1, 5)), - ArgsKwargs(kernel_size=3, sigma=0.7), - ArgsKwargs(kernel_size=5, sigma=(0.3, 1.4)), - ], - closeness_kwargs={"rtol": 1e-5, "atol": 1e-5}, - ), ConsistencyConfig( v2_transforms.RandomPerspective, legacy_transforms.RandomPerspective, @@ -512,7 +501,6 @@ def test_call_consistency(config, args_kwargs): ) for transform_cls, get_params_args_kwargs in [ (v2_transforms.ColorJitter, ArgsKwargs(brightness=None, contrast=None, saturation=None, hue=None)), - (v2_transforms.GaussianBlur, ArgsKwargs(0.3, 1.4)), (v2_transforms.RandomPerspective, ArgsKwargs(23, 17, 0.5)), (v2_transforms.AutoAugment, ArgsKwargs(5)), ] diff --git a/test/test_transforms_v2_functional.py b/test/test_transforms_v2_functional.py index 03b7656d739..a7d8496708c 100644 --- a/test/test_transforms_v2_functional.py +++ b/test/test_transforms_v2_functional.py @@ -1,5 +1,4 @@ import inspect -import os import re import numpy as np @@ -740,63 +739,6 @@ def _compute_expected_mask(mask, output_size): torch.testing.assert_close(expected, actual) -# Copied from test/test_functional_tensor.py -@pytest.mark.parametrize("device", cpu_and_cuda()) -@pytest.mark.parametrize("canvas_size", ("small", "large")) -@pytest.mark.parametrize("dt", [None, torch.float32, torch.float64, torch.float16]) -@pytest.mark.parametrize("ksize", [(3, 3), [3, 5], (23, 23)]) -@pytest.mark.parametrize("sigma", [[0.5, 0.5], (0.5, 0.5), (0.8, 0.8), (1.7, 1.7)]) -def test_correctness_gaussian_blur_image_tensor(device, canvas_size, dt, ksize, sigma): - fn = F.gaussian_blur_image - - # true_cv2_results = { - # # np_img = np.arange(3 * 10 * 12, dtype="uint8").reshape((10, 12, 3)) - # # cv2.GaussianBlur(np_img, ksize=(3, 3), sigmaX=0.8) - # "3_3_0.8": ... - # # cv2.GaussianBlur(np_img, ksize=(3, 3), sigmaX=0.5) - # "3_3_0.5": ... - # # cv2.GaussianBlur(np_img, ksize=(3, 5), sigmaX=0.8) - # "3_5_0.8": ... - # # cv2.GaussianBlur(np_img, ksize=(3, 5), sigmaX=0.5) - # "3_5_0.5": ... - # # np_img2 = np.arange(26 * 28, dtype="uint8").reshape((26, 28)) - # # cv2.GaussianBlur(np_img2, ksize=(23, 23), sigmaX=1.7) - # "23_23_1.7": ... - # } - p = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "gaussian_blur_opencv_results.pt") - true_cv2_results = torch.load(p) - - if canvas_size == "small": - tensor = ( - torch.from_numpy(np.arange(3 * 10 * 12, dtype="uint8").reshape((10, 12, 3))).permute(2, 0, 1).to(device) - ) - else: - tensor = torch.from_numpy(np.arange(26 * 28, dtype="uint8").reshape((1, 26, 28))).to(device) - - if dt == torch.float16 and device == "cpu": - # skip float16 on CPU case - return - - if dt is not None: - tensor = tensor.to(dtype=dt) - - _ksize = (ksize, ksize) if isinstance(ksize, int) else ksize - _sigma = sigma[0] if sigma is not None else None - shape = tensor.shape - gt_key = f"{shape[-2]}_{shape[-1]}_{shape[-3]}__{_ksize[0]}_{_ksize[1]}_{_sigma}" - if gt_key not in true_cv2_results: - return - - true_out = ( - torch.tensor(true_cv2_results[gt_key]).reshape(shape[-2], shape[-1], shape[-3]).permute(2, 0, 1).to(tensor) - ) - - image = tv_tensors.Image(tensor) - - out = fn(image, kernel_size=ksize, sigma=sigma) - torch.testing.assert_close(out, true_out, rtol=0.0, atol=1.0, msg=f"{ksize}, {sigma}") - - @pytest.mark.parametrize( "inpt", [ diff --git a/test/test_transforms_v2_refactored.py b/test/test_transforms_v2_refactored.py index 1650e2ca7f9..99fecb0ce9d 100644 --- a/test/test_transforms_v2_refactored.py +++ b/test/test_transforms_v2_refactored.py @@ -2863,12 +2863,64 @@ def test_transform_passthrough(self, make_input): class TestGaussianBlur: + @pytest.mark.parametrize("kernel_size", [1, 3, (3, 1), [3, 5]]) + @pytest.mark.parametrize("sigma", [None, 1.0, 1, (0.5,), [0.3], (0.3, 0.7), [0.9, 0.2]]) + def test_kernel_image(self, kernel_size, sigma): + check_kernel( + F.gaussian_blur_image, + make_image(), + kernel_size=kernel_size, + sigma=sigma, + check_scripted_vs_eager=not (isinstance(kernel_size, int) or isinstance(sigma, (float, int))), + ) + + def test_kernel_image_errors(self): + image = make_image_tensor() + + with pytest.raises(ValueError, match="kernel_size is a sequence its length should be 2"): + F.gaussian_blur_image(image, kernel_size=[1, 2, 3]) + + for kernel_size in [2, -1]: + with pytest.raises(ValueError, match="kernel_size should have odd and positive integers"): + F.gaussian_blur_image(image, kernel_size=kernel_size) + + with pytest.raises(ValueError, match="sigma is a sequence, its length should be 2"): + F.gaussian_blur_image(image, kernel_size=1, sigma=[1, 2, 3]) + + with pytest.raises(TypeError, match="sigma should be either float or sequence of floats"): + F.gaussian_blur_image(image, kernel_size=1, sigma=object()) + + with pytest.raises(ValueError, match="sigma should have positive values"): + F.gaussian_blur_image(image, kernel_size=1, sigma=-1) + + def test_kernel_video(self): + check_kernel(F.gaussian_blur_video, make_video(), kernel_size=(3, 3)) + + @pytest.mark.parametrize( + "make_input", + [make_image_tensor, make_image_pil, make_image, make_video], + ) + def test_functional(self, make_input): + check_functional(F.gaussian_blur, make_input(), kernel_size=(3, 3)) + + @pytest.mark.parametrize( + ("kernel", "input_type"), + [ + (F.gaussian_blur_image, torch.Tensor), + (F._gaussian_blur_image_pil, PIL.Image.Image), + (F.gaussian_blur_image, tv_tensors.Image), + (F.gaussian_blur_video, tv_tensors.Video), + ], + ) + def test_functional_signature(self, kernel, input_type): + check_functional_kernel_signature_match(F.gaussian_blur, kernel=kernel, input_type=input_type) + @pytest.mark.parametrize( "make_input", [make_image_tensor, make_image_pil, make_image, make_bounding_boxes, make_segmentation_mask, make_video], ) @pytest.mark.parametrize("device", cpu_and_cuda()) - @pytest.mark.parametrize("sigma", [5, (0.5, 2)]) + @pytest.mark.parametrize("sigma", [5, 2.0, (0.5, 2), [1.3, 2.7]]) def test_transform(self, make_input, device, sigma): check_transform(transforms.GaussianBlur(kernel_size=3, sigma=sigma), make_input(device=device)) @@ -2904,6 +2956,57 @@ def test__get_params(self, sigma): assert sigma[0] <= params["sigma"][0] <= sigma[1] assert sigma[0] <= params["sigma"][1] <= sigma[1] + # np_img = np.arange(3 * 10 * 12, dtype="uint8").reshape((10, 12, 3)) + # np_img2 = np.arange(26 * 28, dtype="uint8").reshape((26, 28)) + # { + # "10_12_3__3_3_0.8": cv2.GaussianBlur(np_img, ksize=(3, 3), sigmaX=0.8), + # "10_12_3__3_3_0.5": cv2.GaussianBlur(np_img, ksize=(3, 3), sigmaX=0.5), + # "10_12_3__3_5_0.8": cv2.GaussianBlur(np_img, ksize=(3, 5), sigmaX=0.8), + # "10_12_3__3_5_0.5": cv2.GaussianBlur(np_img, ksize=(3, 5), sigmaX=0.5), + # "26_28_1__23_23_1.7": cv2.GaussianBlur(np_img2, ksize=(23, 23), sigmaX=1.7), + # } + REFERENCE_GAUSSIAN_BLUR_IMAGE_RESULTS = torch.load( + Path(__file__).parent / "assets" / "gaussian_blur_opencv_results.pt" + ) + + @pytest.mark.parametrize( + ("dimensions", "kernel_size", "sigma"), + [ + ((3, 10, 12), (3, 3), 0.8), + ((3, 10, 12), (3, 3), 0.5), + ((3, 10, 12), (3, 5), 0.8), + ((3, 10, 12), (3, 5), 0.5), + ((1, 26, 28), (23, 23), 1.7), + ], + ) + @pytest.mark.parametrize("dtype", [torch.float32, torch.float64, torch.float16]) + @pytest.mark.parametrize("device", cpu_and_cuda()) + def test_functional_image_correctness(self, dimensions, kernel_size, sigma, dtype, device): + if dtype is torch.float16 and device == "cpu": + pytest.skip("The CPU implementation of float16 on CPU differs from opencv") + + num_channels, height, width = dimensions + + reference_results_key = f"{height}_{width}_{num_channels}__{kernel_size[0]}_{kernel_size[1]}_{sigma}" + expected = ( + torch.tensor(self.REFERENCE_GAUSSIAN_BLUR_IMAGE_RESULTS[reference_results_key]) + .reshape(height, width, num_channels) + .permute(2, 0, 1) + .to(dtype=dtype, device=device) + ) + + image = tv_tensors.Image( + torch.arange(num_channels * height * width, dtype=torch.uint8) + .reshape(height, width, num_channels) + .permute(2, 0, 1), + dtype=dtype, + device=device, + ) + + actual = F.gaussian_blur_image(image, kernel_size=kernel_size, sigma=sigma) + + torch.testing.assert_close(actual, expected, rtol=0, atol=1) + class TestAutoAugmentTransforms: # These transforms have a lot of branches in their `forward()` passes which are conditioned on random sampling. diff --git a/test/transforms_v2_dispatcher_infos.py b/test/transforms_v2_dispatcher_infos.py index 592fce4e17f..0346ca89ad9 100644 --- a/test/transforms_v2_dispatcher_infos.py +++ b/test/transforms_v2_dispatcher_infos.py @@ -162,18 +162,6 @@ def xfail_jit_python_scalar_arg(name, *, reason=None): xfail_jit_python_scalar_arg("output_size"), ], ), - DispatcherInfo( - F.gaussian_blur, - kernels={ - tv_tensors.Image: F.gaussian_blur_image, - tv_tensors.Video: F.gaussian_blur_video, - }, - pil_kernel_info=PILKernelInfo(F._gaussian_blur_image_pil), - test_marks=[ - xfail_jit_python_scalar_arg("kernel_size"), - xfail_jit_python_scalar_arg("sigma"), - ], - ), DispatcherInfo( F.equalize, kernels={ diff --git a/test/transforms_v2_kernel_infos.py b/test/transforms_v2_kernel_infos.py index 65e66240e50..e6efb7a349a 100644 --- a/test/transforms_v2_kernel_infos.py +++ b/test/transforms_v2_kernel_infos.py @@ -686,43 +686,6 @@ def sample_inputs_center_crop_video(): ) -def sample_inputs_gaussian_blur_image_tensor(): - make_gaussian_blur_image_loaders = functools.partial(make_image_loaders, sizes=[(7, 33)], color_spaces=["RGB"]) - - for image_loader, kernel_size in itertools.product(make_gaussian_blur_image_loaders(), [5, (3, 3), [3, 3]]): - yield ArgsKwargs(image_loader, kernel_size=kernel_size) - - for image_loader, sigma in itertools.product( - make_gaussian_blur_image_loaders(), [None, (3.0, 3.0), [2.0, 2.0], 4.0, [1.5], (3.14,)] - ): - yield ArgsKwargs(image_loader, kernel_size=5, sigma=sigma) - - -def sample_inputs_gaussian_blur_video(): - for video_loader in make_video_loaders(sizes=[(7, 33)], num_frames=[5]): - yield ArgsKwargs(video_loader, kernel_size=[3, 3]) - - -KERNEL_INFOS.extend( - [ - KernelInfo( - F.gaussian_blur_image, - sample_inputs_fn=sample_inputs_gaussian_blur_image_tensor, - closeness_kwargs=cuda_vs_cpu_pixel_difference(), - test_marks=[ - xfail_jit_python_scalar_arg("kernel_size"), - xfail_jit_python_scalar_arg("sigma"), - ], - ), - KernelInfo( - F.gaussian_blur_video, - sample_inputs_fn=sample_inputs_gaussian_blur_video, - closeness_kwargs=cuda_vs_cpu_pixel_difference(), - ), - ] -) - - def sample_inputs_equalize_image_tensor(): for image_loader in make_image_loaders(sizes=[DEFAULT_PORTRAIT_SPATIAL_SIZE], color_spaces=("GRAY", "RGB")): yield ArgsKwargs(image_loader)