Skip to content

Commit 4a6100e

Browse files
NicolasHugfacebook-github-bot
authored andcommitted
[fbsync] port tests for F.pad and transforms.Pad (#7939)
Reviewed By: matteobettini Differential Revision: D49600776 fbshipit-source-id: d98db4c1415f85a210cfc5905d292ec0d76b2f65
1 parent 26e173b commit 4a6100e

6 files changed

+171
-322
lines changed

test/test_transforms_v2.py

-15
Original file line numberDiff line numberDiff line change
@@ -390,21 +390,6 @@ def was_applied(output, inpt):
390390
assert transform.was_applied(output, input)
391391

392392

393-
class TestPad:
394-
def test_assertions(self):
395-
with pytest.raises(TypeError, match="Got inappropriate padding arg"):
396-
transforms.Pad("abc")
397-
398-
with pytest.raises(ValueError, match="Padding must be an int or a 1, 2, or 4"):
399-
transforms.Pad([-0.7, 0, 0.7])
400-
401-
with pytest.raises(TypeError, match="Got inappropriate fill arg"):
402-
transforms.Pad(12, fill="abc")
403-
404-
with pytest.raises(ValueError, match="Padding mode should be either"):
405-
transforms.Pad(12, padding_mode="abc")
406-
407-
408393
class TestRandomZoomOut:
409394
def test_assertions(self):
410395
with pytest.raises(TypeError, match="Got inappropriate fill arg"):

test/test_transforms_v2_consistency.py

-15
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,6 @@ def __init__(
109109
],
110110
make_images_kwargs=dict(DEFAULT_MAKE_IMAGES_KWARGS, sizes=[(20, 19)]),
111111
),
112-
ConsistencyConfig(
113-
v2_transforms.Pad,
114-
legacy_transforms.Pad,
115-
[
116-
NotScriptableArgsKwargs(3),
117-
ArgsKwargs([3]),
118-
ArgsKwargs([2, 3]),
119-
ArgsKwargs([3, 2, 1, 4]),
120-
NotScriptableArgsKwargs(5, fill=1, padding_mode="constant"),
121-
ArgsKwargs([5], fill=1, padding_mode="constant"),
122-
NotScriptableArgsKwargs(5, padding_mode="edge"),
123-
NotScriptableArgsKwargs(5, padding_mode="reflect"),
124-
NotScriptableArgsKwargs(5, padding_mode="symmetric"),
125-
],
126-
),
127112
*[
128113
ConsistencyConfig(
129114
v2_transforms.LinearTransformation,

test/test_transforms_v2_functional.py

-69
Original file line numberDiff line numberDiff line change
@@ -524,75 +524,6 @@ def test_tv_tensor_explicit_metadata(self, metadata):
524524
# `transforms_v2_kernel_infos.py`
525525

526526

527-
def _parse_padding(padding):
528-
if isinstance(padding, int):
529-
return [padding] * 4
530-
if isinstance(padding, list):
531-
if len(padding) == 1:
532-
return padding * 4
533-
if len(padding) == 2:
534-
return padding * 2 # [left, up, right, down]
535-
536-
return padding
537-
538-
539-
@pytest.mark.parametrize("device", cpu_and_cuda())
540-
@pytest.mark.parametrize("padding", [[1], [1, 1], [1, 1, 2, 2]])
541-
def test_correctness_pad_bounding_boxes(device, padding):
542-
def _compute_expected_bbox(bbox, format, padding_):
543-
pad_left, pad_up, _, _ = _parse_padding(padding_)
544-
545-
dtype = bbox.dtype
546-
bbox = (
547-
bbox.clone()
548-
if format == tv_tensors.BoundingBoxFormat.XYXY
549-
else convert_bounding_box_format(bbox, old_format=format, new_format=tv_tensors.BoundingBoxFormat.XYXY)
550-
)
551-
552-
bbox[0::2] += pad_left
553-
bbox[1::2] += pad_up
554-
555-
bbox = convert_bounding_box_format(bbox, old_format=tv_tensors.BoundingBoxFormat.XYXY, new_format=format)
556-
if bbox.dtype != dtype:
557-
# Temporary cast to original dtype
558-
# e.g. float32 -> int
559-
bbox = bbox.to(dtype)
560-
return bbox
561-
562-
def _compute_expected_canvas_size(bbox, padding_):
563-
pad_left, pad_up, pad_right, pad_down = _parse_padding(padding_)
564-
height, width = bbox.canvas_size
565-
return height + pad_up + pad_down, width + pad_left + pad_right
566-
567-
for bboxes in make_multiple_bounding_boxes(extra_dims=((4,),)):
568-
bboxes = bboxes.to(device)
569-
bboxes_format = bboxes.format
570-
bboxes_canvas_size = bboxes.canvas_size
571-
572-
output_boxes, output_canvas_size = F.pad_bounding_boxes(
573-
bboxes, format=bboxes_format, canvas_size=bboxes_canvas_size, padding=padding
574-
)
575-
576-
torch.testing.assert_close(output_canvas_size, _compute_expected_canvas_size(bboxes, padding))
577-
578-
expected_bboxes = torch.stack(
579-
[_compute_expected_bbox(b, bboxes_format, padding) for b in bboxes.reshape(-1, 4).unbind()]
580-
).reshape(bboxes.shape)
581-
582-
torch.testing.assert_close(output_boxes, expected_bboxes, atol=1, rtol=0)
583-
584-
585-
@pytest.mark.parametrize("device", cpu_and_cuda())
586-
def test_correctness_pad_segmentation_mask_on_fixed_input(device):
587-
mask = torch.ones((1, 3, 3), dtype=torch.long, device=device)
588-
589-
out_mask = F.pad_mask(mask, padding=[1, 1, 1, 1])
590-
591-
expected_mask = torch.zeros((1, 5, 5), dtype=torch.long, device=device)
592-
expected_mask[:, 1:-1, 1:-1] = 1
593-
torch.testing.assert_close(out_mask, expected_mask)
594-
595-
596527
@pytest.mark.parametrize("device", cpu_and_cuda())
597528
@pytest.mark.parametrize(
598529
"startpoints, endpoints",

test/test_transforms_v2_refactored.py

+168
Original file line numberDiff line numberDiff line change
@@ -3346,3 +3346,171 @@ def test_transform_errors_warnings(self):
33463346
for param in ["scale", "ratio"]:
33473347
with pytest.warns(match="Scale and ratio should be of kind"):
33483348
transforms.RandomResizedCrop(size=self.INPUT_SIZE, **{param: [1, 0]})
3349+
3350+
3351+
class TestPad:
3352+
EXHAUSTIVE_TYPE_PADDINGS = [1, (1,), (1, 2), (1, 2, 3, 4), [1], [1, 2], [1, 2, 3, 4]]
3353+
CORRECTNESS_PADDINGS = [
3354+
padding
3355+
for padding in EXHAUSTIVE_TYPE_PADDINGS
3356+
if isinstance(padding, int) or isinstance(padding, list) and len(padding) > 1
3357+
]
3358+
PADDING_MODES = ["constant", "symmetric", "edge", "reflect"]
3359+
3360+
@param_value_parametrization(
3361+
padding=EXHAUSTIVE_TYPE_PADDINGS,
3362+
fill=EXHAUSTIVE_TYPE_FILLS,
3363+
padding_mode=PADDING_MODES,
3364+
)
3365+
@pytest.mark.parametrize("dtype", [torch.uint8, torch.float32])
3366+
@pytest.mark.parametrize("device", cpu_and_cuda())
3367+
def test_kernel_image(self, param, value, dtype, device):
3368+
if param == "fill":
3369+
value = adapt_fill(value, dtype=dtype)
3370+
kwargs = {param: value}
3371+
if param != "padding":
3372+
kwargs["padding"] = [1]
3373+
3374+
image = make_image(dtype=dtype, device=device)
3375+
3376+
check_kernel(
3377+
F.pad_image,
3378+
image,
3379+
**kwargs,
3380+
check_scripted_vs_eager=not (
3381+
(param == "padding" and isinstance(value, int))
3382+
# See https://github.com/pytorch/vision/pull/7252#issue-1585585521 for details
3383+
or (
3384+
param == "fill"
3385+
and (
3386+
isinstance(value, tuple) or (isinstance(value, list) and any(isinstance(v, int) for v in value))
3387+
)
3388+
)
3389+
),
3390+
)
3391+
3392+
@pytest.mark.parametrize("format", list(tv_tensors.BoundingBoxFormat))
3393+
def test_kernel_bounding_boxes(self, format):
3394+
bounding_boxes = make_bounding_boxes(format=format)
3395+
check_kernel(
3396+
F.pad_bounding_boxes,
3397+
bounding_boxes,
3398+
format=bounding_boxes.format,
3399+
canvas_size=bounding_boxes.canvas_size,
3400+
padding=[1],
3401+
)
3402+
3403+
@pytest.mark.parametrize("padding_mode", ["symmetric", "edge", "reflect"])
3404+
def test_kernel_bounding_boxes_errors(self, padding_mode):
3405+
bounding_boxes = make_bounding_boxes()
3406+
with pytest.raises(ValueError, match=f"'{padding_mode}' is not supported"):
3407+
F.pad_bounding_boxes(
3408+
bounding_boxes,
3409+
format=bounding_boxes.format,
3410+
canvas_size=bounding_boxes.canvas_size,
3411+
padding=[1],
3412+
padding_mode=padding_mode,
3413+
)
3414+
3415+
@pytest.mark.parametrize("make_mask", [make_segmentation_mask, make_detection_mask])
3416+
def test_kernel_mask(self, make_mask):
3417+
check_kernel(F.pad_mask, make_mask(), padding=[1])
3418+
3419+
@pytest.mark.parametrize("fill", [[1], (0,), [1, 0, 1], (0, 1, 0)])
3420+
def test_kernel_mask_errors(self, fill):
3421+
with pytest.raises(ValueError, match="Non-scalar fill value is not supported"):
3422+
check_kernel(F.pad_mask, make_segmentation_mask(), padding=[1], fill=fill)
3423+
3424+
@pytest.mark.parametrize(
3425+
"make_input",
3426+
[make_image_tensor, make_image_pil, make_image, make_bounding_boxes, make_segmentation_mask, make_video],
3427+
)
3428+
def test_functional(self, make_input):
3429+
check_functional(F.pad, make_input(), padding=[1])
3430+
3431+
@pytest.mark.parametrize(
3432+
("kernel", "input_type"),
3433+
[
3434+
(F.pad_image, torch.Tensor),
3435+
# The PIL kernel uses fill=0 as default rather than fill=None as all others.
3436+
# Since the whole fill story is already really inconsistent, we won't introduce yet another case to allow
3437+
# for this test to pass.
3438+
# See https://github.com/pytorch/vision/issues/6623 for a discussion.
3439+
# (F._pad_image_pil, PIL.Image.Image),
3440+
(F.pad_image, tv_tensors.Image),
3441+
(F.pad_bounding_boxes, tv_tensors.BoundingBoxes),
3442+
(F.pad_mask, tv_tensors.Mask),
3443+
(F.pad_video, tv_tensors.Video),
3444+
],
3445+
)
3446+
def test_functional_signature(self, kernel, input_type):
3447+
check_functional_kernel_signature_match(F.pad, kernel=kernel, input_type=input_type)
3448+
3449+
@pytest.mark.parametrize(
3450+
"make_input",
3451+
[make_image_tensor, make_image_pil, make_image, make_bounding_boxes, make_segmentation_mask, make_video],
3452+
)
3453+
def test_transform(self, make_input):
3454+
check_transform(transforms.Pad(padding=[1]), make_input())
3455+
3456+
def test_transform_errors(self):
3457+
with pytest.raises(TypeError, match="Got inappropriate padding arg"):
3458+
transforms.Pad("abc")
3459+
3460+
with pytest.raises(ValueError, match="Padding must be an int or a 1, 2, or 4"):
3461+
transforms.Pad([-0.7, 0, 0.7])
3462+
3463+
with pytest.raises(TypeError, match="Got inappropriate fill arg"):
3464+
transforms.Pad(12, fill="abc")
3465+
3466+
with pytest.raises(ValueError, match="Padding mode should be either"):
3467+
transforms.Pad(12, padding_mode="abc")
3468+
3469+
@pytest.mark.parametrize("padding", CORRECTNESS_PADDINGS)
3470+
@pytest.mark.parametrize(
3471+
("padding_mode", "fill"),
3472+
[
3473+
*[("constant", fill) for fill in CORRECTNESS_FILLS],
3474+
*[(padding_mode, None) for padding_mode in ["symmetric", "edge", "reflect"]],
3475+
],
3476+
)
3477+
@pytest.mark.parametrize("fn", [F.pad, transform_cls_to_functional(transforms.Pad)])
3478+
def test_image_correctness(self, padding, padding_mode, fill, fn):
3479+
image = make_image(dtype=torch.uint8, device="cpu")
3480+
3481+
actual = fn(image, padding=padding, padding_mode=padding_mode, fill=fill)
3482+
expected = F.to_image(F.pad(F.to_pil_image(image), padding=padding, padding_mode=padding_mode, fill=fill))
3483+
3484+
assert_equal(actual, expected)
3485+
3486+
def _reference_pad_bounding_boxes(self, bounding_boxes, *, padding):
3487+
if isinstance(padding, int):
3488+
padding = [padding]
3489+
left, top, right, bottom = padding * (4 // len(padding))
3490+
3491+
affine_matrix = np.array(
3492+
[
3493+
[1, 0, left],
3494+
[0, 1, top],
3495+
],
3496+
)
3497+
3498+
height = bounding_boxes.canvas_size[0] + top + bottom
3499+
width = bounding_boxes.canvas_size[1] + left + right
3500+
3501+
return reference_affine_bounding_boxes_helper(
3502+
bounding_boxes, affine_matrix=affine_matrix, new_canvas_size=(height, width)
3503+
)
3504+
3505+
@pytest.mark.parametrize("padding", CORRECTNESS_PADDINGS)
3506+
@pytest.mark.parametrize("format", list(tv_tensors.BoundingBoxFormat))
3507+
@pytest.mark.parametrize("dtype", [torch.int64, torch.float32])
3508+
@pytest.mark.parametrize("device", cpu_and_cuda())
3509+
@pytest.mark.parametrize("fn", [F.pad, transform_cls_to_functional(transforms.Pad)])
3510+
def test_bounding_boxes_correctness(self, padding, format, dtype, device, fn):
3511+
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
3512+
3513+
actual = fn(bounding_boxes, padding=padding)
3514+
expected = self._reference_pad_bounding_boxes(bounding_boxes, padding=padding)
3515+
3516+
assert_equal(actual, expected)

test/transforms_v2_dispatcher_infos.py

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
import torchvision.transforms.v2.functional as F
33
from torchvision import tv_tensors
4-
from transforms_v2_kernel_infos import KERNEL_INFOS, pad_xfail_jit_fill_condition
4+
from transforms_v2_kernel_infos import KERNEL_INFOS
55
from transforms_v2_legacy_utils import InfoBase, TestMark
66

77
__all__ = ["DispatcherInfo", "DISPATCHER_INFOS"]
@@ -111,20 +111,6 @@ def xfail_jit_python_scalar_arg(name, *, reason=None):
111111

112112

113113
DISPATCHER_INFOS = [
114-
DispatcherInfo(
115-
F.pad,
116-
kernels={
117-
tv_tensors.Image: F.pad_image,
118-
tv_tensors.Video: F.pad_video,
119-
tv_tensors.BoundingBoxes: F.pad_bounding_boxes,
120-
tv_tensors.Mask: F.pad_mask,
121-
},
122-
pil_kernel_info=PILKernelInfo(F._pad_image_pil, kernel_name="pad_image_pil"),
123-
test_marks=[
124-
xfail_jit("F.pad only supports vector fills for list of floats", condition=pad_xfail_jit_fill_condition),
125-
xfail_jit_python_scalar_arg("padding"),
126-
],
127-
),
128114
DispatcherInfo(
129115
F.perspective,
130116
kernels={

0 commit comments

Comments
 (0)