@@ -3346,3 +3346,171 @@ def test_transform_errors_warnings(self):
3346
3346
for param in ["scale" , "ratio" ]:
3347
3347
with pytest .warns (match = "Scale and ratio should be of kind" ):
3348
3348
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 )
0 commit comments