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.
Files changed (53) hide show
  1. chugr/__init__.py +46 -0
  2. chugr/app/test.py +105 -0
  3. chugr/common/__init__.py +9 -0
  4. chugr/common/cli.py +234 -0
  5. chugr/common/collate.py +104 -0
  6. chugr/common/config.py +335 -0
  7. chugr/common/random.py +76 -0
  8. chugr/common/serialize.py +150 -0
  9. chugr/common/task_config.py +24 -0
  10. chugr/common/types.py +122 -0
  11. chugr/common/urls.py +88 -0
  12. chugr/doc/__init__.py +17 -0
  13. chugr/doc/constants.py +15 -0
  14. chugr/doc/doc_processor.py +248 -0
  15. chugr/doc/doc_read_processor.py +249 -0
  16. chugr/doc/doc_vqa_processor.py +113 -0
  17. chugr/doc/synth_text.py +212 -0
  18. chugr/hfds/__init__.py +2 -0
  19. chugr/hfds/collate.py +83 -0
  20. chugr/hfds/loader.py +385 -0
  21. chugr/hfds/wrappers.py +108 -0
  22. chugr/image/__init__.py +4 -0
  23. chugr/image/build_transforms_doc.py +427 -0
  24. chugr/image/build_transforms_image.py +203 -0
  25. chugr/image/transforms_alb.py +112 -0
  26. chugr/image/transforms_factory.py +58 -0
  27. chugr/image/transforms_torch.py +137 -0
  28. chugr/loader.py +249 -0
  29. chugr/naflex.py +357 -0
  30. chugr/task_pipeline/__init__.py +7 -0
  31. chugr/task_pipeline/pipeline_doc_read.py +66 -0
  32. chugr/task_pipeline/pipeline_doc_vqa.py +124 -0
  33. chugr/task_pipeline/pipeline_factory.py +32 -0
  34. chugr/task_pipeline/pipeline_gtparse.py +93 -0
  35. chugr/task_pipeline/pipeline_image_text.py +90 -0
  36. chugr/task_pipeline/pipeline_manual.py +27 -0
  37. chugr/text/__init__.py +1 -0
  38. chugr/text/tokenization.py +222 -0
  39. chugr/version.py +1 -0
  40. chugr/wds/__init__.py +7 -0
  41. chugr/wds/dataset_info.py +65 -0
  42. chugr/wds/decode.py +319 -0
  43. chugr/wds/filters.py +142 -0
  44. chugr/wds/helpers.py +68 -0
  45. chugr/wds/loader.py +187 -0
  46. chugr/wds/pipeline.py +182 -0
  47. chugr/wds/shardlists.py +134 -0
  48. chugr/wds/tariterators.py +70 -0
  49. chugr-0.2.0.dev0.dist-info/METADATA +436 -0
  50. chugr-0.2.0.dev0.dist-info/RECORD +53 -0
  51. chugr-0.2.0.dev0.dist-info/WHEEL +4 -0
  52. chugr-0.2.0.dev0.dist-info/entry_points.txt +4 -0
  53. chugr-0.2.0.dev0.dist-info/licenses/LICENSE +201 -0
chugr/__init__.py ADDED
@@ -0,0 +1,46 @@
1
+ from .common import (
2
+ ImageInputCfg,
3
+ ImageAugCfg,
4
+ LoaderBundle,
5
+ ImageFeatureInfo,
6
+ FeatureInfo,
7
+ ShardSpec,
8
+ SourceSpec,
9
+ DataArg,
10
+ DataCfg,
11
+ DistributedCfg,
12
+ )
13
+ from .hfds import create_loader_hf
14
+ from .image import (
15
+ build_image_transforms,
16
+ build_transforms_image_basic,
17
+ build_transforms_image_timm,
18
+ build_transforms_doc_basic,
19
+ build_transforms_doc_better,
20
+ build_transforms_doc_nougat,
21
+ create_image_preprocessor,
22
+ )
23
+ from .loader import create_loader, create_loader_from_config_hf, create_loader_from_config_wds
24
+ from .task_pipeline import (
25
+ create_task_pipeline,
26
+ build_task_pipeline_doc_read,
27
+ build_task_pipeline_doc_vqa,
28
+ build_task_pipeline_gtparse,
29
+ build_task_pipeline_image_text,
30
+ build_task_pipeline_manual,
31
+ DataTaskDocReadCfg,
32
+ DataTaskDocVqaCfg,
33
+ DataTaskImageTextCfg,
34
+ DataTaskManualCfg,
35
+ )
36
+ from .text import tokenize, text_input_to_target, prepare_text_input, create_text_preprocessor
37
+ from .version import __version__
38
+ from .wds import (
39
+ create_loader_wds,
40
+ build_data_pipeline,
41
+ decode_image_pages,
42
+ decode_pdf_pages,
43
+ create_image_decoder,
44
+ DecodeDoc,
45
+ )
46
+
chugr/app/test.py ADDED
@@ -0,0 +1,105 @@
1
+ """ Iterate a document loader for throughput benchmarking & sample inspection.
2
+
3
+ Example:
4
+ python -m chugr.app.test --data.source "idl-train-0000{0..3}.tar" --data.batch-size 8 --data.num-samples 1000 \\
5
+ --input.transform-type doc_naflex --input.patch-size 16 --input.max-seq-len 4096 --task doc_read
6
+ """
7
+ import logging
8
+ import os
9
+ import sys
10
+ import time
11
+ from dataclasses import dataclass, field
12
+ from typing import Optional
13
+
14
+ import torch
15
+
16
+ import chugr
17
+ from chugr.common import DataArg, DataCfg, ImageInputCfg, Serializable, parse_args
18
+
19
+ _logger = logging.getLogger('chugr.test')
20
+
21
+
22
+ @dataclass
23
+ class TestArgs(Serializable):
24
+ data: DataArg = field(default_factory=lambda: DataArg(source=''))
25
+ input: ImageInputCfg = field(default_factory=lambda: ImageInputCfg(transform_type='doc_better', size=(1024, 768)))
26
+ task: str = 'doc_read' # one of 'doc_read', 'doc_vqa'
27
+ tokenizer: Optional[str] = None # HF tokenizer name, text left as strings if not set
28
+ text_max_length: int = 1024
29
+ is_training: bool = True
30
+ num_batches: int = 50
31
+ dump_dir: Optional[str] = None # write first batch images (un-normalized) here
32
+
33
+
34
+ def _dump_image(x, input_cfg: ImageInputCfg, path: str):
35
+ from torchvision.utils import save_image
36
+ if isinstance(x, dict):
37
+ # reassemble naflex patches into an image
38
+ valid = x['patch_valid']
39
+ patches = x['patches'][valid]
40
+ coords = x['patch_coord'][valid]
41
+ p = input_cfg.patch_size
42
+ c = patches.shape[-1] // (p * p)
43
+ h, w = (coords.max(0).values + 1).tolist()
44
+ img = torch.zeros(c, h * p, w * p)
45
+ for patch, (py, px) in zip(patches, coords.tolist()):
46
+ img[:, py * p:(py + 1) * p, px * p:(px + 1) * p] = patch.reshape(p, p, c).permute(2, 0, 1)
47
+ x = img
48
+ mean = torch.tensor(input_cfg.mean).view(-1, 1, 1)
49
+ std = torch.tensor(input_cfg.std).view(-1, 1, 1)
50
+ save_image((x * std + mean).clamp(0, 1), path)
51
+
52
+
53
+ def main(argv=None):
54
+ logging.basicConfig(level=logging.INFO)
55
+ args: TestArgs = parse_args(TestArgs, argv=argv, description='chugr loader test')
56
+ print(args.dumps_yaml())
57
+
58
+ img_fn = chugr.create_image_preprocessor(args.input, is_training=args.is_training)
59
+ txt_fn = None
60
+ if args.tokenizer:
61
+ txt_fn = chugr.create_text_preprocessor(args.tokenizer, max_length=args.text_max_length)
62
+ if args.task == 'doc_read':
63
+ task_cfg = chugr.DataTaskDocReadCfg(
64
+ image_process_fn=img_fn,
65
+ text_process_fn=txt_fn,
66
+ text_target_feat=chugr.FeatureInfo('text_target', input_key=None) if txt_fn else None,
67
+ error_handler='dump_and_reraise',
68
+ )
69
+ elif args.task == 'doc_vqa':
70
+ task_cfg = chugr.DataTaskDocVqaCfg(
71
+ image_process_fn=img_fn,
72
+ text_process_fn=txt_fn,
73
+ text_target_feat=chugr.FeatureInfo('text_target', input_key=None) if txt_fn else None,
74
+ )
75
+ else:
76
+ assert False, f'Unknown task {args.task}'
77
+
78
+ loader = chugr.create_loader(DataCfg.from_arg(args.data), task_cfg, is_training=args.is_training)
79
+ start = time.time()
80
+ num_samples = 0
81
+ for i, batch in enumerate(loader):
82
+ image = batch['image_input']
83
+ bs = (image['patches'] if isinstance(image, dict) else image).shape[0]
84
+ num_samples += bs
85
+ if i == 0:
86
+ if isinstance(image, dict):
87
+ _logger.info(f"patches {tuple(image['patches'].shape)}, valid {image['patch_valid'].sum(1).tolist()}")
88
+ else:
89
+ _logger.info(f'image {tuple(image.shape)}')
90
+ if args.dump_dir:
91
+ os.makedirs(args.dump_dir, exist_ok=True)
92
+ for j in range(bs):
93
+ x = {k: v[j] for k, v in image.items()} if isinstance(image, dict) else image[j]
94
+ _dump_image(x, args.input, os.path.join(args.dump_dir, f'sample_{j}.png'))
95
+ text = batch['text_input'][j]
96
+ with open(os.path.join(args.dump_dir, f'sample_{j}.txt'), 'w') as f:
97
+ f.write(text if isinstance(text, str) else str(text))
98
+ if i + 1 >= args.num_batches:
99
+ break
100
+ elapsed = time.time() - start
101
+ _logger.info(f'{num_samples} samples in {elapsed:.1f}s, {num_samples / elapsed:.1f} samples/s')
102
+
103
+
104
+ if __name__ == '__main__':
105
+ sys.exit(main())
@@ -0,0 +1,9 @@
1
+ from .cli import parse_args, str2bool
2
+ from .collate import collate, collate_naflex, is_naflex_sample
3
+ from .config import ImageInputCfg, ImageAugCfg, PreprocessCfg, image_mode_to_chs
4
+ from .config import DataArg, DataCfg, DistributedCfg, source_to_shard_spec
5
+ from .serialize import Serializable, to_dict, from_dict
6
+ from .random import get_pytorch_worker_seed, rank_seed, seed_worker, shuffled_interval_indices
7
+ from .task_config import DataTaskCfg
8
+ from .types import SourceSpec, SplitInfo, ShardSpec, SharedCount, LoaderBundle, FeatureInfo, ImageFeatureInfo
9
+ from .urls import expand_urls
chugr/common/cli.py ADDED
@@ -0,0 +1,234 @@
1
+ """ Nested dataclass <-> command line arguments
2
+
3
+ A small replacement for the subset of `simple_parsing` used by chugr & pixparse apps.
4
+
5
+ * nested dataclass fields map to dotted args, e.g. `--task.opt.learning-rate 1e-4`
6
+ (dashes and underscores are both accepted)
7
+ * `subgroups` select the dataclass type of a field by name, e.g. `--task cruller_pretrain`
8
+ * a `--config` yaml/json file provides defaults that command line args override
9
+ """
10
+ import argparse
11
+ import dataclasses
12
+ import sys
13
+ import typing
14
+ from typing import Any, Dict, List, Optional, Sequence, Type, TypeVar
15
+
16
+ from .serialize import _is_union, _unwrap_optional, from_dict, resolve_field_types, _load_file
17
+
18
+ T = TypeVar('T')
19
+
20
+ _NONE_STRINGS = ('none', 'null')
21
+
22
+
23
+ def str2bool(v: str) -> bool:
24
+ if isinstance(v, bool):
25
+ return v
26
+ lv = v.lower()
27
+ if lv in ('true', 't', 'yes', 'y', '1'):
28
+ return True
29
+ if lv in ('false', 'f', 'no', 'n', '0'):
30
+ return False
31
+ raise argparse.ArgumentTypeError(f'Boolean value expected, got {v}.')
32
+
33
+
34
+ def _scalar_parser(tp):
35
+ def _parse(v: str):
36
+ if v.lower() in _NONE_STRINGS:
37
+ return None
38
+ if tp is bool:
39
+ return str2bool(v)
40
+ if tp is int:
41
+ return int(v)
42
+ if tp is float:
43
+ return float(v)
44
+ if _is_union(tp):
45
+ # e.g. Union[int, str] -> try members in order
46
+ for a in typing.get_args(tp):
47
+ if a is type(None):
48
+ continue
49
+ try:
50
+ return _scalar_parser(a)(v)
51
+ except (ValueError, argparse.ArgumentTypeError):
52
+ continue
53
+ return v
54
+ return v
55
+ return _parse
56
+
57
+
58
+ def _arg_spec(tp):
59
+ """ Return (nargs, item parser) for a field type. """
60
+ tp = _unwrap_optional(tp)
61
+ origin = typing.get_origin(tp)
62
+ args = typing.get_args(tp)
63
+ if origin is tuple:
64
+ if len(args) == 2 and args[1] is Ellipsis:
65
+ return '+', _scalar_parser(args[0])
66
+ item_tp = args[0] if args else str
67
+ return '+', _scalar_parser(item_tp) # length validated when building the dataclass
68
+ if origin is list:
69
+ return '+', _scalar_parser(args[0] if args else str)
70
+ if _is_union(tp):
71
+ # union containing a sequence type, accept one or more values
72
+ for a in args:
73
+ if typing.get_origin(a) in (list, tuple):
74
+ return '+', _scalar_parser(typing.get_args(a)[0] if typing.get_args(a) else str)
75
+ return None, _scalar_parser(tp)
76
+
77
+
78
+ def _flatten_fields(cls, prefix: str = '') -> Dict[str, Any]:
79
+ """ dotted name -> field type for all leaf fields of a (nested) dataclass type """
80
+ out = {}
81
+ field_types = resolve_field_types(cls)
82
+ for f in dataclasses.fields(cls):
83
+ if not f.init:
84
+ continue
85
+ tp = field_types[f.name]
86
+ name = f'{prefix}{f.name}'
87
+ inner = _unwrap_optional(tp)
88
+ if dataclasses.is_dataclass(inner):
89
+ out.update(_flatten_fields(inner, prefix=name + '.'))
90
+ else:
91
+ out[name] = tp
92
+ return out
93
+
94
+
95
+ def _set_nested(d: Dict[str, Any], dotted: str, value):
96
+ keys = dotted.split('.')
97
+ for k in keys[:-1]:
98
+ if d.get(k) is None:
99
+ d[k] = {}
100
+ d = d[k]
101
+ d[keys[-1]] = value
102
+
103
+
104
+ def _norm(name: str) -> str:
105
+ return name.replace('-', '_')
106
+
107
+
108
+ def parse_args(
109
+ cls: Type[T],
110
+ argv: Optional[Sequence[str]] = None,
111
+ subgroups: Optional[Dict[str, Dict[str, type]]] = None,
112
+ subgroup_defaults: Optional[Dict[str, str]] = None,
113
+ description: Optional[str] = None,
114
+ ) -> T:
115
+ """ Parse command line args into dataclass `cls`.
116
+
117
+ Args:
118
+ cls: The top level config dataclass.
119
+ argv: Arguments, defaults to sys.argv[1:].
120
+ subgroups: Mapping of dotted field name -> {choice name: dataclass type}. The choice is selected
121
+ with `--<field> <name>` and the selected type's fields become `--<field>.<subfield>` args.
122
+ subgroup_defaults: Default choice name per subgroup field.
123
+ description: Parser description.
124
+ """
125
+ argv = list(sys.argv[1:] if argv is None else argv)
126
+ subgroups = subgroups or {}
127
+ subgroup_defaults = subgroup_defaults or {}
128
+
129
+ # stage 1: config file + subgroup selection
130
+ pre = argparse.ArgumentParser(add_help=False)
131
+ pre.add_argument('--config', type=str, default=None)
132
+ for sg_name, choices in subgroups.items():
133
+ pre.add_argument(
134
+ f'--{sg_name.replace("_", "-")}', f'--{sg_name}', dest=sg_name, type=str,
135
+ default=None, choices=list(choices.keys()))
136
+ pre_args, _ = pre.parse_known_args(argv)
137
+
138
+ file_cfg = _load_file(pre_args.config) if pre_args.config else {}
139
+
140
+ selected = {}
141
+ for sg_name, choices in subgroups.items():
142
+ choice = getattr(pre_args, sg_name)
143
+ if choice is None:
144
+ # allow config file to select via a `_name` key in the subgroup dict
145
+ sg_file = file_cfg.get(sg_name)
146
+ if isinstance(sg_file, dict) and '_name' in sg_file:
147
+ choice = sg_file.pop('_name')
148
+ else:
149
+ choice = subgroup_defaults.get(sg_name)
150
+ assert choice is not None, f'A value for --{sg_name} is required (one of {list(choices.keys())}).'
151
+ selected[sg_name] = choice
152
+
153
+ # stage 2: build full parser over all leaf fields
154
+ leaf_types = {}
155
+ field_types = resolve_field_types(cls)
156
+ for f in dataclasses.fields(cls):
157
+ if not f.init:
158
+ continue
159
+ if f.name in subgroups:
160
+ sub_cls = subgroups[f.name][selected[f.name]]
161
+ leaf_types.update(_flatten_fields(sub_cls, prefix=f.name + '.'))
162
+ continue
163
+ tp = field_types[f.name]
164
+ inner = _unwrap_optional(tp)
165
+ if dataclasses.is_dataclass(inner):
166
+ leaf_types.update(_flatten_fields(inner, prefix=f.name + '.'))
167
+ else:
168
+ leaf_types[f.name] = tp
169
+
170
+ parser = argparse.ArgumentParser(description=description)
171
+ parser.add_argument('--config', type=str, default=None, help='yaml or json config file')
172
+ for sg_name, choices in subgroups.items():
173
+ parser.add_argument(
174
+ f'--{sg_name.replace("_", "-")}', f'--{sg_name}', dest=f'__sg_{sg_name}', type=str,
175
+ default=None, choices=list(choices.keys()))
176
+
177
+ for dotted, tp in leaf_types.items():
178
+ nargs, item_parser = _arg_spec(tp)
179
+ flags = {f'--{dotted}', f'--{dotted.replace("_", "-")}'}
180
+ kwargs = dict(dest=dotted, type=item_parser, default=argparse.SUPPRESS)
181
+ if nargs is not None:
182
+ kwargs['nargs'] = nargs
183
+ parser.add_argument(*sorted(flags), **kwargs)
184
+
185
+ args = parser.parse_args(argv)
186
+
187
+ # merge: file config < command line
188
+ merged: Dict[str, Any] = dict(file_cfg)
189
+ for dotted, value in vars(args).items():
190
+ if dotted == 'config' or dotted.startswith('__sg_'):
191
+ continue
192
+ if isinstance(value, list) and len(value) == 1 and value[0] is None:
193
+ value = None # e.g. `--input.size none` for an Optional[Tuple]
194
+ _set_nested(merged, dotted, value)
195
+
196
+ # build nested dataclasses
197
+ kwargs = {}
198
+ for f in dataclasses.fields(cls):
199
+ if not f.init or f.name not in merged:
200
+ if f.name in subgroups:
201
+ kwargs[f.name] = subgroups[f.name][selected[f.name]]()
202
+ continue
203
+ value = merged[f.name]
204
+ if f.name in subgroups:
205
+ sub_cls = subgroups[f.name][selected[f.name]]
206
+ kwargs[f.name] = _build_with_defaults(sub_cls, value or {})
207
+ continue
208
+ tp = _unwrap_optional(field_types[f.name])
209
+ if dataclasses.is_dataclass(tp) and isinstance(value, dict):
210
+ kwargs[f.name] = _build_with_defaults(tp, value)
211
+ else:
212
+ from .serialize import from_value
213
+ kwargs[f.name] = from_value(field_types[f.name], value)
214
+ return cls(**kwargs)
215
+
216
+
217
+ def _build_with_defaults(cls, overrides: Dict[str, Any]):
218
+ """ Build dataclass `cls` from partial (nested) overrides, keeping field defaults for the rest. """
219
+ from .serialize import from_value
220
+ field_types = resolve_field_types(cls)
221
+ kwargs = {}
222
+ for f in dataclasses.fields(cls):
223
+ if not f.init or f.name not in overrides:
224
+ continue
225
+ value = overrides[f.name]
226
+ tp = _unwrap_optional(field_types[f.name])
227
+ if dataclasses.is_dataclass(tp) and isinstance(value, dict):
228
+ kwargs[f.name] = _build_with_defaults(tp, value)
229
+ else:
230
+ kwargs[f.name] = from_value(field_types[f.name], value)
231
+ unknown = set(overrides.keys()) - {f.name for f in dataclasses.fields(cls)}
232
+ if unknown:
233
+ raise TypeError(f'Unknown fields {sorted(unknown)} for {cls.__name__}.')
234
+ return cls(**kwargs)
@@ -0,0 +1,104 @@
1
+ import collections
2
+ from typing import Callable, Dict, List, Optional, Tuple, Type, Union
3
+
4
+ import torch
5
+ from torch.utils.data._utils.collate import default_collate_fn_map, default_collate_err_msg_format
6
+
7
+ NAFLEX_KEYS = ('patches', 'patch_coord', 'patch_valid')
8
+
9
+
10
+ def is_naflex_sample(elem) -> bool:
11
+ return isinstance(elem, collections.abc.Mapping) and all(k in elem for k in NAFLEX_KEYS)
12
+
13
+
14
+ def collate_naflex(
15
+ batch: List[Dict[str, torch.Tensor]],
16
+ pad_multiple: int = 8,
17
+ pad_to: Optional[int] = None,
18
+ ) -> Dict[str, torch.Tensor]:
19
+ """ Pad variable length patch sequences (NaFlex) to the longest in the batch.
20
+
21
+ Args:
22
+ batch: list of dicts w/ 'patches' [N, D] (or [N, Ph, Pw, C]), 'patch_coord' [N, 2], 'patch_valid' [N]
23
+ pad_multiple: round padded length up to a multiple of this (kernel friendly shapes)
24
+ pad_to: pad to this fixed length (e.g. the NaFlex seq_len bucket) instead of the batch max
25
+ """
26
+ max_len = max(b['patches'].shape[0] for b in batch)
27
+ if pad_to is not None:
28
+ assert max_len <= pad_to, f'NaFlex sample length {max_len} exceeds bucket seq_len {pad_to}'
29
+ max_len = pad_to
30
+ elif pad_multiple > 1:
31
+ max_len = ((max_len + pad_multiple - 1) // pad_multiple) * pad_multiple
32
+ batch_size = len(batch)
33
+ patch_shape = batch[0]['patches'].shape[1:]
34
+ patches = torch.zeros((batch_size, max_len, *patch_shape), dtype=batch[0]['patches'].dtype)
35
+ patch_coord = torch.zeros((batch_size, max_len, 2), dtype=torch.int64)
36
+ patch_valid = torch.zeros((batch_size, max_len), dtype=torch.bool)
37
+ for i, b in enumerate(batch):
38
+ n = b['patches'].shape[0]
39
+ patches[i, :n] = b['patches']
40
+ patch_coord[i, :n] = b['patch_coord']
41
+ patch_valid[i, :n] = b['patch_valid']
42
+ return {'patches': patches, 'patch_coord': patch_coord, 'patch_valid': patch_valid}
43
+
44
+
45
+ def collate(batch):
46
+ r"""
47
+ A customized collate function that handles collection type of element within each batch.
48
+
49
+ This collate function has been tweaked to provide different functionality when handling
50
+ dictionary samples. Certain keys are excluded or not tensorized.
51
+
52
+ Args:
53
+ batch: a single batch to be collated
54
+ """
55
+ elem = batch[0]
56
+ elem_type = type(elem)
57
+
58
+ if elem_type in default_collate_fn_map:
59
+ return default_collate_fn_map[elem_type](batch)
60
+
61
+ for collate_type in default_collate_fn_map:
62
+ if isinstance(elem, collate_type):
63
+ return default_collate_fn_map[collate_type](batch)
64
+
65
+ if is_naflex_sample(elem):
66
+ return collate_naflex(batch)
67
+
68
+ if isinstance(elem, collections.abc.Mapping):
69
+ try:
70
+ out = {}
71
+ for key in elem:
72
+ if key.startswith('__'):
73
+ # skip keys starting with '__', e.g. '__key__',
74
+ continue
75
+ elif key.startswith('_'):
76
+ # do not recurse or tensorize values for keys starting with '_', e.g. '_parse'
77
+ out[key] = [d[key] for d in batch]
78
+ else:
79
+ out[key] = collate([d[key] for d in batch])
80
+ out = elem_type(out)
81
+ return out
82
+ except TypeError:
83
+ # The mapping type may not support `__init__(iterable)`.
84
+ return {key: collate([d[key] for d in batch]) for key in elem}
85
+ elif isinstance(elem, tuple) and hasattr(elem, '_fields'): # namedtuple
86
+ return elem_type(*(collate(samples) for samples in zip(*batch)))
87
+ elif isinstance(elem, collections.abc.Sequence):
88
+ # check to make sure that the elements in batch have consistent size
89
+ it = iter(batch)
90
+ elem_size = len(next(it))
91
+ if not all(len(elem) == elem_size for elem in it):
92
+ raise RuntimeError('each element in list of batch should be of equal size')
93
+ transposed = list(zip(*batch)) # It may be accessed twice, so we use a list.
94
+
95
+ if isinstance(elem, tuple):
96
+ return [collate(samples) for samples in transposed] # Backwards compatibility.
97
+ else:
98
+ try:
99
+ return elem_type([collate(samples) for samples in transposed])
100
+ except TypeError:
101
+ # The sequence type may not support `__init__(iterable)` (e.g., `range`).
102
+ return [collate(samples) for samples in transposed]
103
+
104
+ raise TypeError(default_collate_err_msg_format.format(elem_type))