@@ -492,6 +492,7 @@ def adapt_fill(value, *, dtype):
492
492
def reference_affine_bounding_boxes_helper (bounding_boxes , * , affine_matrix , new_canvas_size = None , clamp = True ):
493
493
format = bounding_boxes .format
494
494
canvas_size = new_canvas_size or bounding_boxes .canvas_size
495
+ clamping_mode = bounding_boxes .clamping_mode
495
496
496
497
def affine_bounding_boxes (bounding_boxes ):
497
498
dtype = bounding_boxes .dtype
@@ -535,6 +536,7 @@ def affine_bounding_boxes(bounding_boxes):
535
536
output ,
536
537
format = format ,
537
538
canvas_size = canvas_size ,
539
+ clamping_mode = clamping_mode ,
538
540
)
539
541
else :
540
542
# We leave the bounding box as float64 so the caller gets the full precision to perform any additional
@@ -557,6 +559,7 @@ def reference_affine_rotated_bounding_boxes_helper(
557
559
):
558
560
format = bounding_boxes .format
559
561
canvas_size = new_canvas_size or bounding_boxes .canvas_size
562
+ clamping_mode = bounding_boxes .clamping_mode
560
563
561
564
def affine_rotated_bounding_boxes (bounding_boxes ):
562
565
dtype = bounding_boxes .dtype
@@ -618,6 +621,7 @@ def affine_rotated_bounding_boxes(bounding_boxes):
618
621
output .to (dtype = dtype , device = device ),
619
622
format = format ,
620
623
canvas_size = canvas_size ,
624
+ clamping_mode = clamping_mode ,
621
625
)
622
626
if clamp
623
627
else output .to (dtype = output .dtype , device = device )
@@ -831,7 +835,6 @@ def test_functional(self, size, make_input):
831
835
(F .resize_image , torch .Tensor ),
832
836
(F ._geometry ._resize_image_pil , PIL .Image .Image ),
833
837
(F .resize_image , tv_tensors .Image ),
834
- (F .resize_bounding_boxes , tv_tensors .BoundingBoxes ),
835
838
(F .resize_mask , tv_tensors .Mask ),
836
839
(F .resize_video , tv_tensors .Video ),
837
840
(F .resize_keypoints , tv_tensors .KeyPoints ),
@@ -3289,7 +3292,6 @@ def test_functional(self, make_input):
3289
3292
(F .elastic_image , torch .Tensor ),
3290
3293
(F ._geometry ._elastic_image_pil , PIL .Image .Image ),
3291
3294
(F .elastic_image , tv_tensors .Image ),
3292
- (F .elastic_bounding_boxes , tv_tensors .BoundingBoxes ),
3293
3295
(F .elastic_mask , tv_tensors .Mask ),
3294
3296
(F .elastic_video , tv_tensors .Video ),
3295
3297
(F .elastic_keypoints , tv_tensors .KeyPoints ),
@@ -5126,6 +5128,7 @@ def test_image_functional_correctness(self, coefficients, interpolation, fill):
5126
5128
def _reference_perspective_bounding_boxes (self , bounding_boxes , * , startpoints , endpoints ):
5127
5129
format = bounding_boxes .format
5128
5130
canvas_size = bounding_boxes .canvas_size
5131
+ clamping_mode = bounding_boxes .clamping_mode
5129
5132
dtype = bounding_boxes .dtype
5130
5133
device = bounding_boxes .device
5131
5134
is_rotated = tv_tensors .is_rotated_bounding_format (format )
@@ -5226,6 +5229,7 @@ def perspective_bounding_boxes(bounding_boxes):
5226
5229
output ,
5227
5230
format = format ,
5228
5231
canvas_size = canvas_size ,
5232
+ clamping_mode = clamping_mode ,
5229
5233
).to (dtype = dtype , device = device )
5230
5234
5231
5235
return tv_tensors .BoundingBoxes (
@@ -5506,29 +5510,35 @@ def test_correctness_image(self, mean, std, dtype, fn):
5506
5510
5507
5511
class TestClampBoundingBoxes :
5508
5512
@pytest .mark .parametrize ("format" , list (tv_tensors .BoundingBoxFormat ))
5513
+ @pytest .mark .parametrize ("clamping_mode" , ("hard" , "none" )) # TODOBB add soft
5509
5514
@pytest .mark .parametrize ("dtype" , [torch .int64 , torch .float32 ])
5510
5515
@pytest .mark .parametrize ("device" , cpu_and_cuda ())
5511
- def test_kernel (self , format , dtype , device ):
5512
- bounding_boxes = make_bounding_boxes (format = format , dtype = dtype , device = device )
5516
+ def test_kernel (self , format , clamping_mode , dtype , device ):
5517
+ bounding_boxes = make_bounding_boxes (format = format , clamping_mode = clamping_mode , dtype = dtype , device = device )
5513
5518
check_kernel (
5514
5519
F .clamp_bounding_boxes ,
5515
5520
bounding_boxes ,
5516
5521
format = bounding_boxes .format ,
5517
5522
canvas_size = bounding_boxes .canvas_size ,
5523
+ clamping_mode = clamping_mode ,
5518
5524
)
5519
5525
5520
5526
@pytest .mark .parametrize ("format" , list (tv_tensors .BoundingBoxFormat ))
5521
- def test_functional (self , format ):
5522
- check_functional (F .clamp_bounding_boxes , make_bounding_boxes (format = format ))
5527
+ @pytest .mark .parametrize ("clamping_mode" , ("hard" , "none" )) # TODOBB add soft
5528
+ def test_functional (self , format , clamping_mode ):
5529
+ check_functional (F .clamp_bounding_boxes , make_bounding_boxes (format = format , clamping_mode = clamping_mode ))
5523
5530
5524
5531
def test_errors (self ):
5525
5532
input_tv_tensor = make_bounding_boxes ()
5526
5533
input_pure_tensor = input_tv_tensor .as_subclass (torch .Tensor )
5527
5534
format , canvas_size = input_tv_tensor .format , input_tv_tensor .canvas_size
5528
5535
5529
- for format_ , canvas_size_ in [(None , None ), (format , None ), (None , canvas_size )]:
5536
+ for format_ , canvas_size_ , clamping_mode_ in itertools .product (
5537
+ (format , None ), (canvas_size , None ), (input_tv_tensor .clamping_mode , None )
5538
+ ):
5530
5539
with pytest .raises (
5531
- ValueError , match = "For pure tensor inputs, `format` and `canvas_size` have to be passed."
5540
+ ValueError ,
5541
+ match = "For pure tensor inputs, `format`, `canvas_size` and `clamping_mode` have to be passed." ,
5532
5542
):
5533
5543
F .clamp_bounding_boxes (input_pure_tensor , format = format_ , canvas_size = canvas_size_ )
5534
5544
@@ -5541,6 +5551,103 @@ def test_errors(self):
5541
5551
def test_transform (self ):
5542
5552
check_transform (transforms .ClampBoundingBoxes (), make_bounding_boxes ())
5543
5553
5554
+ @pytest .mark .parametrize ("rotated" , (True , False ))
5555
+ @pytest .mark .parametrize ("constructor_clamping_mode" , ("hard" , "none" ))
5556
+ @pytest .mark .parametrize ("clamping_mode" , ("hard" , "none" , None )) # TODOBB add soft here.
5557
+ @pytest .mark .parametrize ("pass_pure_tensor" , (True , False ))
5558
+ @pytest .mark .parametrize ("fn" , [F .clamp_bounding_boxes , transform_cls_to_functional (transforms .ClampBoundingBoxes )])
5559
+ def test_clamping_mode (self , rotated , constructor_clamping_mode , clamping_mode , pass_pure_tensor , fn ):
5560
+ # This test checks 2 things:
5561
+ # - That passing clamping_mode=None to the clamp_bounding_boxes
5562
+ # functional (or to the class) relies on the box's `.clamping_mode`
5563
+ # attribute
5564
+ # - That clamping happens when it should, and only when it should, i.e.
5565
+ # when the clamping mode is not "none". It doesn't validate the
5566
+ # nunmerical results, only that clamping happened. For that, we create
5567
+ # a large 100x100 box inside of a small 10x10 image.
5568
+
5569
+ if pass_pure_tensor and fn is not F .clamp_bounding_boxes :
5570
+ # Only the functional supports pure tensors, not the class
5571
+ return
5572
+ if pass_pure_tensor and clamping_mode is None :
5573
+ # cannot leave clamping_mode=None when passing pure tensor
5574
+ return
5575
+
5576
+ if rotated :
5577
+ boxes = tv_tensors .BoundingBoxes (
5578
+ [0 , 0 , 100 , 100 , 0 ], format = "XYWHR" , canvas_size = (10 , 10 ), clamping_mode = constructor_clamping_mode
5579
+ )
5580
+ expected_clamped_output = torch .tensor ([[0 , 0 , 10 , 10 , 0 ]])
5581
+ else :
5582
+ boxes = tv_tensors .BoundingBoxes (
5583
+ [0 , 100 , 0 , 100 ], format = "XYXY" , canvas_size = (10 , 10 ), clamping_mode = constructor_clamping_mode
5584
+ )
5585
+ expected_clamped_output = torch .tensor ([[0 , 10 , 0 , 10 ]])
5586
+
5587
+ if pass_pure_tensor :
5588
+ out = fn (
5589
+ boxes .as_subclass (torch .Tensor ),
5590
+ format = boxes .format ,
5591
+ canvas_size = boxes .canvas_size ,
5592
+ clamping_mode = clamping_mode ,
5593
+ )
5594
+ else :
5595
+ out = fn (boxes , clamping_mode = clamping_mode )
5596
+
5597
+ clamping_mode_prevailing = constructor_clamping_mode if clamping_mode is None else clamping_mode
5598
+ if clamping_mode_prevailing == "none" :
5599
+ assert_equal (boxes , out ) # should be a pass-through
5600
+ else :
5601
+ assert_equal (out , expected_clamped_output )
5602
+
5603
+
5604
+ class TestSetClampingMode :
5605
+ @pytest .mark .parametrize ("format" , list (tv_tensors .BoundingBoxFormat ))
5606
+ @pytest .mark .parametrize ("constructor_clamping_mode" , ("hard" , "none" )) # TODOBB add soft
5607
+ @pytest .mark .parametrize ("desired_clamping_mode" , ("hard" , "none" )) # TODOBB add soft
5608
+ def test_setter (self , format , constructor_clamping_mode , desired_clamping_mode ):
5609
+
5610
+ in_boxes = make_bounding_boxes (format = format , clamping_mode = constructor_clamping_mode )
5611
+ out_boxes = transforms .SetClampingMode (clamping_mode = desired_clamping_mode )(in_boxes )
5612
+
5613
+ assert in_boxes .clamping_mode == constructor_clamping_mode # input is unchanged: no leak
5614
+ assert out_boxes .clamping_mode == desired_clamping_mode
5615
+
5616
+ @pytest .mark .parametrize ("format" , list (tv_tensors .BoundingBoxFormat ))
5617
+ @pytest .mark .parametrize ("constructor_clamping_mode" , ("hard" , "none" )) # TODOBB add soft
5618
+ def test_pipeline_no_leak (self , format , constructor_clamping_mode ):
5619
+ class AssertClampingMode (transforms .Transform ):
5620
+ def __init__ (self , expected_clamping_mode ):
5621
+ super ().__init__ ()
5622
+ self .expected_clamping_mode = expected_clamping_mode
5623
+
5624
+ _transformed_types = (tv_tensors .BoundingBoxes ,)
5625
+
5626
+ def transform (self , inpt , _ ):
5627
+ assert inpt .clamping_mode == self .expected_clamping_mode
5628
+ return inpt
5629
+
5630
+ t = transforms .Compose (
5631
+ [
5632
+ transforms .SetClampingMode ("none" ),
5633
+ AssertClampingMode ("none" ),
5634
+ transforms .SetClampingMode ("hard" ),
5635
+ AssertClampingMode ("hard" ),
5636
+ transforms .SetClampingMode ("none" ),
5637
+ AssertClampingMode ("none" ),
5638
+ transforms .ClampBoundingBoxes ("hard" ),
5639
+ ]
5640
+ )
5641
+
5642
+ in_boxes = make_bounding_boxes (format = format , clamping_mode = constructor_clamping_mode )
5643
+ out_boxes = t (in_boxes )
5644
+
5645
+ assert in_boxes .clamping_mode == constructor_clamping_mode # input is unchanged: no leak
5646
+
5647
+ # assert that the output boxes clamping_mode is the one set by the last SetClampingMode.
5648
+ # ClampBoundingBoxes doesn't set clamping_mode.
5649
+ assert out_boxes .clamping_mode == "none"
5650
+
5544
5651
5545
5652
class TestClampKeyPoints :
5546
5653
@pytest .mark .parametrize ("dtype" , [torch .int64 , torch .float32 ])
0 commit comments