Repository: pytorch/vision

Issue:
Pad() fails on floating point image with TypeError
## 🐛 Bug

torchvision.transforms.Pad raises `TypeError: must be real number, not tuple`.

## To Reproduce

```
import torch, torchvision, PIL
t = torch.empty(20,16).uniform_(0,1)
x = PIL.Image.fromarray(t.numpy())
f = torchvision.transforms.Pad(2)
f(x)
```

## Expected behavior

`x` should be padded. However, instead, `TypeError: must be real number, not tuple` is raised.

## Environment

```
PyTorch version: 1.6.0
Is debug build: No
CUDA used to build PyTorch: 10.1

OS: Ubuntu 18.04.4 LTS
GCC version: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
CMake version: version 3.10.2

Python version: 3.7
Is CUDA available: Yes
CUDA runtime version: 10.1.243
GPU models and configuration:
GPU 0: TITAN RTX
GPU 1: TITAN RTX
GPU 2: TITAN RTX
GPU 3: TITAN RTX
GPU 4: TITAN RTX
GPU 5: TITAN RTX
GPU 6: TITAN RTX

Nvidia driver version: 440.26
cuDNN version: Probably one of the following:
/usr/lib/x86_64-linux-gnu/libcudnn.so.7.6.4
/usr/local/cuda-10.0/lib64/libcudnn.so.7.6.1

Versions of relevant libraries:
[pip3] numpy==1.13.3
[conda] blas                      1.0                         mkl
[conda] cudatoolkit               10.1.243             h6bb024c_0
[conda] mkl                       2019.4                      243
[conda] mkl-service               2.3.0            py37he904b0f_0
[conda] mkl_fft                   1.1.0            py37h23d657b_0
[conda] mkl_random                1.1.0            py37hd6b4f25_0
[conda] numpy                     1.18.5           py37ha1c710e_0
[conda] numpy-base                1.18.5           py37hde5b4d6_0
[conda] numpydoc                  1.1.0                      py_0
[conda] pytorch                   1.6.0           py3.7_cuda10.1.243_cudnn7.6.3_0    pytorch
[conda] swish-torch               0.0.1                     dev_0    <develop>
[conda] torchvision               0.7.0                py37_cu101    pytorch
```

## Additional context

Stack trace:

```
TypeError                                 Traceback (most recent call last)
<ipython-input-28-086b6b55847c> in <module>
      4 x = PIL.Image.fromarray(t.numpy())
      5 f = torchvision.transforms.Pad(2)
----> 6 f(x)

~/anaconda3/lib/python3.7/site-packages/torchvision/transforms/transforms.py in __call__(self, img)
    338             PIL Image: Padded image.
    339         """
--> 340         return F.pad(img, self.padding, self.fill, self.padding_mode)
    341 
    342     def __repr__(self):

~/anaconda3/lib/python3.7/site-packages/torchvision/transforms/functional.py in pad(img, padding, fill, padding_mode)
    405             return image
    406 
--> 407         return ImageOps.expand(img, border=padding, fill=fill)
    408     else:
    409         if isinstance(padding, int):

~/anaconda3/lib/python3.7/site-packages/PIL/ImageOps.py in expand(image, border, fill)
    360     width = left + image.size[0] + right
    361     height = top + image.size[1] + bottom
--> 362     out = Image.new(image.mode, (width, height), _color(fill, image.mode))
    363     out.paste(image, (left, top))
    364     return out

~/anaconda3/lib/python3.7/site-packages/PIL/Image.py in new(mode, size, color)
   2611         im.palette = ImagePalette.ImagePalette()
   2612         color = im.palette.getcolor(color)
-> 2613     return im._new(core.fill(mode, size, color))
```


Discussion:
Thanks for the report @jph00. This is a bug in 

https://github.com/pytorch/vision/blob/1aef87d01eec2c0989458387fa04baebcc86ea7b/torchvision/transforms/functional_pil.py#L397-L398

since images with mode `F` do not accept a tuple of fill colors. 

@fmassa I have a vague recollection of you fixing this already but I can't find the commit.
@pmeier I think I fixed this for `rotate` in https://github.com/pytorch/vision/pull/1828 
I'll send a fix later.

Resolving patch:
diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py
--- a/torchvision/transforms/functional_pil.py
+++ b/torchvision/transforms/functional_pil.py
@@ -261,19 +261,14 @@ def pad(img, padding, fill=0, padding_mode="constant"):
         raise ValueError("Padding mode should be either constant, edge, reflect or symmetric")
 
     if padding_mode == "constant":
-        if isinstance(fill, numbers.Number):
-            fill = (fill,) * len(img.getbands())
-        if len(fill) != len(img.getbands()):
-            raise ValueError("fill should have the same number of elements "
-                             "as the number of channels in the image "
-                             "({}), got {} instead".format(len(img.getbands()), len(fill)))
+        opts = _parse_fill(fill, img, "2.3.0", name="fill")
         if img.mode == "P":
             palette = img.getpalette()
-            image = ImageOps.expand(img, border=padding, fill=fill)
+            image = ImageOps.expand(img, border=padding, **opts)
             image.putpalette(palette)
             return image
 
-        return ImageOps.expand(img, border=padding, fill=fill)
+        return ImageOps.expand(img, border=padding, **opts)
     else:
         if isinstance(padding, int):
             pad_left = pad_right = pad_top = pad_bottom = padding
@@ -367,8 +362,8 @@ def resize(img, size, interpolation=Image.BILINEAR):
 
 
 @torch.jit.unused
-def _parse_fill(fill, img, min_pil_version):
-    """Helper function to get the fill color for rotate and perspective transforms.
+def _parse_fill(fill, img, min_pil_version, name="fillcolor"):
+    """Helper function to get the fill color for rotate, perspective transforms, and pad.
 
     Args:
         fill (n-tuple or int or float): Pixel fill value for area outside the transformed
@@ -377,6 +372,7 @@ def _parse_fill(fill, img, min_pil_version):
         img (PIL Image): Image to be filled.
         min_pil_version (str): The minimum PILLOW version for when the ``fillcolor`` option
             was first introduced in the calling function. (e.g. rotate->5.2.0, perspective->5.0.0)
+        name (str): Name of the ``fillcolor`` option in the output. Defaults to ``"fillcolor"``.
 
     Returns:
         dict: kwarg for ``fillcolor``
@@ -401,7 +397,7 @@ def _parse_fill(fill, img, min_pil_version):
                "bands of the image ({} != {})")
         raise ValueError(msg.format(len(fill), num_bands))
 
-    return {"fillcolor": fill}
+    return {name: fill}
 
 
 @torch.jit.unused


Test patch:
diff --git a/test/test_transforms.py b/test/test_transforms.py
--- a/test/test_transforms.py
+++ b/test/test_transforms.py
@@ -390,6 +390,14 @@ def test_pad_raises_with_invalid_pad_sequence_len(self):
         with self.assertRaises(ValueError):
             transforms.Pad((1, 2, 3, 4, 5))
 
+    def test_pad_with_mode_F_images(self):
+        pad = 2
+        transform = transforms.Pad(pad)
+
+        img = Image.new("F", (10, 10))
+        padded_img = transform(img)
+        self.assertSequenceEqual(padded_img.size, [edge_size + 2 * pad for edge_size in img.size])
+
     def test_lambda(self):
         trans = transforms.Lambda(lambda x: x.add(10))
         x = torch.randn(10)