PointRSP / model /NGC.py
Mo-nan's picture
Upload 16 files
913ec88 verified
Raw
History Blame Contribute Delete
15.1 kB
import torch
import math
import numpy as np
from os import sys, path
import torch.nn.functional as F
from torch.linalg import eigh
import time
def get_NGC_structure(points, raw_xyz, N2, N1, token_stage):
B, N3, channels = points.shape
reduce_factor = 2
# first use nvg-style method clustering N3 to N1
N1to3_binary, N1to3_lab2id, cur_points = clustering_points(points, B, N1, N3, reduce_factor)
# now get N1 tokens structured
ids = N1to3_lab2id[:, :, 0].unsqueeze(-1).expand(-1, -1, channels)
cur_points = torch.gather(cur_points, 1, ids)
N0to1_binary, N0to1_lab2id = build_tree(cur_points)
time3 = time.perf_counter()
# concat N0 -> N1 & N1 -> N3 binary structure
N0to3_binarys = concat_N0to3_binary(N0to1_binary, N1to3_lab2id, N1to3_binary, N1)
# get N3 -> N2 token masks
if N2 == N3:
structure_gt02_bin, points_new, raw_xyz_new = N0to3_binarys, points, raw_xyz
else:
structure_gt02_bin, points_new, raw_xyz_new = get_3to2(N0to3_binarys, N2, points, raw_xyz)
# get N2 final decimal structure
structure_gt02_dec = get_structure_dec(structure_gt02_bin, N2, token_stage)
return structure_gt02_bin, structure_gt02_dec, points_new, raw_xyz_new
def get_structure_dec(structure_gt02, N2, token_stage):
B = len(structure_gt02)
_, len_max = structure_gt02[0].shape
len_start = int(math.log(N2, 2)) + 1
structure_gt02_dec = []
for b in range(B):
structure_gt02_b = structure_gt02[b]
structure_gt02_b_dec = []
len_end = len_start
label = None
for i in range(1, len_max):
labels = binary_prefix_to_label(structure_gt02_b[:, :i]) # (N,)
# first store the decimal infrom
structure_gt02_b_dec.append(labels)
if i < len_start:
continue
# judge if get the end length
unique_labels, inverse = torch.unique(labels, return_inverse=True)
M = unique_labels.numel()
len_end = i
if M == N2:
break
if token_stage > len_end:
buffer = token_stage - len_end
for _ in range(buffer):
labels = torch.arange(N2, device=structure_gt02[0].device, dtype=torch.long)
structure_gt02_b_dec.append(labels)
structure_gt02_b_dec = torch.stack(structure_gt02_b_dec, dim=0).permute(1, 0)
structure_gt02_dec.append(structure_gt02_b_dec)
return structure_gt02_dec
def get_3to2(N0to3_binarys, N2, points, raw_xyz):
B = len(N0to3_binarys)
device = N0to3_binarys[0].device
structure_gt02 = []
points_new = []
raw_xyz_new = []
for b in range(B):
binary_b03 = N0to3_binarys[b]
N3, length = binary_b03.shape
labels = binary_prefix_to_label(binary_b03)
token_mask = select_diverse_tokens(labels, N2)
assert N2 == token_mask.sum()
binary_b02 = binary_b03[token_mask] # N2 len1+len2
points_new_b = points[b, :, :][token_mask] # B N3 -> N2 C
raw_xyz_new_b = raw_xyz[b, :, :][token_mask] # B N3 -> N2 C
points_new.append(points_new_b)
raw_xyz_new.append(raw_xyz_new_b)
structure_gt02.append(binary_b02)
points_new = torch.stack(points_new, dim=0)
raw_xyz_new = torch.stack(raw_xyz_new, dim=0)
return structure_gt02, points_new, raw_xyz_new
def select_diverse_tokens(labels, N):
"""
labels: (8192,) int64
return: token_mask (8192,) bool, sum == N
"""
device = labels.device
unique_labels, inverse = torch.unique(labels, return_inverse=True)
M = unique_labels.numel()
assert M >= N
# === Step 1: ancestor-based grouping ===
shift = max(0, int(torch.floor(torch.log2(torch.tensor(M / N))).item()))
group_id = unique_labels >> shift
unique_groups, group_inv = torch.unique(group_id, return_inverse=True)
# === Step 2: select N labels, one per group first ===
perm = torch.randperm(M, device=device)
selected_label = torch.zeros(M, dtype=torch.bool, device=device)
used_group = torch.zeros(unique_groups.numel(), dtype=torch.bool, device=device)
cnt = 0
for idx in perm:
g = group_inv[idx]
if not used_group[g]:
selected_label[idx] = True
used_group[g] = True
cnt += 1
if cnt == N:
break
# === Step 3: map label → first token ===
token_mask = torch.zeros(labels.size(0), dtype=torch.bool, device=device)
# inverse: token -> label index
for lab_idx in selected_label.nonzero(as_tuple=True)[0]:
t = (inverse == lab_idx).nonzero(as_tuple=True)[0][0]
token_mask[t] = True
return token_mask
def binary_prefix_to_label(prefix):
k = prefix.size(1)
weights = (2 ** torch.arange(k - 1, -1, -1, device=prefix.device))
labels = (prefix * weights).sum(dim=1)
return labels
def concat_N0to3_binary(N0to1_binary, N1to3_lab2id, N1to3_binary, N1):
B, N3, length2 = N1to3_binary.shape
device = N1to3_binary.device
K = N3 // N1
N0to3_binarys = []
# end_indxs = []
for b in range(B):
binary_b01 = N0to1_binary[b] # N1 len1
length1 = binary_b01.shape[-1]
binary_b03 = - torch.ones(N3, length1 + length2, device=device) # N3 len1+len2
binary_b03 = binary_b03.long()
for i in range(N1):
binary_b03[N1to3_lab2id[b, i, :], :length1] = binary_b01[i, :]
mask = (binary_b03 == -1)
has_neg1 = mask.any(dim=1) # (N,) bool
first_idx = torch.where(
has_neg1,
mask.int().argmax(dim=1),
torch.full((binary_b03.size(0),),
length1,
device=binary_b03.device,
dtype=torch.long)
)
offset = torch.arange(length2, device=device).unsqueeze(0) # (1, len2)
write_idx = first_idx.unsqueeze(1) + offset # (N, len2)
binary_b03.scatter_(1, write_idx, N1to3_binary[b, :, :])
binary_b03 = (binary_b03 > 0).long() # turns -1 into 0
N0to3_binarys.append(binary_b03)
return N0to3_binarys
def clustering_points(points, B, N1, N3, reduce_factor):
B, N3, channels = points.shape
device = points.device
iter_nums = int(math.log(N3, reduce_factor) - math.log(N1, reduce_factor))
N1to3_decimal = torch.arange(N3, device=device).unsqueeze(0).unsqueeze(0).repeat(B, iter_nums+1, 1)
cur_points = points.clone()
id2lab = torch.arange(N3, device=device).unsqueeze(0).repeat(B, 1)
lab2id = torch.arange(N3, device=device).unsqueeze(0).repeat(B, 1).unsqueeze(-1)
for i in range(1, iter_nums + 2):
N1to3_decimal[:, iter_nums+1-i, :] = id2lab
if i == iter_nums + 1:
break
ids = lab2id[:, :, 0].unsqueeze(-1).expand(-1, -1, channels)
cur_points = torch.gather(cur_points, 1, ids)
lab2id, id2lab = pair_points(cur_points, i, lab2id, reduce_factor)
cur_points = pr2vis(lab2id, points, channels)
N1to3_decimal = reorganize(N1to3_decimal, int(math.log(N1, reduce_factor)))
# turn decimal to binary
N1to3_binary = N1to3_decimal[:, 1:, :] & 1
N1to3_binary = N1to3_binary.permute(0, 2, 1) # B L len2
return N1to3_binary, lab2id, cur_points
def build_tree(tokens, min_ratio=0.25, k=8):
"""
tokens: (B, L, C)
return:
id2lab: List of B tensors, each size (L, depth)
lab2id: List of B lists: each layer is list of tensors of token ids
"""
B, L, C = tokens.shape
device = tokens.device
# all_id2lab = []
all_lab2id = []
all_id2lab_binary = []
for b in range(B):
feats = tokens[b] # (L, C)
# id2lab_b = []
lab2id_b = []
id2lab_b_binary = []
# id2lab_b.append(torch.zeros(L, dtype=torch.long, device=device))
id2lab_b_binary.append(torch.zeros(L, dtype=torch.long, device=device))
lab2id_b.append([torch.arange(L, device=device)])
frontier = [torch.arange(L, device=device)]
depth = 1
while True:
next_frontier = []
# id2lab_level = - torch.ones(L, dtype=torch.long, device=device)
lab2id_level = []
id2lab_level_binary = - torch.ones(L, dtype=torch.long, device=device)
for group_ids in frontier:
if group_ids.numel() == 1:
ids = group_ids
# label_counter = id2lab_b[depth - 1][ids]
# id2lab_level[ids] = 2 * label_counter
id2lab_level_binary[ids] = -1
lab2id_level.append(ids)
elif group_ids.numel() == 2:
# label_counter = id2lab_b[depth - 1][group_ids[0]]
ids0 = group_ids[0]
ids1 = group_ids[1]
# id2lab_level[ids0] = 2 * label_counter
lab2id_level.append(ids0)
id2lab_level_binary[ids0] = 0
# id2lab_level[ids1] = 2 * label_counter + 1
lab2id_level.append(ids1)
id2lab_level_binary[ids1] = 1
next_frontier.append(ids0)
next_frontier.append(ids1)
else:
# label_counter = id2lab_b[depth - 1][group_ids[0]] #
subfeat = feats[group_ids]
A = build_knn_graph(subfeat, k=k)
part0, part1 = constrained_bipartition(A, min_ratio=min_ratio)
ids0 = group_ids[part0]
ids1 = group_ids[part1]
# id2lab_level[ids0] = 2 * label_counter
id2lab_level_binary[ids0] = 0
lab2id_level.append(ids0)
# id2lab_level[ids1] = 2 * label_counter + 1
id2lab_level_binary[ids1] = 1
lab2id_level.append(ids1)
next_frontier.append(ids0)
next_frontier.append(ids1)
# id2lab_b.append(id2lab_level)
lab2id_b.append(lab2id_level)
id2lab_b_binary.append(id2lab_level_binary)
if len(next_frontier) == 0:
break
frontier = next_frontier
depth += 1
all_id2lab_binary.append(torch.stack(id2lab_b_binary, dim=1))
# all_id2lab.append(torch.stack(id2lab_b, dim=1)) # (L, depth)
all_lab2id.append(lab2id_b)
return all_id2lab_binary, all_lab2id
def pairwise_dist(x):
# x: (N, C)
# returns (N, N)
diff = x.unsqueeze(1) - x.unsqueeze(0)
return (diff * diff).sum(-1)
def build_knn_graph(feature, k=8):
# feature: (N, C)
N = feature.size(0)
if N <= 1:
return torch.zeros(N, N, device=feature.device)
k_eff = min(k, N - 1)
dist = pairwise_dist(feature) # (N, N)
knn_idx = dist.topk(k_eff + 1, dim=1, largest=False).indices[:, 1:]
N = feature.size(0)
A = torch.zeros(N, N, device=feature.device)
for i in range(N):
A[i, knn_idx[i]] = 1
A = torch.maximum(A, A.T)
return A
def spectral_bipartition(A):
# A: (N, N)
N = A.size(0)
D = A.sum(dim=1)
L = torch.diag(D) - A
vals, vecs = eigh(L)
fv = vecs[:, 1] # Fiedler vector
part0 = (fv > 0)
part1 = ~part0
return part0, part1
def constrained_bipartition(A, min_ratio=0.25):
part0, part1 = spectral_bipartition(A)
N = A.size(0)
min_size = int(N * min_ratio)
if part0.sum() < min_size or part1.sum() < min_size:
vals, vecs = eigh(torch.diag(A.sum(1)) - A)
fv = vecs[:, 1]
sorted_ids = torch.argsort(fv)
part0 = torch.zeros(N, dtype=torch.bool, device=A.device)
part1 = torch.zeros(N, dtype=torch.bool, device=A.device)
part0[sorted_ids[:min_size]] = True
part1[sorted_ids[min_size:]] = True
return part0, part1
def reorganize(cluster_tensor, start_stage):
reorganized = torch.zeros_like(cluster_tensor)
reorganized[:, :1, :] = cluster_tensor[:, :1, :]
B, L, N = cluster_tensor.shape
for level in range(1, L):
parent2token = reorganized[:, level - 1]
child2token = cluster_tensor[:, level]
children = torch.empty(B, 2**(level + start_stage), device=cluster_tensor.device, dtype=cluster_tensor.dtype)
children.scatter_(1, child2token, parent2token)
current_parent2child = torch.sort(children, dim=1, stable=True)[1]
# Simplified inner loop
new_indices = torch.empty_like(current_parent2child)
new_indices.scatter_(1, current_parent2child, torch.arange(current_parent2child.size(1), device=cluster_tensor.device).unsqueeze(0).repeat(B, 1))
reorganized[:, level] = torch.gather(new_indices, 1, child2token)
return reorganized
def pair_points(points_batch, iter_num, lab2id, reduce_factor):
batch_size = points_batch.size(0)
num_points = points_batch.size(1)
device = points_batch.device
inf_value = 1e8
distance_matrix = torch.cdist(points_batch, points_batch, p=2)
distance_matrix += torch.eye(num_points, device=device).unsqueeze(0).expand(batch_size, -1, -1) * inf_value
cluster_num = reduce_factor ** iter_num
new_lab2id = torch.empty((batch_size, num_points // reduce_factor, cluster_num), dtype=torch.long, device=device)
new_id2lab = torch.empty((batch_size, num_points * cluster_num // reduce_factor), dtype=torch.long, device=device)
for match_id in range(num_points // reduce_factor):
row_indices = torch.arange(batch_size, device=device)
_, min_idx = torch.min(distance_matrix.view(batch_size, -1), dim=1)
i = min_idx // num_points
j = min_idx % num_points
ids_i = lab2id[row_indices, i]
ids_j = lab2id[row_indices, j]
new_lables = torch.ones(batch_size, cluster_num // reduce_factor, dtype=torch.long, device=device) * match_id
new_id2lab.scatter_(1, ids_i, new_lables)
new_id2lab.scatter_(1, ids_j, new_lables)
new_lab2id[:, match_id, :(cluster_num // reduce_factor)] = ids_i
new_lab2id[:, match_id, (cluster_num // reduce_factor):] = ids_j
distance_matrix[row_indices, i, :] = inf_value
distance_matrix[row_indices, :, i] = inf_value
distance_matrix[row_indices, j, :] = inf_value
distance_matrix[row_indices, :, j] = inf_value
return new_lab2id, new_id2lab
def pr2vis(lab2id, colors, channels):
# pair results -> colors
bs, label_num, cluster_num = lab2id.shape
device = lab2id.device
new_colors = torch.zeros(bs, label_num * cluster_num, channels, device=device).to(colors.dtype)
for i in range(label_num):
ids = lab2id[:, i, :].unsqueeze(-1).expand(-1, -1, channels)
target_color = torch.gather(colors, 1, ids)
target_color = torch.mean(target_color, 1)
new_colors.scatter_(1, ids, target_color.unsqueeze(1).repeat(1, cluster_num, 1))
return new_colors