Repository: pytorch/vision

Issue:
Possible inversion of x-axis and y-axis in crop transform
I just realized that the crop transform in `transform/functional.py` with arguments `i`, `j`, `w` and `h` (using PIL.crop function) actually crops a rectangular portion of `w` width and `h` height, having the top left coordinate as `(j, i)`. Wouldn't it be intuitive to have the top left corner coordinates as `(i, j)`?


Discussion:
Related to #1323 
Hi @pmeier ! As pointed out by @fmassa, Pillow has different ordering for width and height but I think the position of `i` and `j` are still reversed. Please check out: https://github.com/python-pillow/Pillow/issues/2729#issuecomment-425388850 for the syntax for crop in Pillow. The first 2 arguments are for the `(x, y)` coordinates for the top left corner. 

If the user input `(i, j)` is in the common format of `(x, y)`, then  the current implementation of crop in Pytorch is setting `(y, x)` as coordinates for top left corner. I think the correct implementation should be:
```
img.crop((i, j, i+w, j+h)
```
Am I missing something?
IMO the current implementation is correct. I did the following to verify it:

```python
from PIL import Image
import numpy as np
from torchvision.transforms import functional as F

np_image = np.random.rand(100, 100)
pil_image = Image.fromarray(np_image * 255.0)

left = 10
top = 20
width = 30
height = 40
right = left + width
bottom = top + height

crop1 = pil_image.crop((left, top, right, bottom))
crop2 = F.crop(pil_image, top, left, height, width)

abs_diff = np.sum(np.abs(np.array(crop1) - np.array(crop2)))
implementation_correct = abs_diff < 1e-6
```

However, we should definitely change the signature and docstring to avoid further confusion. I propose the following:

```python
def crop(img, top, left, height, width):
    """Crop the given PIL Image.
    Args:
        img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image.
        top (int): Vertical component of the top left corner of the crop box.
        left (int): Horizontal component of the top left corner of the crop box.
        height (int): Height of the crop box.
        width (int): Width of the crop box.
    Returns:
        PIL Image: Cropped image.
    """
```
If we follow the convention of `(top, left, height, width)` then the current implementation is indeed fine. However, we have 2 choices for the naming conventions: 
1)  `(top, left, height, width)` 
2)  `(x, y, height, width)` 

Since `top` and `left` correspond to coordinates in y-axis  and x-axis respectively, both of them are equivalent. So the only question remaining is what convention do we follow? IMO `(x, y, height, width)` would be more clear than `(top, left, height, width)`  as even to explain what `top` and `left` mean, we need to use `(x, y)` coordinates, e.g., (0, 0) is used in the docs. 

Is it a standard convention to use `(top, left, height, width)` in Pytorch? Have  we used this convention anywhere else?
> IMO (x, y, height, width) would be more clear than (top, left, height, width) as even to explain what top and left mean, we need to use (x, y) coordinates, e.g., (0, 0) is used in the docs.

If we use `x` and `y` we need to state in which direction the box is expanding. This is clear for `top` and `left`. Thus, even for the amount of documentation both are equivalent. I think this comes down to preference.

If we go with option 2. the signature should be 

`(y, x, height, width)`

to keep BC.


I guess we can leave it as it. You may proceed to change the documentation as you mentioned earlier or if you want I can do it in case you are busy at the moment.
I can send a PR, if the documentation change is OK for @fmassa 
@pmeier the doc improvements that you proposed in https://github.com/pytorch/vision/issues/1380#issuecomment-535871094 are great, it would be great if you could send a PR improving it

Resolving patch:
diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py
--- a/torchvision/transforms/functional.py
+++ b/torchvision/transforms/functional.py
@@ -349,46 +349,53 @@ def pad(img, padding, fill=0, padding_mode='constant'):
         return Image.fromarray(img)
 
 
-def crop(img, i, j, h, w):
+def crop(img, top, left, height, width):
     """Crop the given PIL Image.
-
     Args:
-        img (PIL Image): Image to be cropped.
-        i (int): i in (i,j) i.e coordinates of the upper left corner.
-        j (int): j in (i,j) i.e coordinates of the upper left corner.
-        h (int): Height of the cropped image.
-        w (int): Width of the cropped image.
-
+        img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image.
+        top (int): Vertical component of the top left corner of the crop box.
+        left (int): Horizontal component of the top left corner of the crop box.
+        height (int): Height of the crop box.
+        width (int): Width of the crop box.
     Returns:
         PIL Image: Cropped image.
     """
     if not _is_pil_image(img):
         raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
 
-    return img.crop((j, i, j + w, i + h))
+    return img.crop((left, top, left + width, top + height))
 
 
 def center_crop(img, output_size):
+    """Crop the given PIL Image and resize it to desired size.
+
+        Args:
+            img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image.
+            output_size (sequence or int): (height, width) of the crop box. If int,
+                it is used for both directions
+        Returns:
+            PIL Image: Cropped image.
+        """
     if isinstance(output_size, numbers.Number):
         output_size = (int(output_size), int(output_size))
-    w, h = img.size
-    th, tw = output_size
-    i = int(round((h - th) / 2.))
-    j = int(round((w - tw) / 2.))
-    return crop(img, i, j, th, tw)
+    image_width, image_height = img.size
+    crop_height, crop_width = output_size
+    crop_top = int(round((image_height - crop_height) / 2.))
+    crop_left = int(round((image_width - crop_width) / 2.))
+    return crop(img, crop_top, crop_left, crop_height, crop_width)
 
 
-def resized_crop(img, i, j, h, w, size, interpolation=Image.BILINEAR):
+def resized_crop(img, top, left, height, width, size, interpolation=Image.BILINEAR):
     """Crop the given PIL Image and resize it to desired size.
 
     Notably used in :class:`~torchvision.transforms.RandomResizedCrop`.
 
     Args:
-        img (PIL Image): Image to be cropped.
-        i (int): i in (i,j) i.e coordinates of the upper left corner
-        j (int): j in (i,j) i.e coordinates of the upper left corner
-        h (int): Height of the cropped image.
-        w (int): Width of the cropped image.
+        img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image.
+        top (int): Vertical component of the top left corner of the crop box.
+        left (int): Horizontal component of the top left corner of the crop box.
+        height (int): Height of the crop box.
+        width (int): Width of the crop box.
         size (sequence or int): Desired output size. Same semantics as ``resize``.
         interpolation (int, optional): Desired interpolation. Default is
             ``PIL.Image.BILINEAR``.
@@ -396,7 +403,7 @@ def resized_crop(img, i, j, h, w, size, interpolation=Image.BILINEAR):
         PIL Image: Cropped image.
     """
     assert _is_pil_image(img), 'img should be PIL Image'
-    img = crop(img, i, j, h, w)
+    img = crop(img, top, left, height, width)
     img = resize(img, size, interpolation)
     return img
 
@@ -495,16 +502,18 @@ def five_crop(img, size):
     else:
         assert len(size) == 2, "Please provide only two dimensions (h, w) for size."
 
-    w, h = img.size
-    crop_h, crop_w = size
-    if crop_w > w or crop_h > h:
-        raise ValueError("Requested crop size {} is bigger than input size {}".format(size,
-                                                                                      (h, w)))
-    tl = img.crop((0, 0, crop_w, crop_h))
-    tr = img.crop((w - crop_w, 0, w, crop_h))
-    bl = img.crop((0, h - crop_h, crop_w, h))
-    br = img.crop((w - crop_w, h - crop_h, w, h))
-    center = center_crop(img, (crop_h, crop_w))
+    image_width, image_height = img.size
+    crop_height, crop_width = size
+    if crop_width > image_width or crop_height > image_height:
+        msg = "Requested crop size {} is bigger than input size {}"
+        raise ValueError(msg.format(size, (image_height, image_width)))
+
+    tl = img.crop((0, 0, crop_width, crop_height))
+    tr = img.crop((image_width - crop_width, 0, image_width, crop_height))
+    bl = img.crop((0, image_height - crop_height, crop_width, image_height))
+    br = img.crop((image_width - crop_width, image_height - crop_height,
+                   image_width, image_height))
+    center = center_crop(img, (crop_height, crop_width))
     return (tl, tr, bl, br, center)
 
 


Test patch: