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
chugr/wds/decode.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
import warnings
|
|
7
|
+
from typing import Any, Callable, Dict, Optional, Tuple, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import webdataset as wds
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
from .helpers import log_and_continue
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# IMPORTANT fitz aka PyMuPDF is AGPL licensed w/ a commercial purchase option, manual intervention required to use it.
|
|
17
|
+
_USE_AGPL_PYMUPDF = int(os.environ.get('CHUGR_USE_AGPL_PYMUPDF', -1))
|
|
18
|
+
if _USE_AGPL_PYMUPDF < 1:
|
|
19
|
+
import importlib
|
|
20
|
+
if importlib.util.find_spec('fitz') is not None and _USE_AGPL_PYMUPDF == -1:
|
|
21
|
+
# warn if not explicitly disable by setting to 0
|
|
22
|
+
warnings.warn(
|
|
23
|
+
"The fitz/pymupdf library is installed but disabled as the environment variable CHUGR_USE_AGPL_PYMUPDF is"
|
|
24
|
+
" not set to 1. Please be aware of the licensing concerns in your use cases if you enable it.")
|
|
25
|
+
fitz = None
|
|
26
|
+
else:
|
|
27
|
+
try:
|
|
28
|
+
import fitz
|
|
29
|
+
except ImportError as e:
|
|
30
|
+
fitz = None
|
|
31
|
+
|
|
32
|
+
# defaults to pypdfium2 when fitz is not installed and/or not enabled
|
|
33
|
+
if fitz is None:
|
|
34
|
+
try:
|
|
35
|
+
import pypdfium2
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
pypdfium2 = None
|
|
38
|
+
else:
|
|
39
|
+
pypdfium2 = None
|
|
40
|
+
|
|
41
|
+
_logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
PDF_EXTENSIONS = {
|
|
45
|
+
'pdf',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def decode_pdf_pages(
|
|
50
|
+
data: bytes,
|
|
51
|
+
image_mode: str = 'L',
|
|
52
|
+
page_indices: Optional[Tuple[int]] = None,
|
|
53
|
+
select_random: Optional[int] = None,
|
|
54
|
+
render_dpi: int = 144,
|
|
55
|
+
):
|
|
56
|
+
rendered_pages = []
|
|
57
|
+
|
|
58
|
+
with io.BytesIO(data) as b:
|
|
59
|
+
# FIXME test and use an alternate pdf reader/render as default
|
|
60
|
+
if fitz is not None:
|
|
61
|
+
doc = fitz.Document(stream=b)
|
|
62
|
+
num_doc_pages = doc.page_count
|
|
63
|
+
|
|
64
|
+
if page_indices is not None:
|
|
65
|
+
page_indices = [p % num_doc_pages for p in page_indices] # support -ve indexing
|
|
66
|
+
else:
|
|
67
|
+
page_indices = range(num_doc_pages)
|
|
68
|
+
|
|
69
|
+
if select_random:
|
|
70
|
+
if select_random == 1:
|
|
71
|
+
page_indices = [random.choice(page_indices)]
|
|
72
|
+
else:
|
|
73
|
+
page_indices = random.sample(page_indices, select_random)
|
|
74
|
+
page_indices.sort()
|
|
75
|
+
|
|
76
|
+
for i in page_indices:
|
|
77
|
+
page = doc.load_page(i)
|
|
78
|
+
if image_mode == 'L':
|
|
79
|
+
fitz_cs = fitz.csGRAY
|
|
80
|
+
fitz_mode = 'L'
|
|
81
|
+
alpha = False
|
|
82
|
+
elif image_mode == 'RGB' or image_mode == 'BGR':
|
|
83
|
+
fitz_cs = fitz.csRGB
|
|
84
|
+
fitz_mode = 'RGB'
|
|
85
|
+
alpha = False
|
|
86
|
+
elif image_mode == 'RGBA' or image_mode == 'BGRA':
|
|
87
|
+
fitz_cs = fitz.csRGB
|
|
88
|
+
fitz_mode = 'RGBA'
|
|
89
|
+
alpha = True
|
|
90
|
+
else:
|
|
91
|
+
assert False
|
|
92
|
+
pixmap = page.get_pixmap(dpi=render_dpi, colorspace=fitz_cs, alpha=alpha)
|
|
93
|
+
page_image = Image.frombuffer(fitz_mode, (pixmap.width, pixmap.height), pixmap.samples)
|
|
94
|
+
if fitz_mode != page_image.mode:
|
|
95
|
+
page_image = page_image.convert(image_mode)
|
|
96
|
+
|
|
97
|
+
rendered_pages += [page_image]
|
|
98
|
+
|
|
99
|
+
elif pypdfium2 is not None:
|
|
100
|
+
grayscale = image_mode == "L"
|
|
101
|
+
reverse = 'RGB' in image_mode
|
|
102
|
+
doc = pypdfium2.PdfDocument(data)
|
|
103
|
+
num_doc_pages = len(doc)
|
|
104
|
+
page_indices = page_indices or range(num_doc_pages)
|
|
105
|
+
if select_random:
|
|
106
|
+
page_indices = [random.choice(page_indices)]
|
|
107
|
+
for i in page_indices:
|
|
108
|
+
page = doc[i]
|
|
109
|
+
page_image = page.render(
|
|
110
|
+
scale=render_dpi / 72,
|
|
111
|
+
grayscale=grayscale,
|
|
112
|
+
rev_byteorder=reverse, # RGB instead of BGR(X)
|
|
113
|
+
).to_pil()
|
|
114
|
+
if image_mode != page_image.mode:
|
|
115
|
+
page_image = page_image.convert(image_mode)
|
|
116
|
+
|
|
117
|
+
rendered_pages += [page_image]
|
|
118
|
+
else:
|
|
119
|
+
assert False, "No PDF decoding library installed, please install one of pypdfium2 or fitz (PyMuPDF). " \
|
|
120
|
+
"NOTE: pypdifum2 is Apache 2.0 / BSD 3.0 licensed and fitz is AGPL."
|
|
121
|
+
|
|
122
|
+
return rendered_pages, num_doc_pages
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def decode_image_pages(
|
|
126
|
+
data: bytes,
|
|
127
|
+
image_mode: str = 'L',
|
|
128
|
+
page_indices: Optional[Tuple[int]] = None,
|
|
129
|
+
select_random: Optional[int] = None,
|
|
130
|
+
):
|
|
131
|
+
""" decode multi-page image (e.g. TIFF)"""
|
|
132
|
+
decoded_pages = []
|
|
133
|
+
|
|
134
|
+
if isinstance(data, Image.Image):
|
|
135
|
+
doc_image = data
|
|
136
|
+
else:
|
|
137
|
+
doc_image = Image.open(io.BytesIO(data))
|
|
138
|
+
|
|
139
|
+
num_image_pages = getattr(doc_image, 'n_frames', 1)
|
|
140
|
+
|
|
141
|
+
if page_indices is not None:
|
|
142
|
+
page_indices = [p % num_image_pages for p in page_indices] # support -ve indexing
|
|
143
|
+
else:
|
|
144
|
+
page_indices = range(num_image_pages)
|
|
145
|
+
|
|
146
|
+
if select_random:
|
|
147
|
+
if select_random == 1:
|
|
148
|
+
page_indices = [random.choice(page_indices)]
|
|
149
|
+
else:
|
|
150
|
+
page_indices = random.sample(page_indices, select_random)
|
|
151
|
+
page_indices.sort()
|
|
152
|
+
|
|
153
|
+
for i, page_index in enumerate(page_indices):
|
|
154
|
+
assert page_index < num_image_pages
|
|
155
|
+
if num_image_pages > 1:
|
|
156
|
+
doc_image.seek(page_index)
|
|
157
|
+
else:
|
|
158
|
+
assert page_index == 0, "not a multi-page image"
|
|
159
|
+
doc_image.load()
|
|
160
|
+
|
|
161
|
+
page_image = doc_image.convert(image_mode)
|
|
162
|
+
decoded_pages.append(page_image)
|
|
163
|
+
|
|
164
|
+
return decoded_pages, num_image_pages
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class DecodeDoc:
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
imagespec,
|
|
172
|
+
num_pages=1,
|
|
173
|
+
page_sampling='first',
|
|
174
|
+
):
|
|
175
|
+
"""Create a PDF handler.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
imagespec: short string indicating the type of decoding
|
|
179
|
+
The `imagespec` specifies whether the image is decoded
|
|
180
|
+
to numpy/torch/pi, decoded to uint8/float, and decoded
|
|
181
|
+
to l/rgb/rgba:
|
|
182
|
+
|
|
183
|
+
- l8: numpy uint8 l
|
|
184
|
+
- rgb8: numpy uint8 rgb
|
|
185
|
+
- rgba8: numpy uint8 rgba
|
|
186
|
+
- l: numpy float l
|
|
187
|
+
- rgb: numpy float rgb
|
|
188
|
+
- rgba: numpy float rgba
|
|
189
|
+
- torchl8: torch uint8 l
|
|
190
|
+
- torchrgb8: torch uint8 rgb
|
|
191
|
+
- torchrgba8: torch uint8 rgba
|
|
192
|
+
- torchl: torch float l
|
|
193
|
+
- torchrgb: torch float rgb
|
|
194
|
+
- torch: torch float rgb
|
|
195
|
+
- torchrgba: torch float rgba
|
|
196
|
+
- pill: pil None l
|
|
197
|
+
- pil: pil None rgb
|
|
198
|
+
- pilrgb: pil None rgb
|
|
199
|
+
- pilrgba: pil None rgba
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
if imagespec not in list(wds.autodecode.imagespecs.keys()):
|
|
203
|
+
raise ValueError("Unknown imagespec: %s" % imagespec)
|
|
204
|
+
self.imagespec = imagespec.lower()
|
|
205
|
+
# FIXME need to work out padding / selection issues for multi-page support
|
|
206
|
+
assert num_pages == 1, "Only 1-page decoding supported at present"
|
|
207
|
+
self.num_pages = num_pages
|
|
208
|
+
assert page_sampling in {'random', 'first', 'last'} # TODO add 'all' w/ multi-page support
|
|
209
|
+
self.page_sampling = page_sampling
|
|
210
|
+
|
|
211
|
+
def __call__(self, key, data):
|
|
212
|
+
"""
|
|
213
|
+
Args:
|
|
214
|
+
key: file name extension
|
|
215
|
+
data: data to be decoded
|
|
216
|
+
"""
|
|
217
|
+
extension = re.sub(r".*[.]", "", key)
|
|
218
|
+
if extension not in {'pdf', 'tiff', 'tif'}:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
imagespec = self.imagespec
|
|
222
|
+
atype, etype, mode = wds.autodecode.imagespecs[imagespec]
|
|
223
|
+
|
|
224
|
+
select_random = False
|
|
225
|
+
if self.page_sampling == 'random':
|
|
226
|
+
page_indices = None
|
|
227
|
+
select_random = True
|
|
228
|
+
elif self.page_sampling == 'first':
|
|
229
|
+
page_indices = [0] # first page
|
|
230
|
+
elif self.page_sampling == 'last':
|
|
231
|
+
page_indices = [-1]
|
|
232
|
+
else:
|
|
233
|
+
assert False
|
|
234
|
+
|
|
235
|
+
if extension == 'pdf':
|
|
236
|
+
# pdf document
|
|
237
|
+
result, num_pages = decode_pdf_pages(
|
|
238
|
+
data,
|
|
239
|
+
image_mode=mode.upper(),
|
|
240
|
+
page_indices=page_indices,
|
|
241
|
+
select_random=select_random,
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
# multi-page image doc (e.g. tiff)
|
|
245
|
+
result, num_pages = decode_image_pages(
|
|
246
|
+
data,
|
|
247
|
+
image_mode=mode.upper(),
|
|
248
|
+
page_indices=page_indices,
|
|
249
|
+
select_random=select_random,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if atype == "pil":
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
result = np.asarray(result)
|
|
256
|
+
|
|
257
|
+
if etype == "float":
|
|
258
|
+
result = result.astype(np.float32) / 255.0
|
|
259
|
+
|
|
260
|
+
assert result.ndim in [2, 3], result.shape
|
|
261
|
+
assert mode in ["l", "rgb", "rgba"], mode
|
|
262
|
+
|
|
263
|
+
if mode == "l":
|
|
264
|
+
if result.ndim == 3:
|
|
265
|
+
result = np.mean(result[:, :, :3], axis=2)
|
|
266
|
+
elif mode == "rgb":
|
|
267
|
+
if result.ndim == 2:
|
|
268
|
+
result = np.repeat(result[:, :, np.newaxis], 3, axis=2)
|
|
269
|
+
elif result.shape[2] == 4:
|
|
270
|
+
result = result[:, :, :3]
|
|
271
|
+
elif mode == "rgba":
|
|
272
|
+
if result.ndim == 2:
|
|
273
|
+
result = np.repeat(result[:, :, np.newaxis], 4, axis=2)
|
|
274
|
+
result[:, :, 3] = 255
|
|
275
|
+
elif result.shape[2] == 3:
|
|
276
|
+
result = np.concatenate(
|
|
277
|
+
[result, 255 * np.ones(result.shape[:2])], axis=2
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
assert atype in ["numpy", "torch"], atype
|
|
281
|
+
|
|
282
|
+
if atype == "numpy":
|
|
283
|
+
return result
|
|
284
|
+
elif atype == "torch":
|
|
285
|
+
import torch
|
|
286
|
+
|
|
287
|
+
if result.ndim == 3:
|
|
288
|
+
return torch.from_numpy(result.transpose(2, 0, 1))
|
|
289
|
+
else:
|
|
290
|
+
return torch.from_numpy(result)
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def create_image_decoder(
|
|
296
|
+
decode_fn: Optional[Callable] = None,
|
|
297
|
+
image_mode: str = "RGB",
|
|
298
|
+
enable_doc: bool = False, # FIXME enable doc support by default once tested?
|
|
299
|
+
handler: Callable = log_and_continue,
|
|
300
|
+
):
|
|
301
|
+
if decode_fn is None:
|
|
302
|
+
if image_mode == "L":
|
|
303
|
+
img_type = "pill"
|
|
304
|
+
elif image_mode == "RGB":
|
|
305
|
+
img_type = "pilrgb"
|
|
306
|
+
else:
|
|
307
|
+
assert False, f"Unsupported image_mode ({image_mode})"
|
|
308
|
+
if enable_doc:
|
|
309
|
+
# FIXME, generic img + pdf decode WIP
|
|
310
|
+
decode_fn = wds.decode(DecodeDoc(img_type), img_type, handler=handler)
|
|
311
|
+
else:
|
|
312
|
+
decode_fn = wds.decode(img_type, handler=handler)
|
|
313
|
+
elif isinstance(decode_fn, (list, tuple)):
|
|
314
|
+
decode_fn = wds.decode(*decode_fn, handler=handler)
|
|
315
|
+
else:
|
|
316
|
+
assert isinstance(decode_fn, Callable)
|
|
317
|
+
decode_fn = wds.map(decode_fn)
|
|
318
|
+
|
|
319
|
+
return decode_fn
|
chugr/wds/filters.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import random
|
|
3
|
+
from typing import Mapping, Sequence, Union
|
|
4
|
+
|
|
5
|
+
import webdataset as wds
|
|
6
|
+
from webdataset.filters import _shuffle
|
|
7
|
+
|
|
8
|
+
from chugr.common import SharedCount, get_pytorch_worker_seed
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class detshuffle_v2(wds.PipelineStage):
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
bufsize: int = 1000,
|
|
16
|
+
initial: int = 100,
|
|
17
|
+
seed: int = 0,
|
|
18
|
+
interval: Union[int, SharedCount] =-1,
|
|
19
|
+
unique_worker: bool = False,
|
|
20
|
+
):
|
|
21
|
+
self.bufsize = bufsize
|
|
22
|
+
self.initial = initial
|
|
23
|
+
self.seed = seed
|
|
24
|
+
self.interval = interval
|
|
25
|
+
self.unique_worker = unique_worker
|
|
26
|
+
|
|
27
|
+
def run(self, src):
|
|
28
|
+
if isinstance(self.interval, SharedCount):
|
|
29
|
+
interval = self.interval.get_value()
|
|
30
|
+
else:
|
|
31
|
+
# NOTE: this is epoch tracking is problematic in a multiprocess (dataloader workers or train)
|
|
32
|
+
# situation as different workers may wrap at different times (or not at all).
|
|
33
|
+
self.interval += 1
|
|
34
|
+
interval = self.interval
|
|
35
|
+
|
|
36
|
+
rng = random.Random()
|
|
37
|
+
if self.unique_worker:
|
|
38
|
+
# Use the PyTorch worker's seed, *different* across all nodes/workers
|
|
39
|
+
# but also deterministic if they are set consistently
|
|
40
|
+
seed = get_pytorch_worker_seed(interval, initial_seed=self.seed)
|
|
41
|
+
else:
|
|
42
|
+
# This seed to be deterministic AND the *same* across all nodes/workers in each epoch/interval
|
|
43
|
+
seed = self.seed + interval
|
|
44
|
+
rng.seed(seed)
|
|
45
|
+
|
|
46
|
+
return _shuffle(src, self.bufsize, self.initial, rng)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _map_v2(data, f, handler=wds.reraise_exception):
|
|
50
|
+
""" Map samples.
|
|
51
|
+
|
|
52
|
+
This function differs from wds.map, it only adds '__key__' back to sample if it exists.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
for sample in data:
|
|
56
|
+
try:
|
|
57
|
+
result = f(sample)
|
|
58
|
+
except Exception as exn:
|
|
59
|
+
if handler(exn):
|
|
60
|
+
continue
|
|
61
|
+
else:
|
|
62
|
+
break
|
|
63
|
+
if result is None:
|
|
64
|
+
continue
|
|
65
|
+
if isinstance(sample, dict) and isinstance(result, dict) and "__key__" in sample:
|
|
66
|
+
result["__key__"] = sample.get("__key__")
|
|
67
|
+
yield result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
map_v2 = wds.pipelinefilter(_map_v2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _expand_maybe(data, f, handler=wds.reraise_exception):
|
|
74
|
+
for sample in data:
|
|
75
|
+
if isinstance(sample, Mapping):
|
|
76
|
+
try:
|
|
77
|
+
result = f(sample)
|
|
78
|
+
except Exception as exn:
|
|
79
|
+
if handler(exn):
|
|
80
|
+
continue
|
|
81
|
+
else:
|
|
82
|
+
break
|
|
83
|
+
if result is None:
|
|
84
|
+
continue
|
|
85
|
+
if "__key__" in sample:
|
|
86
|
+
result["__key__"] = sample["__key__"]
|
|
87
|
+
yield result
|
|
88
|
+
else:
|
|
89
|
+
assert isinstance(sample, Sequence)
|
|
90
|
+
for subsample in sample:
|
|
91
|
+
assert isinstance(subsample, Mapping)
|
|
92
|
+
try:
|
|
93
|
+
result = f(subsample)
|
|
94
|
+
except Exception as exn:
|
|
95
|
+
if handler(exn):
|
|
96
|
+
continue
|
|
97
|
+
else:
|
|
98
|
+
break
|
|
99
|
+
if result is None:
|
|
100
|
+
continue
|
|
101
|
+
if "__key__" in subsample:
|
|
102
|
+
result["__key__"] = subsample["__key__"]
|
|
103
|
+
yield result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
map_expand_maybe = wds.pipelinefilter(_expand_maybe)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _expand_always(data, f, handler=wds.reraise_exception):
|
|
110
|
+
for sample in itertools.chain(*data):
|
|
111
|
+
assert isinstance(sample, Mapping)
|
|
112
|
+
try:
|
|
113
|
+
result = f(sample)
|
|
114
|
+
except Exception as exn:
|
|
115
|
+
if handler(exn):
|
|
116
|
+
continue
|
|
117
|
+
else:
|
|
118
|
+
break
|
|
119
|
+
if result is None:
|
|
120
|
+
continue
|
|
121
|
+
if "__key__" in sample:
|
|
122
|
+
result["__key__"] = sample["__key__"]
|
|
123
|
+
yield result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
map_expand_always = wds.pipelinefilter(_expand_always)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _flatten_nested(data, *args, replace_existing=True, remove_original=True):
|
|
130
|
+
"""Convert dict samples to tuples."""
|
|
131
|
+
for sample in data:
|
|
132
|
+
for k in args:
|
|
133
|
+
nested_dict = sample.pop(k, {}) if remove_original else sample.get(k, {})
|
|
134
|
+
if replace_existing:
|
|
135
|
+
sample.update(nested_dict)
|
|
136
|
+
elif k in sample:
|
|
137
|
+
for sk, sv in nested_dict.items():
|
|
138
|
+
sample.setdefault(sk, sv)
|
|
139
|
+
yield sample
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
flatten_nested = wds.pipelinefilter(_flatten_nested)
|
chugr/wds/helpers.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import braceexpand
|
|
7
|
+
import webdataset as wds
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def urldir(url):
|
|
11
|
+
"""Return the directory part of a url."""
|
|
12
|
+
parsed_url = urlparse(url)
|
|
13
|
+
path = parsed_url.path
|
|
14
|
+
directory = os.path.dirname(path)
|
|
15
|
+
return parsed_url._replace(path=directory).geturl()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def expand_urls(urls, weights=None):
|
|
19
|
+
if weights is None:
|
|
20
|
+
expanded_urls = wds.shardlists.expand_urls(urls)
|
|
21
|
+
return expanded_urls
|
|
22
|
+
|
|
23
|
+
if isinstance(urls, str):
|
|
24
|
+
urllist = urls.split("::")
|
|
25
|
+
weights = weights.split('::')
|
|
26
|
+
assert len(weights) == len(urllist), \
|
|
27
|
+
f"Expected the number of data components ({len(urllist)}) and weights({len(weights)}) to match."
|
|
28
|
+
weights = [float(weight) for weight in weights]
|
|
29
|
+
all_urls, all_weights = [], []
|
|
30
|
+
for url, weight in zip(urllist, weights):
|
|
31
|
+
expanded_url = list(braceexpand.braceexpand(url))
|
|
32
|
+
expanded_weights = [weight for _ in expanded_url]
|
|
33
|
+
all_urls.extend(expanded_url)
|
|
34
|
+
all_weights.extend(expanded_weights)
|
|
35
|
+
return all_urls, all_weights
|
|
36
|
+
else:
|
|
37
|
+
all_urls = list(urls)
|
|
38
|
+
return all_urls, weights
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def log_and_continue(exn):
|
|
42
|
+
"""Call in an exception handler to ignore any exception, issue a warning, and continue."""
|
|
43
|
+
logging.warning(f'Handling webdataset error ({repr(exn)}). Ignoring.')
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dump_and_reraise(exn):
|
|
48
|
+
"""Dump stack and stop."""
|
|
49
|
+
import traceback
|
|
50
|
+
exception_trace = ''.join(traceback.format_tb(exn.__traceback__))
|
|
51
|
+
logging.error(f'Handling webdataset {type(exn)}. Exception trace:\n {exception_trace}')
|
|
52
|
+
current_trace = ''.join(traceback.format_tb(exn.__traceback__))
|
|
53
|
+
logging.error(f'Current stack trace:\n {current_trace}')
|
|
54
|
+
raise exn
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_error_handlers = {
|
|
58
|
+
'log_and_continue': log_and_continue,
|
|
59
|
+
'ignore_and_continue': wds.ignore_and_continue,
|
|
60
|
+
'warn_and_continue': wds.warn_and_continue,
|
|
61
|
+
'ignore_and_stop': wds.ignore_and_stop,
|
|
62
|
+
'warn_and_stop': wds.warn_and_stop,
|
|
63
|
+
'dump_and_reraise': dump_and_reraise,
|
|
64
|
+
'reraise_exception': wds.reraise_exception,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def get_error_handler(name: str):
|
|
68
|
+
return _error_handlers.get(name, dump_and_reraise)
|