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