Spaces:
Sleeping
Sleeping
new gpu_crop transformation with test
Browse files
deployment/transforms.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
import math
|
2 |
-
from typing import
|
3 |
|
4 |
import torch
|
5 |
|
6 |
-
|
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
|
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
|
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
|
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)
|
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 |
-
"
|
582 |
]
|
583 |
},
|
584 |
{
|
585 |
"cell_type": "code",
|
586 |
-
"execution_count":
|
587 |
"metadata": {},
|
588 |
"outputs": [
|
589 |
{
|
@@ -592,19 +583,19 @@
|
|
592 |
"(460, 460)"
|
593 |
]
|
594 |
},
|
595 |
-
"execution_count":
|
596 |
"metadata": {},
|
597 |
"output_type": "execute_result"
|
598 |
}
|
599 |
],
|
600 |
"source": [
|
601 |
-
"cropped =
|
602 |
"cropped.shape"
|
603 |
]
|
604 |
},
|
605 |
{
|
606 |
"cell_type": "code",
|
607 |
-
"execution_count":
|
608 |
"metadata": {},
|
609 |
"outputs": [
|
610 |
{
|
@@ -614,7 +605,7 @@
|
|
614 |
"<PIL.Image.Image image mode=RGB size=460x460>"
|
615 |
]
|
616 |
},
|
617 |
-
"execution_count":
|
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 |
}
|