import os import sys import numpy as np import PIL.Image import pytest import torch from common_utils import ( _assert_approx_equal_tensor_to_pil, _assert_equal_tensor_to_pil, _create_data, _create_data_batch, assert_equal, cpu_and_cuda, float_dtypes, get_tmp_dir, int_dtypes, ) from torchvision import transforms as T from torchvision.transforms import functional as F, InterpolationMode from torchvision.transforms.autoaugment import _apply_op NEAREST, NEAREST_EXACT, BILINEAR, BICUBIC = ( InterpolationMode.NEAREST, InterpolationMode.NEAREST_EXACT, InterpolationMode.BILINEAR, InterpolationMode.BICUBIC, ) def _test_transform_vs_scripted(transform, s_transform, tensor, msg=None): torch.manual_seed(12) out1 = transform(tensor) torch.manual_seed(12) out2 = s_transform(tensor) assert_equal(out1, out2, msg=msg) def _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors, msg=None): torch.manual_seed(12) transformed_batch = transform(batch_tensors) for i in range(len(batch_tensors)): img_tensor = batch_tensors[i, ...] torch.manual_seed(12) transformed_img = transform(img_tensor) assert_equal(transformed_img, transformed_batch[i, ...], msg=msg) torch.manual_seed(12) s_transformed_batch = s_transform(batch_tensors) assert_equal(transformed_batch, s_transformed_batch, msg=msg) def _test_functional_op(f, device, channels=3, fn_kwargs=None, test_exact_match=True, **match_kwargs): fn_kwargs = fn_kwargs or {} tensor, pil_img = _create_data(height=10, width=10, channels=channels, device=device) transformed_tensor = f(tensor, **fn_kwargs) transformed_pil_img = f(pil_img, **fn_kwargs) if test_exact_match: _assert_equal_tensor_to_pil(transformed_tensor, transformed_pil_img, **match_kwargs) else: _assert_approx_equal_tensor_to_pil(transformed_tensor, transformed_pil_img, **match_kwargs) def _test_class_op(transform_cls, device, channels=3, meth_kwargs=None, test_exact_match=True, **match_kwargs): meth_kwargs = meth_kwargs or {} # test for class interface f = transform_cls(**meth_kwargs) scripted_fn = torch.jit.script(f) tensor, pil_img = _create_data(26, 34, channels, device=device) # set seed to reproduce the same transformation for tensor and PIL image torch.manual_seed(12) transformed_tensor = f(tensor) torch.manual_seed(12) transformed_pil_img = f(pil_img) if test_exact_match: _assert_equal_tensor_to_pil(transformed_tensor, transformed_pil_img, **match_kwargs) else: _assert_approx_equal_tensor_to_pil(transformed_tensor.float(), transformed_pil_img, **match_kwargs) torch.manual_seed(12) transformed_tensor_script = scripted_fn(tensor) assert_equal(transformed_tensor, transformed_tensor_script) batch_tensors = _create_data_batch(height=23, width=34, channels=channels, num_samples=4, device=device) _test_transform_vs_scripted_on_batch(f, scripted_fn, batch_tensors) with get_tmp_dir() as tmp_dir: scripted_fn.save(os.path.join(tmp_dir, f"t_{transform_cls.__name__}.pt")) def _test_op(func, method, device, channels=3, fn_kwargs=None, meth_kwargs=None, test_exact_match=True, **match_kwargs): _test_functional_op(func, device, channels, fn_kwargs, test_exact_match=test_exact_match, **match_kwargs) _test_class_op(method, device, channels, meth_kwargs, test_exact_match=test_exact_match, **match_kwargs) def _test_fn_save_load(fn, tmpdir): scripted_fn = torch.jit.script(fn) p = os.path.join(tmpdir, f"t_op_list_{getattr(fn, '__name__', fn.__class__.__name__)}.pt") scripted_fn.save(p) _ = torch.jit.load(p) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "func,method,fn_kwargs,match_kwargs", [ (F.hflip, T.RandomHorizontalFlip, None, {}), (F.vflip, T.RandomVerticalFlip, None, {}), (F.invert, T.RandomInvert, None, {}), (F.posterize, T.RandomPosterize, {"bits": 4}, {}), (F.solarize, T.RandomSolarize, {"threshold": 192.0}, {}), (F.adjust_sharpness, T.RandomAdjustSharpness, {"sharpness_factor": 2.0}, {}), ( F.autocontrast, T.RandomAutocontrast, None, {"test_exact_match": False, "agg_method": "max", "tol": (1 + 1e-5), "allowed_percentage_diff": 0.05}, ), (F.equalize, T.RandomEqualize, None, {}), ], ) @pytest.mark.parametrize("channels", [1, 3]) def test_random(func, method, device, channels, fn_kwargs, match_kwargs): _test_op(func, method, device, channels, fn_kwargs, fn_kwargs, **match_kwargs) @pytest.mark.parametrize("seed", range(10)) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("channels", [1, 3]) class TestColorJitter: @pytest.fixture(autouse=True) def set_random_seed(self, seed): torch.random.manual_seed(seed) @pytest.mark.parametrize("brightness", [0.1, 0.5, 1.0, 1.34, (0.3, 0.7), [0.4, 0.5]]) def test_color_jitter_brightness(self, brightness, device, channels): tol = 1.0 + 1e-10 meth_kwargs = {"brightness": brightness} _test_class_op( T.ColorJitter, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=tol, agg_method="max", channels=channels, ) @pytest.mark.parametrize("contrast", [0.2, 0.5, 1.0, 1.5, (0.3, 0.7), [0.4, 0.5]]) def test_color_jitter_contrast(self, contrast, device, channels): tol = 1.0 + 1e-10 meth_kwargs = {"contrast": contrast} _test_class_op( T.ColorJitter, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=tol, agg_method="max", channels=channels, ) @pytest.mark.parametrize("saturation", [0.5, 0.75, 1.0, 1.25, (0.3, 0.7), [0.3, 0.4]]) def test_color_jitter_saturation(self, saturation, device, channels): tol = 1.0 + 1e-10 meth_kwargs = {"saturation": saturation} _test_class_op( T.ColorJitter, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=tol, agg_method="max", channels=channels, ) @pytest.mark.parametrize("hue", [0.2, 0.5, (-0.2, 0.3), [-0.4, 0.5]]) def test_color_jitter_hue(self, hue, device, channels): meth_kwargs = {"hue": hue} _test_class_op( T.ColorJitter, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=16.1, agg_method="max", channels=channels, ) def test_color_jitter_all(self, device, channels): # All 4 parameters together meth_kwargs = {"brightness": 0.2, "contrast": 0.2, "saturation": 0.2, "hue": 0.2} _test_class_op( T.ColorJitter, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=12.1, agg_method="max", channels=channels, ) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("m", ["constant", "edge", "reflect", "symmetric"]) @pytest.mark.parametrize("mul", [1, -1]) def test_pad(m, mul, device): fill = 127 if m == "constant" else 0 # Test functional.pad (PIL and Tensor) with padding as single int _test_functional_op(F.pad, fn_kwargs={"padding": mul * 2, "fill": fill, "padding_mode": m}, device=device) # Test functional.pad and transforms.Pad with padding as [int, ] fn_kwargs = meth_kwargs = { "padding": [mul * 2], "fill": fill, "padding_mode": m, } _test_op(F.pad, T.Pad, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) # Test functional.pad and transforms.Pad with padding as list fn_kwargs = meth_kwargs = {"padding": [mul * 4, 4], "fill": fill, "padding_mode": m} _test_op(F.pad, T.Pad, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) # Test functional.pad and transforms.Pad with padding as tuple fn_kwargs = meth_kwargs = {"padding": (mul * 2, 2, 2, mul * 2), "fill": fill, "padding_mode": m} _test_op(F.pad, T.Pad, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_crop(device): fn_kwargs = {"top": 2, "left": 3, "height": 4, "width": 5} # Test transforms.RandomCrop with size and padding as tuple meth_kwargs = { "size": (4, 5), "padding": (4, 4), "pad_if_needed": True, } _test_op(F.crop, T.RandomCrop, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) # Test transforms.functional.crop including outside the image area fn_kwargs = {"top": -2, "left": 3, "height": 4, "width": 5} # top _test_functional_op(F.crop, fn_kwargs=fn_kwargs, device=device) fn_kwargs = {"top": 1, "left": -3, "height": 4, "width": 5} # left _test_functional_op(F.crop, fn_kwargs=fn_kwargs, device=device) fn_kwargs = {"top": 7, "left": 3, "height": 4, "width": 5} # bottom _test_functional_op(F.crop, fn_kwargs=fn_kwargs, device=device) fn_kwargs = {"top": 3, "left": 8, "height": 4, "width": 5} # right _test_functional_op(F.crop, fn_kwargs=fn_kwargs, device=device) fn_kwargs = {"top": -3, "left": -3, "height": 15, "width": 15} # all _test_functional_op(F.crop, fn_kwargs=fn_kwargs, device=device) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "padding_config", [ {"padding_mode": "constant", "fill": 0}, {"padding_mode": "constant", "fill": 10}, {"padding_mode": "edge"}, {"padding_mode": "reflect"}, ], ) @pytest.mark.parametrize("pad_if_needed", [True, False]) @pytest.mark.parametrize("padding", [[5], [5, 4], [1, 2, 3, 4]]) @pytest.mark.parametrize("size", [5, [5], [6, 6]]) def test_random_crop(size, padding, pad_if_needed, padding_config, device): config = dict(padding_config) config["size"] = size config["padding"] = padding config["pad_if_needed"] = pad_if_needed _test_class_op(T.RandomCrop, device, meth_kwargs=config) def test_random_crop_save_load(tmpdir): fn = T.RandomCrop(32, [4], pad_if_needed=True) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_center_crop(device, tmpdir): fn_kwargs = {"output_size": (4, 5)} meth_kwargs = {"size": (4, 5)} _test_op(F.center_crop, T.CenterCrop, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) fn_kwargs = {"output_size": (5,)} meth_kwargs = {"size": (5,)} _test_op(F.center_crop, T.CenterCrop, device=device, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs) tensor = torch.randint(0, 256, (3, 10, 10), dtype=torch.uint8, device=device) # Test torchscript of transforms.CenterCrop with size as int f = T.CenterCrop(size=5) scripted_fn = torch.jit.script(f) scripted_fn(tensor) # Test torchscript of transforms.CenterCrop with size as [int, ] f = T.CenterCrop(size=[5]) scripted_fn = torch.jit.script(f) scripted_fn(tensor) # Test torchscript of transforms.CenterCrop with size as tuple f = T.CenterCrop(size=(6, 6)) scripted_fn = torch.jit.script(f) scripted_fn(tensor) def test_center_crop_save_load(tmpdir): fn = T.CenterCrop(size=[5]) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "fn, method, out_length", [ # test_five_crop (F.five_crop, T.FiveCrop, 5), # test_ten_crop (F.ten_crop, T.TenCrop, 10), ], ) @pytest.mark.parametrize("size", [(5,), [5], (4, 5), [4, 5]]) def test_x_crop(fn, method, out_length, size, device): meth_kwargs = fn_kwargs = {"size": size} scripted_fn = torch.jit.script(fn) tensor, pil_img = _create_data(height=20, width=20, device=device) transformed_t_list = fn(tensor, **fn_kwargs) transformed_p_list = fn(pil_img, **fn_kwargs) assert len(transformed_t_list) == len(transformed_p_list) assert len(transformed_t_list) == out_length for transformed_tensor, transformed_pil_img in zip(transformed_t_list, transformed_p_list): _assert_equal_tensor_to_pil(transformed_tensor, transformed_pil_img) transformed_t_list_script = scripted_fn(tensor.detach().clone(), **fn_kwargs) assert len(transformed_t_list) == len(transformed_t_list_script) assert len(transformed_t_list_script) == out_length for transformed_tensor, transformed_tensor_script in zip(transformed_t_list, transformed_t_list_script): assert_equal(transformed_tensor, transformed_tensor_script) # test for class interface fn = method(**meth_kwargs) scripted_fn = torch.jit.script(fn) output = scripted_fn(tensor) assert len(output) == len(transformed_t_list_script) # test on batch of tensors batch_tensors = _create_data_batch(height=23, width=34, channels=3, num_samples=4, device=device) torch.manual_seed(12) transformed_batch_list = fn(batch_tensors) for i in range(len(batch_tensors)): img_tensor = batch_tensors[i, ...] torch.manual_seed(12) transformed_img_list = fn(img_tensor) for transformed_img, transformed_batch in zip(transformed_img_list, transformed_batch_list): assert_equal(transformed_img, transformed_batch[i, ...]) @pytest.mark.parametrize("method", ["FiveCrop", "TenCrop"]) def test_x_crop_save_load(method, tmpdir): fn = getattr(T, method)(size=[5]) _test_fn_save_load(fn, tmpdir) class TestResize: @pytest.mark.parametrize("size", [32, 34, 35, 36, 38]) def test_resize_int(self, size): # TODO: Minimal check for bug-fix, improve this later x = torch.rand(3, 32, 46) t = T.Resize(size=size, antialias=True) y = t(x) # If size is an int, smaller edge of the image will be matched to this number. # i.e, if height > width, then image will be rescaled to (size * height / width, size). assert isinstance(y, torch.Tensor) assert y.shape[1] == size assert y.shape[2] == int(size * 46 / 32) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("dt", [None, torch.float32, torch.float64]) @pytest.mark.parametrize("size", [[32], [32, 32], (32, 32), [34, 35]]) @pytest.mark.parametrize("max_size", [None, 35, 1000]) @pytest.mark.parametrize("interpolation", [BILINEAR, BICUBIC, NEAREST, NEAREST_EXACT]) def test_resize_scripted(self, dt, size, max_size, interpolation, device): tensor, _ = _create_data(height=34, width=36, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) if dt is not None: # This is a trivial cast to float of uint8 data to test all cases tensor = tensor.to(dt) if max_size is not None and len(size) != 1: pytest.skip("Size should be an int or a sequence of length 1 if max_size is specified") transform = T.Resize(size=size, interpolation=interpolation, max_size=max_size, antialias=True) s_transform = torch.jit.script(transform) _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) def test_resize_save_load(self, tmpdir): fn = T.Resize(size=[32], antialias=True) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("scale", [(0.7, 1.2), [0.7, 1.2]]) @pytest.mark.parametrize("ratio", [(0.75, 1.333), [0.75, 1.333]]) @pytest.mark.parametrize("size", [(32,), [44], [32], [32, 32], (32, 32), [44, 55]]) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR, BICUBIC, NEAREST_EXACT]) @pytest.mark.parametrize("antialias", [None, True, False]) def test_resized_crop(self, scale, ratio, size, interpolation, antialias, device): if antialias and interpolation in {NEAREST, NEAREST_EXACT}: pytest.skip(f"Can not resize if interpolation mode is {interpolation} and antialias=True") tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.RandomResizedCrop( size=size, scale=scale, ratio=ratio, interpolation=interpolation, antialias=antialias ) s_transform = torch.jit.script(transform) _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) def test_resized_crop_save_load(self, tmpdir): fn = T.RandomResizedCrop(size=[32], antialias=True) _test_fn_save_load(fn, tmpdir) def _test_random_affine_helper(device, **kwargs): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.RandomAffine(**kwargs) s_transform = torch.jit.script(transform) _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) def test_random_affine_save_load(tmpdir): fn = T.RandomAffine(degrees=45.0) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("shear", [15, 10.0, (5.0, 10.0), [-15, 15], [-10.0, 10.0, -11.0, 11.0]]) def test_random_affine_shear(device, interpolation, shear): _test_random_affine_helper(device, degrees=0.0, interpolation=interpolation, shear=shear) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("scale", [(0.7, 1.2), [0.7, 1.2]]) def test_random_affine_scale(device, interpolation, scale): _test_random_affine_helper(device, degrees=0.0, interpolation=interpolation, scale=scale) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("translate", [(0.1, 0.2), [0.2, 0.1]]) def test_random_affine_translate(device, interpolation, translate): _test_random_affine_helper(device, degrees=0.0, interpolation=interpolation, translate=translate) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("degrees", [45, 35.0, (-45, 45), [-90.0, 90.0]]) def test_random_affine_degrees(device, interpolation, degrees): _test_random_affine_helper(device, degrees=degrees, interpolation=interpolation) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("fill", [85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_random_affine_fill(device, interpolation, fill): _test_random_affine_helper(device, degrees=0.0, interpolation=interpolation, fill=fill) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("center", [(0, 0), [10, 10], None, (56, 44)]) @pytest.mark.parametrize("expand", [True, False]) @pytest.mark.parametrize("degrees", [45, 35.0, (-45, 45), [-90.0, 90.0]]) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("fill", [85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_random_rotate(device, center, expand, degrees, interpolation, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.RandomRotation(degrees=degrees, interpolation=interpolation, expand=expand, center=center, fill=fill) s_transform = torch.jit.script(transform) _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) def test_random_rotate_save_load(tmpdir): fn = T.RandomRotation(degrees=45.0) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("distortion_scale", np.linspace(0.1, 1.0, num=20)) @pytest.mark.parametrize("interpolation", [NEAREST, BILINEAR]) @pytest.mark.parametrize("fill", [85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_random_perspective(device, distortion_scale, interpolation, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.RandomPerspective(distortion_scale=distortion_scale, interpolation=interpolation, fill=fill) s_transform = torch.jit.script(transform) _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) def test_random_perspective_save_load(tmpdir): fn = T.RandomPerspective() _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "Klass, meth_kwargs", [(T.Grayscale, {"num_output_channels": 1}), (T.Grayscale, {"num_output_channels": 3}), (T.RandomGrayscale, {})], ) def test_to_grayscale(device, Klass, meth_kwargs): tol = 1.0 + 1e-10 _test_class_op(Klass, meth_kwargs=meth_kwargs, test_exact_match=False, device=device, tol=tol, agg_method="max") @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("in_dtype", int_dtypes() + float_dtypes()) @pytest.mark.parametrize("out_dtype", int_dtypes() + float_dtypes()) def test_convert_image_dtype(device, in_dtype, out_dtype): tensor, _ = _create_data(26, 34, device=device) batch_tensors = torch.rand(4, 3, 44, 56, device=device) in_tensor = tensor.to(in_dtype) in_batch_tensors = batch_tensors.to(in_dtype) fn = T.ConvertImageDtype(dtype=out_dtype) scripted_fn = torch.jit.script(fn) if (in_dtype == torch.float32 and out_dtype in (torch.int32, torch.int64)) or ( in_dtype == torch.float64 and out_dtype == torch.int64 ): with pytest.raises(RuntimeError, match=r"cannot be performed safely"): _test_transform_vs_scripted(fn, scripted_fn, in_tensor) with pytest.raises(RuntimeError, match=r"cannot be performed safely"): _test_transform_vs_scripted_on_batch(fn, scripted_fn, in_batch_tensors) return _test_transform_vs_scripted(fn, scripted_fn, in_tensor) _test_transform_vs_scripted_on_batch(fn, scripted_fn, in_batch_tensors) def test_convert_image_dtype_save_load(tmpdir): fn = T.ConvertImageDtype(dtype=torch.uint8) _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("policy", [policy for policy in T.AutoAugmentPolicy]) @pytest.mark.parametrize("fill", [None, 85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_autoaugment(device, policy, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.AutoAugment(policy=policy, fill=fill) s_transform = torch.jit.script(transform) for _ in range(25): _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("num_ops", [1, 2, 3]) @pytest.mark.parametrize("magnitude", [7, 9, 11]) @pytest.mark.parametrize("fill", [None, 85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_randaugment(device, num_ops, magnitude, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.RandAugment(num_ops=num_ops, magnitude=magnitude, fill=fill) s_transform = torch.jit.script(transform) for _ in range(25): _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("fill", [None, 85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_trivialaugmentwide(device, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) transform = T.TrivialAugmentWide(fill=fill) s_transform = torch.jit.script(transform) for _ in range(25): _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize("fill", [None, 85, (10, -10, 10), 0.7, [0.0, 0.0, 0.0], [1], 1]) def test_augmix(device, fill): tensor = torch.randint(0, 256, size=(3, 44, 56), dtype=torch.uint8, device=device) batch_tensors = torch.randint(0, 256, size=(4, 3, 44, 56), dtype=torch.uint8, device=device) class DeterministicAugMix(T.AugMix): def _sample_dirichlet(self, params: torch.Tensor) -> torch.Tensor: # patch the method to ensure that the order of rand calls doesn't affect the outcome return params.softmax(dim=-1) transform = DeterministicAugMix(fill=fill) s_transform = torch.jit.script(transform) for _ in range(25): _test_transform_vs_scripted(transform, s_transform, tensor) _test_transform_vs_scripted_on_batch(transform, s_transform, batch_tensors) @pytest.mark.parametrize("augmentation", [T.AutoAugment, T.RandAugment, T.TrivialAugmentWide, T.AugMix]) def test_autoaugment_save_load(augmentation, tmpdir): fn = augmentation() _test_fn_save_load(fn, tmpdir) @pytest.mark.parametrize("interpolation", [F.InterpolationMode.NEAREST, F.InterpolationMode.BILINEAR]) @pytest.mark.parametrize("mode", ["X", "Y"]) def test_autoaugment__op_apply_shear(interpolation, mode): # We check that torchvision's implementation of shear is equivalent # to official CIFAR10 autoaugment implementation: # https://github.com/tensorflow/models/blob/885fda091c46c59d6c7bb5c7e760935eacc229da/research/autoaugment/augmentation_transforms.py#L273-L290 image_size = 32 def shear(pil_img, level, mode, resample): if mode == "X": matrix = (1, level, 0, 0, 1, 0) elif mode == "Y": matrix = (1, 0, 0, level, 1, 0) return pil_img.transform((image_size, image_size), PIL.Image.AFFINE, matrix, resample=resample) t_img, pil_img = _create_data(image_size, image_size) resample_pil = { F.InterpolationMode.NEAREST: PIL.Image.NEAREST, F.InterpolationMode.BILINEAR: PIL.Image.BILINEAR, }[interpolation] level = 0.3 expected_out = shear(pil_img, level, mode=mode, resample=resample_pil) # Check pil output vs expected pil out = _apply_op(pil_img, op_name=f"Shear{mode}", magnitude=level, interpolation=interpolation, fill=0) assert out == expected_out if interpolation == F.InterpolationMode.BILINEAR: # We skip bilinear mode for tensors as # affine transformation results are not exactly the same # between tensors and pil images # MAE as around 1.40 # Max Abs error can be 163 or 170 return # Check tensor output vs expected pil out = _apply_op(t_img, op_name=f"Shear{mode}", magnitude=level, interpolation=interpolation, fill=0) _assert_approx_equal_tensor_to_pil(out, expected_out) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "config", [ {}, {"value": 1}, {"value": 0.2}, {"value": "random"}, {"value": (1, 1, 1)}, {"value": (0.2, 0.2, 0.2)}, {"value": [1, 1, 1]}, {"value": [0.2, 0.2, 0.2]}, {"value": "random", "ratio": (0.1, 0.2)}, ], ) def test_random_erasing(device, config): tensor, _ = _create_data(24, 32, channels=3, device=device) batch_tensors = torch.rand(4, 3, 44, 56, device=device) fn = T.RandomErasing(**config) scripted_fn = torch.jit.script(fn) _test_transform_vs_scripted(fn, scripted_fn, tensor) _test_transform_vs_scripted_on_batch(fn, scripted_fn, batch_tensors) def test_random_erasing_save_load(tmpdir): fn = T.RandomErasing(value=0.2) _test_fn_save_load(fn, tmpdir) def test_random_erasing_with_invalid_data(): img = torch.rand(3, 60, 60) # Test Set 0: invalid value random_erasing = T.RandomErasing(value=(0.1, 0.2, 0.3, 0.4), p=1.0) with pytest.raises(ValueError, match="If value is a sequence, it should have either a single value or 3"): random_erasing(img) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_normalize(device, tmpdir): fn = T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) tensor, _ = _create_data(26, 34, device=device) with pytest.raises(TypeError, match="Input tensor should be a float tensor"): fn(tensor) batch_tensors = torch.rand(4, 3, 44, 56, device=device) tensor = tensor.to(dtype=torch.float32) / 255.0 # test for class interface scripted_fn = torch.jit.script(fn) _test_transform_vs_scripted(fn, scripted_fn, tensor) _test_transform_vs_scripted_on_batch(fn, scripted_fn, batch_tensors) scripted_fn.save(os.path.join(tmpdir, "t_norm.pt")) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_linear_transformation(device, tmpdir): c, h, w = 3, 24, 32 tensor, _ = _create_data(h, w, channels=c, device=device) matrix = torch.rand(c * h * w, c * h * w, device=device) mean_vector = torch.rand(c * h * w, device=device) fn = T.LinearTransformation(matrix, mean_vector) scripted_fn = torch.jit.script(fn) _test_transform_vs_scripted(fn, scripted_fn, tensor) batch_tensors = torch.rand(4, c, h, w, device=device) # We skip some tests from _test_transform_vs_scripted_on_batch as # results for scripted and non-scripted transformations are not exactly the same torch.manual_seed(12) transformed_batch = fn(batch_tensors) torch.manual_seed(12) s_transformed_batch = scripted_fn(batch_tensors) assert_equal(transformed_batch, s_transformed_batch) scripted_fn.save(os.path.join(tmpdir, "t_norm.pt")) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_compose(device): tensor, _ = _create_data(26, 34, device=device) tensor = tensor.to(dtype=torch.float32) / 255.0 transforms = T.Compose( [ T.CenterCrop(10), T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)), ] ) s_transforms = torch.nn.Sequential(*transforms.transforms) scripted_fn = torch.jit.script(s_transforms) torch.manual_seed(12) transformed_tensor = transforms(tensor) torch.manual_seed(12) transformed_tensor_script = scripted_fn(tensor) assert_equal(transformed_tensor, transformed_tensor_script, msg=f"{transforms}") t = T.Compose( [ lambda x: x, ] ) with pytest.raises(RuntimeError, match="cannot call a value of type 'Tensor'"): torch.jit.script(t) @pytest.mark.parametrize("device", cpu_and_cuda()) def test_random_apply(device): tensor, _ = _create_data(26, 34, device=device) tensor = tensor.to(dtype=torch.float32) / 255.0 transforms = T.RandomApply( [ T.RandomHorizontalFlip(), T.ColorJitter(), ], p=0.4, ) s_transforms = T.RandomApply( torch.nn.ModuleList( [ T.RandomHorizontalFlip(), T.ColorJitter(), ] ), p=0.4, ) scripted_fn = torch.jit.script(s_transforms) torch.manual_seed(12) transformed_tensor = transforms(tensor) torch.manual_seed(12) transformed_tensor_script = scripted_fn(tensor) assert_equal(transformed_tensor, transformed_tensor_script, msg=f"{transforms}") if device == "cpu": # Can't check this twice, otherwise # "Can't redefine method: forward on class: __torch__.torchvision.transforms.transforms.RandomApply" transforms = T.RandomApply( [ T.ColorJitter(), ], p=0.3, ) with pytest.raises(RuntimeError, match="Module 'RandomApply' has no attribute 'transforms'"): torch.jit.script(transforms) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "meth_kwargs", [ {"kernel_size": 3, "sigma": 0.75}, {"kernel_size": 23, "sigma": [0.1, 2.0]}, {"kernel_size": 23, "sigma": (0.1, 2.0)}, {"kernel_size": [3, 3], "sigma": (1.0, 1.0)}, {"kernel_size": (3, 3), "sigma": (0.1, 2.0)}, {"kernel_size": [23], "sigma": 0.75}, ], ) @pytest.mark.parametrize("channels", [1, 3]) def test_gaussian_blur(device, channels, meth_kwargs): if all( [ device == "cuda", channels == 1, meth_kwargs["kernel_size"] in [23, [23]], torch.version.cuda == "11.3", sys.platform in ("win32", "cygwin"), ] ): pytest.skip("Fails on Windows, see https://github.com/pytorch/vision/issues/5464") tol = 1.0 + 1e-10 torch.manual_seed(12) _test_class_op( T.GaussianBlur, meth_kwargs=meth_kwargs, channels=channels, test_exact_match=False, device=device, agg_method="max", tol=tol, ) @pytest.mark.parametrize("device", cpu_and_cuda()) @pytest.mark.parametrize( "fill", [ 1, 1.0, [1], [1.0], (1,), (1.0,), [1, 2, 3], [1.0, 2.0, 3.0], (1, 2, 3), (1.0, 2.0, 3.0), ], ) @pytest.mark.parametrize("channels", [1, 3]) def test_elastic_transform(device, channels, fill): if isinstance(fill, (list, tuple)) and len(fill) > 1 and channels == 1: # For this the test would correctly fail, since the number of channels in the image does not match `fill`. # Thus, this is not an issue in the transform, but rather a problem of parametrization that just gives the # product of `fill` and `channels`. return _test_class_op( T.ElasticTransform, meth_kwargs=dict(fill=fill), channels=channels, device=device, )