chugr 0.2.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chugr/__init__.py +46 -0
- chugr/app/test.py +105 -0
- chugr/common/__init__.py +9 -0
- chugr/common/cli.py +234 -0
- chugr/common/collate.py +104 -0
- chugr/common/config.py +335 -0
- chugr/common/random.py +76 -0
- chugr/common/serialize.py +150 -0
- chugr/common/task_config.py +24 -0
- chugr/common/types.py +122 -0
- chugr/common/urls.py +88 -0
- chugr/doc/__init__.py +17 -0
- chugr/doc/constants.py +15 -0
- chugr/doc/doc_processor.py +248 -0
- chugr/doc/doc_read_processor.py +249 -0
- chugr/doc/doc_vqa_processor.py +113 -0
- chugr/doc/synth_text.py +212 -0
- chugr/hfds/__init__.py +2 -0
- chugr/hfds/collate.py +83 -0
- chugr/hfds/loader.py +385 -0
- chugr/hfds/wrappers.py +108 -0
- chugr/image/__init__.py +4 -0
- chugr/image/build_transforms_doc.py +427 -0
- chugr/image/build_transforms_image.py +203 -0
- chugr/image/transforms_alb.py +112 -0
- chugr/image/transforms_factory.py +58 -0
- chugr/image/transforms_torch.py +137 -0
- chugr/loader.py +249 -0
- chugr/naflex.py +357 -0
- chugr/task_pipeline/__init__.py +7 -0
- chugr/task_pipeline/pipeline_doc_read.py +66 -0
- chugr/task_pipeline/pipeline_doc_vqa.py +124 -0
- chugr/task_pipeline/pipeline_factory.py +32 -0
- chugr/task_pipeline/pipeline_gtparse.py +93 -0
- chugr/task_pipeline/pipeline_image_text.py +90 -0
- chugr/task_pipeline/pipeline_manual.py +27 -0
- chugr/text/__init__.py +1 -0
- chugr/text/tokenization.py +222 -0
- chugr/version.py +1 -0
- chugr/wds/__init__.py +7 -0
- chugr/wds/dataset_info.py +65 -0
- chugr/wds/decode.py +319 -0
- chugr/wds/filters.py +142 -0
- chugr/wds/helpers.py +68 -0
- chugr/wds/loader.py +187 -0
- chugr/wds/pipeline.py +182 -0
- chugr/wds/shardlists.py +134 -0
- chugr/wds/tariterators.py +70 -0
- chugr-0.2.0.dev0.dist-info/METADATA +436 -0
- chugr-0.2.0.dev0.dist-info/RECORD +53 -0
- chugr-0.2.0.dev0.dist-info/WHEEL +4 -0
- chugr-0.2.0.dev0.dist-info/entry_points.txt +4 -0
- chugr-0.2.0.dev0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import torchvision.transforms.functional as F
|
|
5
|
+
from torchvision import transforms
|
|
6
|
+
|
|
7
|
+
from chugr.common import ImageInputCfg, ImageAugCfg
|
|
8
|
+
|
|
9
|
+
from .transforms_torch import AlignLongAxis, Bitmap, Erosion, Dilation, CropMargin
|
|
10
|
+
|
|
11
|
+
# NOTE, chugr currently depends on some time aug impl, this should be flipped if timm ends up
|
|
12
|
+
# leveraging chugr data pipelines.
|
|
13
|
+
from timm.data import str_to_interp_mode, ResizeKeepRatio, CenterCropOrPad, RandomCropOrPad
|
|
14
|
+
from timm.data.naflex_transforms import Patchify, get_image_size_for_seq
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_DOC_AUG_DEFAULTS = dict(
|
|
18
|
+
scale_prob=0.05,
|
|
19
|
+
scale_range=(0.85, 1.04),
|
|
20
|
+
ratio_prob=0.05,
|
|
21
|
+
ratio_range=(.9, 1.11),
|
|
22
|
+
bitmap_prob=0.05,
|
|
23
|
+
erosion_dilation_prob=0.02,
|
|
24
|
+
shear_prob=0.05,
|
|
25
|
+
shear_range_x=(0, 3.),
|
|
26
|
+
shear_range_y=(-3, 0),
|
|
27
|
+
shift_scale_rotate_prob=0.03,
|
|
28
|
+
shift_range_x=0.04,
|
|
29
|
+
shift_range_y=0.03,
|
|
30
|
+
rotate_range=3,
|
|
31
|
+
elastic_prob=0.04,
|
|
32
|
+
elastic_alpha=50.,
|
|
33
|
+
elastic_sigma=12.,
|
|
34
|
+
brightness_contrast_prob=0.04,
|
|
35
|
+
brightness_range=0.1,
|
|
36
|
+
contrast_range=0.1,
|
|
37
|
+
gaussian_blur_prob=0.03,
|
|
38
|
+
gaussian_blur_kernel=3,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _doc_aug_ops(params, interpolation_mode, fill):
|
|
43
|
+
""" Torchvision based document augmentations applied to an image already at (roughly) target size. """
|
|
44
|
+
return [
|
|
45
|
+
transforms.RandomApply([Bitmap()], p=params['bitmap_prob']),
|
|
46
|
+
transforms.RandomApply([
|
|
47
|
+
transforms.RandomChoice([
|
|
48
|
+
Erosion(3),
|
|
49
|
+
Dilation(3),
|
|
50
|
+
])],
|
|
51
|
+
p=params['erosion_dilation_prob']
|
|
52
|
+
),
|
|
53
|
+
transforms.RandomApply([
|
|
54
|
+
transforms.RandomAffine(
|
|
55
|
+
degrees=0,
|
|
56
|
+
shear=params['shear_range_x'] + params['shear_range_y'],
|
|
57
|
+
interpolation=interpolation_mode,
|
|
58
|
+
fill=fill,
|
|
59
|
+
)],
|
|
60
|
+
p=params['shear_prob'],
|
|
61
|
+
),
|
|
62
|
+
transforms.RandomApply([
|
|
63
|
+
transforms.RandomAffine(
|
|
64
|
+
degrees=params['rotate_range'],
|
|
65
|
+
translate=(params['shift_range_x'], params['shift_range_y']),
|
|
66
|
+
interpolation=interpolation_mode,
|
|
67
|
+
fill=fill,
|
|
68
|
+
)],
|
|
69
|
+
p=params['shift_scale_rotate_prob'],
|
|
70
|
+
),
|
|
71
|
+
transforms.RandomApply([
|
|
72
|
+
transforms.ElasticTransform(
|
|
73
|
+
alpha=params['elastic_alpha'],
|
|
74
|
+
sigma=params['elastic_sigma'],
|
|
75
|
+
interpolation=interpolation_mode,
|
|
76
|
+
fill=fill,
|
|
77
|
+
)],
|
|
78
|
+
p=params['elastic_prob'],
|
|
79
|
+
),
|
|
80
|
+
transforms.RandomApply([
|
|
81
|
+
transforms.ColorJitter(
|
|
82
|
+
brightness=params['brightness_range'],
|
|
83
|
+
contrast=params['contrast_range'],
|
|
84
|
+
)],
|
|
85
|
+
p=params['brightness_contrast_prob'],
|
|
86
|
+
),
|
|
87
|
+
transforms.RandomApply([
|
|
88
|
+
transforms.GaussianBlur(
|
|
89
|
+
params['gaussian_blur_kernel'],
|
|
90
|
+
sigma=(0.1, 0.8),
|
|
91
|
+
)],
|
|
92
|
+
p=params['gaussian_blur_prob'],
|
|
93
|
+
),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def build_transforms_doc_basic(
|
|
98
|
+
input_cfg: ImageInputCfg,
|
|
99
|
+
is_training: bool = False,
|
|
100
|
+
do_normalize: bool = True,
|
|
101
|
+
aug_cfg: Optional[ImageAugCfg] = None,
|
|
102
|
+
composed: bool = True,
|
|
103
|
+
):
|
|
104
|
+
# minimal fixed size doc transforms, resize keeping aspect + pad (random pad position for train)
|
|
105
|
+
image_size = input_cfg.size
|
|
106
|
+
interpolation_mode = str_to_interp_mode(input_cfg.interpolation)
|
|
107
|
+
|
|
108
|
+
pp = []
|
|
109
|
+
|
|
110
|
+
if input_cfg.crop_margin:
|
|
111
|
+
pp += [CropMargin()]
|
|
112
|
+
|
|
113
|
+
if input_cfg.align_long_axis:
|
|
114
|
+
pp += [AlignLongAxis(image_size, interpolation=interpolation_mode)]
|
|
115
|
+
|
|
116
|
+
pp += [ResizeKeepRatio(image_size, longest=1, interpolation=input_cfg.interpolation)]
|
|
117
|
+
if is_training:
|
|
118
|
+
pp += [RandomCropOrPad(image_size, fill=input_cfg.fill_color)]
|
|
119
|
+
else:
|
|
120
|
+
pp += [CenterCropOrPad(image_size, fill=input_cfg.fill_color)]
|
|
121
|
+
|
|
122
|
+
pp += [transforms.ToTensor()]
|
|
123
|
+
|
|
124
|
+
if do_normalize:
|
|
125
|
+
pp += [transforms.Normalize(input_cfg.mean, input_cfg.std)]
|
|
126
|
+
|
|
127
|
+
return transforms.Compose(pp) if composed else pp
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_transforms_doc_better(
|
|
131
|
+
input_cfg: ImageInputCfg,
|
|
132
|
+
is_training: bool = False,
|
|
133
|
+
do_normalize: bool = True,
|
|
134
|
+
aug_cfg: Optional[ImageAugCfg] = None,
|
|
135
|
+
composed: bool = True,
|
|
136
|
+
):
|
|
137
|
+
# an improved torchvision + custom op transforms (no albumentations)
|
|
138
|
+
image_size = input_cfg.size
|
|
139
|
+
interpolation_mode = str_to_interp_mode(input_cfg.interpolation)
|
|
140
|
+
pp = []
|
|
141
|
+
|
|
142
|
+
if input_cfg.crop_margin:
|
|
143
|
+
pp += [CropMargin()]
|
|
144
|
+
|
|
145
|
+
if input_cfg.align_long_axis:
|
|
146
|
+
pp += [AlignLongAxis(image_size, interpolation=interpolation_mode)]
|
|
147
|
+
|
|
148
|
+
if is_training:
|
|
149
|
+
# FIXME merge defaults w/ aug_cfg
|
|
150
|
+
params = dict(_DOC_AUG_DEFAULTS)
|
|
151
|
+
pp += [
|
|
152
|
+
ResizeKeepRatio(
|
|
153
|
+
image_size,
|
|
154
|
+
longest=1,
|
|
155
|
+
interpolation=input_cfg.interpolation,
|
|
156
|
+
random_scale_prob=params['scale_prob'],
|
|
157
|
+
random_scale_range=params['scale_range'],
|
|
158
|
+
random_aspect_prob=params['ratio_prob'],
|
|
159
|
+
random_aspect_range=params['ratio_range'],
|
|
160
|
+
),
|
|
161
|
+
]
|
|
162
|
+
pp += _doc_aug_ops(params, interpolation_mode, input_cfg.fill_color)
|
|
163
|
+
pp += [
|
|
164
|
+
RandomCropOrPad(image_size, fill=input_cfg.fill_color),
|
|
165
|
+
transforms.CenterCrop(image_size),
|
|
166
|
+
]
|
|
167
|
+
else:
|
|
168
|
+
pp += [
|
|
169
|
+
ResizeKeepRatio(image_size, longest=1, interpolation=input_cfg.interpolation),
|
|
170
|
+
CenterCropOrPad(image_size, fill=input_cfg.fill_color),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
pp += [transforms.ToTensor()]
|
|
174
|
+
|
|
175
|
+
if do_normalize:
|
|
176
|
+
pp += [transforms.Normalize(input_cfg.mean, input_cfg.std)]
|
|
177
|
+
|
|
178
|
+
return transforms.Compose(pp) if composed else pp
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ResizeToSeqLen:
|
|
182
|
+
""" Resize (preserving aspect) to the largest size whose patch count fits within a sequence length budget.
|
|
183
|
+
|
|
184
|
+
Optionally (train) sample a reduced budget to vary the resolution.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def __init__(
|
|
188
|
+
self,
|
|
189
|
+
patch_size: int,
|
|
190
|
+
max_seq_len: int,
|
|
191
|
+
interpolation: str = 'bicubic',
|
|
192
|
+
random_seq_len_prob: float = 0.,
|
|
193
|
+
random_seq_len_range=(0.5, 1.0),
|
|
194
|
+
max_ratio: Optional[float] = None,
|
|
195
|
+
):
|
|
196
|
+
self.patch_size = patch_size
|
|
197
|
+
self.max_seq_len = max_seq_len
|
|
198
|
+
self.interpolation = str_to_interp_mode(interpolation)
|
|
199
|
+
self.random_seq_len_prob = random_seq_len_prob
|
|
200
|
+
self.random_seq_len_range = random_seq_len_range
|
|
201
|
+
self.max_ratio = max_ratio
|
|
202
|
+
|
|
203
|
+
def __call__(self, img):
|
|
204
|
+
w, h = F.get_image_size(img)
|
|
205
|
+
seq_len = self.max_seq_len
|
|
206
|
+
if self.random_seq_len_prob > 0 and random.random() < self.random_seq_len_prob:
|
|
207
|
+
seq_len = max(1, int(seq_len * random.uniform(*self.random_seq_len_range)))
|
|
208
|
+
_, (th, tw) = get_image_size_for_seq(
|
|
209
|
+
(h, w),
|
|
210
|
+
patch_size=self.patch_size,
|
|
211
|
+
max_seq_len=seq_len,
|
|
212
|
+
divisible_by_patch=True,
|
|
213
|
+
max_ratio=self.max_ratio,
|
|
214
|
+
)
|
|
215
|
+
return F.resize(img, [th, tw], interpolation=self.interpolation, antialias=True)
|
|
216
|
+
|
|
217
|
+
def __repr__(self):
|
|
218
|
+
return (f'{self.__class__.__name__}(patch_size={self.patch_size}, max_seq_len={self.max_seq_len}, '
|
|
219
|
+
f'random_seq_len_prob={self.random_seq_len_prob})')
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def build_transforms_doc_naflex(
|
|
223
|
+
input_cfg: ImageInputCfg,
|
|
224
|
+
is_training: bool = False,
|
|
225
|
+
do_normalize: bool = True,
|
|
226
|
+
aug_cfg: Optional[ImageAugCfg] = None,
|
|
227
|
+
composed: bool = True,
|
|
228
|
+
):
|
|
229
|
+
""" Native aspect, variable resolution (NaFlex) document transforms.
|
|
230
|
+
|
|
231
|
+
The page is resized (aspect preserved) to fit a patch budget (input_cfg.max_seq_len), augmented, then
|
|
232
|
+
split into a flat sequence of patches w/ (y, x) patch coordinates. Output is a dict that the chugr
|
|
233
|
+
collate fn pads to the longest sequence in the batch.
|
|
234
|
+
"""
|
|
235
|
+
assert input_cfg.patch_size, 'patch_size must be set in ImageInputCfg for naflex transforms.'
|
|
236
|
+
assert input_cfg.max_seq_len, 'max_seq_len must be set in ImageInputCfg for naflex transforms.'
|
|
237
|
+
interpolation_mode = str_to_interp_mode(input_cfg.interpolation)
|
|
238
|
+
pp = []
|
|
239
|
+
|
|
240
|
+
if input_cfg.crop_margin:
|
|
241
|
+
pp += [CropMargin()]
|
|
242
|
+
|
|
243
|
+
if is_training:
|
|
244
|
+
params = dict(_DOC_AUG_DEFAULTS)
|
|
245
|
+
params.update(random_seq_len_prob=0.0, random_seq_len_range=(0.6, 1.0))
|
|
246
|
+
pp += [
|
|
247
|
+
ResizeToSeqLen(
|
|
248
|
+
input_cfg.patch_size,
|
|
249
|
+
input_cfg.max_seq_len,
|
|
250
|
+
interpolation=input_cfg.interpolation,
|
|
251
|
+
random_seq_len_prob=params['random_seq_len_prob'],
|
|
252
|
+
random_seq_len_range=params['random_seq_len_range'],
|
|
253
|
+
)
|
|
254
|
+
]
|
|
255
|
+
pp += _doc_aug_ops(params, interpolation_mode, input_cfg.fill_color)
|
|
256
|
+
else:
|
|
257
|
+
pp += [
|
|
258
|
+
ResizeToSeqLen(
|
|
259
|
+
input_cfg.patch_size,
|
|
260
|
+
input_cfg.max_seq_len,
|
|
261
|
+
interpolation=input_cfg.interpolation,
|
|
262
|
+
)
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
pp += [transforms.ToTensor()]
|
|
266
|
+
|
|
267
|
+
if do_normalize:
|
|
268
|
+
pp += [transforms.Normalize(input_cfg.mean, input_cfg.std)]
|
|
269
|
+
|
|
270
|
+
pp += [Patchify(input_cfg.patch_size)]
|
|
271
|
+
|
|
272
|
+
return transforms.Compose(pp) if composed else pp
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def build_transforms_doc_nougat(
|
|
276
|
+
input_cfg: ImageInputCfg,
|
|
277
|
+
is_training: bool = False,
|
|
278
|
+
do_normalize: bool = True,
|
|
279
|
+
aug_cfg: Optional[ImageAugCfg] = None,
|
|
280
|
+
composed: bool = True,
|
|
281
|
+
):
|
|
282
|
+
import albumentations as alb
|
|
283
|
+
from chugr.image.transforms_alb import BitmapAlb, ErosionAlb, DilationAlb, AlbWrapper, CropMarginCv2
|
|
284
|
+
|
|
285
|
+
# albumentations + custom opencv transforms from nougat
|
|
286
|
+
image_size = input_cfg.size
|
|
287
|
+
if input_cfg.interpolation == 'bilinear':
|
|
288
|
+
interpolation_mode = 1
|
|
289
|
+
else:
|
|
290
|
+
interpolation_mode = 2 # bicubic
|
|
291
|
+
border_mode = 0
|
|
292
|
+
fill = input_cfg.fill_color
|
|
293
|
+
if isinstance(fill, (list, tuple)) and len(fill) == 1:
|
|
294
|
+
fill = fill[0]
|
|
295
|
+
|
|
296
|
+
tv_pp = []
|
|
297
|
+
alb_pp = []
|
|
298
|
+
|
|
299
|
+
if input_cfg.crop_margin:
|
|
300
|
+
tv_pp += [CropMarginCv2()]
|
|
301
|
+
|
|
302
|
+
if input_cfg.align_long_axis:
|
|
303
|
+
tv_pp += [AlignLongAxis(image_size)]
|
|
304
|
+
|
|
305
|
+
if is_training:
|
|
306
|
+
# FIXME merge defaults w/ aug_cfg
|
|
307
|
+
params = dict(
|
|
308
|
+
scale_range=(0.85, 1.03), # done as part of shift_scale_rotate
|
|
309
|
+
bitmap_prob=0.05,
|
|
310
|
+
erosion_dilation_prob=0.02,
|
|
311
|
+
erosion_dilation_scale=(2, 3),
|
|
312
|
+
shear_prob=0.03,
|
|
313
|
+
shear_range_x=(0, 3.),
|
|
314
|
+
shear_range_y=(-3, 0),
|
|
315
|
+
shift_scale_rotate_prob=0.03,
|
|
316
|
+
shift_range_x=(0, 0.04),
|
|
317
|
+
shift_range_y=(0, 0.03),
|
|
318
|
+
rotate_range=2.,
|
|
319
|
+
grid_distort_prob=0.04,
|
|
320
|
+
grid_distort_range=0.05,
|
|
321
|
+
elastic_prob=0.04,
|
|
322
|
+
elastic_alpha=50.,
|
|
323
|
+
elastic_sigma=12.,
|
|
324
|
+
brightness_contrast_prob=0.03,
|
|
325
|
+
brightness_range=0.1,
|
|
326
|
+
constrast_range=0.1,
|
|
327
|
+
gaussian_noise_prob=0.08,
|
|
328
|
+
gaussian_noise_std=(0., 0.0175), # ~ variance 20 in [0, 255] pixel units
|
|
329
|
+
gaussian_blur_prob=0.03,
|
|
330
|
+
gaussian_blur_kernel_range=(3, 3),
|
|
331
|
+
image_compression_prob=0.1,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
tv_pp += [
|
|
335
|
+
# this should be equivalent to initial resize & pad in Donut prepare_input()
|
|
336
|
+
ResizeKeepRatio(image_size, longest=1, interpolation=input_cfg.interpolation),
|
|
337
|
+
RandomCropOrPad(image_size, fill=input_cfg.fill_color),
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
alb_pp += [
|
|
341
|
+
BitmapAlb(p=params['bitmap_prob']),
|
|
342
|
+
alb.OneOf([
|
|
343
|
+
ErosionAlb(params['erosion_dilation_scale']),
|
|
344
|
+
DilationAlb(params['erosion_dilation_scale'])
|
|
345
|
+
],
|
|
346
|
+
p=params['erosion_dilation_prob']
|
|
347
|
+
),
|
|
348
|
+
alb.Affine(
|
|
349
|
+
shear={
|
|
350
|
+
"x": params['shear_range_x'],
|
|
351
|
+
"y": params['shear_range_y']
|
|
352
|
+
},
|
|
353
|
+
border_mode=border_mode,
|
|
354
|
+
fill=fill,
|
|
355
|
+
p=params['shear_prob']
|
|
356
|
+
),
|
|
357
|
+
alb.Affine(
|
|
358
|
+
translate_percent={
|
|
359
|
+
"x": params['shift_range_x'],
|
|
360
|
+
"y": params['shift_range_y'],
|
|
361
|
+
},
|
|
362
|
+
scale=params['scale_range'],
|
|
363
|
+
rotate=(-params['rotate_range'], params['rotate_range']),
|
|
364
|
+
interpolation=interpolation_mode,
|
|
365
|
+
border_mode=border_mode,
|
|
366
|
+
fill=fill,
|
|
367
|
+
p=params['shift_scale_rotate_prob'],
|
|
368
|
+
),
|
|
369
|
+
alb.GridDistortion(
|
|
370
|
+
distort_limit=params['grid_distort_range'],
|
|
371
|
+
interpolation=interpolation_mode,
|
|
372
|
+
border_mode=border_mode,
|
|
373
|
+
fill=fill,
|
|
374
|
+
p=params['grid_distort_prob'],
|
|
375
|
+
),
|
|
376
|
+
alb.Compose(
|
|
377
|
+
[
|
|
378
|
+
alb.Affine(
|
|
379
|
+
translate_px=(0, 5),
|
|
380
|
+
border_mode=border_mode,
|
|
381
|
+
fill=fill,
|
|
382
|
+
p=1.0,
|
|
383
|
+
),
|
|
384
|
+
alb.ElasticTransform(
|
|
385
|
+
alpha=params['elastic_alpha'],
|
|
386
|
+
sigma=params['elastic_sigma'],
|
|
387
|
+
border_mode=border_mode,
|
|
388
|
+
fill=fill,
|
|
389
|
+
p=1.0,
|
|
390
|
+
),
|
|
391
|
+
],
|
|
392
|
+
p=params['elastic_prob'],
|
|
393
|
+
),
|
|
394
|
+
alb.RandomBrightnessContrast(
|
|
395
|
+
brightness_limit=params['brightness_range'],
|
|
396
|
+
contrast_limit=params['constrast_range'],
|
|
397
|
+
p=params['brightness_contrast_prob'],
|
|
398
|
+
),
|
|
399
|
+
alb.ImageCompression(
|
|
400
|
+
quality_range=(95, 100),
|
|
401
|
+
p=params['image_compression_prob'],
|
|
402
|
+
),
|
|
403
|
+
alb.GaussNoise(
|
|
404
|
+
std_range=params['gaussian_noise_std'],
|
|
405
|
+
p=params['gaussian_noise_prob']
|
|
406
|
+
),
|
|
407
|
+
alb.GaussianBlur(
|
|
408
|
+
blur_limit=params['gaussian_blur_kernel_range'],
|
|
409
|
+
p=params['gaussian_blur_prob'],
|
|
410
|
+
),
|
|
411
|
+
]
|
|
412
|
+
else:
|
|
413
|
+
# inference / eval
|
|
414
|
+
tv_pp += [
|
|
415
|
+
ResizeKeepRatio(image_size, longest=1, interpolation=input_cfg.interpolation),
|
|
416
|
+
CenterCropOrPad(image_size, fill=input_cfg.fill_color),
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
if alb_pp:
|
|
420
|
+
# FIXME leave alb uncomposed too if composed=False?
|
|
421
|
+
tv_pp += [AlbWrapper(alb.Compose(alb_pp))]
|
|
422
|
+
|
|
423
|
+
tv_pp += [transforms.ToTensor()]
|
|
424
|
+
if do_normalize:
|
|
425
|
+
tv_pp += [transforms.Normalize(input_cfg.mean, input_cfg.std)]
|
|
426
|
+
|
|
427
|
+
return transforms.Compose(tv_pp) if composed else tv_pp
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from dataclasses import asdict
|
|
2
|
+
from typing import Any, Dict, Optional, Union
|
|
3
|
+
|
|
4
|
+
from torchvision import transforms
|
|
5
|
+
from timm.data import (
|
|
6
|
+
ResizeKeepRatio,
|
|
7
|
+
CenterCropOrPad,
|
|
8
|
+
RandomResizedCropAndInterpolation,
|
|
9
|
+
create_transform,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from chugr.common import ImageInputCfg, ImageAugCfg
|
|
13
|
+
from .transforms_torch import ConvertColor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_transforms_image_timm(
|
|
17
|
+
input_cfg: ImageInputCfg,
|
|
18
|
+
is_training: bool = False,
|
|
19
|
+
do_normalize: bool = True,
|
|
20
|
+
do_convert: bool = False,
|
|
21
|
+
aug_cfg: Optional[Union[Dict[str, Any], ImageAugCfg]] = None,
|
|
22
|
+
composed: bool = True,
|
|
23
|
+
):
|
|
24
|
+
""" Build image transforms leveraging timm's create_transform() functionality.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
input_cfg:
|
|
28
|
+
is_training: In training mode, apply train transforms w/ augmentations
|
|
29
|
+
do_normalize: Enable normalization of ouput tensors by specified mean & std deviation.
|
|
30
|
+
aug_cfg:
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
interpolation = input_cfg.interpolation or 'bicubic'
|
|
36
|
+
assert interpolation in ['bicubic', 'bilinear', 'random']
|
|
37
|
+
|
|
38
|
+
resize_mode = input_cfg.resize_mode or 'shortest'
|
|
39
|
+
assert resize_mode in ('shortest', 'longest', 'squash')
|
|
40
|
+
|
|
41
|
+
if isinstance(aug_cfg, dict):
|
|
42
|
+
aug_cfg = ImageAugCfg(**aug_cfg)
|
|
43
|
+
else:
|
|
44
|
+
aug_cfg = aug_cfg or ImageAugCfg.imagenet()
|
|
45
|
+
|
|
46
|
+
if is_training:
|
|
47
|
+
aug_cfg_dict = {k: v for k, v in asdict(aug_cfg).items() if v is not None}
|
|
48
|
+
aug_cfg_dict.setdefault('color_jitter', None) # disable by default
|
|
49
|
+
# FIXME map aug_cfg -> timm.create_transform args more carefully
|
|
50
|
+
|
|
51
|
+
train_transform = create_transform(
|
|
52
|
+
input_size=input_cfg.size,
|
|
53
|
+
is_training=True,
|
|
54
|
+
use_prefetcher=not do_normalize, # FIXME prefetcher mode disables normalize, but outputs np.array
|
|
55
|
+
hflip=0.,
|
|
56
|
+
mean=input_cfg.mean,
|
|
57
|
+
std=input_cfg.std,
|
|
58
|
+
re_mode='pixel',
|
|
59
|
+
interpolation=interpolation,
|
|
60
|
+
**aug_cfg_dict,
|
|
61
|
+
)
|
|
62
|
+
return train_transform
|
|
63
|
+
else:
|
|
64
|
+
if resize_mode == 'longest':
|
|
65
|
+
timm_crop_mode = 'border'
|
|
66
|
+
elif resize_mode == 'squash':
|
|
67
|
+
timm_crop_mode = 'squash'
|
|
68
|
+
else:
|
|
69
|
+
assert resize_mode == 'shortest'
|
|
70
|
+
timm_crop_mode = 'center'
|
|
71
|
+
|
|
72
|
+
eval_transform = create_transform(
|
|
73
|
+
input_size=input_cfg.size,
|
|
74
|
+
is_training=False,
|
|
75
|
+
use_prefetcher=not do_normalize, # FIXME prefetcher mode disables normalize, but outputs np.array
|
|
76
|
+
mean=input_cfg.mean,
|
|
77
|
+
std=input_cfg.std,
|
|
78
|
+
crop_pct=1.0,
|
|
79
|
+
crop_mode=timm_crop_mode,
|
|
80
|
+
# FIXME
|
|
81
|
+
# composed=composed,
|
|
82
|
+
)
|
|
83
|
+
return eval_transform
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_transforms_image_basic(
|
|
87
|
+
input_cfg: ImageInputCfg,
|
|
88
|
+
is_training: bool = False,
|
|
89
|
+
do_normalize: bool = True,
|
|
90
|
+
do_convert: bool = False,
|
|
91
|
+
aug_cfg: Optional[Union[Dict[str, Any], ImageAugCfg]] = None,
|
|
92
|
+
composed: bool = True,
|
|
93
|
+
):
|
|
94
|
+
""" Build image transfoms leveraging torchvision transforms.
|
|
95
|
+
"""
|
|
96
|
+
if do_normalize:
|
|
97
|
+
normalize = transforms.Normalize(mean=input_cfg.mean, std=input_cfg.std)
|
|
98
|
+
else:
|
|
99
|
+
normalize = None
|
|
100
|
+
|
|
101
|
+
interpolation = input_cfg.interpolation or 'bicubic'
|
|
102
|
+
assert interpolation in ['bicubic', 'bilinear', 'random']
|
|
103
|
+
# NOTE random is ignored for interpolation_mode, so defaults to BICUBIC for inference if set
|
|
104
|
+
if interpolation == 'bilinear':
|
|
105
|
+
interpolation_mode = transforms.InterpolationMode.BILINEAR
|
|
106
|
+
else:
|
|
107
|
+
interpolation_mode = transforms.InterpolationMode.BICUBIC
|
|
108
|
+
|
|
109
|
+
resize_mode = input_cfg.resize_mode or 'shortest'
|
|
110
|
+
assert resize_mode in ('shortest', 'longest', 'squash')
|
|
111
|
+
|
|
112
|
+
if isinstance(aug_cfg, dict):
|
|
113
|
+
aug_cfg = ImageAugCfg(**aug_cfg)
|
|
114
|
+
else:
|
|
115
|
+
aug_cfg = aug_cfg or ImageAugCfg.imagenet()
|
|
116
|
+
|
|
117
|
+
if is_training:
|
|
118
|
+
image_size = input_cfg.size
|
|
119
|
+
if resize_mode == 'shortest':
|
|
120
|
+
if isinstance(image_size, (tuple, list)) and image_size[0] == image_size[1]:
|
|
121
|
+
image_size = image_size[0] # w/ scalar for final resize in RRC will use shortest edge
|
|
122
|
+
# FIXME note we don't have good option for 'longest' resizing w/ RRC
|
|
123
|
+
|
|
124
|
+
transform_list = [
|
|
125
|
+
# like torchvision.transforms.RandomResizedCrop but supports randomized interpolation for robustness
|
|
126
|
+
RandomResizedCropAndInterpolation(
|
|
127
|
+
image_size,
|
|
128
|
+
scale=aug_cfg.scale or (1.0, 1.0),
|
|
129
|
+
ratio=aug_cfg.ratio or (1.0, 1.0),
|
|
130
|
+
interpolation=interpolation,
|
|
131
|
+
),
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if do_convert:
|
|
135
|
+
transform_list.append(ConvertColor(mode=input_cfg.mode))
|
|
136
|
+
|
|
137
|
+
if aug_cfg.color_jitter_prob:
|
|
138
|
+
assert aug_cfg.color_jitter is not None and len(aug_cfg.color_jitter) == 4
|
|
139
|
+
if aug_cfg.color_jitter_prob is not None:
|
|
140
|
+
transform_list.append(
|
|
141
|
+
transforms.RandomApply(
|
|
142
|
+
transforms.ColorJitter(
|
|
143
|
+
*aug_cfg.color_jitter,
|
|
144
|
+
),
|
|
145
|
+
p=aug_cfg.color_jitter_prob,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
elif aug_cfg.color_jitter is not None:
|
|
149
|
+
transform_list.append(transforms.ColorJitter(*aug_cfg.color_jitter))
|
|
150
|
+
|
|
151
|
+
if aug_cfg.grayscale_prob:
|
|
152
|
+
transform_list.append(transforms.RamndomGrayscale(aug_cfg.grayscale_prob))
|
|
153
|
+
|
|
154
|
+
if aug_cfg.gaussian_blur_prob:
|
|
155
|
+
gaussian_blur_kernel = aug_cfg.gaussian_blur_kernel_size or 23
|
|
156
|
+
transforms.RandomApply([
|
|
157
|
+
transforms.GaussianBlur(
|
|
158
|
+
kernel_size=gaussian_blur_kernel,
|
|
159
|
+
)],
|
|
160
|
+
p=aug_cfg.gaussian_blur_prob,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
image_size = input_cfg.size
|
|
165
|
+
|
|
166
|
+
if resize_mode == 'longest':
|
|
167
|
+
transform_list = [
|
|
168
|
+
ResizeKeepRatio(image_size, interpolation=interpolation_mode, longest=1),
|
|
169
|
+
CenterCropOrPad(image_size, fill=input_cfg.fill_color)
|
|
170
|
+
]
|
|
171
|
+
elif resize_mode == 'squash':
|
|
172
|
+
if isinstance(image_size, int):
|
|
173
|
+
image_size = (image_size, image_size)
|
|
174
|
+
|
|
175
|
+
transform_list = [
|
|
176
|
+
transforms.Resize(image_size, interpolation=interpolation_mode, antialias=True),
|
|
177
|
+
]
|
|
178
|
+
else:
|
|
179
|
+
assert resize_mode == 'shortest'
|
|
180
|
+
if not isinstance(image_size, (tuple, list)):
|
|
181
|
+
image_size = (image_size, image_size)
|
|
182
|
+
|
|
183
|
+
if image_size[0] == image_size[1]:
|
|
184
|
+
# simple case, use torchvision built-in Resize w/ shortest edge mode (scalar size arg)
|
|
185
|
+
transform_list = [
|
|
186
|
+
transforms.Resize(image_size[0], interpolation=interpolation_mode, antialias=True)
|
|
187
|
+
]
|
|
188
|
+
else:
|
|
189
|
+
# resize shortest edge to matching target dim for non-square target
|
|
190
|
+
transform_list = [ResizeKeepRatio(image_size)]
|
|
191
|
+
|
|
192
|
+
transform_list += [transforms.CenterCrop(image_size)]
|
|
193
|
+
|
|
194
|
+
if do_convert:
|
|
195
|
+
transform_list.append(ConvertColor(mode=input_cfg.mode))
|
|
196
|
+
# end if is_training
|
|
197
|
+
|
|
198
|
+
transform_list += [transforms.ToTensor()]
|
|
199
|
+
|
|
200
|
+
if normalize is not None:
|
|
201
|
+
transform_list += [normalize]
|
|
202
|
+
|
|
203
|
+
return transforms.Compose(transform_list) if composed else transform_list
|