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,112 @@
|
|
|
1
|
+
|
|
2
|
+
try:
|
|
3
|
+
import albumentations as alb
|
|
4
|
+
from albumentations.pytorch import ToTensorV2
|
|
5
|
+
has_albumentations = True
|
|
6
|
+
except ImportError:
|
|
7
|
+
has_albumentations = False
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import cv2
|
|
11
|
+
has_cv2 = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
has_cv2 = False
|
|
14
|
+
|
|
15
|
+
import random
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AlbWrapper:
|
|
21
|
+
def __init__(self, transforms):
|
|
22
|
+
self.transforms = transforms
|
|
23
|
+
|
|
24
|
+
def __call__(self, im):
|
|
25
|
+
return self.transforms(image=np.asarray(im))["image"]
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
format_string = self.__class__.__name__ + "("
|
|
29
|
+
for t in self.transforms:
|
|
30
|
+
format_string += "\n"
|
|
31
|
+
format_string += f" {t}"
|
|
32
|
+
format_string += "\n)"
|
|
33
|
+
return format_string
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if has_albumentations and has_cv2:
|
|
37
|
+
|
|
38
|
+
class ErosionAlb(alb.ImageOnlyTransform):
|
|
39
|
+
def __init__(self, scale, p=0.5):
|
|
40
|
+
super().__init__(p=p)
|
|
41
|
+
if type(scale) is tuple or type(scale) is list:
|
|
42
|
+
assert len(scale) == 2
|
|
43
|
+
self.scale = scale
|
|
44
|
+
else:
|
|
45
|
+
self.scale = (scale, scale)
|
|
46
|
+
|
|
47
|
+
def get_transform_init_args_names(self):
|
|
48
|
+
return ('scale',)
|
|
49
|
+
|
|
50
|
+
def apply(self, img, **params):
|
|
51
|
+
kernel = cv2.getStructuringElement(
|
|
52
|
+
cv2.MORPH_ELLIPSE, tuple(int(k) for k in random.choices(range(self.scale[0], self.scale[1] + 1), k=2))
|
|
53
|
+
)
|
|
54
|
+
img = cv2.erode(img, kernel, iterations=1)
|
|
55
|
+
return img
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DilationAlb(alb.ImageOnlyTransform):
|
|
59
|
+
def __init__(self, scale, p=0.5):
|
|
60
|
+
super().__init__(p=p)
|
|
61
|
+
if type(scale) is tuple or type(scale) is list:
|
|
62
|
+
assert len(scale) == 2
|
|
63
|
+
self.scale = scale
|
|
64
|
+
else:
|
|
65
|
+
self.scale = (scale, scale)
|
|
66
|
+
|
|
67
|
+
def get_transform_init_args_names(self):
|
|
68
|
+
return ('scale',)
|
|
69
|
+
|
|
70
|
+
def apply(self, img, **params):
|
|
71
|
+
kernel = cv2.getStructuringElement(
|
|
72
|
+
cv2.MORPH_ELLIPSE,
|
|
73
|
+
tuple(int(k) for k in random.choices(range(self.scale[0], self.scale[1] + 1), k=2))
|
|
74
|
+
)
|
|
75
|
+
img = cv2.dilate(img, kernel, iterations=1)
|
|
76
|
+
return img
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BitmapAlb(alb.ImageOnlyTransform):
|
|
80
|
+
def __init__(self, value=0, lower=200, p=0.5):
|
|
81
|
+
super().__init__(p=p)
|
|
82
|
+
self.lower = lower
|
|
83
|
+
self.value = value
|
|
84
|
+
|
|
85
|
+
def get_transform_init_args_names(self):
|
|
86
|
+
return ('value', 'lower')
|
|
87
|
+
|
|
88
|
+
def apply(self, img, **params):
|
|
89
|
+
img = img.copy()
|
|
90
|
+
img[img < self.lower] = self.value
|
|
91
|
+
return img
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CropMarginCv2:
|
|
95
|
+
|
|
96
|
+
def __init__(self):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
def __call__(self, img):
|
|
100
|
+
data = np.array(img.convert("L"))
|
|
101
|
+
data = data.astype(np.uint8)
|
|
102
|
+
max_val = data.max()
|
|
103
|
+
min_val = data.min()
|
|
104
|
+
if max_val == min_val:
|
|
105
|
+
return img
|
|
106
|
+
data = (data - min_val) / (max_val - min_val) * 255
|
|
107
|
+
gray = 255 * (data < 200).astype(np.uint8)
|
|
108
|
+
|
|
109
|
+
coords = cv2.findNonZero(gray) # Find all non-zero points (text)
|
|
110
|
+
a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box
|
|
111
|
+
return img.crop((a, b, w + a, h + b))
|
|
112
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
from chugr.common import ImageInputCfg, ImageAugCfg
|
|
4
|
+
from .build_transforms_doc import build_transforms_doc_better, build_transforms_doc_nougat, build_transforms_doc_basic, \
|
|
5
|
+
build_transforms_doc_naflex
|
|
6
|
+
from .build_transforms_image import build_transforms_image_timm, build_transforms_image_basic
|
|
7
|
+
|
|
8
|
+
_transform_factories = {
|
|
9
|
+
"image_basic": build_transforms_image_basic,
|
|
10
|
+
"image_timm": build_transforms_image_timm,
|
|
11
|
+
"doc_basic": build_transforms_doc_basic,
|
|
12
|
+
"doc_nougat": build_transforms_doc_nougat,
|
|
13
|
+
"doc_better": build_transforms_doc_better,
|
|
14
|
+
"doc_naflex": build_transforms_doc_naflex,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def build_image_transforms(
|
|
18
|
+
input_cfg: ImageInputCfg,
|
|
19
|
+
is_training=True,
|
|
20
|
+
do_normalize=True,
|
|
21
|
+
do_convert=False,
|
|
22
|
+
composed=True,
|
|
23
|
+
aug_cfg: Optional[Union[Dict[str, Any], ImageAugCfg]] = None,
|
|
24
|
+
):
|
|
25
|
+
common_args = dict(
|
|
26
|
+
input_cfg=input_cfg,
|
|
27
|
+
is_training=is_training,
|
|
28
|
+
do_normalize=do_normalize,
|
|
29
|
+
aug_cfg=aug_cfg,
|
|
30
|
+
composed=composed,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
tt = input_cfg.transform_type
|
|
34
|
+
assert tt in _transform_factories, \
|
|
35
|
+
f"Unrecognized transform type: {tt}. Must be one of {list(_transform_factories.keys())}."
|
|
36
|
+
transforms = _transform_factories[tt](**common_args)
|
|
37
|
+
|
|
38
|
+
return transforms
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_image_preprocessor(
|
|
42
|
+
input_cfg: ImageInputCfg,
|
|
43
|
+
is_training=True,
|
|
44
|
+
do_normalize=True,
|
|
45
|
+
do_convert=False,
|
|
46
|
+
aug_cfg: Optional[Union[Dict[str, Any], ImageAugCfg]] = None,
|
|
47
|
+
):
|
|
48
|
+
transforms = build_image_transforms(
|
|
49
|
+
input_cfg=input_cfg,
|
|
50
|
+
is_training=is_training,
|
|
51
|
+
do_normalize=do_normalize,
|
|
52
|
+
do_convert=do_convert,
|
|
53
|
+
aug_cfg=aug_cfg,
|
|
54
|
+
composed=True,
|
|
55
|
+
)
|
|
56
|
+
# NOTE for now, a stack of composed transforms are the image pre-processor
|
|
57
|
+
return transforms
|
|
58
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
import torchvision.transforms.functional as F
|
|
6
|
+
from torchvision import transforms
|
|
7
|
+
from PIL import Image, ImageFilter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlignLongAxis:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
input_size,
|
|
14
|
+
interpolation=transforms.InterpolationMode.BICUBIC
|
|
15
|
+
):
|
|
16
|
+
self.input_size = input_size
|
|
17
|
+
self.interpolation = interpolation
|
|
18
|
+
|
|
19
|
+
def __call__(self, img):
|
|
20
|
+
img_width, img_height = F.get_image_size(img)
|
|
21
|
+
if (
|
|
22
|
+
(self.input_size[0] > self.input_size[1] and img_width > img_height) or
|
|
23
|
+
(self.input_size[0] < self.input_size[1] and img_width < img_height)
|
|
24
|
+
):
|
|
25
|
+
img = F.rotate(img, angle=-90, expand=True, interpolation=self.interpolation)
|
|
26
|
+
return img
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Bitmap:
|
|
30
|
+
def __init__(self, threshold=200):
|
|
31
|
+
self.lut = [0 if i < threshold else i for i in range(256)]
|
|
32
|
+
|
|
33
|
+
def __call__(self, img):
|
|
34
|
+
if img.mode == "RGB" and len(self.lut) == 256:
|
|
35
|
+
lut = self.lut + self.lut + self.lut
|
|
36
|
+
else:
|
|
37
|
+
lut = self.lut
|
|
38
|
+
return img.point(lut)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Erosion:
|
|
42
|
+
def __init__(self, scale=3):
|
|
43
|
+
super().__init__()
|
|
44
|
+
if type(scale) is tuple or type(scale) is list:
|
|
45
|
+
assert len(scale) == 2
|
|
46
|
+
self.scale = scale
|
|
47
|
+
else:
|
|
48
|
+
self.scale = (scale, scale)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def get_params(scale):
|
|
52
|
+
if type(scale) is tuple or type(scale) is list:
|
|
53
|
+
assert len(scale) == 2
|
|
54
|
+
scale = random.choice(scale)
|
|
55
|
+
return scale
|
|
56
|
+
|
|
57
|
+
def __call__(self, img):
|
|
58
|
+
kernel_size = self.get_params(self.scale)
|
|
59
|
+
if isinstance(img, torch.Tensor):
|
|
60
|
+
padding = kernel_size // 2
|
|
61
|
+
img = -torch.nn.functional.max_pool2d(-img, kernel_size=kernel_size, stride=1, padding=padding) # minpool
|
|
62
|
+
elif isinstance(img, Image.Image):
|
|
63
|
+
img = img.filter(ImageFilter.MinFilter(kernel_size))
|
|
64
|
+
return img
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Dilation:
|
|
68
|
+
def __init__(self, scale=3):
|
|
69
|
+
super().__init__()
|
|
70
|
+
self.scale = scale
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_params(scale):
|
|
74
|
+
if type(scale) is tuple or type(scale) is list:
|
|
75
|
+
assert len(scale) == 2
|
|
76
|
+
scale = random.choice(scale)
|
|
77
|
+
return scale
|
|
78
|
+
|
|
79
|
+
def __call__(self, img):
|
|
80
|
+
kernel_size = self.get_params(self.scale)
|
|
81
|
+
if isinstance(img, torch.Tensor):
|
|
82
|
+
padding = kernel_size // 2
|
|
83
|
+
img = torch.nn.functional.max_pool2d(img, kernel_size=kernel_size, stride=1, padding=padding)
|
|
84
|
+
elif isinstance(img, Image.Image):
|
|
85
|
+
img = img.filter(ImageFilter.MaxFilter(kernel_size))
|
|
86
|
+
return img
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def python_find_non_zero(image: np.array):
|
|
90
|
+
"""This is a reimplementation of a findNonZero function equivalent to cv2."""
|
|
91
|
+
non_zero_indices = np.column_stack(np.nonzero(image))
|
|
92
|
+
idxvec = non_zero_indices[:, [1, 0]]
|
|
93
|
+
idxvec = idxvec.reshape(-1, 1, 2)
|
|
94
|
+
return idxvec
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def python_bounding_rect(coordinates):
|
|
98
|
+
"""This is a reimplementation of a BoundingRect function equivalent to cv2."""
|
|
99
|
+
min_values = np.min(coordinates, axis=(0, 1)).astype(int)
|
|
100
|
+
max_values = np.max(coordinates, axis=(0, 1)).astype(int)
|
|
101
|
+
x_min, y_min = min_values[0], min_values[1]
|
|
102
|
+
width = max_values[0] - x_min + 1
|
|
103
|
+
height = max_values[1] - y_min + 1
|
|
104
|
+
return x_min, y_min, width, height
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class CropMargin:
|
|
108
|
+
def __init__(self) -> None:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
def __call__(
|
|
112
|
+
self,
|
|
113
|
+
image,
|
|
114
|
+
gray_threshold: int = 200,
|
|
115
|
+
) -> np.array:
|
|
116
|
+
# FIXME check tensor vs PIL and convert as needed, this is assuming PIL right now
|
|
117
|
+
assert not isinstance(image, torch.Tensor)
|
|
118
|
+
data = np.array(image.convert("L")).astype(np.uint8)
|
|
119
|
+
max_val = data.max()
|
|
120
|
+
min_val = data.min()
|
|
121
|
+
if max_val == min_val:
|
|
122
|
+
return image
|
|
123
|
+
data = (data - min_val) / (max_val - min_val) * 255
|
|
124
|
+
gray = data < gray_threshold
|
|
125
|
+
coords = python_find_non_zero(gray)
|
|
126
|
+
x_min, y_min, width, height = python_bounding_rect(coords)
|
|
127
|
+
image = F.crop(image, y_min, x_min, height, width)
|
|
128
|
+
return image
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ConvertColor:
|
|
132
|
+
def __init__(self, mode='RGB'):
|
|
133
|
+
self.mode = mode
|
|
134
|
+
|
|
135
|
+
def __call__(self, image):
|
|
136
|
+
assert isinstance(image, Image.Image)
|
|
137
|
+
return image.convert(self.mode)
|
chugr/loader.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Callable, List, Optional
|
|
4
|
+
|
|
5
|
+
from chugr.common import DataCfg, DataTaskCfg, DistributedCfg, LoaderBundle, source_to_shard_spec, SourceSpec
|
|
6
|
+
from chugr.hfds import create_loader_hf
|
|
7
|
+
from chugr.task_pipeline import create_task_pipeline
|
|
8
|
+
from chugr.wds import create_loader_wds, get_error_handler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_loader(
|
|
12
|
+
data_cfg: DataCfg,
|
|
13
|
+
task_cfg: DataTaskCfg,
|
|
14
|
+
task_pipeline: Optional[List[Callable]] = None,
|
|
15
|
+
is_training: bool = False,
|
|
16
|
+
start_interval: int = 0,
|
|
17
|
+
seed: int = 0,
|
|
18
|
+
distributed: DistributedCfg = DistributedCfg(),
|
|
19
|
+
) -> LoaderBundle:
|
|
20
|
+
"""
|
|
21
|
+
Creates a dataloader for training or validation based on configuration settings.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
data_cfg: Configuration object for the dataset.
|
|
25
|
+
task_cfg: Configuration object for the task specific processing.
|
|
26
|
+
task_pipeline: Task specific processing pipeline (takes priority over task_cfg).
|
|
27
|
+
is_training : Indicates if the loader is for training data (True) or validation data (False).
|
|
28
|
+
start_interval: The starting interval (epoch for full passes) for setting seed, etc. appropriately.
|
|
29
|
+
seed: Seed for random operations to ensure reproducibility.
|
|
30
|
+
distributed: Distributed device information.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
DataLoader: A PyTorch DataLoader instance configured according to the provided settings.
|
|
34
|
+
|
|
35
|
+
Note:
|
|
36
|
+
Currently supports "wds" and "hf_dataset" as dataset formats.
|
|
37
|
+
"""
|
|
38
|
+
if data_cfg.format == "wds":
|
|
39
|
+
loader = create_loader_from_config_wds(
|
|
40
|
+
data_cfg=data_cfg,
|
|
41
|
+
task_cfg=task_cfg,
|
|
42
|
+
task_pipeline=task_pipeline,
|
|
43
|
+
is_training=is_training,
|
|
44
|
+
start_interval=start_interval,
|
|
45
|
+
seed=seed,
|
|
46
|
+
distributed=distributed,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
elif data_cfg.format.startswith("hf"):
|
|
50
|
+
loader = create_loader_from_config_hf(
|
|
51
|
+
data_cfg=data_cfg,
|
|
52
|
+
task_cfg=task_cfg,
|
|
53
|
+
task_pipeline=task_pipeline,
|
|
54
|
+
is_training=is_training,
|
|
55
|
+
start_interval=start_interval,
|
|
56
|
+
seed=seed,
|
|
57
|
+
distributed=distributed,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
assert False, f"Unsupported dataset format ({data_cfg.format})."
|
|
62
|
+
|
|
63
|
+
return loader
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _validate_cfgs(
|
|
67
|
+
data_cfg: DataCfg,
|
|
68
|
+
task_cfg: Optional[DataTaskCfg],
|
|
69
|
+
is_training: bool = False,
|
|
70
|
+
):
|
|
71
|
+
batch_size = data_cfg.batch_size
|
|
72
|
+
if batch_size is not None:
|
|
73
|
+
if task_cfg.decode_and_process_fn is None:
|
|
74
|
+
# images must be processed into tensors to batch, text may be left as strings (collated as lists)
|
|
75
|
+
assert task_cfg.image_process_fn is not None, \
|
|
76
|
+
'task_cfg.image_process_fn must be set if batching enabled'
|
|
77
|
+
else:
|
|
78
|
+
assert task_cfg.decode_fn is None, \
|
|
79
|
+
'task_cfg.decode_fn should not be set at the same time as task_cfg.decode_and_process_fn'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def create_loader_from_config_wds(
|
|
83
|
+
data_cfg: DataCfg,
|
|
84
|
+
task_cfg: Optional[DataTaskCfg],
|
|
85
|
+
task_pipeline: Optional[List[Callable]] = None,
|
|
86
|
+
is_training: bool = False,
|
|
87
|
+
start_interval: int = 0,
|
|
88
|
+
seed: int = 0,
|
|
89
|
+
collate_fn: Optional[Callable] = None,
|
|
90
|
+
distributed: DistributedCfg = DistributedCfg(),
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
data_cfg:
|
|
96
|
+
task_cfg:
|
|
97
|
+
task_pipeline:
|
|
98
|
+
is_training:
|
|
99
|
+
start_interval:
|
|
100
|
+
seed:
|
|
101
|
+
collate_fn:
|
|
102
|
+
distributed:
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
_validate_cfgs(data_cfg, task_cfg, is_training=is_training)
|
|
108
|
+
|
|
109
|
+
if task_pipeline is None:
|
|
110
|
+
assert task_cfg is not None
|
|
111
|
+
task_pipeline = create_task_pipeline(
|
|
112
|
+
task_cfg,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
handler = get_error_handler(task_cfg.error_handler)
|
|
116
|
+
|
|
117
|
+
naflex_scheduler = None
|
|
118
|
+
naflex_stage_factory = None
|
|
119
|
+
if is_training and data_cfg.naflex_seq_lens:
|
|
120
|
+
from chugr.naflex import LengthBucketer, NaFlexBatcher
|
|
121
|
+
|
|
122
|
+
assert data_cfg.num_samples, 'NaFlex batching requires num_samples (per interval).'
|
|
123
|
+
naflex_scheduler, processors, image_key = _create_naflex_batching(
|
|
124
|
+
data_cfg, task_cfg, num_samples=data_cfg.num_samples, seed=seed, world_size=distributed.world_size)
|
|
125
|
+
|
|
126
|
+
def naflex_stage_factory(interval):
|
|
127
|
+
stages = []
|
|
128
|
+
if data_cfg.length_bucketing:
|
|
129
|
+
stages.append(LengthBucketer(seed=seed, interval=interval))
|
|
130
|
+
stages.append(NaFlexBatcher(
|
|
131
|
+
naflex_scheduler, processors, image_key=image_key, interval=interval, handler=handler))
|
|
132
|
+
return stages
|
|
133
|
+
|
|
134
|
+
return create_loader_wds(
|
|
135
|
+
shards=data_cfg.shard_spec,
|
|
136
|
+
task_pipeline=task_pipeline,
|
|
137
|
+
num_samples=data_cfg.num_samples,
|
|
138
|
+
is_training=is_training,
|
|
139
|
+
resampled=data_cfg.resampled,
|
|
140
|
+
multi_interval=True, # FIXME via config?
|
|
141
|
+
num_workers=data_cfg.num_workers,
|
|
142
|
+
batch_size=data_cfg.batch_size,
|
|
143
|
+
persistent_workers=data_cfg.persistent_workers,
|
|
144
|
+
collate_fn=collate_fn,
|
|
145
|
+
start_interval=start_interval,
|
|
146
|
+
seed=seed,
|
|
147
|
+
handler=handler,
|
|
148
|
+
distributed=distributed,
|
|
149
|
+
naflex_scheduler=naflex_scheduler,
|
|
150
|
+
naflex_stage_factory=naflex_stage_factory,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _create_naflex_batching(data_cfg: DataCfg, task_cfg: DataTaskCfg, num_samples: int, seed: int, world_size: int):
|
|
155
|
+
""" NaFlex batch schedule (for `num_samples` per interval), per seq_len task processors, and image key. """
|
|
156
|
+
from chugr.naflex import NaFlexBatchScheduler
|
|
157
|
+
from chugr.task_pipeline import create_task_processor
|
|
158
|
+
|
|
159
|
+
assert task_cfg.image_process_factory is not None, \
|
|
160
|
+
'NaFlex batching requires task_cfg.image_process_factory (seq_len -> image process fn).'
|
|
161
|
+
max_tokens = data_cfg.naflex_max_tokens or (data_cfg.batch_size or 1) * max(data_cfg.naflex_seq_lens)
|
|
162
|
+
scheduler = NaFlexBatchScheduler(
|
|
163
|
+
seq_lens=data_cfg.naflex_seq_lens,
|
|
164
|
+
max_tokens_per_batch=max_tokens,
|
|
165
|
+
num_samples=num_samples,
|
|
166
|
+
seq_len_probs=data_cfg.naflex_seq_len_probs,
|
|
167
|
+
per_row_text_tokens=data_cfg.naflex_text_tokens,
|
|
168
|
+
batch_divisor=data_cfg.naflex_batch_divisor,
|
|
169
|
+
seed=seed,
|
|
170
|
+
world_size=world_size,
|
|
171
|
+
)
|
|
172
|
+
processors = {
|
|
173
|
+
s: create_task_processor(task_cfg, image_process_fn=task_cfg.image_process_factory(s))
|
|
174
|
+
for s in scheduler.seq_lens
|
|
175
|
+
}
|
|
176
|
+
image_key = getattr(task_cfg, 'image_input_feat').output_name
|
|
177
|
+
return scheduler, processors, image_key
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def create_loader_from_config_hf(
|
|
181
|
+
data_cfg: DataCfg,
|
|
182
|
+
task_cfg: DataTaskCfg,
|
|
183
|
+
task_pipeline: Optional[List[Callable]] = None,
|
|
184
|
+
is_training: bool = False,
|
|
185
|
+
start_interval: int = 0,
|
|
186
|
+
seed: int = 0,
|
|
187
|
+
distributed: DistributedCfg = DistributedCfg(),
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
data_cfg:
|
|
193
|
+
task_cfg:
|
|
194
|
+
task_pipeline:
|
|
195
|
+
is_training:
|
|
196
|
+
start_interval:
|
|
197
|
+
seed:
|
|
198
|
+
distributed:
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
assert not isinstance(data_cfg.source, (list, tuple)), "Multiple sources not supported for HF datasets."
|
|
204
|
+
assert isinstance(data_cfg.source, (str, SourceSpec)), \
|
|
205
|
+
"The specified source for HF dataset must be a string or SourceSpec."
|
|
206
|
+
|
|
207
|
+
_validate_cfgs(data_cfg, task_cfg, is_training=is_training)
|
|
208
|
+
|
|
209
|
+
if isinstance(data_cfg.source, SourceSpec):
|
|
210
|
+
source = data_cfg.source.url
|
|
211
|
+
split = data_cfg.source.split
|
|
212
|
+
assert split, "Split must be set in SourceSpec with HF datasets."
|
|
213
|
+
else:
|
|
214
|
+
source = data_cfg.source
|
|
215
|
+
split = data_cfg.split
|
|
216
|
+
assert split, "Split must be set in DataCfg when string source is used with HF datasets."
|
|
217
|
+
|
|
218
|
+
if task_pipeline is None:
|
|
219
|
+
assert task_cfg is not None
|
|
220
|
+
task_pipeline = create_task_pipeline(
|
|
221
|
+
task_cfg,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
streaming = 'hfids' in data_cfg.format
|
|
225
|
+
|
|
226
|
+
naflex_factory = None
|
|
227
|
+
if is_training and data_cfg.naflex_seq_lens:
|
|
228
|
+
# schedule driven NaFlex batching, the schedule is built once the dataset length is known (map-style)
|
|
229
|
+
if data_cfg.length_bucketing:
|
|
230
|
+
warnings.warn('length_bucketing is not supported for HF datasets, ignored.')
|
|
231
|
+
naflex_factory = partial(
|
|
232
|
+
_create_naflex_batching, data_cfg, task_cfg, seed=seed, world_size=distributed.world_size)
|
|
233
|
+
|
|
234
|
+
return create_loader_hf(
|
|
235
|
+
source=source,
|
|
236
|
+
split=split,
|
|
237
|
+
task_pipeline=task_pipeline,
|
|
238
|
+
streaming=streaming,
|
|
239
|
+
is_training=is_training,
|
|
240
|
+
batch_size=data_cfg.batch_size,
|
|
241
|
+
data_dir=data_cfg.data_dir,
|
|
242
|
+
num_samples=data_cfg.num_samples,
|
|
243
|
+
num_workers=data_cfg.num_workers,
|
|
244
|
+
persistent_workers=data_cfg.persistent_workers,
|
|
245
|
+
seed=seed,
|
|
246
|
+
distributed=distributed,
|
|
247
|
+
naflex_factory=naflex_factory,
|
|
248
|
+
handler=get_error_handler(task_cfg.error_handler),
|
|
249
|
+
)
|