jcarnero commited on
Commit
c16c201
1 Parent(s): 6bd27ab

new gpu_crop transformation with test

Browse files
deployment/transforms.py CHANGED
@@ -1,9 +1,9 @@
1
  import math
2
- from typing import Literal, Union, Tuple
3
 
4
  import torch
5
 
6
- # # import torch.nn.functional as F
7
  import torchvision.transforms.functional as tvf
8
 
9
  # import torchvision.transforms as tvtfms
@@ -99,3 +99,44 @@ def resized_crop_pad(
99
  resized_image = pad(crop(resized_image, size), size)
100
 
101
  return resized_image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import math
2
+ from typing import Union, Tuple
3
 
4
  import torch
5
 
6
+ import torch.nn.functional as F
7
  import torchvision.transforms.functional as tvf
8
 
9
  # import torchvision.transforms as tvtfms
 
99
  resized_image = pad(crop(resized_image, size), size)
100
 
101
  return resized_image
102
+
103
+
104
+ def gpu_crop(batch: torch.tensor, size: Tuple[int, int]):
105
+ """
106
+ Crops each image in `batch` to a particular `size`.
107
+
108
+ Args:
109
+ batch (array of `torch.Tensor`):
110
+ A batch of images, should be of shape `NxCxWxH`
111
+ size (`tuple` of integers):
112
+ A size to pad to, should be in the form
113
+ of (width, height)
114
+
115
+ Returns:
116
+ A batch of cropped images
117
+ """
118
+ # Split into multiple lines for clarity
119
+ affine_matrix = torch.eye(3, device=batch.device).float()
120
+ affine_matrix = affine_matrix.unsqueeze(0)
121
+ affine_matrix = affine_matrix.expand(batch.size(0), 3, 3)
122
+ affine_matrix = affine_matrix.contiguous()[:, :2]
123
+
124
+ coords = F.affine_grid(affine_matrix, batch.shape[:2] + size, align_corners=True)
125
+
126
+ top_range, bottom_range = coords.min(), coords.max()
127
+ zoom = 1 / (bottom_range - top_range).item() * 2
128
+
129
+ resizing_limit = (
130
+ min(batch.shape[-2] / coords.shape[-2], batch.shape[-1] / coords.shape[-1]) / 2
131
+ )
132
+
133
+ if resizing_limit > 1 and resizing_limit > zoom:
134
+ batch = F.interpolate(
135
+ batch,
136
+ scale_factor=1 / resizing_limit,
137
+ mode="area",
138
+ recompute_scale_factor=True,
139
+ )
140
+ return F.grid_sample(
141
+ batch, coords, mode="bilinear", padding_mode="reflection", align_corners=True
142
+ )
tests/{test_transforms.py → test_validation_transforms.py} RENAMED
@@ -4,10 +4,12 @@ from pathlib import Path
4
  from typing import List
5
  import numpy as np
6
  from PIL import Image
 
 
7
  from fastai.vision.data import PILImage
8
  import fastai.vision.augment as fastai_aug
9
 
10
- from deployment.transforms import resized_crop_pad
11
 
12
  DATA_PATH = "data/200-bird-species-with-11788-images"
13
 
@@ -43,7 +45,7 @@ class TestTransforms:
43
  assert (np.array(im_fastai) == np.array(im_pil)).all()
44
 
45
  # RandomResizedCrop is not exactly equal to CropPad in validation
46
- # # def testRandomResizedCropEqualsCropPadInValidation(self, im_fastai: PILImage):
47
  # # crop_fastai = fastai_aug.CropPad((460, 460))
48
  # # crop_rrc = fastai_aug.RandomResizedCrop((460, 460))
49
 
@@ -52,7 +54,7 @@ class TestTransforms:
52
 
53
  # # assert (np.array(cropped_rrc) == np.array(cropped_fastai)).all()
54
 
55
- def testRandomResizedCropInValidationEqualsCustomResizedCropPad(
56
  self, im_fastai: PILImage, im_pil: Image
57
  ):
58
  crop_rrc = fastai_aug.RandomResizedCrop((460, 460))
@@ -61,3 +63,20 @@ class TestTransforms:
61
  np.array(crop_rrc(im_fastai, split_idx=1))
62
  == np.array(resized_crop_pad(im_pil, (460, 460)))
63
  ).all()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  from typing import List
5
  import numpy as np
6
  from PIL import Image
7
+ import torch
8
+ import torchvision.transforms as tvtfms
9
  from fastai.vision.data import PILImage
10
  import fastai.vision.augment as fastai_aug
11
 
12
+ from deployment.transforms import resized_crop_pad, gpu_crop
13
 
14
  DATA_PATH = "data/200-bird-species-with-11788-images"
15
 
 
45
  assert (np.array(im_fastai) == np.array(im_pil)).all()
46
 
47
  # RandomResizedCrop is not exactly equal to CropPad in validation
48
+ # # def testRandomResizedCropEqualsCropPad(self, im_fastai: PILImage):
49
  # # crop_fastai = fastai_aug.CropPad((460, 460))
50
  # # crop_rrc = fastai_aug.RandomResizedCrop((460, 460))
51
 
 
54
 
55
  # # assert (np.array(cropped_rrc) == np.array(cropped_fastai)).all()
56
 
57
+ def testRandomResizedCropEqualsCustomResizedCropPad(
58
  self, im_fastai: PILImage, im_pil: Image
59
  ):
60
  crop_rrc = fastai_aug.RandomResizedCrop((460, 460))
 
63
  np.array(crop_rrc(im_fastai, split_idx=1))
64
  == np.array(resized_crop_pad(im_pil, (460, 460)))
65
  ).all()
66
+
67
+ def testFlipEqualsCustomGPUCrop(self, im_fastai: PILImage, im_pil: Image):
68
+ tt_fastai = fastai_aug.ToTensor()
69
+ i2f_fastai = fastai_aug.IntToFloatTensor()
70
+ flip = fastai_aug.Flip(size=(224, 224))
71
+ tt_torch = tvtfms.ToTensor()
72
+
73
+ # apply flip augmentation on validation
74
+ result_im_fastai = flip(
75
+ i2f_fastai(tt_fastai(im_fastai).unsqueeze(0)), split_idx=1
76
+ )
77
+
78
+ # apply custom gpu crop
79
+ result_im_tv = gpu_crop(tt_torch(im_pil).unsqueeze(0), size=(224, 224))
80
+
81
+ assert torch.allclose(result_im_fastai, result_im_tv)
82
+ assert (result_im_fastai == result_im_tv).all()
training/notebooks/transforms-lab.ipynb CHANGED
@@ -181,7 +181,7 @@
181
  {
182
  "data": {
183
  "text/plain": [
184
- "<bound method DataBlock.datasets of <fastai.data.block.DataBlock object at 0x7f01c4a27eb0>>"
185
  ]
186
  },
187
  "execution_count": 12,
@@ -287,7 +287,7 @@
287
  "metadata": {},
288
  "source": [
289
  "* IntToFloatTensor seems easy enough and we can probably use the torch version\n",
290
- "* Flip and Brightness are RandTransforms and are not applied to validation, but as we are using the size parameter, a RandomResizeCropGPU is done (doing center croping on validation). **WHY?**\n",
291
  "* Normalize seems easy enough to try replacing it with torch version"
292
  ]
293
  },
@@ -563,27 +563,18 @@
563
  "%cd .."
564
  ]
565
  },
566
- {
567
- "cell_type": "code",
568
- "execution_count": 28,
569
- "metadata": {},
570
- "outputs": [],
571
- "source": [
572
- "from deployment.transforms import ResizedCropPad"
573
- ]
574
- },
575
  {
576
  "cell_type": "code",
577
  "execution_count": 29,
578
  "metadata": {},
579
  "outputs": [],
580
  "source": [
581
- "custom_crop = ResizedCropPad((460, 460))"
582
  ]
583
  },
584
  {
585
  "cell_type": "code",
586
- "execution_count": 30,
587
  "metadata": {},
588
  "outputs": [
589
  {
@@ -592,19 +583,19 @@
592
  "(460, 460)"
593
  ]
594
  },
595
- "execution_count": 30,
596
  "metadata": {},
597
  "output_type": "execute_result"
598
  }
599
  ],
600
  "source": [
601
- "cropped = custom_crop(im)\n",
602
  "cropped.shape"
603
  ]
604
  },
605
  {
606
  "cell_type": "code",
607
- "execution_count": 31,
608
  "metadata": {},
609
  "outputs": [
610
  {
@@ -614,7 +605,7 @@
614
  "<PIL.Image.Image image mode=RGB size=460x460>"
615
  ]
616
  },
617
- "execution_count": 31,
618
  "metadata": {},
619
  "output_type": "execute_result"
620
  }
 
181
  {
182
  "data": {
183
  "text/plain": [
184
+ "<bound method DataBlock.datasets of <fastai.data.block.DataBlock object at 0x7fcc180cab90>>"
185
  ]
186
  },
187
  "execution_count": 12,
 
287
  "metadata": {},
288
  "source": [
289
  "* IntToFloatTensor seems easy enough and we can probably use the torch version\n",
290
+ "* Flip and Brightness are RandTransforms and are not applied to validation, but as we are using the size parameter, a RandomResizeCropGPU is done (doing center croping on validation) using the affine matrices.\n",
291
  "* Normalize seems easy enough to try replacing it with torch version"
292
  ]
293
  },
 
563
  "%cd .."
564
  ]
565
  },
 
 
 
 
 
 
 
 
 
566
  {
567
  "cell_type": "code",
568
  "execution_count": 29,
569
  "metadata": {},
570
  "outputs": [],
571
  "source": [
572
+ "from deployment.transforms import resized_crop_pad"
573
  ]
574
  },
575
  {
576
  "cell_type": "code",
577
+ "execution_count": 32,
578
  "metadata": {},
579
  "outputs": [
580
  {
 
583
  "(460, 460)"
584
  ]
585
  },
586
+ "execution_count": 32,
587
  "metadata": {},
588
  "output_type": "execute_result"
589
  }
590
  ],
591
  "source": [
592
+ "cropped = resized_crop_pad(im, (460, 460))\n",
593
  "cropped.shape"
594
  ]
595
  },
596
  {
597
  "cell_type": "code",
598
+ "execution_count": 33,
599
  "metadata": {},
600
  "outputs": [
601
  {
 
605
  "<PIL.Image.Image image mode=RGB size=460x460>"
606
  ]
607
  },
608
+ "execution_count": 33,
609
  "metadata": {},
610
  "output_type": "execute_result"
611
  }