Skip to content

Commit e79879b

Browse files
NicolasHugfacebook-github-bot
authored andcommitted
[fbsync] Disable integer dtype for rotated bounding boxes (#9133)
Reviewed By: AntoineSimoulin Differential Revision: D79175049 fbshipit-source-id: 28e287b1dd7d874f060c220014b978a00803ba90 Co-authored-by: Nicolas Hug <[email protected]> Co-authored-by: Nicolas Hug <[email protected]>
1 parent 5eb6c3b commit e79879b

File tree

6 files changed

+106
-104
lines changed

6 files changed

+106
-104
lines changed

test/common_utils.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -424,13 +424,6 @@ def sample_position(values, max_value):
424424
format = tv_tensors.BoundingBoxFormat[format]
425425

426426
dtype = dtype or torch.float32
427-
int_dtype = dtype in (
428-
torch.uint8,
429-
torch.int8,
430-
torch.int16,
431-
torch.int32,
432-
torch.int64,
433-
)
434427

435428
h, w = (torch.randint(1, s, (num_boxes,)) for s in canvas_size)
436429
y = sample_position(h, canvas_size[0])
@@ -457,14 +450,14 @@ def sample_position(values, max_value):
457450
elif format is tv_tensors.BoundingBoxFormat.XYXYXYXY:
458451
r_rad = r * torch.pi / 180.0
459452
cos, sin = torch.cos(r_rad), torch.sin(r_rad)
460-
x1 = torch.round(x) if int_dtype else x
461-
y1 = torch.round(y) if int_dtype else y
462-
x2 = torch.round(x1 + w * cos) if int_dtype else x1 + w * cos
463-
y2 = torch.round(y1 - w * sin) if int_dtype else y1 - w * sin
464-
x3 = torch.round(x2 + h * sin) if int_dtype else x2 + h * sin
465-
y3 = torch.round(y2 + h * cos) if int_dtype else y2 + h * cos
466-
x4 = torch.round(x1 + h * sin) if int_dtype else x1 + h * sin
467-
y4 = torch.round(y1 + h * cos) if int_dtype else y1 + h * cos
453+
x1 = x
454+
y1 = y
455+
x2 = x1 + w * cos
456+
y2 = y1 - w * sin
457+
x3 = x2 + h * sin
458+
y3 = y2 + h * cos
459+
x4 = x1 + h * sin
460+
y4 = y1 + h * cos
468461
parts = (x1, y1, x2, y2, x3, y3, x4, y4)
469462
else:
470463
raise ValueError(f"Format {format} is not supported")

test/test_transforms_v2.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -564,13 +564,6 @@ def reference_affine_rotated_bounding_boxes_helper(
564564

565565
def affine_rotated_bounding_boxes(bounding_boxes):
566566
dtype = bounding_boxes.dtype
567-
int_dtype = dtype in (
568-
torch.uint8,
569-
torch.int8,
570-
torch.int16,
571-
torch.int32,
572-
torch.int64,
573-
)
574567
device = bounding_boxes.device
575568

576569
# Go to float before converting to prevent precision loss in case of CXCYWHR -> XYXYXYXY and W or H is 1
@@ -605,18 +598,12 @@ def affine_rotated_bounding_boxes(bounding_boxes):
605598
)
606599

607600
output = output[[2, 3, 0, 1, 6, 7, 4, 5]] if flip else output
608-
if not int_dtype:
609-
output = _parallelogram_to_bounding_boxes(output)
601+
output = _parallelogram_to_bounding_boxes(output)
610602

611603
output = F.convert_bounding_box_format(
612604
output, old_format=tv_tensors.BoundingBoxFormat.XYXYXYXY, new_format=format
613605
)
614606

615-
if torch.is_floating_point(output) and int_dtype:
616-
# It is important to round before cast.
617-
output = torch.round(output)
618-
619-
# For rotated boxes, it is important to cast before clamping.
620607
return (
621608
F.clamp_bounding_boxes(
622609
output.to(dtype=dtype, device=device),
@@ -760,6 +747,8 @@ def test_kernel_image(self, size, interpolation, use_max_size, antialias, dtype,
760747
def test_kernel_bounding_boxes(self, format, size, use_max_size, dtype, device):
761748
if not (max_size_kwarg := self._make_max_size_kwarg(use_max_size=use_max_size, size=size)):
762749
return
750+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
751+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
763752

764753
bounding_boxes = make_bounding_boxes(
765754
format=format,
@@ -1212,6 +1201,8 @@ def test_kernel_image(self, dtype, device):
12121201
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
12131202
@pytest.mark.parametrize("device", cpu_and_cuda())
12141203
def test_kernel_bounding_boxes(self, format, dtype, device):
1204+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
1205+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
12151206
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
12161207
check_kernel(
12171208
F.horizontal_flip_bounding_boxes,
@@ -1441,6 +1432,8 @@ def test_kernel_image(self, param, value, dtype, device):
14411432
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
14421433
@pytest.mark.parametrize("device", cpu_and_cuda())
14431434
def test_kernel_bounding_boxes(self, param, value, format, dtype, device):
1435+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
1436+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
14441437
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
14451438
self._check_kernel(
14461439
F.affine_bounding_boxes,
@@ -1655,7 +1648,7 @@ def test_functional_bounding_boxes_correctness(self, format, angle, translate, s
16551648
center=center,
16561649
)
16571650

1658-
torch.testing.assert_close(actual, expected, atol=1e-5, rtol=1e-5)
1651+
torch.testing.assert_close(actual, expected, atol=1e-4, rtol=1e-4)
16591652

16601653
@pytest.mark.parametrize("format", list(tv_tensors.BoundingBoxFormat))
16611654
@pytest.mark.parametrize("center", _CORRECTNESS_AFFINE_KWARGS["center"])
@@ -1823,6 +1816,8 @@ def test_kernel_image(self, dtype, device):
18231816
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
18241817
@pytest.mark.parametrize("device", cpu_and_cuda())
18251818
def test_kernel_bounding_boxes(self, format, dtype, device):
1819+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
1820+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
18261821
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
18271822
check_kernel(
18281823
F.vertical_flip_bounding_boxes,
@@ -2021,8 +2016,14 @@ def test_kernel_bounding_boxes(self, param, value, format, dtype, device):
20212016
kwargs = {param: value}
20222017
if param != "angle":
20232018
kwargs["angle"] = self._MINIMAL_AFFINE_KWARGS["angle"]
2019+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
2020+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
20242021

20252022
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
2023+
if tv_tensors.is_rotated_bounding_format(format):
2024+
# TODO there is a 1e-6 difference between GPU and CPU outputs
2025+
# due to clamping. To avoid failing this test, we do clamp before hand.
2026+
bounding_boxes = F.clamp_bounding_boxes(bounding_boxes)
20262027

20272028
check_kernel(
20282029
F.rotate_bounding_boxes,
@@ -3236,6 +3237,8 @@ def test_kernel_image(self, param, value, dtype, device):
32363237
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
32373238
@pytest.mark.parametrize("device", cpu_and_cuda())
32383239
def test_kernel_bounding_boxes(self, format, dtype, device):
3240+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
3241+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
32393242
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
32403243

32413244
check_kernel(
@@ -3399,6 +3402,8 @@ def test_kernel_image(self, kwargs, dtype, device):
33993402
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
34003403
@pytest.mark.parametrize("device", cpu_and_cuda())
34013404
def test_kernel_bounding_boxes(self, kwargs, format, dtype, device):
3405+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
3406+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
34023407
bounding_boxes = make_bounding_boxes(self.INPUT_SIZE, format=format, dtype=dtype, device=device)
34033408
check_kernel(F.crop_bounding_boxes, bounding_boxes, format=format, **kwargs)
34043409

@@ -3576,6 +3581,8 @@ def _reference_crop_bounding_boxes(self, bounding_boxes, *, top, left, height, w
35763581
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
35773582
@pytest.mark.parametrize("device", cpu_and_cuda())
35783583
def test_functional_bounding_box_correctness(self, kwargs, format, dtype, device):
3584+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
3585+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
35793586
bounding_boxes = make_bounding_boxes(self.INPUT_SIZE, format=format, dtype=dtype, device=device)
35803587

35813588
actual = F.crop(bounding_boxes, **kwargs)
@@ -3590,6 +3597,8 @@ def test_functional_bounding_box_correctness(self, kwargs, format, dtype, device
35903597
@pytest.mark.parametrize("device", cpu_and_cuda())
35913598
@pytest.mark.parametrize("seed", list(range(5)))
35923599
def test_transform_bounding_boxes_correctness(self, output_size, format, dtype, device, seed):
3600+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
3601+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
35933602
input_size = [s * 2 for s in output_size]
35943603
bounding_boxes = make_bounding_boxes(input_size, format=format, dtype=dtype, device=device)
35953604

@@ -4267,6 +4276,10 @@ def _reference_convert_bounding_box_format(self, bounding_boxes, new_format):
42674276
@pytest.mark.parametrize("device", cpu_and_cuda())
42684277
@pytest.mark.parametrize("fn_type", ["functional", "transform"])
42694278
def test_correctness(self, old_format, new_format, dtype, device, fn_type):
4279+
if not dtype.is_floating_point and (
4280+
tv_tensors.is_rotated_bounding_format(old_format) or tv_tensors.is_rotated_bounding_format(new_format)
4281+
):
4282+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
42704283
bounding_boxes = make_bounding_boxes(format=old_format, dtype=dtype, device=device)
42714284

42724285
if fn_type == "functional":
@@ -4706,6 +4719,8 @@ def _reference_pad_bounding_boxes(self, bounding_boxes, *, padding):
47064719
@pytest.mark.parametrize("device", cpu_and_cuda())
47074720
@pytest.mark.parametrize("fn", [F.pad, transform_cls_to_functional(transforms.Pad)])
47084721
def test_bounding_boxes_correctness(self, padding, format, dtype, device, fn):
4722+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
4723+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
47094724
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
47104725

47114726
actual = fn(bounding_boxes, padding=padding)
@@ -4876,6 +4891,8 @@ def _reference_center_crop_bounding_boxes(self, bounding_boxes, output_size):
48764891
@pytest.mark.parametrize("device", cpu_and_cuda())
48774892
@pytest.mark.parametrize("fn", [F.center_crop, transform_cls_to_functional(transforms.CenterCrop)])
48784893
def test_bounding_boxes_correctness(self, output_size, format, dtype, device, fn):
4894+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
4895+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
48794896
bounding_boxes = make_bounding_boxes(self.INPUT_SIZE, format=format, dtype=dtype, device=device)
48804897

48814898
actual = fn(bounding_boxes, output_size)
@@ -5242,6 +5259,8 @@ def perspective_bounding_boxes(bounding_boxes):
52425259
@pytest.mark.parametrize("dtype", [torch.int64, torch.float32])
52435260
@pytest.mark.parametrize("device", cpu_and_cuda())
52445261
def test_correctness_perspective_bounding_boxes(self, startpoints, endpoints, format, dtype, device):
5262+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
5263+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
52455264
bounding_boxes = make_bounding_boxes(format=format, dtype=dtype, device=device)
52465265

52475266
actual = F.perspective(bounding_boxes, startpoints=startpoints, endpoints=endpoints)
@@ -5511,6 +5530,8 @@ class TestClampBoundingBoxes:
55115530
@pytest.mark.parametrize("dtype", [torch.int64, torch.float32])
55125531
@pytest.mark.parametrize("device", cpu_and_cuda())
55135532
def test_kernel(self, format, clamping_mode, dtype, device):
5533+
if not dtype.is_floating_point and tv_tensors.is_rotated_bounding_format(format):
5534+
pytest.xfail("Rotated bounding boxes should be floating point tensors")
55145535
bounding_boxes = make_bounding_boxes(format=format, clamping_mode=clamping_mode, dtype=dtype, device=device)
55155536
check_kernel(
55165537
F.clamp_bounding_boxes,
@@ -5572,9 +5593,12 @@ def test_clamping_mode(self, rotated, constructor_clamping_mode, clamping_mode,
55725593

55735594
if rotated:
55745595
boxes = tv_tensors.BoundingBoxes(
5575-
[0, 0, 100, 100, 0], format="XYWHR", canvas_size=(10, 10), clamping_mode=constructor_clamping_mode
5596+
[0.0, 0.0, 100.0, 100.0, 0.0],
5597+
format="XYWHR",
5598+
canvas_size=(10, 10),
5599+
clamping_mode=constructor_clamping_mode,
55765600
)
5577-
expected_clamped_output = torch.tensor([[0, 0, 10, 10, 0]])
5601+
expected_clamped_output = torch.tensor([[0.0, 0.0, 10.0, 10.0, 0.0]])
55785602
else:
55795603
boxes = tv_tensors.BoundingBoxes(
55805604
[0, 100, 0, 100], format="XYXY", canvas_size=(10, 10), clamping_mode=constructor_clamping_mode
@@ -6938,14 +6962,11 @@ def test_classification_preset(image_type, label_type, dataset_return_type, to_t
69386962

69396963

69406964
@pytest.mark.parametrize("input_size", [(17, 11), (11, 17), (11, 11)])
6941-
@pytest.mark.parametrize("dtype", [torch.float32, torch.int64])
69426965
@pytest.mark.parametrize("device", cpu_and_cuda())
6943-
def test_parallelogram_to_bounding_boxes(input_size, dtype, device):
6966+
def test_parallelogram_to_bounding_boxes(input_size, device):
69446967
# Assert that applying `_parallelogram_to_bounding_boxes` to rotated boxes
69456968
# does not modify the input.
6946-
bounding_boxes = make_bounding_boxes(
6947-
input_size, format=tv_tensors.BoundingBoxFormat.XYXYXYXY, dtype=dtype, device=device
6948-
)
6969+
bounding_boxes = make_bounding_boxes(input_size, format=tv_tensors.BoundingBoxFormat.XYXYXYXY, device=device)
69496970
actual = _parallelogram_to_bounding_boxes(bounding_boxes)
69506971
torch.testing.assert_close(actual, bounding_boxes, rtol=0, atol=1)
69516972

test/test_tv_tensors.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,39 @@ def test_bbox_instance(data, format):
6969
)
7070
@pytest.mark.parametrize("scripted", (False, True))
7171
def test_bbox_format(format, is_rotated_expected, scripted):
72-
if isinstance(format, str):
73-
format = tv_tensors.BoundingBoxFormat[(format.upper())]
74-
7572
fn = tv_tensors.is_rotated_bounding_format
7673
if scripted:
7774
fn = torch.jit.script(fn)
7875
assert fn(format) == is_rotated_expected
7976

8077

78+
@pytest.mark.parametrize(
79+
"format, support_integer_dtype",
80+
[
81+
("XYXY", True),
82+
("XYWH", True),
83+
("CXCYWH", True),
84+
("XYXYXYXY", False),
85+
("XYWHR", False),
86+
("CXCYWHR", False),
87+
(tv_tensors.BoundingBoxFormat.XYXY, True),
88+
(tv_tensors.BoundingBoxFormat.XYWH, True),
89+
(tv_tensors.BoundingBoxFormat.CXCYWH, True),
90+
(tv_tensors.BoundingBoxFormat.XYXYXYXY, False),
91+
(tv_tensors.BoundingBoxFormat.XYWHR, False),
92+
(tv_tensors.BoundingBoxFormat.CXCYWHR, False),
93+
],
94+
)
95+
@pytest.mark.parametrize("input_dtype", [torch.float32, torch.float64, torch.uint8])
96+
def test_bbox_format_dtype(format, support_integer_dtype, input_dtype):
97+
tensor = torch.randint(0, 32, size=(5, 2), dtype=input_dtype)
98+
if not input_dtype.is_floating_point and not support_integer_dtype:
99+
with pytest.raises(ValueError, match="Rotated bounding boxes should be floating point tensors"):
100+
tv_tensors.BoundingBoxes(tensor, format=format, canvas_size=(32, 32))
101+
else:
102+
tv_tensors.BoundingBoxes(tensor, format=format, canvas_size=(32, 32))
103+
104+
81105
def test_bbox_dim_error():
82106
data_3d = [[[1, 2, 3, 4]]]
83107
with pytest.raises(ValueError, match="Expected a 1D or 2D tensor, got 3D"):
@@ -409,5 +433,10 @@ def test_return_type_input():
409433

410434

411435
def test_box_clamping_mode_default():
412-
assert tv_tensors.BoundingBoxes([0, 0, 10, 10], format="XYXY", canvas_size=(100, 100)).clamping_mode == "soft"
413-
assert tv_tensors.BoundingBoxes([0, 0, 10, 10, 0], format="XYWHR", canvas_size=(100, 100)).clamping_mode == "soft"
436+
assert (
437+
tv_tensors.BoundingBoxes([0.0, 0.0, 10.0, 10.0], format="XYXY", canvas_size=(100, 100)).clamping_mode == "soft"
438+
)
439+
assert (
440+
tv_tensors.BoundingBoxes([0.0, 0.0, 10.0, 10.0, 0.0], format="XYWHR", canvas_size=(100, 100)).clamping_mode
441+
== "soft"
442+
)

0 commit comments

Comments
 (0)