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,170 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import os.path as osp
|
|
5
|
+
import sys
|
|
6
|
+
from termcolor import colored
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import shortuuid
|
|
10
|
+
import pathlib
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _ColorfulFormatter(logging.Formatter):
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
self._root_name = kwargs.pop("root_name") + "."
|
|
17
|
+
self._abbrev_name = kwargs.pop("abbrev_name", "")
|
|
18
|
+
if len(self._abbrev_name):
|
|
19
|
+
self._abbrev_name = self._abbrev_name + "."
|
|
20
|
+
super(_ColorfulFormatter, self).__init__(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
def formatMessage(self, record):
|
|
23
|
+
record.name = record.name.replace(self._root_name, self._abbrev_name)
|
|
24
|
+
log = super(_ColorfulFormatter, self).formatMessage(record)
|
|
25
|
+
if record.levelno == logging.WARNING:
|
|
26
|
+
prefix = colored("WARNING", "red", attrs=["blink"])
|
|
27
|
+
elif record.levelno == logging.ERROR or record.levelno == logging.CRITICAL:
|
|
28
|
+
prefix = colored("ERROR", "red", attrs=["blink", "underline"])
|
|
29
|
+
else:
|
|
30
|
+
return log
|
|
31
|
+
return prefix + " " + log
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# so that calling setup_logger multiple times won't add many handlers
|
|
35
|
+
@functools.lru_cache()
|
|
36
|
+
def setup_logger_dist(output=None,
|
|
37
|
+
distributed_rank=0,
|
|
38
|
+
*,
|
|
39
|
+
color=True,
|
|
40
|
+
name="moco",
|
|
41
|
+
abbrev_name=None):
|
|
42
|
+
"""
|
|
43
|
+
Initialize the detectron2 logger and set its verbosity level to "INFO".
|
|
44
|
+
Args:
|
|
45
|
+
output (str): a file name or a directory to save log. If None, will not save log file.
|
|
46
|
+
If ends with ".txt" or ".log", assumed to be a file name.
|
|
47
|
+
Otherwise, logs will be saved to `output/log.txt`.
|
|
48
|
+
name (str): the root module name of this logger
|
|
49
|
+
Returns:
|
|
50
|
+
logging.Logger: a logger
|
|
51
|
+
"""
|
|
52
|
+
logger = logging.getLogger(name)
|
|
53
|
+
logger.setLevel(logging.DEBUG)
|
|
54
|
+
logger.propagate = False
|
|
55
|
+
|
|
56
|
+
if abbrev_name is None:
|
|
57
|
+
abbrev_name = name
|
|
58
|
+
|
|
59
|
+
plain_formatter = logging.Formatter(
|
|
60
|
+
"[%(asctime)s] %(name)s %(levelname)s: %(message)s",
|
|
61
|
+
datefmt="%m/%d %H:%M:%S")
|
|
62
|
+
# stdout logging: master only
|
|
63
|
+
if distributed_rank == 0:
|
|
64
|
+
ch = logging.StreamHandler(stream=sys.stdout)
|
|
65
|
+
ch.setLevel(logging.DEBUG)
|
|
66
|
+
if color:
|
|
67
|
+
formatter = _ColorfulFormatter(
|
|
68
|
+
colored("[%(asctime)s %(name)s]: ", "green") + "%(message)s",
|
|
69
|
+
datefmt="%m/%d %H:%M:%S",
|
|
70
|
+
root_name=name,
|
|
71
|
+
abbrev_name=str(abbrev_name),
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
formatter = plain_formatter
|
|
75
|
+
ch.setFormatter(formatter)
|
|
76
|
+
logger.addHandler(ch)
|
|
77
|
+
|
|
78
|
+
# file logging: all workers
|
|
79
|
+
if output is not None:
|
|
80
|
+
if output.endswith(".txt") or output.endswith(".log"):
|
|
81
|
+
filename = output
|
|
82
|
+
else:
|
|
83
|
+
filename = os.path.join(output, "log.txt")
|
|
84
|
+
if distributed_rank > 0:
|
|
85
|
+
filename = filename + f".rank{distributed_rank}"
|
|
86
|
+
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
|
87
|
+
|
|
88
|
+
fh = logging.StreamHandler(_cached_log_stream(filename))
|
|
89
|
+
fh.setLevel(logging.DEBUG)
|
|
90
|
+
fh.setFormatter(plain_formatter)
|
|
91
|
+
logger.addHandler(fh)
|
|
92
|
+
logging.root = logger # main logger.
|
|
93
|
+
return logger
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# cache the opened file object, so that different calls to `setup_logger`
|
|
97
|
+
# with the same file name can safely write to the same file.
|
|
98
|
+
@functools.lru_cache(maxsize=None)
|
|
99
|
+
def _cached_log_stream(filename):
|
|
100
|
+
return open(filename, "a")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ================ experiment folder ==================
|
|
104
|
+
def generate_exp_directory(cfg,
|
|
105
|
+
exp_name=None,
|
|
106
|
+
expid=None,
|
|
107
|
+
run_name=None,
|
|
108
|
+
additional_id=None):
|
|
109
|
+
"""Function to create checkpoint folder.
|
|
110
|
+
Args:
|
|
111
|
+
cfg: configuration dict
|
|
112
|
+
cfg.root_dir: the root dir for saving log files.
|
|
113
|
+
exp_name: exp_name or tags for saving and generating the exp_name
|
|
114
|
+
expid: id for the current run
|
|
115
|
+
run_name: the name for the current run. auto generated if None
|
|
116
|
+
Returns:
|
|
117
|
+
the exp_name, jobname, and folders into cfg
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
if run_name is None:
|
|
121
|
+
if expid is None:
|
|
122
|
+
expid = time.strftime('%Y%m%d-%H%M%S-') + str(shortuuid.uuid())
|
|
123
|
+
# expid = time.strftime('%Y%m%d-%H%M%S')
|
|
124
|
+
if additional_id is not None:
|
|
125
|
+
expid += '-' + str(additional_id)
|
|
126
|
+
if isinstance(exp_name, list):
|
|
127
|
+
exp_name = '-'.join(exp_name)
|
|
128
|
+
run_name = '-'.join([exp_name, expid])
|
|
129
|
+
cfg.run_name = run_name
|
|
130
|
+
cfg.run_dir = os.path.join(cfg.root_dir, cfg.run_name)
|
|
131
|
+
cfg.exp_dir = cfg.run_dir
|
|
132
|
+
cfg.log_dir = cfg.run_dir
|
|
133
|
+
cfg.ckpt_dir = os.path.join(cfg.run_dir, 'checkpoint')
|
|
134
|
+
cfg.log_path = os.path.join(cfg.run_dir, cfg.run_name + '.log')
|
|
135
|
+
|
|
136
|
+
if cfg.get('rank', 0) == 0:
|
|
137
|
+
pathlib.Path(cfg.ckpt_dir).mkdir(parents=True, exist_ok=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def resume_exp_directory(cfg, pretrained_path=None):
|
|
141
|
+
"""Function to resume the exp folder from the checkpoint folder.
|
|
142
|
+
Args:
|
|
143
|
+
cfg
|
|
144
|
+
pretrained_path: the path to the pretrained model
|
|
145
|
+
Returns:
|
|
146
|
+
the exp_name, jobname, and folders into cfg
|
|
147
|
+
"""
|
|
148
|
+
pretrained_path = pretrained_path or cfg.get('pretrained_path', None) or cfg.get('pretrained_path', None)
|
|
149
|
+
if os.path.basename(os.path.dirname(pretrained_path)) == 'checkpoint':
|
|
150
|
+
cfg.run_dir = os.path.dirname(os.path.dirname(cfg.pretrained_path))
|
|
151
|
+
cfg.log_dir = cfg.run_dir
|
|
152
|
+
cfg.run_name = os.path.basename(cfg.run_dir)
|
|
153
|
+
cfg.ckpt_dir = os.path.join(cfg.run_dir, 'checkpoint')
|
|
154
|
+
cfg.code_dir = os.path.join(cfg.run_dir, 'code')
|
|
155
|
+
# we further config the name by datetime
|
|
156
|
+
cfg.log_path = os.path.join(
|
|
157
|
+
cfg.run_dir, cfg.run_name + time.strftime('%Y%m%d-%H%M%S-') +
|
|
158
|
+
str(shortuuid.uuid()) + '.log')
|
|
159
|
+
else:
|
|
160
|
+
expid = time.strftime('%Y%m%d-%H%M%S-') + str(shortuuid.uuid())
|
|
161
|
+
cfg.run_name = '_'.join([os.path.basename(pretrained_path), expid])
|
|
162
|
+
cfg.run_dir = os.path.join(cfg.root_dir, cfg.run_name)
|
|
163
|
+
cfg.log_dir = cfg.run_dir
|
|
164
|
+
cfg.ckpt_dir = os.path.join(cfg.run_dir, 'checkpoint')
|
|
165
|
+
cfg.code_dir = os.path.join(cfg.run_dir, 'code')
|
|
166
|
+
cfg.log_path = os.path.join(cfg.run_dir, cfg.run_name + '.log')
|
|
167
|
+
|
|
168
|
+
if cfg.get('rank', 0) == 0:
|
|
169
|
+
os.makedirs(cfg.run_dir, exist_ok=True)
|
|
170
|
+
cfg.wandb.tags = ['resume']
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
from math import log10
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
from sklearn.metrics import confusion_matrix
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def PSNR(mse, peak=1.):
|
|
9
|
+
return 10 * log10((peak ** 2) / mse)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SegMetric:
|
|
13
|
+
def __init__(self, values=0.):
|
|
14
|
+
assert isinstance(values, dict)
|
|
15
|
+
self.miou = values.miou
|
|
16
|
+
self.oa = values.get('oa', None)
|
|
17
|
+
self.miou = values.miou
|
|
18
|
+
self.miou = values.miou
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def better_than(self, other):
|
|
22
|
+
if self.acc > other.acc:
|
|
23
|
+
return True
|
|
24
|
+
else:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
def state_dict(self):
|
|
28
|
+
_dict = dict()
|
|
29
|
+
_dict['acc'] = self.acc
|
|
30
|
+
return _dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AverageMeter(object):
|
|
34
|
+
"""Computes and stores the average and current value"""
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.reset()
|
|
37
|
+
|
|
38
|
+
def reset(self):
|
|
39
|
+
self.val = 0
|
|
40
|
+
self.avg = 0
|
|
41
|
+
self.sum = 0
|
|
42
|
+
self.count = 0
|
|
43
|
+
|
|
44
|
+
def update(self, val, n=1):
|
|
45
|
+
self.val = val
|
|
46
|
+
self.sum += val * n
|
|
47
|
+
self.count += n
|
|
48
|
+
self.avg = self.sum / self.count
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ConfusionMatrix:
|
|
52
|
+
"""Accumulate a confusion matrix for a classification task.
|
|
53
|
+
ignore_index only supports index <0, or > num_classes
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, num_classes, ignore_index=None):
|
|
57
|
+
self.value = 0
|
|
58
|
+
self.num_classes = num_classes
|
|
59
|
+
self.virtual_num_classes = num_classes + 1 if ignore_index is not None else num_classes
|
|
60
|
+
self.ignore_index = ignore_index
|
|
61
|
+
|
|
62
|
+
@torch.no_grad()
|
|
63
|
+
def update(self, pred, true):
|
|
64
|
+
"""Update the confusion matrix with the given predictions."""
|
|
65
|
+
true = true.flatten()
|
|
66
|
+
pred = pred.flatten()
|
|
67
|
+
if self.ignore_index is not None:
|
|
68
|
+
if (true == self.ignore_index).sum() > 0:
|
|
69
|
+
pred[true == self.ignore_index] = self.virtual_num_classes -1
|
|
70
|
+
true[true == self.ignore_index] = self.virtual_num_classes -1
|
|
71
|
+
unique_mapping = true.flatten() * self.virtual_num_classes + pred.flatten()
|
|
72
|
+
bins = torch.bincount(unique_mapping, minlength=self.virtual_num_classes**2)
|
|
73
|
+
self.value += bins.view(self.virtual_num_classes, self.virtual_num_classes)[:self.num_classes, :self.num_classes]
|
|
74
|
+
|
|
75
|
+
def reset(self):
|
|
76
|
+
"""Reset all accumulated values."""
|
|
77
|
+
self.value = 0
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def tp(self):
|
|
81
|
+
"""Get the true positive samples per-class."""
|
|
82
|
+
return self.value.diag()
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def actual(self):
|
|
86
|
+
"""Get the false negative samples per-class."""
|
|
87
|
+
return self.value.sum(dim=1)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def predicted(self):
|
|
91
|
+
"""Get the false negative samples per-class."""
|
|
92
|
+
return self.value.sum(dim=0)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def fn(self):
|
|
96
|
+
"""Get the false negative samples per-class."""
|
|
97
|
+
return self.actual - self.tp
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def fp(self):
|
|
101
|
+
"""Get the false positive samples per-class."""
|
|
102
|
+
return self.predicted - self.tp
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def tn(self):
|
|
106
|
+
"""Get the true negative samples per-class."""
|
|
107
|
+
actual = self.actual
|
|
108
|
+
predicted = self.predicted
|
|
109
|
+
return actual.sum() + self.tp - (actual + predicted)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def count(self): # a.k.a. actual positive class
|
|
113
|
+
"""Get the number of samples per-class."""
|
|
114
|
+
# return self.tp + self.fn
|
|
115
|
+
return self.value.sum(dim=1)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def frequency(self):
|
|
119
|
+
"""Get the per-class frequency."""
|
|
120
|
+
# we avoid dividing by zero using: max(denomenator, 1)
|
|
121
|
+
# return self.count / self.total.clamp(min=1)
|
|
122
|
+
count = self.value.sum(dim=1)
|
|
123
|
+
return count / count.sum().clamp(min=1)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def total(self):
|
|
127
|
+
"""Get the total number of samples."""
|
|
128
|
+
return self.value.sum()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def overall_accuray(self):
|
|
132
|
+
return self.tp.sum() / self.total
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def union(self):
|
|
136
|
+
return self.value.sum(dim=0) + self.value.sum(dim=1) - self.value.diag()
|
|
137
|
+
|
|
138
|
+
def all_acc(self):
|
|
139
|
+
return self.cal_acc(self.tp, self.count)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def cal_acc(tp, count):
|
|
143
|
+
acc_per_cls = tp / count.clamp(min=1) * 100
|
|
144
|
+
over_all_acc = tp.sum() / count.sum() * 100
|
|
145
|
+
macc = torch.mean(acc_per_cls) # class accuracy
|
|
146
|
+
return macc.item(), over_all_acc.item(), acc_per_cls.cpu().numpy()
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def print_acc(accs):
|
|
150
|
+
out = '\n Class ' + ' Acc '
|
|
151
|
+
for i, values in enumerate(accs):
|
|
152
|
+
out += '\n' + str(i).rjust(8) + f'{values.item():.2f}'.rjust(8)
|
|
153
|
+
out += '\n' + '-' * 20
|
|
154
|
+
out += '\n' + ' Mean ' + f'{torch.mean(accs).item():.2f}'.rjust(8)
|
|
155
|
+
logging.info(out)
|
|
156
|
+
|
|
157
|
+
def all_metrics(self):
|
|
158
|
+
tp, fp, fn = self.tp, self.fp, self.fn,
|
|
159
|
+
|
|
160
|
+
iou_per_cls = tp / (tp + fp + fn).clamp(min=1) * 100
|
|
161
|
+
acc_per_cls = tp / self.count.clamp(min=1) * 100
|
|
162
|
+
over_all_acc = tp.sum() / self.total * 100
|
|
163
|
+
|
|
164
|
+
miou = torch.mean(iou_per_cls)
|
|
165
|
+
macc = torch.mean(acc_per_cls) # class accuracy
|
|
166
|
+
return miou.item(), macc.item(), over_all_acc.item(), iou_per_cls.cpu().numpy(), acc_per_cls.cpu().numpy()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_mious(tp, union, count):
|
|
170
|
+
iou_per_cls = (tp + 1e-10) / (union + 1e-10) * 100
|
|
171
|
+
acc_per_cls = (tp + 1e-10) / (count + 1e-10) * 100
|
|
172
|
+
over_all_acc = tp.sum() / count.sum() * 100
|
|
173
|
+
|
|
174
|
+
miou = torch.mean(iou_per_cls)
|
|
175
|
+
macc = torch.mean(acc_per_cls) # class accuracy
|
|
176
|
+
return miou.item(), macc.item(), over_all_acc.item(), iou_per_cls.cpu().numpy(), acc_per_cls.cpu().numpy()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def partnet_metrics(num_classes, num_parts, objects, preds, targets):
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
num_classes:
|
|
184
|
+
num_parts:
|
|
185
|
+
objects: [int]
|
|
186
|
+
preds:[(num_parts,num_points)]
|
|
187
|
+
targets: [(num_points)]
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
shape_iou_tot = [0.0] * num_classes
|
|
193
|
+
shape_iou_cnt = [0] * num_classes
|
|
194
|
+
part_intersect = [np.zeros((num_parts[o_l]), dtype=np.float32) for o_l in range(num_classes)]
|
|
195
|
+
part_union = [np.zeros((num_parts[o_l]), dtype=np.float32) + 1e-6 for o_l in range(num_classes)]
|
|
196
|
+
|
|
197
|
+
for obj, cur_pred, cur_gt in zip(objects, preds, targets):
|
|
198
|
+
cur_num_parts = num_parts[obj]
|
|
199
|
+
cur_pred = np.argmax(cur_pred[1:, :], axis=0) + 1
|
|
200
|
+
cur_pred[cur_gt == 0] = 0
|
|
201
|
+
cur_shape_iou_tot = 0.0
|
|
202
|
+
cur_shape_iou_cnt = 0
|
|
203
|
+
for j in range(1, cur_num_parts):
|
|
204
|
+
cur_gt_mask = (cur_gt == j)
|
|
205
|
+
cur_pred_mask = (cur_pred == j)
|
|
206
|
+
|
|
207
|
+
has_gt = (np.sum(cur_gt_mask) > 0)
|
|
208
|
+
has_pred = (np.sum(cur_pred_mask) > 0)
|
|
209
|
+
|
|
210
|
+
if has_gt or has_pred:
|
|
211
|
+
intersect = np.sum(cur_gt_mask & cur_pred_mask)
|
|
212
|
+
union = np.sum(cur_gt_mask | cur_pred_mask)
|
|
213
|
+
iou = intersect / union
|
|
214
|
+
|
|
215
|
+
cur_shape_iou_tot += iou
|
|
216
|
+
cur_shape_iou_cnt += 1
|
|
217
|
+
|
|
218
|
+
part_intersect[obj][j] += intersect
|
|
219
|
+
part_union[obj][j] += union
|
|
220
|
+
if cur_shape_iou_cnt > 0:
|
|
221
|
+
cur_shape_miou = cur_shape_iou_tot / cur_shape_iou_cnt
|
|
222
|
+
shape_iou_tot[obj] += cur_shape_miou
|
|
223
|
+
shape_iou_cnt[obj] += 1
|
|
224
|
+
|
|
225
|
+
msIoU = [shape_iou_tot[o_l] / shape_iou_cnt[o_l] for o_l in range(num_classes)]
|
|
226
|
+
part_iou = [np.divide(part_intersect[o_l][1:], part_union[o_l][1:]) for o_l in range(num_classes)]
|
|
227
|
+
mpIoU = [np.mean(part_iou[o_l]) for o_l in range(num_classes)]
|
|
228
|
+
|
|
229
|
+
# Print instance mean
|
|
230
|
+
mmsIoU = np.mean(np.array(msIoU))
|
|
231
|
+
mmpIoU = np.mean(mpIoU)
|
|
232
|
+
|
|
233
|
+
return msIoU, mpIoU, mmsIoU, mmpIoU
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def IoU_from_confusions(confusions):
|
|
237
|
+
"""
|
|
238
|
+
Computes IoU from confusion matrices.
|
|
239
|
+
:param confusions: ([..., n_c, n_c] np.int32). Can be any dimension, the confusion matrices should be described by
|
|
240
|
+
the last axes. n_c = number of classes
|
|
241
|
+
:param ignore_unclassified: (bool). True if the the first class should be ignored in the results
|
|
242
|
+
:return: ([..., n_c] np.float32) IoU score
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
# Compute TP, FP, FN. This assume that the second to last axis counts the truths (like the first axis of a
|
|
246
|
+
# confusion matrix), and that the last axis counts the predictions (like the second axis of a confusion matrix)
|
|
247
|
+
TP = np.diagonal(confusions, axis1=-2, axis2=-1)
|
|
248
|
+
TP_plus_FN = np.sum(confusions, axis=-1)
|
|
249
|
+
TP_plus_FP = np.sum(confusions, axis=-2)
|
|
250
|
+
|
|
251
|
+
# Compute IoU
|
|
252
|
+
IoU = TP / (TP_plus_FP + TP_plus_FN - TP + 1e-6)
|
|
253
|
+
|
|
254
|
+
# Compute miou with only the actual classes
|
|
255
|
+
mask = TP_plus_FN < 1e-3
|
|
256
|
+
counts = np.sum(1 - mask, axis=-1, keepdims=True)
|
|
257
|
+
miou = np.sum(IoU, axis=-1, keepdims=True) / (counts + 1e-6)
|
|
258
|
+
|
|
259
|
+
# If class is absent, place miou in place of 0 IoU to get the actual mean later
|
|
260
|
+
IoU += mask * miou
|
|
261
|
+
|
|
262
|
+
return IoU
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def shapenetpart_metrics(num_classes, num_parts, objects, preds, targets, masks):
|
|
266
|
+
"""
|
|
267
|
+
Args:
|
|
268
|
+
num_classes:
|
|
269
|
+
num_parts:
|
|
270
|
+
objects: [int]
|
|
271
|
+
preds:[(num_parts,num_points)]
|
|
272
|
+
targets: [(num_points)]
|
|
273
|
+
masks: [(num_points)]
|
|
274
|
+
"""
|
|
275
|
+
total_correct = 0.0
|
|
276
|
+
total_seen = 0.0
|
|
277
|
+
Confs = []
|
|
278
|
+
for obj, cur_pred, cur_gt, cur_mask in zip(objects, preds, targets, masks):
|
|
279
|
+
obj = int(obj)
|
|
280
|
+
cur_num_parts = num_parts[obj]
|
|
281
|
+
cur_pred = np.argmax(cur_pred, axis=0)
|
|
282
|
+
cur_pred = cur_pred[cur_mask]
|
|
283
|
+
cur_gt = cur_gt[cur_mask]
|
|
284
|
+
correct = np.sum(cur_pred == cur_gt)
|
|
285
|
+
total_correct += correct
|
|
286
|
+
total_seen += cur_pred.shape[0]
|
|
287
|
+
parts = [j for j in range(cur_num_parts)]
|
|
288
|
+
Confs += [confusion_matrix(cur_gt, cur_pred, labels=parts)]
|
|
289
|
+
|
|
290
|
+
Confs = np.array(Confs)
|
|
291
|
+
obj_mious = []
|
|
292
|
+
objects = np.asarray(objects)
|
|
293
|
+
for l in range(num_classes):
|
|
294
|
+
obj_inds = np.where(objects == l)[0]
|
|
295
|
+
obj_confs = np.stack(Confs[obj_inds])
|
|
296
|
+
obj_IoUs = IoU_from_confusions(obj_confs)
|
|
297
|
+
obj_mious += [np.mean(obj_IoUs, axis=-1)]
|
|
298
|
+
|
|
299
|
+
objs_average = [np.mean(mious) for mious in obj_mious]
|
|
300
|
+
instance_average = np.mean(np.hstack(obj_mious))
|
|
301
|
+
class_average = np.mean(objs_average)
|
|
302
|
+
acc = total_correct / total_seen
|
|
303
|
+
|
|
304
|
+
print('Objs | Inst | Air Bag Cap Car Cha Ear Gui Kni Lam Lap Mot Mug Pis Roc Ska Tab')
|
|
305
|
+
print('-----|------|--------------------------------------------------------------------------------')
|
|
306
|
+
|
|
307
|
+
s = '{:4.1f} | {:4.1f} | '.format(100 * class_average, 100 * instance_average)
|
|
308
|
+
for Amiou in objs_average:
|
|
309
|
+
s += '{:4.1f} '.format(100 * Amiou)
|
|
310
|
+
print(s + '\n')
|
|
311
|
+
return acc, objs_average, class_average, instance_average
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import random
|
|
3
|
+
import torch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def set_random_seed(seed=0, deterministic=False):
|
|
7
|
+
random.seed(seed)
|
|
8
|
+
np.random.seed(seed)
|
|
9
|
+
torch.manual_seed(seed)
|
|
10
|
+
torch.cuda.manual_seed(seed)
|
|
11
|
+
torch.cuda.manual_seed_all(seed)
|
|
12
|
+
torch.backends.cudnn.benchmark = True
|
|
13
|
+
|
|
14
|
+
if deterministic:
|
|
15
|
+
torch.backends.cudnn.deterministic = True
|
|
16
|
+
torch.backends.cudnn.benchmark = False
|