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,192 @@
|
|
|
1
|
+
from curses import keyname
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
import os
|
|
5
|
+
import os.path as osp
|
|
6
|
+
import ssl
|
|
7
|
+
import sys
|
|
8
|
+
import urllib
|
|
9
|
+
import h5py
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IO:
|
|
14
|
+
@classmethod
|
|
15
|
+
def get(cls, file_path):
|
|
16
|
+
_, file_extension = os.path.splitext(file_path)
|
|
17
|
+
|
|
18
|
+
if file_extension in ['.npy']:
|
|
19
|
+
return cls._read_npy(file_path)
|
|
20
|
+
elif file_extension in ['.pcd']:
|
|
21
|
+
return cls._read_pcd(file_path)
|
|
22
|
+
elif file_extension in ['.h5']:
|
|
23
|
+
return cls._read_h5(file_path)
|
|
24
|
+
elif file_extension in ['.txt']:
|
|
25
|
+
return cls._read_txt(file_path)
|
|
26
|
+
else:
|
|
27
|
+
raise Exception('Unsupported file extension: %s' % file_extension)
|
|
28
|
+
|
|
29
|
+
# References: https://github.com/numpy/numpy/blob/master/numpy/lib/format.py
|
|
30
|
+
@classmethod
|
|
31
|
+
def _read_npy(cls, file_path):
|
|
32
|
+
return np.load(file_path)
|
|
33
|
+
# # References: https://github.com/dimatura/pypcd/blob/master/pypcd/pypcd.py#L275
|
|
34
|
+
# # Support PCD files without compression ONLY!
|
|
35
|
+
# @classmethod
|
|
36
|
+
# def _read_pcd(cls, file_path):
|
|
37
|
+
# pc = open3d.io.read_point_cloud(file_path)
|
|
38
|
+
# ptcloud = np.array(pc.points)
|
|
39
|
+
# return ptcloud
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _read_txt(cls, file_path):
|
|
43
|
+
return np.loadtxt(file_path)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _read_h5(cls, file_path):
|
|
47
|
+
f = h5py.File(file_path, 'r')
|
|
48
|
+
return f['data'][()]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# download
|
|
52
|
+
def download_url(url: str, folder: str, log: bool = True,
|
|
53
|
+
filename: Optional[str] = None):
|
|
54
|
+
r"""Downloads the content of an URL to a specific folder.
|
|
55
|
+
Borrowed from https://github.com/pyg-team/pytorch_geometric/blob/master/torch_geometric/data/download.py
|
|
56
|
+
Args:
|
|
57
|
+
url (string): The url.
|
|
58
|
+
folder (string): The folder.
|
|
59
|
+
log (bool, optional): If :obj:`False`, will not print anything to the
|
|
60
|
+
console. (default: :obj:`True`)
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if filename is None:
|
|
64
|
+
filename = url.rpartition('/')[2]
|
|
65
|
+
filename = filename if filename[0] == '?' else filename.split('?')[0]
|
|
66
|
+
|
|
67
|
+
path = osp.join(folder, filename)
|
|
68
|
+
|
|
69
|
+
if osp.exists(path): # pragma: no cover
|
|
70
|
+
if log:
|
|
71
|
+
print(f'Using existing file {filename}', file=sys.stderr)
|
|
72
|
+
return path
|
|
73
|
+
|
|
74
|
+
if log:
|
|
75
|
+
print(f'Downloading {url}', file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
os.makedirs(folder, exist_ok=True)
|
|
78
|
+
context = ssl._create_unverified_context()
|
|
79
|
+
data = urllib.request.urlopen(url, context=context)
|
|
80
|
+
|
|
81
|
+
with open(path, 'wb') as f:
|
|
82
|
+
# workaround for https://bugs.python.org/issue42853
|
|
83
|
+
while True:
|
|
84
|
+
chunk = data.read(10 * 1024 * 1024)
|
|
85
|
+
if not chunk:
|
|
86
|
+
break
|
|
87
|
+
f.write(chunk)
|
|
88
|
+
|
|
89
|
+
return path
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def fnv_hash_vec(arr):
|
|
93
|
+
"""
|
|
94
|
+
FNV64-1A
|
|
95
|
+
"""
|
|
96
|
+
assert arr.ndim == 2
|
|
97
|
+
arr = arr.copy()
|
|
98
|
+
arr -= arr.min(0)
|
|
99
|
+
arr = arr.astype(np.uint64, copy=False)
|
|
100
|
+
hashed_arr = np.uint64(14695981039346656037) * \
|
|
101
|
+
np.ones(arr.shape[0], dtype=np.uint64)
|
|
102
|
+
for j in range(arr.shape[1]):
|
|
103
|
+
hashed_arr *= np.uint64(1099511628211)
|
|
104
|
+
hashed_arr = np.bitwise_xor(hashed_arr, arr[:, j])
|
|
105
|
+
return hashed_arr
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ravel_hash_vec(arr):
|
|
109
|
+
"""
|
|
110
|
+
Ravel the coordinates after subtracting the min coordinates.
|
|
111
|
+
"""
|
|
112
|
+
assert arr.ndim == 2
|
|
113
|
+
arr = arr.copy()
|
|
114
|
+
arr -= arr.min(0)
|
|
115
|
+
arr = arr.astype(np.uint64, copy=False)
|
|
116
|
+
arr_max = arr.max(0).astype(np.uint64) + 1
|
|
117
|
+
|
|
118
|
+
keys = np.zeros(arr.shape[0], dtype=np.uint64)
|
|
119
|
+
# Fortran style indexing
|
|
120
|
+
for j in range(arr.shape[1] - 1):
|
|
121
|
+
keys += arr[:, j]
|
|
122
|
+
keys *= arr_max[j + 1]
|
|
123
|
+
keys += arr[:, -1]
|
|
124
|
+
return keys
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def voxelize(coord, voxel_size=0.05, hash_type='fnv', mode=0):
|
|
128
|
+
discrete_coord = np.floor(coord / np.array(voxel_size))
|
|
129
|
+
if hash_type == 'ravel':
|
|
130
|
+
key = ravel_hash_vec(discrete_coord)
|
|
131
|
+
else:
|
|
132
|
+
key = fnv_hash_vec(discrete_coord)
|
|
133
|
+
|
|
134
|
+
idx_sort = np.argsort(key)
|
|
135
|
+
key_sort = key[idx_sort]
|
|
136
|
+
_, voxel_idx, count = np.unique(key_sort, return_counts=True, return_inverse=True)
|
|
137
|
+
if mode == 0: # train mode
|
|
138
|
+
idx_select = np.cumsum(np.insert(count, 0, 0)[
|
|
139
|
+
0:-1]) + np.random.randint(0, count.max(), count.size) % count
|
|
140
|
+
idx_unique = idx_sort[idx_select]
|
|
141
|
+
return idx_unique
|
|
142
|
+
else: # val mode
|
|
143
|
+
return idx_sort, voxel_idx, count
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def crop_pc(coord, feat, label, split='train',
|
|
147
|
+
voxel_size=0.04, voxel_max=None,
|
|
148
|
+
downsample=True, variable=True, shuffle=True):
|
|
149
|
+
if voxel_size and downsample:
|
|
150
|
+
# Is this shifting a must? I borrow it from Stratified Transformer and Point Transformer.
|
|
151
|
+
coord -= coord.min(0)
|
|
152
|
+
uniq_idx = voxelize(coord, voxel_size)
|
|
153
|
+
coord, feat, label = coord[uniq_idx], feat[uniq_idx] if feat is not None else None, label[uniq_idx] if label is not None else None
|
|
154
|
+
if voxel_max is not None:
|
|
155
|
+
crop_idx = None
|
|
156
|
+
N = len(label) # the number of points
|
|
157
|
+
if N >= voxel_max:
|
|
158
|
+
init_idx = np.random.randint(N) if 'train' in split else N // 2
|
|
159
|
+
crop_idx = np.argsort(
|
|
160
|
+
np.sum(np.square(coord - coord[init_idx]), 1))[:voxel_max]
|
|
161
|
+
elif not variable:
|
|
162
|
+
# fill more points for non-variable case (batched data)
|
|
163
|
+
cur_num_points = N
|
|
164
|
+
query_inds = np.arange(cur_num_points)
|
|
165
|
+
padding_choice = np.random.choice(
|
|
166
|
+
cur_num_points, voxel_max - cur_num_points)
|
|
167
|
+
crop_idx = np.hstack([query_inds, query_inds[padding_choice]])
|
|
168
|
+
crop_idx = np.arange(coord.shape[0]) if crop_idx is None else crop_idx
|
|
169
|
+
if shuffle:
|
|
170
|
+
shuffle_choice = np.random.permutation(np.arange(len(crop_idx)))
|
|
171
|
+
crop_idx = crop_idx[shuffle_choice]
|
|
172
|
+
coord, feat, label = coord[crop_idx], feat[crop_idx] if feat is not None else None, label[crop_idx] if label is not None else None
|
|
173
|
+
coord -= coord.min(0)
|
|
174
|
+
return coord.astype(np.float32), feat.astype(np.float32) if feat is not None else None , label.astype(np.long) if label is not None else None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_features_by_keys(data, keys='pos,x'):
|
|
178
|
+
key_list = keys.split(',')
|
|
179
|
+
if len(key_list) == 1:
|
|
180
|
+
return data[keys].transpose(1,2).contiguous()
|
|
181
|
+
else:
|
|
182
|
+
return torch.cat([data[key] for key in keys.split(',')], -1).transpose(1,2).contiguous()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_class_weights(num_per_class, normalize=False):
|
|
186
|
+
weight = num_per_class / float(sum(num_per_class))
|
|
187
|
+
ce_label_weight = 1 / (weight + 0.02)
|
|
188
|
+
|
|
189
|
+
if normalize:
|
|
190
|
+
ce_label_weight = (ce_label_weight *
|
|
191
|
+
len(ce_label_weight)) / ce_label_weight.sum()
|
|
192
|
+
return torch.from_numpy(ce_label_weight.astype(np.float32))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
from torch.utils.data import dataset
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DataList(dataset.Dataset):
|
|
10
|
+
def __init__(self,
|
|
11
|
+
dataset_name,
|
|
12
|
+
split,
|
|
13
|
+
data_list,
|
|
14
|
+
**kwargs):
|
|
15
|
+
super().__init__(**kwargs)
|
|
16
|
+
self.dataset_name = dataset_name
|
|
17
|
+
self.data_list = data_list
|
|
18
|
+
self.split = split
|
|
19
|
+
|
|
20
|
+
def load_data(self, data_path):
|
|
21
|
+
if 's3dis' in self.dataset_name:
|
|
22
|
+
data = np.load(data_path) # xyzrgbl, N*7
|
|
23
|
+
coord, feat, label = data[:, :3], data[:, 3:6], data[:, 6]
|
|
24
|
+
feat = np.clip(feat / 255., 0, 1).astype(np.float32)
|
|
25
|
+
elif 'scannet' in self.dataset_name:
|
|
26
|
+
data = torch.load(data_path) # xyzrgbl, N*7
|
|
27
|
+
if self.split != 'test':
|
|
28
|
+
coord, feat, label = data[0], data[1], data[2]
|
|
29
|
+
else:
|
|
30
|
+
coord, feat, label = data[0], data[1], None
|
|
31
|
+
feat = np.clip((feat + 1) / 2., 0, 1).astype(np.float32)
|
|
32
|
+
elif 'semantickitti' in self.dataset_name:
|
|
33
|
+
points = self.load_pc_kitti(pc_path)
|
|
34
|
+
labels = self.load_label_kitti(label_path, self.remap_lut_read)
|
|
35
|
+
|
|
36
|
+
coord -= coord.min(0)
|
|
37
|
+
|
|
38
|
+
idx_points = []
|
|
39
|
+
voxel_size = cfg.dataset.common.get('voxel_size', None)
|
|
40
|
+
if voxel_size is not None:
|
|
41
|
+
idx_sort, count = voxelize(coord, voxel_size, mode=1)
|
|
42
|
+
for i in range(count.max()):
|
|
43
|
+
idx_select = np.cumsum(np.insert(count, 0, 0)[0:-1]) + i % count
|
|
44
|
+
idx_part = idx_sort[idx_select]
|
|
45
|
+
np.random.shuffle(idx_part)
|
|
46
|
+
idx_points.append(idx_part)
|
|
47
|
+
else:
|
|
48
|
+
idx_points.append(np.arange(label.shape[0]))
|
|
49
|
+
return coord, feat, label, idx_points
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __len__(self):
|
|
54
|
+
return len(self.record_tokens)
|
|
55
|
+
|
|
56
|
+
def __getitem__(self, index):
|
|
57
|
+
token = self.record_tokens[index]
|
|
58
|
+
try:
|
|
59
|
+
return self._records[token]
|
|
60
|
+
except AttributeError:
|
|
61
|
+
record = self.read_record(token)
|
|
62
|
+
self._records = {token:record}
|
|
63
|
+
return record
|
|
64
|
+
except KeyError:
|
|
65
|
+
record = self.read_record(token)
|
|
66
|
+
self._records[token] = record
|
|
67
|
+
return record
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
from torch.utils.data import dataset
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatasetBase(dataset.Dataset):
|
|
9
|
+
def __init__(self,
|
|
10
|
+
dataset_name, split,
|
|
11
|
+
cache_dir = None,
|
|
12
|
+
load_cache_if_exists=True,
|
|
13
|
+
**kwargs):
|
|
14
|
+
super().__init__(**kwargs)
|
|
15
|
+
self.dataset_name = dataset_name
|
|
16
|
+
self.split = split
|
|
17
|
+
self.cache_dir = cache_dir
|
|
18
|
+
|
|
19
|
+
self.is_cached = False
|
|
20
|
+
if load_cache_if_exists:
|
|
21
|
+
self.cache(verbose=0, must_exist=True)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def record_tokens(self):
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
def read_record(self, token):
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
def __len__(self):
|
|
31
|
+
return len(self.record_tokens)
|
|
32
|
+
|
|
33
|
+
def __getitem__(self, index):
|
|
34
|
+
token = self.record_tokens[index]
|
|
35
|
+
try:
|
|
36
|
+
return self._records[token]
|
|
37
|
+
except AttributeError:
|
|
38
|
+
record = self.read_record(token)
|
|
39
|
+
self._records = {token:record}
|
|
40
|
+
return record
|
|
41
|
+
except KeyError:
|
|
42
|
+
record = self.read_record(token)
|
|
43
|
+
self._records[token] = record
|
|
44
|
+
return record
|
|
45
|
+
|
|
46
|
+
def read_all_records(self, verbose=1):
|
|
47
|
+
self._records = {}
|
|
48
|
+
if verbose:
|
|
49
|
+
print(f'Reading all {self.split} records...', flush=True)
|
|
50
|
+
for token in tqdm(self.record_tokens):
|
|
51
|
+
self._records[token] = self.read_record(token)
|
|
52
|
+
else:
|
|
53
|
+
for token in self.record_tokens:
|
|
54
|
+
self._records[token] = self.read_record(token)
|
|
55
|
+
|
|
56
|
+
def get_cache_path(self, path=None):
|
|
57
|
+
if path is None: path = self.cache_dir
|
|
58
|
+
base_path = (Path(path)/self.dataset_name)/self.split
|
|
59
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
return base_path
|
|
61
|
+
|
|
62
|
+
def cache_load_and_save(self, base_path, op, verbose):
|
|
63
|
+
tokens_path = base_path/'tokens.pt'
|
|
64
|
+
records_path = base_path/'records.pt'
|
|
65
|
+
|
|
66
|
+
if op == 'load':
|
|
67
|
+
self._record_tokens = torch.load(str(tokens_path))
|
|
68
|
+
self._records = torch.load(str(records_path))
|
|
69
|
+
elif op == 'save':
|
|
70
|
+
if tokens_path.exists() and records_path.exists() \
|
|
71
|
+
and hasattr(self, '_record_tokens') and hasattr(self, '_records'):
|
|
72
|
+
return
|
|
73
|
+
self.read_all_records(verbose=verbose)
|
|
74
|
+
torch.save(self.record_tokens, str(tokens_path))
|
|
75
|
+
torch.save(self._records, str(records_path))
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(f'Unknown operation: {op}')
|
|
78
|
+
|
|
79
|
+
def cache(self, path=None, verbose=1, must_exist=False):
|
|
80
|
+
if self.is_cached: return
|
|
81
|
+
|
|
82
|
+
base_path = self.get_cache_path(path)
|
|
83
|
+
try:
|
|
84
|
+
if verbose: print(f'Trying to load {self.split} cache from disk...', flush=True)
|
|
85
|
+
self.cache_load_and_save(base_path, 'load', verbose)
|
|
86
|
+
if verbose: print(f'Loaded {self.split} cache from disk.', flush=True)
|
|
87
|
+
except FileNotFoundError:
|
|
88
|
+
if must_exist: return
|
|
89
|
+
|
|
90
|
+
if verbose: print(f'{self.split} cache does not exist! Cacheing...', flush=True)
|
|
91
|
+
self.cache_load_and_save(base_path, 'save', verbose)
|
|
92
|
+
if verbose: print(f'Saved {self.split} cache to disk.', flush=True)
|
|
93
|
+
|
|
94
|
+
self.is_cached = True
|
|
95
|
+
|
|
96
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
import torch
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ..dataset_base import DatasetBase
|
|
6
|
+
|
|
7
|
+
from .stack_with_pad import stack_with_pad
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from numba.typed import List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GraphDataset(DatasetBase):
|
|
13
|
+
def __init__(self,
|
|
14
|
+
num_nodes_key = 'num_nodes',
|
|
15
|
+
edges_key = 'edges',
|
|
16
|
+
node_features_key = 'node_features',
|
|
17
|
+
edge_features_key = 'edge_features',
|
|
18
|
+
node_mask_key = 'node_mask',
|
|
19
|
+
targets_key = 'target',
|
|
20
|
+
include_node_mask = True,
|
|
21
|
+
**kwargs):
|
|
22
|
+
super().__init__(**kwargs)
|
|
23
|
+
self.num_nodes_key = num_nodes_key
|
|
24
|
+
self.edges_key = edges_key
|
|
25
|
+
self.node_features_key = node_features_key
|
|
26
|
+
self.edge_features_key = edge_features_key
|
|
27
|
+
self.node_mask_key = node_mask_key
|
|
28
|
+
self.targets_key = targets_key
|
|
29
|
+
self.include_node_mask = include_node_mask
|
|
30
|
+
|
|
31
|
+
def __getitem__(self, index):
|
|
32
|
+
item = super().__getitem__(index)
|
|
33
|
+
if self.include_node_mask:
|
|
34
|
+
item = item.copy()
|
|
35
|
+
item[self.node_mask_key] = np.ones((item[self.num_nodes_key],), dtype=np.uint8)
|
|
36
|
+
return item
|
|
37
|
+
|
|
38
|
+
def _calculate_max_nodes(self):
|
|
39
|
+
self._max_nodes = self[0][self.num_nodes_key]
|
|
40
|
+
self._max_nodes_index = 0
|
|
41
|
+
for i in range(1, super().__len__()):
|
|
42
|
+
graph = super().__getitem__(i)
|
|
43
|
+
cur_nodes = graph[self.num_nodes_key]
|
|
44
|
+
if cur_nodes > self._max_nodes:
|
|
45
|
+
self._max_nodes = cur_nodes
|
|
46
|
+
self._max_nodes_index = i
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def max_nodes(self):
|
|
50
|
+
try:
|
|
51
|
+
return self._max_nodes
|
|
52
|
+
except AttributeError:
|
|
53
|
+
self._calculate_max_nodes()
|
|
54
|
+
return self._max_nodes
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def max_nodes_index(self):
|
|
58
|
+
try:
|
|
59
|
+
return self._max_nodes_index
|
|
60
|
+
except AttributeError:
|
|
61
|
+
self._calculate_max_nodes()
|
|
62
|
+
return self._max_nodes_index
|
|
63
|
+
|
|
64
|
+
def cache_load_and_save(self, base_path, op, verbose):
|
|
65
|
+
super().cache_load_and_save(base_path, op, verbose)
|
|
66
|
+
max_nodes_path = base_path/'max_nodes_data.pt'
|
|
67
|
+
|
|
68
|
+
if op == 'load':
|
|
69
|
+
max_nodes_data = torch.load(str(max_nodes_path))
|
|
70
|
+
self._max_nodes = max_nodes_data['max_nodes']
|
|
71
|
+
self._max_nodes_index = max_nodes_data['max_nodes_index']
|
|
72
|
+
elif op == 'save':
|
|
73
|
+
if verbose: print(f'Calculating {self.split} max nodes...',flush=True)
|
|
74
|
+
max_nodes_data = {'max_nodes': self.max_nodes,
|
|
75
|
+
'max_nodes_index': self.max_nodes_index}
|
|
76
|
+
torch.save(max_nodes_data, str(max_nodes_path))
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f'Unknown operation: {op}')
|
|
79
|
+
|
|
80
|
+
def max_batch(self, batch_size, collate_fn):
|
|
81
|
+
return collate_fn([self.__getitem__(self.max_nodes_index)] * batch_size)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def graphdata_collate(batch):
|
|
86
|
+
batch_data = defaultdict(List)
|
|
87
|
+
for elem in batch:
|
|
88
|
+
for k,v in elem.items():
|
|
89
|
+
batch_data[k].append(v)
|
|
90
|
+
|
|
91
|
+
out = {k:torch.from_numpy(stack_with_pad(dat))
|
|
92
|
+
for k, dat in batch_data.items()}
|
|
93
|
+
return out
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numba as nb
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@nb.njit
|
|
6
|
+
def stack_with_pad_4d(inputs):
|
|
7
|
+
num_elem = len(inputs)
|
|
8
|
+
ms_0, ms_1, ms_2, ms_3 = inputs[0].shape
|
|
9
|
+
|
|
10
|
+
for i in range(1,num_elem):
|
|
11
|
+
is_0, is_1, is_2, is_3 = inputs[i].shape
|
|
12
|
+
ms_0 = max(is_0, ms_0)
|
|
13
|
+
ms_1 = max(is_1, ms_1)
|
|
14
|
+
ms_2 = max(is_2, ms_2)
|
|
15
|
+
ms_3 = max(is_3, ms_3)
|
|
16
|
+
|
|
17
|
+
stacked_shape = (num_elem,ms_0,ms_1,ms_2,ms_3)
|
|
18
|
+
stacked = np.zeros(stacked_shape, dtype=inputs[0].dtype)
|
|
19
|
+
|
|
20
|
+
for i, elem in enumerate(inputs):
|
|
21
|
+
stacked[i][:elem.shape[0],:elem.shape[1],:elem.shape[2],:elem.shape[3]] = elem
|
|
22
|
+
return stacked
|
|
23
|
+
|
|
24
|
+
@nb.njit
|
|
25
|
+
def stack_with_pad_3d(inputs):
|
|
26
|
+
num_elem = len(inputs)
|
|
27
|
+
ms_0, ms_1, ms_2 = inputs[0].shape
|
|
28
|
+
|
|
29
|
+
for i in range(1,num_elem):
|
|
30
|
+
is_0, is_1, is_2 = inputs[i].shape
|
|
31
|
+
ms_0 = max(is_0, ms_0)
|
|
32
|
+
ms_1 = max(is_1, ms_1)
|
|
33
|
+
ms_2 = max(is_2, ms_2)
|
|
34
|
+
|
|
35
|
+
stacked_shape = (num_elem,ms_0,ms_1,ms_2)
|
|
36
|
+
stacked = np.zeros(stacked_shape, dtype=inputs[0].dtype)
|
|
37
|
+
|
|
38
|
+
for i, elem in enumerate(inputs):
|
|
39
|
+
stacked[i][:elem.shape[0],:elem.shape[1],:elem.shape[2]] = elem
|
|
40
|
+
return stacked
|
|
41
|
+
|
|
42
|
+
@nb.njit
|
|
43
|
+
def stack_with_pad_2d(inputs):
|
|
44
|
+
num_elem = len(inputs)
|
|
45
|
+
ms_0, ms_1 = inputs[0].shape
|
|
46
|
+
|
|
47
|
+
for i in range(1,num_elem):
|
|
48
|
+
is_0, is_1 = inputs[i].shape
|
|
49
|
+
ms_0 = max(is_0, ms_0)
|
|
50
|
+
ms_1 = max(is_1, ms_1)
|
|
51
|
+
|
|
52
|
+
stacked_shape = (num_elem,ms_0,ms_1)
|
|
53
|
+
stacked = np.zeros(stacked_shape, dtype=inputs[0].dtype)
|
|
54
|
+
|
|
55
|
+
for i, elem in enumerate(inputs):
|
|
56
|
+
stacked[i][:elem.shape[0],:elem.shape[1]] = elem
|
|
57
|
+
return stacked
|
|
58
|
+
|
|
59
|
+
@nb.njit
|
|
60
|
+
def stack_with_pad_1d(inputs):
|
|
61
|
+
num_elem = len(inputs)
|
|
62
|
+
ms_0 = inputs[0].shape[0]
|
|
63
|
+
|
|
64
|
+
for i in range(1,num_elem):
|
|
65
|
+
is_0 = inputs[i].shape[0]
|
|
66
|
+
ms_0 = max(is_0, ms_0)
|
|
67
|
+
|
|
68
|
+
stacked_shape = (num_elem,ms_0)
|
|
69
|
+
stacked = np.zeros(stacked_shape, dtype=inputs[0].dtype)
|
|
70
|
+
|
|
71
|
+
for i, elem in enumerate(inputs):
|
|
72
|
+
stacked[i][:elem.shape[0]] = elem
|
|
73
|
+
return stacked
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def stack_with_pad(inputs):
|
|
77
|
+
shape_rank = np.ndim(inputs[0])
|
|
78
|
+
if shape_rank == 0:
|
|
79
|
+
return np.stack(inputs)
|
|
80
|
+
if shape_rank == 1:
|
|
81
|
+
return stack_with_pad_1d(inputs)
|
|
82
|
+
elif shape_rank == 2:
|
|
83
|
+
return stack_with_pad_2d(inputs)
|
|
84
|
+
elif shape_rank == 3:
|
|
85
|
+
return stack_with_pad_3d(inputs)
|
|
86
|
+
elif shape_rank == 4:
|
|
87
|
+
return stack_with_pad_4d(inputs)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError('Only support up to 4D tensor')
|
|
90
|
+
|
|
91
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numba as nb
|
|
3
|
+
|
|
4
|
+
from .graph_dataset import GraphDataset
|
|
5
|
+
|
|
6
|
+
NODE_FEATURES_OFFSET = 128
|
|
7
|
+
EDGE_FEATURES_OFFSET = 8
|
|
8
|
+
|
|
9
|
+
@nb.njit
|
|
10
|
+
def floyd_warshall(A):
|
|
11
|
+
n = A.shape[0]
|
|
12
|
+
D = np.zeros((n,n), dtype=np.int16)
|
|
13
|
+
|
|
14
|
+
for i in range(n):
|
|
15
|
+
for j in range(n):
|
|
16
|
+
if i == j:
|
|
17
|
+
pass
|
|
18
|
+
elif A[i,j] == 0:
|
|
19
|
+
D[i,j] = 510
|
|
20
|
+
else:
|
|
21
|
+
D[i,j] = 1
|
|
22
|
+
|
|
23
|
+
for k in range(n):
|
|
24
|
+
for i in range(n):
|
|
25
|
+
for j in range(n):
|
|
26
|
+
old_dist = D[i,j]
|
|
27
|
+
new_dist = D[i,k] + D[k,j]
|
|
28
|
+
if new_dist < old_dist:
|
|
29
|
+
D[i,j] = new_dist
|
|
30
|
+
return D
|
|
31
|
+
|
|
32
|
+
@nb.njit
|
|
33
|
+
def preprocess_data(num_nodes, edges, node_feats, edge_feats):
|
|
34
|
+
node_feats = node_feats + np.arange(1,node_feats.shape[-1]*NODE_FEATURES_OFFSET+1,
|
|
35
|
+
NODE_FEATURES_OFFSET,dtype=np.int16)
|
|
36
|
+
edge_feats = edge_feats + np.arange(1,edge_feats.shape[-1]*EDGE_FEATURES_OFFSET+1,
|
|
37
|
+
EDGE_FEATURES_OFFSET,dtype=np.int16)
|
|
38
|
+
|
|
39
|
+
A = np.zeros((num_nodes,num_nodes),dtype=np.int16)
|
|
40
|
+
E = np.zeros((num_nodes,num_nodes,edge_feats.shape[-1]),dtype=np.int16)
|
|
41
|
+
for k in range(edges.shape[0]):
|
|
42
|
+
i,j = edges[k,0], edges[k,1]
|
|
43
|
+
A[i,j] = 1
|
|
44
|
+
E[i,j] = edge_feats[k]
|
|
45
|
+
|
|
46
|
+
D = floyd_warshall(A)
|
|
47
|
+
return node_feats, D, E
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StructuralDataset(GraphDataset):
|
|
51
|
+
def __init__(self,
|
|
52
|
+
distance_matrix_key = 'distance_matrix',
|
|
53
|
+
feature_matrix_key = 'feature_matrix',
|
|
54
|
+
**kwargs):
|
|
55
|
+
super().__init__(**kwargs)
|
|
56
|
+
self.distance_matrix_key = distance_matrix_key
|
|
57
|
+
self.feature_matrix_key = feature_matrix_key
|
|
58
|
+
|
|
59
|
+
def __getitem__(self, index):
|
|
60
|
+
item = super().__getitem__(index)
|
|
61
|
+
|
|
62
|
+
num_nodes = int(item[self.num_nodes_key])
|
|
63
|
+
edges = item.pop(self.edges_key)
|
|
64
|
+
node_feats = item.pop(self.node_features_key)
|
|
65
|
+
edge_feats = item.pop(self.edge_features_key)
|
|
66
|
+
|
|
67
|
+
node_feats, dist_mat, edge_feats_mat = preprocess_data(num_nodes, edges, node_feats, edge_feats)
|
|
68
|
+
item[self.node_features_key] = node_feats
|
|
69
|
+
item[self.distance_matrix_key] = dist_mat
|
|
70
|
+
item[self.feature_matrix_key] = edge_feats_mat
|
|
71
|
+
|
|
72
|
+
return item
|
|
73
|
+
|