-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathad.py
More file actions
152 lines (147 loc) · 5.96 KB
/
Copy pathad.py
File metadata and controls
152 lines (147 loc) · 5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import time
import random
import argparse
import numpy as np
import scipy.sparse as sp
from utils.io import load_tiff_pc
from probreg.cpd import AffineCPD
from sklearn.neighbors import KDTree
from utils.graphics import plot_pointcloud
from manifold.utils import adjacency_matrix
from sklearn.metrics.pairwise import cosine_distances
from manifold.coupling import couple_adjacency_matrices
from manifold.spectral_embedding import spectral_embedding
from scipy.sparse.csgraph import laplacian as csgraph_laplacian
from utils.pre_processing import largest_connected_component, ransac_registration
_SEED = 42
np.random.seed = _SEED
random.seed = _SEED
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--cross', type=float, default=1,
help='Fraction of source nodes used for coupled graph building')
parser.add_argument('-m', '--maps', type=int, default=200,
help='Number of coupled maps')
parser.add_argument('-p', '--points', type=int, default=13000,
help='Number of points to subsample for matching')
parser.add_argument('-th', '--threshold', type=float, default=0.001,
help='Threshold for plane removal')
parser.add_argument('-v', '--voxel_size', type=float, default=0.001,
help='RANSAC voxel size')
parser.add_argument('-d', '--cpd_down', type=int, default=2000,
help='Downsampling to speed-up CPD')
parser.add_argument('-s', '--source', type=str, default='data/MVTec 3D-AD/bagel/train/good/xyz/000.tiff',
help='path to source shape')
parser.add_argument('-t', '--target', type=str, default='data/MVTec 3D-AD/bagel/test/crack/xyz/005.tiff',
help='path to target shape')
args = parser.parse_args()
start = time.time()
# Load source point cloud
source, source_pca, source_sampling = load_tiff_pc(
args.source,
n_points=args.points,
threshold=args.threshold
)
target, target_pca, target_sampling = load_tiff_pc(
args.target,
n_points=args.points,
threshold=args.threshold
)
adjacency_source = adjacency_matrix(source, mode="gaussian", method="knn")
adjacency_target = adjacency_matrix(target, mode="gaussian", method="knn")
source = largest_connected_component(source, adj=adjacency_source, num_components=1)
target = largest_connected_component(target, adj=adjacency_target, num_components=1)
# We do first RANSAC registration and then Affine CPD since the latter may fail if point clouds
# are initially very far
source = ransac_registration(
source=source,
target=target,
voxel_size=args.voxel_size
)
# Rough Affine CPD on downsampled point clouds in order to have an idea of the transformation and
# do it faster
print("Affine CPD on sub-sampled point cloud (", args.cpd_down, "points).")
source_ds = source[np.random.choice(range(len(source)), args.cpd_down, replace=False)]
target_ds = target[np.random.choice(range(len(target)), args.cpd_down, replace=False)]
affine_cpd = AffineCPD(source=source_ds)
reg = affine_cpd.registration(target_ds)
source = reg.transformation.transform(source)
# Re-compute adjacencies and degree matrices because they may have changed with pre-processing
adjacency_source = adjacency_matrix(source, mode="gaussian", method="knn")
_, dd_source = csgraph_laplacian(
adjacency_source, normed=False, return_diag=True
)
adjacency_target = adjacency_matrix(target, mode="gaussian", method="knn")
_, dd_target = csgraph_laplacian(
adjacency_target, normed=False, return_diag=True
)
# Stochastically select target points for cross connections
n_cross = int(args.cross * len(target))
cross_target = np.random.choice(
range(len(target)),
size=n_cross,
replace=False
)
# if l=0, we need empty lists to not have errors later
if n_cross > 0:
cross_kdtree = KDTree(source)
dist, cross_source = cross_kdtree.query(target[cross_target])
dist = np.exp(-dist ** 2 / np.max(dist) ** 2)
else:
dist = np.array([])
cross_source = np.array([])
print("Coupling graphs ...")
print(":: Number of cross connections", n_cross)
# Compute coupled adjacency
adjacency_coupled = couple_adjacency_matrices(
adjacency_a=adjacency_source,
adjacency_b=adjacency_target,
inds_a=cross_source.reshape(-1),
inds_b=cross_target.reshape(-1),
dist=sp.diags(dist.reshape(-1)),
return_uncoupled=False
)
print("Compute coupled", args.maps, "eigenmaps")
# Compute coupled embeddings
coupled_embeddings = spectral_embedding(
adjacency=adjacency_coupled,
n_components=args.maps,
eigen_solver='amg',
random_state=_SEED,
eigen_tol='auto',
norm_laplacian=False,
drop_first=True,
B=sp.diags(np.hstack(
(dd_source,
dd_target
)
))
)
# Split them
source_embeddings = coupled_embeddings[:len(source)]
target_embeddings = coupled_embeddings[len(source):]
# Embedding restriceted to cross nodes
source_subemb = source_embeddings[cross_source.reshape(-1), :]
target_subemb = target_embeddings[cross_target.reshape(-1), :]
# point to point distance
p2p_dist = cosine_distances(source_subemb, target_subemb).diagonal()
print("Anomaly localization performed in ", time.time()-start, "seconds.")
# Plot prediction
fig = plot_pointcloud(
target[cross_target],
marker=dict(
size=3,
color=p2p_dist,
colorscale="Jet",
cmax=1,
showscale=True
)
)
fig.update_layout(
scene=dict(
xaxis=dict(visible=False),
yaxis=dict(visible=False),
zaxis=dict(visible=False)
)
)
fig.show()