From c5855d684ace2667314c5902f990d42265995908 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Thu, 7 Apr 2022 09:17:30 +0000 Subject: [PATCH 1/4] [proto] Added crop_bounding_box op --- test/test_prototype_transforms_functional.py | 58 +++++++++++++++++++ .../transforms/functional/__init__.py | 3 +- .../transforms/functional/_geometry.py | 26 +++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/test/test_prototype_transforms_functional.py b/test/test_prototype_transforms_functional.py index 2c8540f093c..a9e250a22b9 100644 --- a/test/test_prototype_transforms_functional.py +++ b/test/test_prototype_transforms_functional.py @@ -321,6 +321,20 @@ def rotate_segmentation_mask(): ) +@register_kernel_info_from_sample_inputs_fn +def crop_bounding_box(): + for bounding_box, top, left in itertools.product(make_bounding_boxes(), [-8, 0, 9], [-8, 0, 9]): + yield SampleInput( + bounding_box, + format=bounding_box.format, + image_size=bounding_box.image_size, + top=top, + left=left, + height=top + 10, # this argument is unused + width=left + 10, # this argument is unused + ) + + @pytest.mark.parametrize( "kernel", [ @@ -808,3 +822,47 @@ def test_correctness_rotate_segmentation_mask_on_fixed_input(device): expected_mask = torch.rot90(mask, k=1, dims=(-2, -1)) out_mask = F.rotate_segmentation_mask(mask, 90, expand=False) torch.testing.assert_close(out_mask, expected_mask) + + +@pytest.mark.parametrize("device", cpu_and_gpu()) +@pytest.mark.parametrize( + "top, left, height, width, expected_bboxes", + [ + [8, 12, 30, 40, [(-2.0, 7.0, 13.0, 27.0), (38.0, -3.0, 58.0, 14.0), (33.0, 38.0, 44.0, 54.0)]], + [-8, 12, 70, 40, [(-2.0, 23.0, 13.0, 43.0), (38.0, 13.0, 58.0, 30.0), (33.0, 54.0, 44.0, 70.0)]], + ], +) +def test_correctness_crop_bounding_box(device, top, left, height, width, expected_bboxes): + + # Expected bboxes computed using Albumentations: + # import numpy as np + # from albumentations.augmentations.crops.functional import crop_bbox_by_coords, normalize_bbox, denormalize_bbox + # expected_bboxes = [] + # for in_box in in_boxes: + # n_in_box = normalize_bbox(in_box, *size) + # n_out_box = crop_bbox_by_coords( + # n_in_box, (left, top, left + width, top + height), height, width, *size + # ) + # out_box = denormalize_bbox(n_out_box, height, width) + # expected_bboxes.append(out_box) + + size = (64, 76) + # xyxy format + in_boxes = [ + [10.0, 15.0, 25.0, 35.0], + [50.0, 5.0, 70.0, 22.0], + [45.0, 46.0, 56.0, 62.0], + ] + in_boxes = features.BoundingBox(in_boxes, format=features.BoundingBoxFormat.XYXY, image_size=size, device=device) + + output_boxes = F.crop_bounding_box( + in_boxes, + in_boxes.format, + in_boxes.image_size, + top, + left, + height, + width, + ) + + torch.testing.assert_close(output_boxes.tolist(), expected_bboxes) diff --git a/torchvision/prototype/transforms/functional/__init__.py b/torchvision/prototype/transforms/functional/__init__.py index 64d47958b96..decf9e21020 100644 --- a/torchvision/prototype/transforms/functional/__init__.py +++ b/torchvision/prototype/transforms/functional/__init__.py @@ -57,9 +57,10 @@ rotate_image_tensor, rotate_image_pil, rotate_segmentation_mask, + pad_bounding_box, pad_image_tensor, pad_image_pil, - pad_bounding_box, + crop_bounding_box, crop_image_tensor, crop_image_pil, perspective_image_tensor, diff --git a/torchvision/prototype/transforms/functional/_geometry.py b/torchvision/prototype/transforms/functional/_geometry.py index 7629766c0e2..755e51ce81c 100644 --- a/torchvision/prototype/transforms/functional/_geometry.py +++ b/torchvision/prototype/transforms/functional/_geometry.py @@ -419,6 +419,32 @@ def pad_bounding_box( crop_image_pil = _FP.crop +def crop_bounding_box( + bounding_box: torch.Tensor, + format: features.BoundingBoxFormat, + image_size: Tuple[int, int], + top: int, + left: int, + height: int, + width: int, +) -> torch.Tensor: + pass + + shape = bounding_box.shape + + bounding_box = convert_bounding_box_format( + bounding_box, old_format=format, new_format=features.BoundingBoxFormat.XYXY + ).view(-1, 4) + + # Crop and optionally pad: + bounding_box[:, 0::2] -= left + bounding_box[:, 1::2] -= top + + return convert_bounding_box_format( + bounding_box, old_format=features.BoundingBoxFormat.XYXY, new_format=format, copy=False + ).view(shape) + + def perspective_image_tensor( img: torch.Tensor, perspective_coeffs: List[float], From f190f7409280d7cf333095c8ca275895763ab728 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Thu, 7 Apr 2022 10:31:16 +0000 Subject: [PATCH 2/4] Removed "pass" --- torchvision/prototype/transforms/functional/_geometry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/torchvision/prototype/transforms/functional/_geometry.py b/torchvision/prototype/transforms/functional/_geometry.py index 755e51ce81c..d94da9b7209 100644 --- a/torchvision/prototype/transforms/functional/_geometry.py +++ b/torchvision/prototype/transforms/functional/_geometry.py @@ -428,8 +428,6 @@ def crop_bounding_box( height: int, width: int, ) -> torch.Tensor: - pass - shape = bounding_box.shape bounding_box = convert_bounding_box_format( From 9f74222a80a14f8df7f95d0372a5f1302c5c27f8 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Thu, 7 Apr 2022 12:30:24 +0000 Subject: [PATCH 3/4] Updated comment --- torchvision/prototype/transforms/functional/_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchvision/prototype/transforms/functional/_geometry.py b/torchvision/prototype/transforms/functional/_geometry.py index d94da9b7209..ae2baccb2ed 100644 --- a/torchvision/prototype/transforms/functional/_geometry.py +++ b/torchvision/prototype/transforms/functional/_geometry.py @@ -434,7 +434,7 @@ def crop_bounding_box( bounding_box, old_format=format, new_format=features.BoundingBoxFormat.XYXY ).view(-1, 4) - # Crop and optionally pad: + # Crop or implicit pad if left and/or top have negative values: bounding_box[:, 0::2] -= left bounding_box[:, 1::2] -= top From 349bd0c628be06ffecee04f9615ec85d527c0e41 Mon Sep 17 00:00:00 2001 From: vfdev-5 Date: Thu, 7 Apr 2022 13:01:28 +0000 Subject: [PATCH 4/4] Removed unused args from signature --- test/test_prototype_transforms_functional.py | 6 ------ torchvision/prototype/transforms/functional/_geometry.py | 3 --- 2 files changed, 9 deletions(-) diff --git a/test/test_prototype_transforms_functional.py b/test/test_prototype_transforms_functional.py index a9e250a22b9..91623854330 100644 --- a/test/test_prototype_transforms_functional.py +++ b/test/test_prototype_transforms_functional.py @@ -327,11 +327,8 @@ def crop_bounding_box(): yield SampleInput( bounding_box, format=bounding_box.format, - image_size=bounding_box.image_size, top=top, left=left, - height=top + 10, # this argument is unused - width=left + 10, # this argument is unused ) @@ -858,11 +855,8 @@ def test_correctness_crop_bounding_box(device, top, left, height, width, expecte output_boxes = F.crop_bounding_box( in_boxes, in_boxes.format, - in_boxes.image_size, top, left, - height, - width, ) torch.testing.assert_close(output_boxes.tolist(), expected_bboxes) diff --git a/torchvision/prototype/transforms/functional/_geometry.py b/torchvision/prototype/transforms/functional/_geometry.py index ae2baccb2ed..71be0a22c00 100644 --- a/torchvision/prototype/transforms/functional/_geometry.py +++ b/torchvision/prototype/transforms/functional/_geometry.py @@ -422,11 +422,8 @@ def pad_bounding_box( def crop_bounding_box( bounding_box: torch.Tensor, format: features.BoundingBoxFormat, - image_size: Tuple[int, int], top: int, left: int, - height: int, - width: int, ) -> torch.Tensor: shape = bounding_box.shape