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.
- openpoints/__init__.py +3 -0
- openpoints/cpp/__init__.py +6 -0
- openpoints/cpp/chamfer_dist/__init__.py +110 -0
- openpoints/cpp/chamfer_dist/setup.py +19 -0
- openpoints/cpp/chamfer_dist/test.py +34 -0
- openpoints/cpp/emd/__init__.py +3 -0
- openpoints/cpp/emd/emd.py +88 -0
- openpoints/cpp/emd/setup.py +27 -0
- openpoints/cpp/emd/test_emd_loss.py +45 -0
- openpoints/cpp/pointnet2_batch/__init__.py +24 -0
- openpoints/cpp/pointnet2_batch/setup.py +23 -0
- openpoints/cpp/pointops/__init__.py +0 -0
- openpoints/cpp/pointops/functions/__init__.py +0 -0
- openpoints/cpp/pointops/functions/pointops.py +314 -0
- openpoints/cpp/pointops/setup.py +37 -0
- openpoints/cpp/pointops/src/__init__.py +0 -0
- openpoints/dataset/__init__.py +10 -0
- openpoints/dataset/atom3d/__init__.py +1 -0
- openpoints/dataset/atom3d/psr.py +38 -0
- openpoints/dataset/build.py +98 -0
- openpoints/dataset/data_util.py +192 -0
- openpoints/dataset/datalist.py +67 -0
- openpoints/dataset/dataset_base.py +96 -0
- openpoints/dataset/graph_dataset/__init__.py +3 -0
- openpoints/dataset/graph_dataset/graph_dataset.py +93 -0
- openpoints/dataset/graph_dataset/stack_with_pad.py +91 -0
- openpoints/dataset/graph_dataset/structural_dataset.py +73 -0
- openpoints/dataset/graph_dataset/svd_encodings_dataset.py +110 -0
- openpoints/dataset/grid_sample.py +21 -0
- openpoints/dataset/matterport3d/__init__.py +1 -0
- openpoints/dataset/matterport3d/category_mapping.tsv +1660 -0
- openpoints/dataset/matterport3d/matterport3d.py +210 -0
- openpoints/dataset/matterport3d/matterport3d_dataprocessing.py +105 -0
- openpoints/dataset/modelnet/__init__.py +3 -0
- openpoints/dataset/modelnet/modelnet40_normal_resampled_loader.py +124 -0
- openpoints/dataset/modelnet/modelnet40_ply_2048_loader.py +160 -0
- openpoints/dataset/molhiv/__init__.py +1 -0
- openpoints/dataset/molhiv/data.py +59 -0
- openpoints/dataset/molpcba/__init__.py +1 -0
- openpoints/dataset/molpcba/data.py +59 -0
- openpoints/dataset/parsers/__init__.py +1 -0
- openpoints/dataset/parsers/class_map.py +19 -0
- openpoints/dataset/parsers/constants.py +1 -0
- openpoints/dataset/parsers/parser.py +17 -0
- openpoints/dataset/parsers/parser_factory.py +29 -0
- openpoints/dataset/parsers/parser_image_folder.py +69 -0
- openpoints/dataset/parsers/parser_image_in_tar.py +222 -0
- openpoints/dataset/parsers/parser_image_tar.py +72 -0
- openpoints/dataset/parsers/parser_tfds.py +297 -0
- openpoints/dataset/pcqm4m/__init__.py +1 -0
- openpoints/dataset/pcqm4m/data.py +62 -0
- openpoints/dataset/pcqm4mv2/__init__.py +1 -0
- openpoints/dataset/pcqm4mv2/data.py +87 -0
- openpoints/dataset/s3dis/__init__.py +2 -0
- openpoints/dataset/s3dis/s3dis.py +156 -0
- openpoints/dataset/s3dis/s3dis_block.py +96 -0
- openpoints/dataset/s3dis/s3dis_sphere.py +349 -0
- openpoints/dataset/scannetv2/__init__.py +1 -0
- openpoints/dataset/scannetv2/scannet.py +176 -0
- openpoints/dataset/scanobjectnn/__init__.py +3 -0
- openpoints/dataset/scanobjectnn/scanobjectnn.py +110 -0
- openpoints/dataset/semantic_kitti/__init__.py +1 -0
- openpoints/dataset/semantic_kitti/helper_tool.py +286 -0
- openpoints/dataset/semantic_kitti/label_mapping.yaml +211 -0
- openpoints/dataset/semantic_kitti/semantickitti.py +229 -0
- openpoints/dataset/semantic_kitti/utils/meta/anno_paths.txt +272 -0
- openpoints/dataset/semantic_kitti/utils/meta/class_names.txt +13 -0
- openpoints/dataset/semantic_kitti/utils/semantic-kitti.yaml +211 -0
- openpoints/dataset/shapenet/__init__.py +1 -0
- openpoints/dataset/shapenet/shapenet55.py +76 -0
- openpoints/dataset/shapenet/shapenetpart.py +121 -0
- openpoints/dataset/shapenetpart/__init__.py +1 -0
- openpoints/dataset/shapenetpart/shapenet55.py +75 -0
- openpoints/dataset/shapenetpart/shapenetpart.py +388 -0
- openpoints/dataset/vis2d.py +17 -0
- openpoints/dataset/vis3d.py +153 -0
- openpoints/loss/__init__.py +3 -0
- openpoints/loss/build.py +281 -0
- openpoints/loss/cross_entropy.py +38 -0
- openpoints/loss/distill_loss.py +76 -0
- openpoints/models/__init__.py +10 -0
- openpoints/models/backbone/Stratified_transformer.py +558 -0
- openpoints/models/backbone/__init__.py +11 -0
- openpoints/models/backbone/baafnet.py +527 -0
- openpoints/models/backbone/ball_dgcnn.py +123 -0
- openpoints/models/backbone/curvenet.py +793 -0
- openpoints/models/backbone/debug_invvit.py +114 -0
- openpoints/models/backbone/deepgcn.py +143 -0
- openpoints/models/backbone/dgcnn.py +119 -0
- openpoints/models/backbone/graphvit3d.py +134 -0
- openpoints/models/backbone/grouppointnet.py +100 -0
- openpoints/models/backbone/pct.py +163 -0
- openpoints/models/backbone/pointmlp.py +417 -0
- openpoints/models/backbone/pointnet.py +199 -0
- openpoints/models/backbone/pointnetv2.py +511 -0
- openpoints/models/backbone/pointnext.py +663 -0
- openpoints/models/backbone/pointnextPyG.py +555 -0
- openpoints/models/backbone/pointtransformer.py +293 -0
- openpoints/models/backbone/pointvector.py +853 -0
- openpoints/models/backbone/pointvit.py +392 -0
- openpoints/models/backbone/pointvit_inv.py +942 -0
- openpoints/models/backbone/pointvit_inv_old.py +784 -0
- openpoints/models/backbone/randlenet.py +318 -0
- openpoints/models/backbone/resnet.py +342 -0
- openpoints/models/backbone/simpleview.py +153 -0
- openpoints/models/backbone/simpleview_util.py +292 -0
- openpoints/models/build.py +13 -0
- openpoints/models/classification/__init__.py +5 -0
- openpoints/models/classification/cls_base.py +136 -0
- openpoints/models/classification/point_bert.py +154 -0
- openpoints/models/layers/__init__.py +14 -0
- openpoints/models/layers/activation.py +57 -0
- openpoints/models/layers/attention.py +103 -0
- openpoints/models/layers/conv.py +167 -0
- openpoints/models/layers/drop.py +164 -0
- openpoints/models/layers/graph_conv.py +122 -0
- openpoints/models/layers/group.py +415 -0
- openpoints/models/layers/group_embed.py +286 -0
- openpoints/models/layers/helpers.py +43 -0
- openpoints/models/layers/kmeans.py +119 -0
- openpoints/models/layers/knn.py +110 -0
- openpoints/models/layers/local_aggregation.py +286 -0
- openpoints/models/layers/mlp.py +129 -0
- openpoints/models/layers/norm.py +106 -0
- openpoints/models/layers/padding.py +56 -0
- openpoints/models/layers/patch_embed.py +37 -0
- openpoints/models/layers/registry.py +168 -0
- openpoints/models/layers/subsample.py +185 -0
- openpoints/models/layers/upsampling.py +106 -0
- openpoints/models/layers/weight_init.py +89 -0
- openpoints/models/reconstruction/__init__.py +8 -0
- openpoints/models/reconstruction/base_recontruct.py +216 -0
- openpoints/models/reconstruction/maskedpoint.py +116 -0
- openpoints/models/reconstruction/maskedpointgroup.py +168 -0
- openpoints/models/reconstruction/maskedpointvit.py +253 -0
- openpoints/models/reconstruction/nodeshuffle.py +11 -0
- openpoints/models/registry.py +149 -0
- openpoints/models/segmentation/__init__.py +6 -0
- openpoints/models/segmentation/base_seg.py +278 -0
- openpoints/models/segmentation/vit_seg.py +126 -0
- openpoints/optim/__init__.py +15 -0
- openpoints/optim/adabelief.py +201 -0
- openpoints/optim/adafactor.py +167 -0
- openpoints/optim/adahessian.py +156 -0
- openpoints/optim/adamp.py +105 -0
- openpoints/optim/adamw.py +122 -0
- openpoints/optim/lamb.py +192 -0
- openpoints/optim/lars.py +135 -0
- openpoints/optim/lookahead.py +61 -0
- openpoints/optim/madgrad.py +184 -0
- openpoints/optim/nadam.py +92 -0
- openpoints/optim/nvnovograd.py +120 -0
- openpoints/optim/optim_factory.py +306 -0
- openpoints/optim/radam.py +89 -0
- openpoints/optim/rmsprop_tf.py +139 -0
- openpoints/optim/sgdp.py +70 -0
- openpoints/scheduler/__init__.py +8 -0
- openpoints/scheduler/cosine_lr.py +124 -0
- openpoints/scheduler/multistep_lr.py +65 -0
- openpoints/scheduler/plateau_lr.py +113 -0
- openpoints/scheduler/poly_lr.py +116 -0
- openpoints/scheduler/scheduler.py +110 -0
- openpoints/scheduler/scheduler_factory.py +118 -0
- openpoints/scheduler/step_lr.py +65 -0
- openpoints/scheduler/tanh_lr.py +117 -0
- openpoints/transforms/__init__.py +7 -0
- openpoints/transforms/point_transform_cpu.py +332 -0
- openpoints/transforms/point_transformer_gpu.py +764 -0
- openpoints/transforms/transforms_factory.py +60 -0
- openpoints/utils/__init__.py +8 -0
- openpoints/utils/ckpt_util.py +438 -0
- openpoints/utils/config.py +113 -0
- openpoints/utils/dist_utils.py +54 -0
- openpoints/utils/logger.py +170 -0
- openpoints/utils/metrics.py +311 -0
- openpoints/utils/random.py +16 -0
- openpoints/utils/registry.py +294 -0
- openpoints/utils/str2bool.py +11 -0
- openpoints/utils/wandb.py +88 -0
- openpoints-0.1.0.dist-info/METADATA +150 -0
- openpoints-0.1.0.dist-info/RECORD +184 -0
- openpoints-0.1.0.dist-info/WHEEL +5 -0
- openpoints-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|