From d58b694cb48062ef8090b35cdaf72c97ae2bc74e Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Fri, 15 Sep 2017 10:43:40 +0100 Subject: [PATCH 1/8] add FiveCrop and TenCrop --- torchvision/transforms.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/torchvision/transforms.py b/torchvision/transforms.py index a4c7d4a7f76..3e9597de4b4 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -380,3 +380,41 @@ def __call__(self, img): scale = Scale(self.size, interpolation=self.interpolation) crop = CenterCrop(self.size) return crop(scale(img)) + +class FiveCrop(object): + """Crop the given PIL.Image into four corners and the central crop.""" + + def __call__(self, img): + w, h = img.size + crop_w = w // 2 + crop_h = h // 2 + + 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)) + # TODO: change center crop to (w, h) once PR #256 is resolved + center = CenterCrop((crop_h, crop_w))(img) + return [tl, tr, bl, br, center] + + +class TenCrop(object): + """Crop the given PIL.Image into four corners and the central crop plus the + flipped version of these (horizontal flipping is used by default) + + Args: + vflip bool: Use vertical flipping instead of horizontal + """ + + def __init__(self, vflip=False): + self.vflip = vflip + + def __call__(self, img): + first_five = FiveCrop()(img) + if self.vflip: + img = img.transpose(Image.FLIP_LEFT_RIGHT) + else: + img = img.transpose(Image.FLIP_TOP_BOTTOM) + + second_five = FiveCrop()(img) + return first_five + second_five From fb77ca61cbf115e8ba9b72f9162459c7d471f2be Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sat, 16 Sep 2017 16:02:57 +0100 Subject: [PATCH 2/8] add some tests for five_crop --- test/test_transforms.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_transforms.py b/test/test_transforms.py index 952d411d609..9526bb79fc3 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -15,6 +15,24 @@ class Tester(unittest.TestCase): + def test_five_crop(self): + for h, w, crop_h, crop_w in [(8, 8, 4, 4), (9, 9, 4, 4), (10, 5, 5, 2), (5, 10, 2, 5)]: + img = torch.Tensor(3, h, w).uniform_() + results = transforms.Compose([ + transforms.ToPILImage(), + transforms.FiveCrop(), + ])(img) + + assert len(results) == 5 + for crop in results: + assert crop.size == (crop_w, crop_h) + tl = img[:, 0:crop_h, 0:crop_w] + tr = img[:, 0:crop_h, w - crop_w:] + bl = img[:, h - crop_h:, 0:crop_w] + br = img[:, h - crop_:, w - crop_w:] + center = transforms.CenterCrop((crop_h, crop_w))(transforms.ToPILImage()(img)) + expected_output = [tl, tr, bl, br, center] + def test_crop(self): height = random.randint(10, 32) * 2 width = random.randint(10, 32) * 2 From 9842eaea1a50a5c02692586cb00cca5924cec3ad Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sun, 17 Sep 2017 11:56:17 +0100 Subject: [PATCH 3/8] added tests for FiveCrop and TenCrop --- test/test_transforms.py | 58 +++++++++++++++++++++++++++------------ torchvision/transforms.py | 38 ++++++++++++------------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/test/test_transforms.py b/test/test_transforms.py index 9526bb79fc3..77ea113b498 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -15,24 +15,6 @@ class Tester(unittest.TestCase): - def test_five_crop(self): - for h, w, crop_h, crop_w in [(8, 8, 4, 4), (9, 9, 4, 4), (10, 5, 5, 2), (5, 10, 2, 5)]: - img = torch.Tensor(3, h, w).uniform_() - results = transforms.Compose([ - transforms.ToPILImage(), - transforms.FiveCrop(), - ])(img) - - assert len(results) == 5 - for crop in results: - assert crop.size == (crop_w, crop_h) - tl = img[:, 0:crop_h, 0:crop_w] - tr = img[:, 0:crop_h, w - crop_w:] - bl = img[:, h - crop_h:, 0:crop_w] - br = img[:, h - crop_:, w - crop_w:] - center = transforms.CenterCrop((crop_h, crop_w))(transforms.ToPILImage()(img)) - expected_output = [tl, tr, bl, br, center] - def test_crop(self): height = random.randint(10, 32) * 2 width = random.randint(10, 32) * 2 @@ -74,6 +56,46 @@ def test_crop(self): assert sum2 > sum1, "height: " + str(height) + " width: " \ + str(width) + " oheight: " + str(oheight) + " owidth: " + str(owidth) + def test_five_crop(self): + for h, w, expected_crop_h, expected_crop_w in [(8, 8, 4, 4), (9, 9, 4, 4), (10, 5, 5, 2), + (5, 10, 2, 5)]: + img = torch.FloatTensor(3, h, w).uniform_() + + results = transforms.Compose([ + transforms.ToPILImage(), + transforms.FiveCrop(), + ])(img) + + assert len(results) == 5 + for crop in results: + assert crop.size == (expected_crop_w, expected_crop_h) + + to_pil_image = transforms.ToPILImage() + tl = to_pil_image(img[:, 0:expected_crop_h, 0:expected_crop_w]) + tr = to_pil_image(img[:, 0:expected_crop_h, w - expected_crop_w:]) + bl = to_pil_image(img[:, h - expected_crop_h:, 0:expected_crop_w]) + br = to_pil_image(img[:, h - expected_crop_h:, w - expected_crop_w:]) + center = transforms.CenterCrop((expected_crop_h, + expected_crop_w))(to_pil_image(img)) + expected_output = [tl, tr, bl, br, center] + assert results == expected_output + + def test_ten_crop(self): + for h, w in [(8, 8), (9, 9), (10, 5), (5, 10)]: + img = transforms.ToPILImage()(torch.FloatTensor(3, h, w).uniform_()) + hflipped_img = img.transpose(Image.FLIP_LEFT_RIGHT) + vflipped_img = img.transpose(Image.FLIP_TOP_BOTTOM) + + results = transforms.TenCrop()(img) + expected_output = transforms.FiveCrop()(img) + transforms.FiveCrop()(hflipped_img) + assert len(results) == 10 + assert expected_output == results + + results = transforms.TenCrop(vflip=True)(img) + expected_output = transforms.FiveCrop()(img) + transforms.FiveCrop()(vflipped_img) + assert len(results) == 10 + assert expected_output == results + def test_scale(self): height = random.randint(24, 32) * 2 width = random.randint(24, 32) * 2 diff --git a/torchvision/transforms.py b/torchvision/transforms.py index 3e9597de4b4..30ff19274b3 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -381,21 +381,21 @@ def __call__(self, img): crop = CenterCrop(self.size) return crop(scale(img)) + class FiveCrop(object): """Crop the given PIL.Image into four corners and the central crop.""" def __call__(self, img): - w, h = img.size - crop_w = w // 2 - crop_h = h // 2 + w, h = img.size + crop_w = w // 2 + crop_h = h // 2 - 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)) - # TODO: change center crop to (w, h) once PR #256 is resolved - center = CenterCrop((crop_h, crop_w))(img) - return [tl, tr, bl, br, center] + 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 = CenterCrop((crop_h, crop_w))(img) + return [tl, tr, bl, br, center] class TenCrop(object): @@ -407,14 +407,14 @@ class TenCrop(object): """ def __init__(self, vflip=False): - self.vflip = vflip + self.vflip = vflip def __call__(self, img): - first_five = FiveCrop()(img) - if self.vflip: - img = img.transpose(Image.FLIP_LEFT_RIGHT) - else: - img = img.transpose(Image.FLIP_TOP_BOTTOM) - - second_five = FiveCrop()(img) - return first_five + second_five + first_five = FiveCrop()(img) + if self.vflip: + img = img.transpose(Image.FLIP_TOP_BOTTOM) + else: + img = img.transpose(Image.FLIP_LEFT_RIGHT) + + second_five = FiveCrop()(img) + return first_five + second_five From b1b9179dfdc36f19e8de5e9fd9763017580d4c10 Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sat, 23 Sep 2017 15:12:56 +0100 Subject: [PATCH 4/8] address comments --- test/test_transforms.py | 36 +++++++++++++++++++++--------------- torchvision/transforms.py | 25 ++++++++++++++++++++----- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/test/test_transforms.py b/test/test_transforms.py index 1a21226e617..e16dfba1d15 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -57,27 +57,33 @@ def test_crop(self): + str(width) + " oheight: " + str(oheight) + " owidth: " + str(owidth) def test_five_crop(self): - for h, w, expected_crop_h, expected_crop_w in [(8, 8, 4, 4), (9, 9, 4, 4), (10, 5, 5, 2), - (5, 10, 2, 5)]: - img = torch.FloatTensor(3, h, w).uniform_() + to_pil_image = transforms.ToPILImage() + h = random.randint(5, 25) + w = random.randint(5, 25) + for single_dim in [True, False]: + crop_h = random.randint(1, h) + crop_w = random.randint(1, w) + if single_dim: + crop_h = min(crop_h, crop_w) + crop_w = crop_h + transform = transforms.FiveCrop(crop_h) + else: + transform = transforms.FiveCrop((crop_h, crop_w)) - results = transforms.Compose([ - transforms.ToPILImage(), - transforms.FiveCrop(), - ])(img) + img = torch.FloatTensor(3, h, w).uniform_() + results = transform(to_pil_image(img)) assert len(results) == 5 for crop in results: - assert crop.size == (expected_crop_w, expected_crop_h) + assert crop.size == (crop_w, crop_h) to_pil_image = transforms.ToPILImage() - tl = to_pil_image(img[:, 0:expected_crop_h, 0:expected_crop_w]) - tr = to_pil_image(img[:, 0:expected_crop_h, w - expected_crop_w:]) - bl = to_pil_image(img[:, h - expected_crop_h:, 0:expected_crop_w]) - br = to_pil_image(img[:, h - expected_crop_h:, w - expected_crop_w:]) - center = transforms.CenterCrop((expected_crop_h, - expected_crop_w))(to_pil_image(img)) - expected_output = [tl, tr, bl, br, center] + tl = to_pil_image(img[:, 0:crop_h, 0:crop_w]) + tr = to_pil_image(img[:, 0:crop_h, w - crop_w:]) + bl = to_pil_image(img[:, h - crop_h:, 0:crop_w]) + br = to_pil_image(img[:, h - crop_h:, w - crop_w:]) + center = transforms.CenterCrop((crop_h, crop_w))(to_pil_image(img)) + expected_output = (tl, tr, bl, br, center) assert results == expected_output def test_ten_crop(self): diff --git a/torchvision/transforms.py b/torchvision/transforms.py index a093fe4f783..ffd4b52f7b4 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -383,19 +383,34 @@ def __call__(self, img): class FiveCrop(object): - """Crop the given PIL.Image into four corners and the central crop.""" + """Crop the given PIL.Image into four corners and the central crop. + + Args: + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. + """ + + def __init__(self, size): + self.size = size + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + self.size = size def __call__(self, img): w, h = img.size - crop_w = w // 2 - crop_h = h // 2 - + crop_h, crop_w = self.size + if crop_w > w or crop_h > h: + raise ValueError("Requested crop size {} is bigger than input size {}".format(self.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 = CenterCrop((crop_h, crop_w))(img) - return [tl, tr, bl, br, center] + return (tl, tr, bl, br, center) class TenCrop(object): From 5229838c06b183535e6a4b7c2a4fd2d118ef9fa4 Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sat, 23 Sep 2017 16:32:38 +0100 Subject: [PATCH 5/8] add size param to FiveCrop and TenCrop --- test/test_transforms.py | 50 +++++++++++++++++++++++++-------------- torchvision/transforms.py | 13 +++++++--- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/test/test_transforms.py b/test/test_transforms.py index e16dfba1d15..976761dc77f 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -64,11 +64,11 @@ def test_five_crop(self): crop_h = random.randint(1, h) crop_w = random.randint(1, w) if single_dim: - crop_h = min(crop_h, crop_w) - crop_w = crop_h - transform = transforms.FiveCrop(crop_h) + crop_h = min(crop_h, crop_w) + crop_w = crop_h + transform = transforms.FiveCrop(crop_h) else: - transform = transforms.FiveCrop((crop_h, crop_w)) + transform = transforms.FiveCrop((crop_h, crop_w)) img = torch.FloatTensor(3, h, w).uniform_() results = transform(to_pil_image(img)) @@ -87,20 +87,34 @@ def test_five_crop(self): assert results == expected_output def test_ten_crop(self): - for h, w in [(8, 8), (9, 9), (10, 5), (5, 10)]: - img = transforms.ToPILImage()(torch.FloatTensor(3, h, w).uniform_()) - hflipped_img = img.transpose(Image.FLIP_LEFT_RIGHT) - vflipped_img = img.transpose(Image.FLIP_TOP_BOTTOM) - - results = transforms.TenCrop()(img) - expected_output = transforms.FiveCrop()(img) + transforms.FiveCrop()(hflipped_img) - assert len(results) == 10 - assert expected_output == results - - results = transforms.TenCrop(vflip=True)(img) - expected_output = transforms.FiveCrop()(img) + transforms.FiveCrop()(vflipped_img) - assert len(results) == 10 - assert expected_output == results + to_pil_image = transforms.ToPILImage() + h = random.randint(5, 25) + w = random.randint(5, 25) + for should_vflip in [True, False]: + for single_dim in [True, False]: + crop_h = random.randint(1, h) + crop_w = random.randint(1, w) + if single_dim: + crop_h = min(crop_h, crop_w) + crop_w = crop_h + transform = transforms.TenCrop(crop_h, vflip=should_vflip) + five_crop = transforms.FiveCrop(crop_h) + else: + transform = transforms.TenCrop((crop_h, crop_w), vflip=should_vflip) + five_crop = transforms.FiveCrop((crop_h, crop_w)) + + img = to_pil_image(torch.FloatTensor(3, h, w).uniform_()) + results = transform(img) + expected_output = five_crop(img) + if should_vflip: + vflipped_img = img.transpose(Image.FLIP_TOP_BOTTOM) + expected_output += five_crop(vflipped_img) + else: + hflipped_img = img.transpose(Image.FLIP_LEFT_RIGHT) + expected_output += five_crop(hflipped_img) + + assert len(results) == 10 + assert expected_output == results def test_scale(self): height = random.randint(24, 32) * 2 diff --git a/torchvision/transforms.py b/torchvision/transforms.py index ffd4b52f7b4..e73e354fd42 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -421,15 +421,22 @@ class TenCrop(object): vflip bool: Use vertical flipping instead of horizontal """ - def __init__(self, vflip=False): + def __init__(self, size, vflip=False): + self.size = size + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + else: + assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + self.size = size self.vflip = vflip def __call__(self, img): - first_five = FiveCrop()(img) + five_crop = FiveCrop(self.size) + first_five = five_crop(img) if self.vflip: img = img.transpose(Image.FLIP_TOP_BOTTOM) else: img = img.transpose(Image.FLIP_LEFT_RIGHT) - second_five = FiveCrop()(img) + second_five = five_crop(img) return first_five + second_five From da24e03a119378272c890291755dbb7eab82e931 Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sat, 23 Sep 2017 16:38:01 +0100 Subject: [PATCH 6/8] flake8 fixes --- torchvision/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/transforms.py b/torchvision/transforms.py index e73e354fd42..b091333ef28 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -403,8 +403,8 @@ def __call__(self, img): w, h = img.size crop_h, crop_w = self.size if crop_w > w or crop_h > h: - raise ValueError("Requested crop size {} is bigger than input size {}".format(self.size, - (h, w))) + raise ValueError("Requested crop size {} is bigger than input size {}".format(self.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)) From 41e6653b05972c2c8e8653a79377d731036224a3 Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Sat, 23 Sep 2017 16:54:26 +0100 Subject: [PATCH 7/8] update docstring --- torchvision/transforms.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/torchvision/transforms.py b/torchvision/transforms.py index b091333ef28..ab1d385f1cc 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -383,12 +383,15 @@ def __call__(self, img): class FiveCrop(object): - """Crop the given PIL.Image into four corners and the central crop. + """Crop the given PIL.Image into four corners and the central crop.abs - Args: - size (sequence or int): Desired output size of the crop. If size is an - int instead of sequence like (h, w), a square crop (size, size) is - made. + Note: this transform returns a tuple of images and there may be a mismatch in the number of + inputs and targets your `Dataset` returns. + + Args: + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. """ def __init__(self, size): @@ -417,7 +420,13 @@ class TenCrop(object): """Crop the given PIL.Image into four corners and the central crop plus the flipped version of these (horizontal flipping is used by default) + Note: this transform returns a tuple of images and there may be a mismatch in the number of + inputs and targets your `Dataset` returns. + Args: + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. vflip bool: Use vertical flipping instead of horizontal """ From 3be62c8355b81724759d2eeaf226b5e86052c28d Mon Sep 17 00:00:00 2001 From: Alykhan Tejani Date: Tue, 26 Sep 2017 19:06:10 +0100 Subject: [PATCH 8/8] flake8 fix --- torchvision/transforms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/torchvision/transforms.py b/torchvision/transforms.py index 2e36ee58586..27fe4e2d1bc 100644 --- a/torchvision/transforms.py +++ b/torchvision/transforms.py @@ -639,6 +639,7 @@ def __call__(self, img): i, j, h, w = self.get_params(img) return scaled_crop(img, i, j, h, w, self.size, self.interpolation) + class FiveCrop(object): """Crop the given PIL.Image into four corners and the central crop.abs