15
15
import torchvision .transforms .functional as F
16
16
17
17
18
+ def run_on_cpu_and_cuda (fn ):
19
+
20
+ devices = ["cpu" , ]
21
+ if torch .cuda .is_available ():
22
+ devices .append ("cuda" )
23
+
24
+ def wrapper (self , * args , ** kwargs ):
25
+ for device in devices :
26
+ fn (self , device , * args , ** kwargs )
27
+
28
+ return wrapper
29
+
30
+
18
31
class Tester (unittest .TestCase ):
19
32
20
- def _create_data (self , height = 3 , width = 3 , channels = 3 ):
21
- tensor = torch .randint (0 , 255 , (channels , height , width ), dtype = torch .uint8 )
22
- pil_img = Image .fromarray (tensor .permute (1 , 2 , 0 ).contiguous ().numpy ())
33
+ def _create_data (self , height = 3 , width = 3 , channels = 3 , device = "cpu" ):
34
+ tensor = torch .randint (0 , 255 , (channels , height , width ), dtype = torch .uint8 , device = device )
35
+ pil_img = Image .fromarray (tensor .permute (1 , 2 , 0 ).contiguous ().cpu (). numpy ())
23
36
return tensor , pil_img
24
37
25
38
def compareTensorToPIL (self , tensor , pil_image , msg = None ):
26
39
pil_tensor = torch .as_tensor (np .array (pil_image ).transpose ((2 , 0 , 1 )))
27
40
if msg is None :
28
41
msg = "tensor:\n {} \n did not equal PIL tensor:\n {}" .format (tensor , pil_tensor )
29
- self .assertTrue (tensor .equal (pil_tensor ), msg )
42
+ self .assertTrue (tensor .cpu (). equal (pil_tensor ), msg )
30
43
31
44
def approxEqualTensorToPIL (self , tensor , pil_image , tol = 1e-5 , msg = None ):
32
45
pil_tensor = torch .as_tensor (np .array (pil_image ).transpose ((2 , 0 , 1 ))).to (tensor )
@@ -36,9 +49,10 @@ def approxEqualTensorToPIL(self, tensor, pil_image, tol=1e-5, msg=None):
36
49
msg = "{}: mae={}, tol={}: \n {}\n vs\n {}" .format (msg , mae , tol , tensor [0 , :10 , :10 ], pil_tensor [0 , :10 , :10 ])
37
50
)
38
51
39
- def test_vflip (self ):
52
+ @run_on_cpu_and_cuda
53
+ def test_vflip (self , device ):
40
54
script_vflip = torch .jit .script (F_t .vflip )
41
- img_tensor = torch .randn (3 , 16 , 16 )
55
+ img_tensor = torch .randn (3 , 16 , 16 ). to ( device = device )
42
56
img_tensor_clone = img_tensor .clone ()
43
57
vflipped_img = F_t .vflip (img_tensor )
44
58
vflipped_img_again = F_t .vflip (vflipped_img )
@@ -49,9 +63,10 @@ def test_vflip(self):
49
63
vflipped_img_script = script_vflip (img_tensor )
50
64
self .assertTrue (torch .equal (vflipped_img , vflipped_img_script ))
51
65
52
- def test_hflip (self ):
66
+ @run_on_cpu_and_cuda
67
+ def test_hflip (self , device ):
53
68
script_hflip = torch .jit .script (F_t .hflip )
54
- img_tensor = torch .randn (3 , 16 , 16 )
69
+ img_tensor = torch .randn (3 , 16 , 16 ). to ( device )
55
70
img_tensor_clone = img_tensor .clone ()
56
71
hflipped_img = F_t .hflip (img_tensor )
57
72
hflipped_img_again = F_t .hflip (hflipped_img )
@@ -62,9 +77,10 @@ def test_hflip(self):
62
77
hflipped_img_script = script_hflip (img_tensor )
63
78
self .assertTrue (torch .equal (hflipped_img , hflipped_img_script ))
64
79
65
- def test_crop (self ):
80
+ @run_on_cpu_and_cuda
81
+ def test_crop (self , device ):
66
82
script_crop = torch .jit .script (F_t .crop )
67
- img_tensor = torch .randint (0 , 255 , (3 , 16 , 16 ), dtype = torch .uint8 )
83
+ img_tensor = torch .randint (0 , 255 , (3 , 16 , 16 ), dtype = torch .uint8 , device = device )
68
84
img_tensor_clone = img_tensor .clone ()
69
85
top = random .randint (0 , 15 )
70
86
left = random .randint (0 , 15 )
@@ -73,7 +89,7 @@ def test_crop(self):
73
89
img_cropped = F_t .crop (img_tensor , top , left , height , width )
74
90
img_PIL = transforms .ToPILImage ()(img_tensor )
75
91
img_PIL_cropped = F .crop (img_PIL , top , left , height , width )
76
- img_cropped_GT = transforms .ToTensor ()(img_PIL_cropped )
92
+ img_cropped_GT = transforms .ToTensor ()(img_PIL_cropped ). to ( device )
77
93
self .assertTrue (torch .equal (img_tensor , img_tensor_clone ))
78
94
self .assertTrue (torch .equal (img_cropped , (img_cropped_GT * 255 ).to (torch .uint8 )),
79
95
"functional_tensor crop not working" )
@@ -203,17 +219,15 @@ def test_center_crop(self):
203
219
img_tensor_clone = img_tensor .clone ()
204
220
cropped_tensor = F_t .center_crop (img_tensor , [10 , 10 ])
205
221
cropped_pil_image = F .center_crop (transforms .ToPILImage ()(img_tensor ), [10 , 10 ])
206
- cropped_pil_tensor = (transforms .ToTensor ()(cropped_pil_image ) * 255 ).to (torch .uint8 )
222
+ cropped_pil_tensor = (transforms .ToTensor ()(cropped_pil_image ) * 255 ).to (dtype = torch .uint8 )
207
223
self .assertTrue (torch .equal (cropped_tensor , cropped_pil_tensor ))
208
224
self .assertTrue (torch .equal (img_tensor , img_tensor_clone ))
209
225
# scriptable function test
210
226
cropped_script = script_center_crop (img_tensor , [10 , 10 ])
211
227
self .assertTrue (torch .equal (cropped_script , cropped_tensor ))
212
228
213
229
def test_five_crop (self ):
214
- script_five_crop = torch .jit .script (F_t .five_crop )
215
230
img_tensor = torch .randint (0 , 255 , (1 , 32 , 32 ), dtype = torch .uint8 )
216
- img_tensor_clone = img_tensor .clone ()
217
231
cropped_tensor = F_t .five_crop (img_tensor , [10 , 10 ])
218
232
cropped_pil_image = F .five_crop (transforms .ToPILImage ()(img_tensor ), [10 , 10 ])
219
233
self .assertTrue (torch .equal (cropped_tensor [0 ],
@@ -226,11 +240,6 @@ def test_five_crop(self):
226
240
(transforms .ToTensor ()(cropped_pil_image [3 ]) * 255 ).to (torch .uint8 )))
227
241
self .assertTrue (torch .equal (cropped_tensor [4 ],
228
242
(transforms .ToTensor ()(cropped_pil_image [4 ]) * 255 ).to (torch .uint8 )))
229
- self .assertTrue (torch .equal (img_tensor , img_tensor_clone ))
230
- # scriptable function test
231
- cropped_script = script_five_crop (img_tensor , [10 , 10 ])
232
- for cropped_script_img , cropped_tensor_img in zip (cropped_script , cropped_tensor ):
233
- self .assertTrue (torch .equal (cropped_script_img , cropped_tensor_img ))
234
243
235
244
def test_ten_crop (self ):
236
245
script_ten_crop = torch .jit .script (F_t .ten_crop )
@@ -264,9 +273,10 @@ def test_ten_crop(self):
264
273
for cropped_script_img , cropped_tensor_img in zip (cropped_script , cropped_tensor ):
265
274
self .assertTrue (torch .equal (cropped_script_img , cropped_tensor_img ))
266
275
267
- def test_pad (self ):
276
+ @run_on_cpu_and_cuda
277
+ def test_pad (self , device ):
268
278
script_fn = torch .jit .script (F_t .pad )
269
- tensor , pil_img = self ._create_data (7 , 8 )
279
+ tensor , pil_img = self ._create_data (7 , 8 , device = device )
270
280
271
281
for dt in [None , torch .float32 , torch .float64 ]:
272
282
if dt is not None :
@@ -302,9 +312,10 @@ def test_pad(self):
302
312
with self .assertRaises (ValueError , msg = "Padding can not be negative for symmetric padding_mode" ):
303
313
F_t .pad (tensor , (- 2 , - 3 ), padding_mode = "symmetric" )
304
314
305
- def test_adjust_gamma (self ):
306
- script_fn = torch .jit .script (F_t .adjust_gamma )
307
- tensor , pil_img = self ._create_data (26 , 36 )
315
+ @run_on_cpu_and_cuda
316
+ def test_adjust_gamma (self , device ):
317
+ script_fn = torch .jit .script (F .adjust_gamma )
318
+ tensor , pil_img = self ._create_data (26 , 36 , device = device )
308
319
309
320
for dt in [torch .float64 , torch .float32 , None ]:
310
321
@@ -315,8 +326,8 @@ def test_adjust_gamma(self):
315
326
gains = [0.7 , 1.0 , 1.3 ]
316
327
for gamma , gain in zip (gammas , gains ):
317
328
318
- adjusted_tensor = F_t .adjust_gamma (tensor , gamma , gain )
319
- adjusted_pil = F_pil .adjust_gamma (pil_img , gamma , gain )
329
+ adjusted_tensor = F .adjust_gamma (tensor , gamma , gain )
330
+ adjusted_pil = F .adjust_gamma (pil_img , gamma , gain )
320
331
scripted_result = script_fn (tensor , gamma , gain )
321
332
self .assertEqual (adjusted_tensor .dtype , scripted_result .dtype )
322
333
self .assertEqual (adjusted_tensor .size ()[1 :], adjusted_pil .size [::- 1 ])
@@ -327,11 +338,12 @@ def test_adjust_gamma(self):
327
338
328
339
self .compareTensorToPIL (rbg_tensor , adjusted_pil )
329
340
330
- self .assertTrue (adjusted_tensor .equal (scripted_result ))
341
+ self .assertTrue (adjusted_tensor .allclose (scripted_result ))
331
342
332
- def test_resize (self ):
343
+ @run_on_cpu_and_cuda
344
+ def test_resize (self , device ):
333
345
script_fn = torch .jit .script (F_t .resize )
334
- tensor , pil_img = self ._create_data (26 , 36 )
346
+ tensor , pil_img = self ._create_data (26 , 36 , device = device )
335
347
336
348
for dt in [None , torch .float32 , torch .float64 ]:
337
349
if dt is not None :
@@ -367,28 +379,30 @@ def test_resize(self):
367
379
resize_result = script_fn (tensor , size = script_size , interpolation = interpolation )
368
380
self .assertTrue (resized_tensor .equal (resize_result ), msg = "{}, {}" .format (size , interpolation ))
369
381
370
- def test_resized_crop (self ):
382
+ @run_on_cpu_and_cuda
383
+ def test_resized_crop (self , device ):
371
384
# test values of F.resized_crop in several cases:
372
385
# 1) resize to the same size, crop to the same size => should be identity
373
- tensor , _ = self ._create_data (26 , 36 )
386
+ tensor , _ = self ._create_data (26 , 36 , device = device )
374
387
for i in [0 , 2 , 3 ]:
375
388
out_tensor = F .resized_crop (tensor , top = 0 , left = 0 , height = 26 , width = 36 , size = [26 , 36 ], interpolation = i )
376
389
self .assertTrue (tensor .equal (out_tensor ), msg = "{} vs {}" .format (out_tensor [0 , :5 , :5 ], tensor [0 , :5 , :5 ]))
377
390
378
391
# 2) resize by half and crop a TL corner
379
- tensor , _ = self ._create_data (26 , 36 )
392
+ tensor , _ = self ._create_data (26 , 36 , device = device )
380
393
out_tensor = F .resized_crop (tensor , top = 0 , left = 0 , height = 20 , width = 30 , size = [10 , 15 ], interpolation = 0 )
381
394
expected_out_tensor = tensor [:, :20 :2 , :30 :2 ]
382
395
self .assertTrue (
383
396
expected_out_tensor .equal (out_tensor ),
384
397
msg = "{} vs {}" .format (expected_out_tensor [0 , :10 , :10 ], out_tensor [0 , :10 , :10 ])
385
398
)
386
399
387
- def test_affine (self ):
400
+ @run_on_cpu_and_cuda
401
+ def test_affine (self , device ):
388
402
# Tests on square and rectangular images
389
403
scripted_affine = torch .jit .script (F .affine )
390
404
391
- for tensor , pil_img in [self ._create_data (26 , 26 ), self ._create_data (32 , 26 )]:
405
+ for tensor , pil_img in [self ._create_data (26 , 26 , device = device ), self ._create_data (32 , 26 , device = device )]:
392
406
393
407
# 1) identity map
394
408
out_tensor = F .affine (tensor , angle = 0 , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0 )
@@ -412,8 +426,16 @@ def test_affine(self):
412
426
(180 , torch .rot90 (tensor , k = 2 , dims = (- 1 , - 2 ))),
413
427
]
414
428
for a , true_tensor in test_configs :
429
+
430
+ out_pil_img = F .affine (
431
+ pil_img , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
432
+ )
433
+ out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 ))).to (device )
434
+
415
435
for fn in [F .affine , scripted_affine ]:
416
- out_tensor = fn (tensor , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0 )
436
+ out_tensor = fn (
437
+ tensor , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
438
+ )
417
439
if true_tensor is not None :
418
440
self .assertTrue (
419
441
true_tensor .equal (out_tensor ),
@@ -422,11 +444,6 @@ def test_affine(self):
422
444
else :
423
445
true_tensor = out_tensor
424
446
425
- out_pil_img = F .affine (
426
- pil_img , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
427
- )
428
- out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
429
-
430
447
num_diff_pixels = (true_tensor != out_pil_tensor ).sum ().item () / 3.0
431
448
ratio_diff_pixels = num_diff_pixels / true_tensor .shape [- 1 ] / true_tensor .shape [- 2 ]
432
449
# Tolerance : less than 6% of different pixels
@@ -442,12 +459,16 @@ def test_affine(self):
442
459
90 , 45 , 15 , - 30 , - 60 , - 120
443
460
]
444
461
for a in test_configs :
462
+
463
+ out_pil_img = F .affine (
464
+ pil_img , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
465
+ )
466
+ out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
467
+
445
468
for fn in [F .affine , scripted_affine ]:
446
- out_tensor = fn (tensor , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0 )
447
- out_pil_img = F .affine (
448
- pil_img , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
449
- )
450
- out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
469
+ out_tensor = fn (
470
+ tensor , angle = a , translate = [0 , 0 ], scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0
471
+ ).cpu ()
451
472
452
473
num_diff_pixels = (out_tensor != out_pil_tensor ).sum ().item () / 3.0
453
474
ratio_diff_pixels = num_diff_pixels / out_tensor .shape [- 1 ] / out_tensor .shape [- 2 ]
@@ -465,9 +486,12 @@ def test_affine(self):
465
486
[10 , 12 ], (- 12 , - 13 )
466
487
]
467
488
for t in test_configs :
489
+
490
+ out_pil_img = F .affine (pil_img , angle = 0 , translate = t , scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0 )
491
+
468
492
for fn in [F .affine , scripted_affine ]:
469
493
out_tensor = fn (tensor , angle = 0 , translate = t , scale = 1.0 , shear = [0.0 , 0.0 ], resample = 0 )
470
- out_pil_img = F . affine ( pil_img , angle = 0 , translate = t , scale = 1.0 , shear = [ 0.0 , 0.0 ], resample = 0 )
494
+
471
495
self .compareTensorToPIL (out_tensor , out_pil_img )
472
496
473
497
# 3) Test rotation + translation + scale + share
@@ -489,23 +513,25 @@ def test_affine(self):
489
513
out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
490
514
491
515
for fn in [F .affine , scripted_affine ]:
492
- out_tensor = fn (tensor , angle = a , translate = t , scale = s , shear = sh , resample = r )
516
+ out_tensor = fn (tensor , angle = a , translate = t , scale = s , shear = sh , resample = r ). cpu ()
493
517
num_diff_pixels = (out_tensor != out_pil_tensor ).sum ().item () / 3.0
494
518
ratio_diff_pixels = num_diff_pixels / out_tensor .shape [- 1 ] / out_tensor .shape [- 2 ]
495
- # Tolerance : less than 5% of different pixels
519
+ # Tolerance : less than 5% (cpu), 6% (cuda) of different pixels
520
+ tol = 0.06 if device == "cuda" else 0.05
496
521
self .assertLess (
497
522
ratio_diff_pixels ,
498
- 0.05 ,
523
+ tol ,
499
524
msg = "{}: {}\n {} vs \n {}" .format (
500
525
(r , a , t , s , sh ), ratio_diff_pixels , out_tensor [0 , :7 , :7 ], out_pil_tensor [0 , :7 , :7 ]
501
526
)
502
527
)
503
528
504
- def test_rotate (self ):
529
+ @run_on_cpu_and_cuda
530
+ def test_rotate (self , device ):
505
531
# Tests on square image
506
532
scripted_rotate = torch .jit .script (F .rotate )
507
533
508
- for tensor , pil_img in [self ._create_data (26 , 26 ), self ._create_data (32 , 26 )]:
534
+ for tensor , pil_img in [self ._create_data (26 , 26 , device = device ), self ._create_data (32 , 26 , device = device )]:
509
535
510
536
img_size = pil_img .size
511
537
centers = [
@@ -522,7 +548,7 @@ def test_rotate(self):
522
548
out_pil_img = F .rotate (pil_img , angle = a , resample = r , expand = e , center = c )
523
549
out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
524
550
for fn in [F .rotate , scripted_rotate ]:
525
- out_tensor = fn (tensor , angle = a , resample = r , expand = e , center = c )
551
+ out_tensor = fn (tensor , angle = a , resample = r , expand = e , center = c ). cpu ()
526
552
527
553
self .assertEqual (
528
554
out_tensor .shape ,
@@ -545,11 +571,12 @@ def test_rotate(self):
545
571
)
546
572
)
547
573
548
- def test_perspective (self ):
574
+ @run_on_cpu_and_cuda
575
+ def test_perspective (self , device ):
549
576
550
577
from torchvision .transforms import RandomPerspective
551
578
552
- for tensor , pil_img in [self ._create_data (26 , 34 ), self ._create_data (26 , 26 )]:
579
+ for tensor , pil_img in [self ._create_data (26 , 34 , device = device ), self ._create_data (26 , 26 , device = device )]:
553
580
554
581
scripted_tranform = torch .jit .script (F .perspective )
555
582
@@ -569,7 +596,7 @@ def test_perspective(self):
569
596
out_pil_tensor = torch .from_numpy (np .array (out_pil_img ).transpose ((2 , 0 , 1 )))
570
597
571
598
for fn in [F .perspective , scripted_tranform ]:
572
- out_tensor = fn (tensor , startpoints = spoints , endpoints = epoints , interpolation = r )
599
+ out_tensor = fn (tensor , startpoints = spoints , endpoints = epoints , interpolation = r ). cpu ()
573
600
574
601
num_diff_pixels = (out_tensor != out_pil_tensor ).sum ().item () / 3.0
575
602
ratio_diff_pixels = num_diff_pixels / out_tensor .shape [- 1 ] / out_tensor .shape [- 2 ]
0 commit comments