49
49
from torchvision .transforms .functional import pil_modes_mapping , to_pil_image
50
50
from torchvision .transforms .v2 import functional as F
51
51
from torchvision .transforms .v2 ._utils import check_type , is_pure_tensor
52
- from torchvision .transforms .v2 .functional ._geometry import _get_perspective_coeffs
52
+ from torchvision .transforms .v2 .functional ._geometry import _get_perspective_coeffs , _parallelogram_to_bounding_boxes
53
53
from torchvision .transforms .v2 .functional ._utils import _get_kernel , _register_kernel_internal
54
54
55
55
@@ -560,7 +560,9 @@ def affine_bounding_boxes(bounding_boxes):
560
560
)
561
561
562
562
563
- def reference_affine_rotated_bounding_boxes_helper (bounding_boxes , * , affine_matrix , new_canvas_size = None , clamp = True ):
563
+ def reference_affine_rotated_bounding_boxes_helper (
564
+ bounding_boxes , * , affine_matrix , new_canvas_size = None , clamp = True , flip = False
565
+ ):
564
566
format = bounding_boxes .format
565
567
canvas_size = new_canvas_size or bounding_boxes .canvas_size
566
568
@@ -588,21 +590,34 @@ def affine_rotated_bounding_boxes(bounding_boxes):
588
590
transformed_points = np .matmul (points , affine_matrix .astype (points .dtype ).T )
589
591
output = torch .tensor (
590
592
[
591
- float (transformed_points [1 , 0 ]),
592
- float (transformed_points [1 , 1 ]),
593
593
float (transformed_points [0 , 0 ]),
594
594
float (transformed_points [0 , 1 ]),
595
- float (transformed_points [3 , 0 ]),
596
- float (transformed_points [3 , 1 ]),
595
+ float (transformed_points [1 , 0 ]),
596
+ float (transformed_points [1 , 1 ]),
597
597
float (transformed_points [2 , 0 ]),
598
598
float (transformed_points [2 , 1 ]),
599
+ float (transformed_points [3 , 0 ]),
600
+ float (transformed_points [3 , 1 ]),
599
601
]
600
602
)
601
603
604
+ output = output [[2 , 3 , 0 , 1 , 6 , 7 , 4 , 5 ]] if flip else output
605
+ output = _parallelogram_to_bounding_boxes (output )
606
+
602
607
output = F .convert_bounding_box_format (
603
608
output , old_format = tv_tensors .BoundingBoxFormat .XYXYXYXY , new_format = format
604
609
)
605
610
611
+ if torch .is_floating_point (output ) and dtype in (
612
+ torch .uint8 ,
613
+ torch .int8 ,
614
+ torch .int16 ,
615
+ torch .int32 ,
616
+ torch .int64 ,
617
+ ):
618
+ # it is better to round before cast
619
+ output = torch .round (output )
620
+
606
621
if clamp :
607
622
# It is important to clamp before casting, especially for CXCYWHR format, dtype=int64
608
623
output = F .clamp_bounding_boxes (
@@ -707,7 +722,7 @@ def test_kernel_image(self, size, interpolation, use_max_size, antialias, dtype,
707
722
check_scripted_vs_eager = not isinstance (size , int ),
708
723
)
709
724
710
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
725
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
711
726
@pytest .mark .parametrize ("size" , OUTPUT_SIZES )
712
727
@pytest .mark .parametrize ("use_max_size" , [True , False ])
713
728
@pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
@@ -725,6 +740,7 @@ def test_kernel_bounding_boxes(self, format, size, use_max_size, dtype, device):
725
740
check_kernel (
726
741
F .resize_bounding_boxes ,
727
742
bounding_boxes ,
743
+ format = format ,
728
744
canvas_size = bounding_boxes .canvas_size ,
729
745
size = size ,
730
746
** max_size_kwarg ,
@@ -816,7 +832,7 @@ def test_image_correctness(self, size, interpolation, use_max_size, fn):
816
832
self ._check_output_size (image , actual , size = size , ** max_size_kwarg )
817
833
torch .testing .assert_close (actual , expected , atol = 1 , rtol = 0 )
818
834
819
- def _reference_resize_bounding_boxes (self , bounding_boxes , * , size , max_size = None ):
835
+ def _reference_resize_bounding_boxes (self , bounding_boxes , format , * , size , max_size = None ):
820
836
old_height , old_width = bounding_boxes .canvas_size
821
837
new_height , new_width = self ._compute_output_size (
822
838
input_size = bounding_boxes .canvas_size , size = size , max_size = max_size
@@ -832,13 +848,19 @@ def _reference_resize_bounding_boxes(self, bounding_boxes, *, size, max_size=Non
832
848
],
833
849
)
834
850
835
- return reference_affine_bounding_boxes_helper (
851
+ helper = (
852
+ reference_affine_rotated_bounding_boxes_helper
853
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
854
+ else reference_affine_bounding_boxes_helper
855
+ )
856
+
857
+ return helper (
836
858
bounding_boxes ,
837
859
affine_matrix = affine_matrix ,
838
860
new_canvas_size = (new_height , new_width ),
839
861
)
840
862
841
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
863
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
842
864
@pytest .mark .parametrize ("size" , OUTPUT_SIZES )
843
865
@pytest .mark .parametrize ("use_max_size" , [True , False ])
844
866
@pytest .mark .parametrize ("fn" , [F .resize , transform_cls_to_functional (transforms .Resize )])
@@ -849,7 +871,7 @@ def test_bounding_boxes_correctness(self, format, size, use_max_size, fn):
849
871
bounding_boxes = make_bounding_boxes (format = format , canvas_size = self .INPUT_SIZE )
850
872
851
873
actual = fn (bounding_boxes , size = size , ** max_size_kwarg )
852
- expected = self ._reference_resize_bounding_boxes (bounding_boxes , size = size , ** max_size_kwarg )
874
+ expected = self ._reference_resize_bounding_boxes (bounding_boxes , format = format , size = size , ** max_size_kwarg )
853
875
854
876
self ._check_output_size (bounding_boxes , actual , size = size , ** max_size_kwarg )
855
877
torch .testing .assert_close (actual , expected )
@@ -1152,7 +1174,7 @@ def _reference_horizontal_flip_bounding_boxes(self, bounding_boxes: tv_tensors.B
1152
1174
)
1153
1175
1154
1176
helper = (
1155
- reference_affine_rotated_bounding_boxes_helper
1177
+ functools . partial ( reference_affine_rotated_bounding_boxes_helper , flip = True )
1156
1178
if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
1157
1179
else reference_affine_bounding_boxes_helper
1158
1180
)
@@ -1257,7 +1279,7 @@ def test_kernel_image(self, param, value, dtype, device):
1257
1279
shear = _EXHAUSTIVE_TYPE_AFFINE_KWARGS ["shear" ],
1258
1280
center = _EXHAUSTIVE_TYPE_AFFINE_KWARGS ["center" ],
1259
1281
)
1260
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
1282
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
1261
1283
@pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
1262
1284
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
1263
1285
def test_kernel_bounding_boxes (self , param , value , format , dtype , device ):
@@ -1399,14 +1421,22 @@ def _reference_affine_bounding_boxes(self, bounding_boxes, *, angle, translate,
1399
1421
if center is None :
1400
1422
center = [s * 0.5 for s in bounding_boxes .canvas_size [::- 1 ]]
1401
1423
1402
- return reference_affine_bounding_boxes_helper (
1424
+ affine_matrix = self ._compute_affine_matrix (
1425
+ angle = angle , translate = translate , scale = scale , shear = shear , center = center
1426
+ )
1427
+
1428
+ helper = (
1429
+ reference_affine_rotated_bounding_boxes_helper
1430
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
1431
+ else reference_affine_bounding_boxes_helper
1432
+ )
1433
+
1434
+ return helper (
1403
1435
bounding_boxes ,
1404
- affine_matrix = self ._compute_affine_matrix (
1405
- angle = angle , translate = translate , scale = scale , shear = shear , center = center
1406
- ),
1436
+ affine_matrix = affine_matrix ,
1407
1437
)
1408
1438
1409
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
1439
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
1410
1440
@pytest .mark .parametrize ("angle" , _CORRECTNESS_AFFINE_KWARGS ["angle" ])
1411
1441
@pytest .mark .parametrize ("translate" , _CORRECTNESS_AFFINE_KWARGS ["translate" ])
1412
1442
@pytest .mark .parametrize ("scale" , _CORRECTNESS_AFFINE_KWARGS ["scale" ])
@@ -1607,7 +1637,7 @@ def _reference_vertical_flip_bounding_boxes(self, bounding_boxes: tv_tensors.Bou
1607
1637
)
1608
1638
1609
1639
helper = (
1610
- reference_affine_rotated_bounding_boxes_helper
1640
+ functools . partial ( reference_affine_rotated_bounding_boxes_helper , flip = True )
1611
1641
if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
1612
1642
else reference_affine_bounding_boxes_helper
1613
1643
)
@@ -2914,7 +2944,7 @@ def test_kernel_image(self, kwargs, dtype, device):
2914
2944
check_kernel (F .crop_image , make_image (self .INPUT_SIZE , dtype = dtype , device = device ), ** kwargs )
2915
2945
2916
2946
@pytest .mark .parametrize ("kwargs" , CORRECTNESS_CROP_KWARGS )
2917
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
2947
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
2918
2948
@pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
2919
2949
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
2920
2950
def test_kernel_bounding_box (self , kwargs , format , dtype , device ):
@@ -3059,12 +3089,15 @@ def _reference_crop_bounding_boxes(self, bounding_boxes, *, top, left, height, w
3059
3089
[0 , 1 , - top ],
3060
3090
],
3061
3091
)
3062
- return reference_affine_bounding_boxes_helper (
3063
- bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = (height , width )
3092
+ helper = (
3093
+ reference_affine_rotated_bounding_boxes_helper
3094
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
3095
+ else reference_affine_bounding_boxes_helper
3064
3096
)
3097
+ return helper (bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = (height , width ))
3065
3098
3066
3099
@pytest .mark .parametrize ("kwargs" , CORRECTNESS_CROP_KWARGS )
3067
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
3100
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
3068
3101
@pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
3069
3102
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
3070
3103
def test_functional_bounding_box_correctness (self , kwargs , format , dtype , device ):
@@ -3077,7 +3110,7 @@ def test_functional_bounding_box_correctness(self, kwargs, format, dtype, device
3077
3110
assert_equal (F .get_size (actual ), F .get_size (expected ))
3078
3111
3079
3112
@pytest .mark .parametrize ("output_size" , [(17 , 11 ), (11 , 17 ), (11 , 11 )])
3080
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
3113
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
3081
3114
@pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
3082
3115
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
3083
3116
@pytest .mark .parametrize ("seed" , list (range (5 )))
@@ -3099,7 +3132,7 @@ def test_transform_bounding_boxes_correctness(self, output_size, format, dtype,
3099
3132
3100
3133
expected = self ._reference_crop_bounding_boxes (bounding_boxes , ** params )
3101
3134
3102
- assert_equal (actual , expected )
3135
+ torch . testing . assert_close (actual , expected )
3103
3136
assert_equal (F .get_size (actual ), F .get_size (expected ))
3104
3137
3105
3138
def test_errors (self ):
@@ -3834,13 +3867,19 @@ def _reference_resized_crop_bounding_boxes(self, bounding_boxes, *, top, left, h
3834
3867
)
3835
3868
affine_matrix = (resize_affine_matrix @ crop_affine_matrix )[:2 , :]
3836
3869
3837
- return reference_affine_bounding_boxes_helper (
3870
+ helper = (
3871
+ reference_affine_rotated_bounding_boxes_helper
3872
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
3873
+ else reference_affine_bounding_boxes_helper
3874
+ )
3875
+
3876
+ return helper (
3838
3877
bounding_boxes ,
3839
3878
affine_matrix = affine_matrix ,
3840
3879
new_canvas_size = size ,
3841
3880
)
3842
3881
3843
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
3882
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
3844
3883
def test_functional_bounding_boxes_correctness (self , format ):
3845
3884
bounding_boxes = make_bounding_boxes (self .INPUT_SIZE , format = format )
3846
3885
@@ -3849,7 +3888,7 @@ def test_functional_bounding_boxes_correctness(self, format):
3849
3888
bounding_boxes , ** self .CROP_KWARGS , size = self .OUTPUT_SIZE
3850
3889
)
3851
3890
3852
- assert_equal (actual , expected )
3891
+ torch . testing . assert_close (actual , expected )
3853
3892
assert_equal (F .get_size (actual ), F .get_size (expected ))
3854
3893
3855
3894
def test_transform_errors_warnings (self ):
@@ -3914,7 +3953,7 @@ def test_kernel_image(self, param, value, dtype, device):
3914
3953
),
3915
3954
)
3916
3955
3917
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
3956
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
3918
3957
def test_kernel_bounding_boxes (self , format ):
3919
3958
bounding_boxes = make_bounding_boxes (format = format )
3920
3959
check_kernel (
@@ -4034,12 +4073,15 @@ def _reference_pad_bounding_boxes(self, bounding_boxes, *, padding):
4034
4073
height = bounding_boxes .canvas_size [0 ] + top + bottom
4035
4074
width = bounding_boxes .canvas_size [1 ] + left + right
4036
4075
4037
- return reference_affine_bounding_boxes_helper (
4038
- bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = (height , width )
4076
+ helper = (
4077
+ reference_affine_rotated_bounding_boxes_helper
4078
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
4079
+ else reference_affine_bounding_boxes_helper
4039
4080
)
4081
+ return helper (bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = (height , width ))
4040
4082
4041
4083
@pytest .mark .parametrize ("padding" , CORRECTNESS_PADDINGS )
4042
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
4084
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
4043
4085
@pytest .mark .parametrize ("dtype" , [torch .int64 , torch .float32 ])
4044
4086
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
4045
4087
@pytest .mark .parametrize ("fn" , [F .pad , transform_cls_to_functional (transforms .Pad )])
@@ -4049,7 +4091,7 @@ def test_bounding_boxes_correctness(self, padding, format, dtype, device, fn):
4049
4091
actual = fn (bounding_boxes , padding = padding )
4050
4092
expected = self ._reference_pad_bounding_boxes (bounding_boxes , padding = padding )
4051
4093
4052
- assert_equal (actual , expected )
4094
+ torch . testing . assert_close (actual , expected )
4053
4095
4054
4096
4055
4097
class TestCenterCrop :
@@ -4068,7 +4110,7 @@ def test_kernel_image(self, output_size, dtype, device):
4068
4110
)
4069
4111
4070
4112
@pytest .mark .parametrize ("output_size" , OUTPUT_SIZES )
4071
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
4113
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
4072
4114
def test_kernel_bounding_boxes (self , output_size , format ):
4073
4115
bounding_boxes = make_bounding_boxes (self .INPUT_SIZE , format = format )
4074
4116
check_kernel (
@@ -4142,12 +4184,15 @@ def _reference_center_crop_bounding_boxes(self, bounding_boxes, output_size):
4142
4184
[0 , 1 , - top ],
4143
4185
],
4144
4186
)
4145
- return reference_affine_bounding_boxes_helper (
4146
- bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = output_size
4187
+ helper = (
4188
+ reference_affine_rotated_bounding_boxes_helper
4189
+ if tv_tensors .is_rotated_bounding_format (bounding_boxes .format )
4190
+ else reference_affine_bounding_boxes_helper
4147
4191
)
4192
+ return helper (bounding_boxes , affine_matrix = affine_matrix , new_canvas_size = output_size )
4148
4193
4149
4194
@pytest .mark .parametrize ("output_size" , OUTPUT_SIZES )
4150
- @pytest .mark .parametrize ("format" , SUPPORTED_BOX_FORMATS )
4195
+ @pytest .mark .parametrize ("format" , list ( tv_tensors . BoundingBoxFormat ) )
4151
4196
@pytest .mark .parametrize ("dtype" , [torch .int64 , torch .float32 ])
4152
4197
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
4153
4198
@pytest .mark .parametrize ("fn" , [F .center_crop , transform_cls_to_functional (transforms .CenterCrop )])
@@ -4157,7 +4202,7 @@ def test_bounding_boxes_correctness(self, output_size, format, dtype, device, fn
4157
4202
actual = fn (bounding_boxes , output_size )
4158
4203
expected = self ._reference_center_crop_bounding_boxes (bounding_boxes , output_size )
4159
4204
4160
- assert_equal (actual , expected )
4205
+ torch . testing . assert_close (actual , expected )
4161
4206
4162
4207
4163
4208
class TestPerspective :
@@ -5894,6 +5939,37 @@ def test_classification_preset(image_type, label_type, dataset_return_type, to_t
5894
5939
assert out_label == label
5895
5940
5896
5941
5942
+ @pytest .mark .parametrize ("input_size" , [(17 , 11 ), (11 , 17 ), (11 , 11 )])
5943
+ @pytest .mark .parametrize ("dtype" , [torch .float32 , torch .int64 ])
5944
+ @pytest .mark .parametrize ("device" , cpu_and_cuda ())
5945
+ def test_parallelogram_to_bounding_boxes (input_size , dtype , device ):
5946
+ # Assert that applying `_parallelogram_to_bounding_boxes` to rotated boxes
5947
+ # does not modify the input.
5948
+ bounding_boxes = make_bounding_boxes (
5949
+ input_size , format = tv_tensors .BoundingBoxFormat .XYXYXYXY , dtype = dtype , device = device
5950
+ )
5951
+ actual = _parallelogram_to_bounding_boxes (bounding_boxes )
5952
+ torch .testing .assert_close (actual , bounding_boxes , rtol = 0 , atol = 1 )
5953
+
5954
+ # Test the transformation of two simple parallelograms.
5955
+ # 1---2 1----2
5956
+ # / / -> | |
5957
+ # 4---3 4----3
5958
+
5959
+ # 1---2 1----2
5960
+ # \ \ -> | |
5961
+ # 4---3 4----3
5962
+ parallelogram = torch .tensor ([[1 , 0 , 4 , 0 , 3 , 2 , 0 , 2 ], [0 , 0 , 3 , 0 , 4 , 2 , 1 , 2 ]])
5963
+ expected = torch .tensor (
5964
+ [
5965
+ [0 , 0 , 4 , 0 , 4 , 2 , 0 , 2 ],
5966
+ [0 , 0 , 4 , 0 , 4 , 2 , 0 , 2 ],
5967
+ ]
5968
+ )
5969
+ actual = _parallelogram_to_bounding_boxes (parallelogram )
5970
+ assert_equal (actual , expected )
5971
+
5972
+
5897
5973
@pytest .mark .parametrize ("image_type" , (PIL .Image , torch .Tensor , tv_tensors .Image ))
5898
5974
@pytest .mark .parametrize ("data_augmentation" , ("hflip" , "lsj" , "multiscale" , "ssd" , "ssdlite" ))
5899
5975
@pytest .mark .parametrize ("to_tensor" , (transforms .ToTensor , transforms .ToImage ))
0 commit comments