openpoints 0.1.0__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 (184) hide show
  1. openpoints/__init__.py +3 -0
  2. openpoints/cpp/__init__.py +6 -0
  3. openpoints/cpp/chamfer_dist/__init__.py +110 -0
  4. openpoints/cpp/chamfer_dist/setup.py +19 -0
  5. openpoints/cpp/chamfer_dist/test.py +34 -0
  6. openpoints/cpp/emd/__init__.py +3 -0
  7. openpoints/cpp/emd/emd.py +88 -0
  8. openpoints/cpp/emd/setup.py +27 -0
  9. openpoints/cpp/emd/test_emd_loss.py +45 -0
  10. openpoints/cpp/pointnet2_batch/__init__.py +24 -0
  11. openpoints/cpp/pointnet2_batch/setup.py +23 -0
  12. openpoints/cpp/pointops/__init__.py +0 -0
  13. openpoints/cpp/pointops/functions/__init__.py +0 -0
  14. openpoints/cpp/pointops/functions/pointops.py +314 -0
  15. openpoints/cpp/pointops/setup.py +37 -0
  16. openpoints/cpp/pointops/src/__init__.py +0 -0
  17. openpoints/dataset/__init__.py +10 -0
  18. openpoints/dataset/atom3d/__init__.py +1 -0
  19. openpoints/dataset/atom3d/psr.py +38 -0
  20. openpoints/dataset/build.py +98 -0
  21. openpoints/dataset/data_util.py +192 -0
  22. openpoints/dataset/datalist.py +67 -0
  23. openpoints/dataset/dataset_base.py +96 -0
  24. openpoints/dataset/graph_dataset/__init__.py +3 -0
  25. openpoints/dataset/graph_dataset/graph_dataset.py +93 -0
  26. openpoints/dataset/graph_dataset/stack_with_pad.py +91 -0
  27. openpoints/dataset/graph_dataset/structural_dataset.py +73 -0
  28. openpoints/dataset/graph_dataset/svd_encodings_dataset.py +110 -0
  29. openpoints/dataset/grid_sample.py +21 -0
  30. openpoints/dataset/matterport3d/__init__.py +1 -0
  31. openpoints/dataset/matterport3d/category_mapping.tsv +1660 -0
  32. openpoints/dataset/matterport3d/matterport3d.py +210 -0
  33. openpoints/dataset/matterport3d/matterport3d_dataprocessing.py +105 -0
  34. openpoints/dataset/modelnet/__init__.py +3 -0
  35. openpoints/dataset/modelnet/modelnet40_normal_resampled_loader.py +124 -0
  36. openpoints/dataset/modelnet/modelnet40_ply_2048_loader.py +160 -0
  37. openpoints/dataset/molhiv/__init__.py +1 -0
  38. openpoints/dataset/molhiv/data.py +59 -0
  39. openpoints/dataset/molpcba/__init__.py +1 -0
  40. openpoints/dataset/molpcba/data.py +59 -0
  41. openpoints/dataset/parsers/__init__.py +1 -0
  42. openpoints/dataset/parsers/class_map.py +19 -0
  43. openpoints/dataset/parsers/constants.py +1 -0
  44. openpoints/dataset/parsers/parser.py +17 -0
  45. openpoints/dataset/parsers/parser_factory.py +29 -0
  46. openpoints/dataset/parsers/parser_image_folder.py +69 -0
  47. openpoints/dataset/parsers/parser_image_in_tar.py +222 -0
  48. openpoints/dataset/parsers/parser_image_tar.py +72 -0
  49. openpoints/dataset/parsers/parser_tfds.py +297 -0
  50. openpoints/dataset/pcqm4m/__init__.py +1 -0
  51. openpoints/dataset/pcqm4m/data.py +62 -0
  52. openpoints/dataset/pcqm4mv2/__init__.py +1 -0
  53. openpoints/dataset/pcqm4mv2/data.py +87 -0
  54. openpoints/dataset/s3dis/__init__.py +2 -0
  55. openpoints/dataset/s3dis/s3dis.py +156 -0
  56. openpoints/dataset/s3dis/s3dis_block.py +96 -0
  57. openpoints/dataset/s3dis/s3dis_sphere.py +349 -0
  58. openpoints/dataset/scannetv2/__init__.py +1 -0
  59. openpoints/dataset/scannetv2/scannet.py +176 -0
  60. openpoints/dataset/scanobjectnn/__init__.py +3 -0
  61. openpoints/dataset/scanobjectnn/scanobjectnn.py +110 -0
  62. openpoints/dataset/semantic_kitti/__init__.py +1 -0
  63. openpoints/dataset/semantic_kitti/helper_tool.py +286 -0
  64. openpoints/dataset/semantic_kitti/label_mapping.yaml +211 -0
  65. openpoints/dataset/semantic_kitti/semantickitti.py +229 -0
  66. openpoints/dataset/semantic_kitti/utils/meta/anno_paths.txt +272 -0
  67. openpoints/dataset/semantic_kitti/utils/meta/class_names.txt +13 -0
  68. openpoints/dataset/semantic_kitti/utils/semantic-kitti.yaml +211 -0
  69. openpoints/dataset/shapenet/__init__.py +1 -0
  70. openpoints/dataset/shapenet/shapenet55.py +76 -0
  71. openpoints/dataset/shapenet/shapenetpart.py +121 -0
  72. openpoints/dataset/shapenetpart/__init__.py +1 -0
  73. openpoints/dataset/shapenetpart/shapenet55.py +75 -0
  74. openpoints/dataset/shapenetpart/shapenetpart.py +388 -0
  75. openpoints/dataset/vis2d.py +17 -0
  76. openpoints/dataset/vis3d.py +153 -0
  77. openpoints/loss/__init__.py +3 -0
  78. openpoints/loss/build.py +281 -0
  79. openpoints/loss/cross_entropy.py +38 -0
  80. openpoints/loss/distill_loss.py +76 -0
  81. openpoints/models/__init__.py +10 -0
  82. openpoints/models/backbone/Stratified_transformer.py +558 -0
  83. openpoints/models/backbone/__init__.py +11 -0
  84. openpoints/models/backbone/baafnet.py +527 -0
  85. openpoints/models/backbone/ball_dgcnn.py +123 -0
  86. openpoints/models/backbone/curvenet.py +793 -0
  87. openpoints/models/backbone/debug_invvit.py +114 -0
  88. openpoints/models/backbone/deepgcn.py +143 -0
  89. openpoints/models/backbone/dgcnn.py +119 -0
  90. openpoints/models/backbone/graphvit3d.py +134 -0
  91. openpoints/models/backbone/grouppointnet.py +100 -0
  92. openpoints/models/backbone/pct.py +163 -0
  93. openpoints/models/backbone/pointmlp.py +417 -0
  94. openpoints/models/backbone/pointnet.py +199 -0
  95. openpoints/models/backbone/pointnetv2.py +511 -0
  96. openpoints/models/backbone/pointnext.py +663 -0
  97. openpoints/models/backbone/pointnextPyG.py +555 -0
  98. openpoints/models/backbone/pointtransformer.py +293 -0
  99. openpoints/models/backbone/pointvector.py +853 -0
  100. openpoints/models/backbone/pointvit.py +392 -0
  101. openpoints/models/backbone/pointvit_inv.py +942 -0
  102. openpoints/models/backbone/pointvit_inv_old.py +784 -0
  103. openpoints/models/backbone/randlenet.py +318 -0
  104. openpoints/models/backbone/resnet.py +342 -0
  105. openpoints/models/backbone/simpleview.py +153 -0
  106. openpoints/models/backbone/simpleview_util.py +292 -0
  107. openpoints/models/build.py +13 -0
  108. openpoints/models/classification/__init__.py +5 -0
  109. openpoints/models/classification/cls_base.py +136 -0
  110. openpoints/models/classification/point_bert.py +154 -0
  111. openpoints/models/layers/__init__.py +14 -0
  112. openpoints/models/layers/activation.py +57 -0
  113. openpoints/models/layers/attention.py +103 -0
  114. openpoints/models/layers/conv.py +167 -0
  115. openpoints/models/layers/drop.py +164 -0
  116. openpoints/models/layers/graph_conv.py +122 -0
  117. openpoints/models/layers/group.py +415 -0
  118. openpoints/models/layers/group_embed.py +286 -0
  119. openpoints/models/layers/helpers.py +43 -0
  120. openpoints/models/layers/kmeans.py +119 -0
  121. openpoints/models/layers/knn.py +110 -0
  122. openpoints/models/layers/local_aggregation.py +286 -0
  123. openpoints/models/layers/mlp.py +129 -0
  124. openpoints/models/layers/norm.py +106 -0
  125. openpoints/models/layers/padding.py +56 -0
  126. openpoints/models/layers/patch_embed.py +37 -0
  127. openpoints/models/layers/registry.py +168 -0
  128. openpoints/models/layers/subsample.py +185 -0
  129. openpoints/models/layers/upsampling.py +106 -0
  130. openpoints/models/layers/weight_init.py +89 -0
  131. openpoints/models/reconstruction/__init__.py +8 -0
  132. openpoints/models/reconstruction/base_recontruct.py +216 -0
  133. openpoints/models/reconstruction/maskedpoint.py +116 -0
  134. openpoints/models/reconstruction/maskedpointgroup.py +168 -0
  135. openpoints/models/reconstruction/maskedpointvit.py +253 -0
  136. openpoints/models/reconstruction/nodeshuffle.py +11 -0
  137. openpoints/models/registry.py +149 -0
  138. openpoints/models/segmentation/__init__.py +6 -0
  139. openpoints/models/segmentation/base_seg.py +278 -0
  140. openpoints/models/segmentation/vit_seg.py +126 -0
  141. openpoints/optim/__init__.py +15 -0
  142. openpoints/optim/adabelief.py +201 -0
  143. openpoints/optim/adafactor.py +167 -0
  144. openpoints/optim/adahessian.py +156 -0
  145. openpoints/optim/adamp.py +105 -0
  146. openpoints/optim/adamw.py +122 -0
  147. openpoints/optim/lamb.py +192 -0
  148. openpoints/optim/lars.py +135 -0
  149. openpoints/optim/lookahead.py +61 -0
  150. openpoints/optim/madgrad.py +184 -0
  151. openpoints/optim/nadam.py +92 -0
  152. openpoints/optim/nvnovograd.py +120 -0
  153. openpoints/optim/optim_factory.py +306 -0
  154. openpoints/optim/radam.py +89 -0
  155. openpoints/optim/rmsprop_tf.py +139 -0
  156. openpoints/optim/sgdp.py +70 -0
  157. openpoints/scheduler/__init__.py +8 -0
  158. openpoints/scheduler/cosine_lr.py +124 -0
  159. openpoints/scheduler/multistep_lr.py +65 -0
  160. openpoints/scheduler/plateau_lr.py +113 -0
  161. openpoints/scheduler/poly_lr.py +116 -0
  162. openpoints/scheduler/scheduler.py +110 -0
  163. openpoints/scheduler/scheduler_factory.py +118 -0
  164. openpoints/scheduler/step_lr.py +65 -0
  165. openpoints/scheduler/tanh_lr.py +117 -0
  166. openpoints/transforms/__init__.py +7 -0
  167. openpoints/transforms/point_transform_cpu.py +332 -0
  168. openpoints/transforms/point_transformer_gpu.py +764 -0
  169. openpoints/transforms/transforms_factory.py +60 -0
  170. openpoints/utils/__init__.py +8 -0
  171. openpoints/utils/ckpt_util.py +438 -0
  172. openpoints/utils/config.py +113 -0
  173. openpoints/utils/dist_utils.py +54 -0
  174. openpoints/utils/logger.py +170 -0
  175. openpoints/utils/metrics.py +311 -0
  176. openpoints/utils/random.py +16 -0
  177. openpoints/utils/registry.py +294 -0
  178. openpoints/utils/str2bool.py +11 -0
  179. openpoints/utils/wandb.py +88 -0
  180. openpoints-0.1.0.dist-info/METADATA +150 -0
  181. openpoints-0.1.0.dist-info/RECORD +184 -0
  182. openpoints-0.1.0.dist-info/WHEEL +5 -0
  183. openpoints-0.1.0.dist-info/licenses/LICENSE +21 -0
  184. openpoints-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ import numpy as np
2
+ import torch
3
+ from ..dataset_base import DatasetBase
4
+ from ..graph_dataset import GraphDataset
5
+ from ..graph_dataset import SVDEncodingsGraphDataset
6
+ from ..graph_dataset import StructuralDataset
7
+
8
+ class MOLPCBADataset(DatasetBase):
9
+ def __init__(self,
10
+ dataset_path ,
11
+ dataset_name = 'MOLPCBA' ,
12
+ **kwargs
13
+ ):
14
+ super().__init__(dataset_name = dataset_name,
15
+ **kwargs)
16
+ self.dataset_path = dataset_path
17
+
18
+ @property
19
+ def dataset(self):
20
+ try:
21
+ return self._dataset
22
+ except AttributeError:
23
+ from ogb.graphproppred import GraphPropPredDataset
24
+ self._dataset = GraphPropPredDataset(name='ogbg-molpcba', root=self.dataset_path)
25
+ return self._dataset
26
+
27
+ @property
28
+ def record_tokens(self):
29
+ try:
30
+ return self._record_tokens
31
+ except AttributeError:
32
+ split = {'training':'train',
33
+ 'validation':'valid',
34
+ 'test':'test'}[self.split]
35
+ self._record_tokens = self.dataset.get_idx_split()[split]
36
+ return self._record_tokens
37
+
38
+ def read_record(self, token):
39
+ graph, target = self.dataset[token]
40
+ graph['num_nodes'] = np.array(graph['num_nodes'], dtype=np.int16)
41
+ graph['edges'] = graph.pop('edge_index').T.astype(np.int16)
42
+ graph['edge_features'] = graph.pop('edge_feat').astype(np.int16)
43
+ graph['node_features'] = graph.pop('node_feat').astype(np.int16)
44
+ graph['target'] = np.array(target, np.float32)
45
+ return graph
46
+
47
+
48
+
49
+ class MOLPCBAGraphDataset(GraphDataset,MOLPCBADataset):
50
+ pass
51
+
52
+ class MOLPCBASVDGraphDataset(SVDEncodingsGraphDataset,MOLPCBADataset):
53
+ pass
54
+
55
+ class MOLPCBAStructuralGraphDataset(StructuralDataset,MOLPCBAGraphDataset):
56
+ pass
57
+
58
+ class MOLPCBAStructuralSVDGraphDataset(StructuralDataset,MOLPCBASVDGraphDataset):
59
+ pass
@@ -0,0 +1 @@
1
+ from .parser_factory import create_parser
@@ -0,0 +1,19 @@
1
+ import os
2
+
3
+
4
+ def load_class_map(map_or_filename, root=''):
5
+ if isinstance(map_or_filename, dict):
6
+ assert dict, 'class_map dict must be non-empty'
7
+ return map_or_filename
8
+ class_map_path = map_or_filename
9
+ if not os.path.exists(class_map_path):
10
+ class_map_path = os.path.join(root, class_map_path)
11
+ assert os.path.exists(class_map_path), 'Cannot locate specified class map file (%s)' % map_or_filename
12
+ class_map_ext = os.path.splitext(map_or_filename)[-1].lower()
13
+ if class_map_ext == '.txt':
14
+ with open(class_map_path) as f:
15
+ class_to_idx = {v.strip(): k for k, v in enumerate(f)}
16
+ else:
17
+ assert False, f'Unsupported class map file extension ({class_map_ext}).'
18
+ return class_to_idx
19
+
@@ -0,0 +1 @@
1
+ IMG_EXTENSIONS = ('.png', '.jpg', '.jpeg')
@@ -0,0 +1,17 @@
1
+ from abc import abstractmethod
2
+
3
+
4
+ class Parser:
5
+ def __init__(self):
6
+ pass
7
+
8
+ @abstractmethod
9
+ def _filename(self, index, basename=False, absolute=False):
10
+ pass
11
+
12
+ def filename(self, index, basename=False, absolute=False):
13
+ return self._filename(index, basename=basename, absolute=absolute)
14
+
15
+ def filenames(self, basename=False, absolute=False):
16
+ return [self._filename(index, basename=basename, absolute=absolute) for index in range(len(self))]
17
+
@@ -0,0 +1,29 @@
1
+ import os
2
+
3
+ from .parser_image_folder import ParserImageFolder
4
+ from .parser_image_tar import ParserImageTar
5
+ from .parser_image_in_tar import ParserImageInTar
6
+
7
+
8
+ def create_parser(name, root, split='train', **kwargs):
9
+ name = name.lower()
10
+ name = name.split('/', 2)
11
+ prefix = ''
12
+ if len(name) > 1:
13
+ prefix = name[0]
14
+ name = name[-1]
15
+
16
+ # FIXME improve the selection right now just tfds prefix or fallback path, will need options to
17
+ # explicitly select other options shortly
18
+ if prefix == 'tfds':
19
+ from .parser_tfds import ParserTfds # defer tensorflow import
20
+ parser = ParserTfds(root, name, split=split, **kwargs)
21
+ else:
22
+ assert os.path.exists(root)
23
+ # default fallback path (backwards compat), use image tar if root is a .tar file, otherwise image folder
24
+ # FIXME support split here, in parser?
25
+ if os.path.isfile(root) and os.path.splitext(root)[1] == '.tar':
26
+ parser = ParserImageInTar(root, **kwargs)
27
+ else:
28
+ parser = ParserImageFolder(root, **kwargs)
29
+ return parser
@@ -0,0 +1,69 @@
1
+ """ A dataset parser that reads images from folders
2
+
3
+ Folders are scannerd recursively to find image files. Labels are based
4
+ on the folder hierarchy, just leaf folders by default.
5
+
6
+ Hacked together by / Copyright 2020 Ross Wightman
7
+ """
8
+ import os
9
+
10
+ from openpoints.utils.misc import natural_key
11
+
12
+ from .parser import Parser
13
+ from .class_map import load_class_map
14
+ from .constants import IMG_EXTENSIONS
15
+
16
+
17
+ def find_images_and_targets(folder, types=IMG_EXTENSIONS, class_to_idx=None, leaf_name_only=True, sort=True):
18
+ labels = []
19
+ filenames = []
20
+ for root, subdirs, files in os.walk(folder, topdown=False, followlinks=True):
21
+ rel_path = os.path.relpath(root, folder) if (root != folder) else ''
22
+ label = os.path.basename(rel_path) if leaf_name_only else rel_path.replace(os.path.sep, '_')
23
+ for f in files:
24
+ base, ext = os.path.splitext(f)
25
+ if ext.lower() in types:
26
+ filenames.append(os.path.join(root, f))
27
+ labels.append(label)
28
+ if class_to_idx is None:
29
+ # building class index
30
+ unique_labels = set(labels)
31
+ sorted_labels = list(sorted(unique_labels, key=natural_key))
32
+ class_to_idx = {c: idx for idx, c in enumerate(sorted_labels)}
33
+ images_and_targets = [(f, class_to_idx[l]) for f, l in zip(filenames, labels) if l in class_to_idx]
34
+ if sort:
35
+ images_and_targets = sorted(images_and_targets, key=lambda k: natural_key(k[0]))
36
+ return images_and_targets, class_to_idx
37
+
38
+
39
+ class ParserImageFolder(Parser):
40
+
41
+ def __init__(
42
+ self,
43
+ root,
44
+ class_map=''):
45
+ super().__init__()
46
+
47
+ self.root = root
48
+ class_to_idx = None
49
+ if class_map:
50
+ class_to_idx = load_class_map(class_map, root)
51
+ self.samples, self.class_to_idx = find_images_and_targets(root, class_to_idx=class_to_idx)
52
+ if len(self.samples) == 0:
53
+ raise RuntimeError(
54
+ f'Found 0 images in subfolders of {root}. Supported image extensions are {", ".join(IMG_EXTENSIONS)}')
55
+
56
+ def __getitem__(self, index):
57
+ path, target = self.samples[index]
58
+ return open(path, 'rb'), target
59
+
60
+ def __len__(self):
61
+ return len(self.samples)
62
+
63
+ def _filename(self, index, basename=False, absolute=False):
64
+ filename = self.samples[index][0]
65
+ if basename:
66
+ filename = os.path.basename(filename)
67
+ elif not absolute:
68
+ filename = os.path.relpath(filename, self.root)
69
+ return filename
@@ -0,0 +1,222 @@
1
+ """ A dataset parser that reads tarfile based datasets
2
+
3
+ This parser can read and extract image samples from:
4
+ * a single tar of image files
5
+ * a folder of multiple tarfiles containing imagefiles
6
+ * a tar of tars containing image files
7
+
8
+ Labels are based on the combined folder and/or tar name structure.
9
+
10
+ Hacked together by / Copyright 2020 Ross Wightman
11
+ """
12
+ import os
13
+ import tarfile
14
+ import pickle
15
+ import logging
16
+ import numpy as np
17
+ from glob import glob
18
+ from typing import List, Dict
19
+
20
+ from openpoints.utils.misc import natural_key
21
+
22
+ from .parser import Parser
23
+ from .class_map import load_class_map
24
+ from .constants import IMG_EXTENSIONS
25
+
26
+
27
+ _logger = logging.getLogger(__name__)
28
+ CACHE_FILENAME_SUFFIX = '_tarinfos.pickle'
29
+
30
+
31
+ class TarState:
32
+
33
+ def __init__(self, tf: tarfile.TarFile = None, ti: tarfile.TarInfo = None):
34
+ self.tf: tarfile.TarFile = tf
35
+ self.ti: tarfile.TarInfo = ti
36
+ self.children: Dict[str, TarState] = {} # child states (tars within tars)
37
+
38
+ def reset(self):
39
+ self.tf = None
40
+
41
+
42
+ def _extract_tarinfo(tf: tarfile.TarFile, parent_info: Dict, extensions=IMG_EXTENSIONS):
43
+ sample_count = 0
44
+ for i, ti in enumerate(tf):
45
+ if not ti.isfile():
46
+ continue
47
+ dirname, basename = os.path.split(ti.path)
48
+ name, ext = os.path.splitext(basename)
49
+ ext = ext.lower()
50
+ if ext == '.tar':
51
+ with tarfile.open(fileobj=tf.extractfile(ti), mode='r|') as ctf:
52
+ child_info = dict(
53
+ name=ti.name, path=os.path.join(parent_info['path'], name), ti=ti, children=[], samples=[])
54
+ sample_count += _extract_tarinfo(ctf, child_info, extensions=extensions)
55
+ _logger.debug(f'{i}/?. Extracted child tarinfos from {ti.name}. {len(child_info["samples"])} images.')
56
+ parent_info['children'].append(child_info)
57
+ elif ext in extensions:
58
+ parent_info['samples'].append(ti)
59
+ sample_count += 1
60
+ return sample_count
61
+
62
+
63
+ def extract_tarinfos(root, class_name_to_idx=None, cache_tarinfo=None, extensions=IMG_EXTENSIONS, sort=True):
64
+ root_is_tar = False
65
+ if os.path.isfile(root):
66
+ assert os.path.splitext(root)[-1].lower() == '.tar'
67
+ tar_filenames = [root]
68
+ root, root_name = os.path.split(root)
69
+ root_name = os.path.splitext(root_name)[0]
70
+ root_is_tar = True
71
+ else:
72
+ root_name = root.strip(os.path.sep).split(os.path.sep)[-1]
73
+ tar_filenames = glob(os.path.join(root, '*.tar'), recursive=True)
74
+ num_tars = len(tar_filenames)
75
+ tar_bytes = sum([os.path.getsize(f) for f in tar_filenames])
76
+ assert num_tars, f'No .tar files found at specified path ({root}).'
77
+
78
+ _logger.info(f'Scanning {tar_bytes/1024**2:.2f}MB of tar files...')
79
+ info = dict(tartrees=[])
80
+ cache_path = ''
81
+ if cache_tarinfo is None:
82
+ cache_tarinfo = True if tar_bytes > 10*1024**3 else False # FIXME magic number, 10GB
83
+ if cache_tarinfo:
84
+ cache_filename = '_' + root_name + CACHE_FILENAME_SUFFIX
85
+ cache_path = os.path.join(root, cache_filename)
86
+ if os.path.exists(cache_path):
87
+ _logger.info(f'Reading tar info from cache file {cache_path}.')
88
+ with open(cache_path, 'rb') as pf:
89
+ info = pickle.load(pf)
90
+ assert len(info['tartrees']) == num_tars, "Cached tartree len doesn't match number of tarfiles"
91
+ else:
92
+ for i, fn in enumerate(tar_filenames):
93
+ path = '' if root_is_tar else os.path.splitext(os.path.basename(fn))[0]
94
+ with tarfile.open(fn, mode='r|') as tf: # tarinfo scans done in streaming mode
95
+ parent_info = dict(name=os.path.relpath(fn, root), path=path, ti=None, children=[], samples=[])
96
+ num_samples = _extract_tarinfo(tf, parent_info, extensions=extensions)
97
+ num_children = len(parent_info["children"])
98
+ _logger.debug(
99
+ f'{i}/{num_tars}. Extracted tarinfos from {fn}. {num_children} children, {num_samples} samples.')
100
+ info['tartrees'].append(parent_info)
101
+ if cache_path:
102
+ _logger.info(f'Writing tar info to cache file {cache_path}.')
103
+ with open(cache_path, 'wb') as pf:
104
+ pickle.dump(info, pf)
105
+
106
+ samples = []
107
+ labels = []
108
+ build_class_map = False
109
+ if class_name_to_idx is None:
110
+ build_class_map = True
111
+
112
+ # Flatten tartree info into lists of samples and targets w/ targets based on label id via
113
+ # class map arg or from unique paths.
114
+ # NOTE: currently only flattening up to two-levels, filesystem .tars and then one level of sub-tar children
115
+ # this covers my current use cases and keeps things a little easier to test for now.
116
+ tarfiles = []
117
+
118
+ def _label_from_paths(*path, leaf_only=True):
119
+ path = os.path.join(*path).strip(os.path.sep)
120
+ return path.split(os.path.sep)[-1] if leaf_only else path.replace(os.path.sep, '_')
121
+
122
+ def _add_samples(info, fn):
123
+ added = 0
124
+ for s in info['samples']:
125
+ label = _label_from_paths(info['path'], os.path.dirname(s.path))
126
+ if not build_class_map and label not in class_name_to_idx:
127
+ continue
128
+ samples.append((s, fn, info['ti']))
129
+ labels.append(label)
130
+ added += 1
131
+ return added
132
+
133
+ _logger.info(f'Collecting samples and building tar states.')
134
+ for parent_info in info['tartrees']:
135
+ # if tartree has children, we assume all samples are at the child level
136
+ tar_name = None if root_is_tar else parent_info['name']
137
+ tar_state = TarState()
138
+ parent_added = 0
139
+ for child_info in parent_info['children']:
140
+ child_added = _add_samples(child_info, fn=tar_name)
141
+ if child_added:
142
+ tar_state.children[child_info['name']] = TarState(ti=child_info['ti'])
143
+ parent_added += child_added
144
+ parent_added += _add_samples(parent_info, fn=tar_name)
145
+ if parent_added:
146
+ tarfiles.append((tar_name, tar_state))
147
+ del info
148
+
149
+ if build_class_map:
150
+ # build class index
151
+ sorted_labels = list(sorted(set(labels), key=natural_key))
152
+ class_name_to_idx = {c: idx for idx, c in enumerate(sorted_labels)}
153
+
154
+ _logger.info(f'Mapping targets and sorting samples.')
155
+ samples_and_targets = [(s, class_name_to_idx[l]) for s, l in zip(samples, labels) if l in class_name_to_idx]
156
+ if sort:
157
+ samples_and_targets = sorted(samples_and_targets, key=lambda k: natural_key(k[0][0].path))
158
+ samples, targets = zip(*samples_and_targets)
159
+ samples = np.array(samples)
160
+ targets = np.array(targets)
161
+ _logger.info(f'Finished processing {len(samples)} samples across {len(tarfiles)} tar files.')
162
+ return samples, targets, class_name_to_idx, tarfiles
163
+
164
+
165
+ class ParserImageInTar(Parser):
166
+ """ Multi-tarfile dataset parser where there is one .tar file per class
167
+ """
168
+
169
+ def __init__(self, root, class_map='', cache_tarfiles=True, cache_tarinfo=None):
170
+ super().__init__()
171
+
172
+ class_name_to_idx = None
173
+ if class_map:
174
+ class_name_to_idx = load_class_map(class_map, root)
175
+ self.root = root
176
+ self.samples, self.targets, self.class_name_to_idx, tarfiles = extract_tarinfos(
177
+ self.root,
178
+ class_name_to_idx=class_name_to_idx,
179
+ cache_tarinfo=cache_tarinfo,
180
+ extensions=IMG_EXTENSIONS)
181
+ self.class_idx_to_name = {v: k for k, v in self.class_name_to_idx.items()}
182
+ if len(tarfiles) == 1 and tarfiles[0][0] is None:
183
+ self.root_is_tar = True
184
+ self.tar_state = tarfiles[0][1]
185
+ else:
186
+ self.root_is_tar = False
187
+ self.tar_state = dict(tarfiles)
188
+ self.cache_tarfiles = cache_tarfiles
189
+
190
+ def __len__(self):
191
+ return len(self.samples)
192
+
193
+ def __getitem__(self, index):
194
+ sample = self.samples[index]
195
+ target = self.targets[index]
196
+ sample_ti, parent_fn, child_ti = sample
197
+ parent_abs = os.path.join(self.root, parent_fn) if parent_fn else self.root
198
+
199
+ tf = None
200
+ cache_state = None
201
+ if self.cache_tarfiles:
202
+ cache_state = self.tar_state if self.root_is_tar else self.tar_state[parent_fn]
203
+ tf = cache_state.tf
204
+ if tf is None:
205
+ tf = tarfile.open(parent_abs)
206
+ if self.cache_tarfiles:
207
+ cache_state.tf = tf
208
+ if child_ti is not None:
209
+ ctf = cache_state.children[child_ti.name].tf if self.cache_tarfiles else None
210
+ if ctf is None:
211
+ ctf = tarfile.open(fileobj=tf.extractfile(child_ti))
212
+ if self.cache_tarfiles:
213
+ cache_state.children[child_ti.name].tf = ctf
214
+ tf = ctf
215
+
216
+ return tf.extractfile(sample_ti), target
217
+
218
+ def _filename(self, index, basename=False, absolute=False):
219
+ filename = self.samples[index][0].name
220
+ if basename:
221
+ filename = os.path.basename(filename)
222
+ return filename
@@ -0,0 +1,72 @@
1
+ """ A dataset parser that reads single tarfile based datasets
2
+
3
+ This parser can read datasets consisting if a single tarfile containing images.
4
+ I am planning to deprecated it in favour of ParerImageInTar.
5
+
6
+ Hacked together by / Copyright 2020 Ross Wightman
7
+ """
8
+ import os
9
+ import tarfile
10
+
11
+ from .parser import Parser
12
+ from .class_map import load_class_map
13
+ from .constants import IMG_EXTENSIONS
14
+ from openpoints.utils.misc import natural_key
15
+
16
+
17
+ def extract_tarinfo(tarfile, class_to_idx=None, sort=True):
18
+ files = []
19
+ labels = []
20
+ for ti in tarfile.getmembers():
21
+ if not ti.isfile():
22
+ continue
23
+ dirname, basename = os.path.split(ti.path)
24
+ label = os.path.basename(dirname)
25
+ ext = os.path.splitext(basename)[1]
26
+ if ext.lower() in IMG_EXTENSIONS:
27
+ files.append(ti)
28
+ labels.append(label)
29
+ if class_to_idx is None:
30
+ unique_labels = set(labels)
31
+ sorted_labels = list(sorted(unique_labels, key=natural_key))
32
+ class_to_idx = {c: idx for idx, c in enumerate(sorted_labels)}
33
+ tarinfo_and_targets = [(f, class_to_idx[l]) for f, l in zip(files, labels) if l in class_to_idx]
34
+ if sort:
35
+ tarinfo_and_targets = sorted(tarinfo_and_targets, key=lambda k: natural_key(k[0].path))
36
+ return tarinfo_and_targets, class_to_idx
37
+
38
+
39
+ class ParserImageTar(Parser):
40
+ """ Single tarfile dataset where classes are mapped to folders within tar
41
+ NOTE: This class is being deprecated in favour of the more capable ParserImageInTar that can
42
+ operate on folders of tars or tars in tars.
43
+ """
44
+ def __init__(self, root, class_map=''):
45
+ super().__init__()
46
+
47
+ class_to_idx = None
48
+ if class_map:
49
+ class_to_idx = load_class_map(class_map, root)
50
+ assert os.path.isfile(root)
51
+ self.root = root
52
+
53
+ with tarfile.open(root) as tf: # cannot keep this open across processes, reopen later
54
+ self.samples, self.class_to_idx = extract_tarinfo(tf, class_to_idx)
55
+ self.imgs = self.samples
56
+ self.tarfile = None # lazy init in __getitem__
57
+
58
+ def __getitem__(self, index):
59
+ if self.tarfile is None:
60
+ self.tarfile = tarfile.open(self.root)
61
+ tarinfo, target = self.samples[index]
62
+ fileobj = self.tarfile.extractfile(tarinfo)
63
+ return fileobj, target
64
+
65
+ def __len__(self):
66
+ return len(self.samples)
67
+
68
+ def _filename(self, index, basename=False, absolute=False):
69
+ filename = self.samples[index][0].name
70
+ if basename:
71
+ filename = os.path.basename(filename)
72
+ return filename