|
import numpy as np |
|
import torch |
|
import torch.nn.functional as F |
|
|
|
import utils.geom |
|
|
|
class Vox_util(object): |
|
def __init__(self, Z, Y, X, scene_centroid, bounds, pad=None, assert_cube=False): |
|
self.XMIN, self.XMAX, self.YMIN, self.YMAX, self.ZMIN, self.ZMAX = bounds |
|
B, D = list(scene_centroid.shape) |
|
self.Z, self.Y, self.X = Z, Y, X |
|
|
|
scene_centroid = scene_centroid.detach().cpu().numpy() |
|
x_centroid, y_centroid, z_centroid = scene_centroid[0] |
|
self.XMIN += x_centroid |
|
self.XMAX += x_centroid |
|
self.YMIN += y_centroid |
|
self.YMAX += y_centroid |
|
self.ZMIN += z_centroid |
|
self.ZMAX += z_centroid |
|
|
|
self.default_vox_size_X = (self.XMAX-self.XMIN)/float(X) |
|
self.default_vox_size_Y = (self.YMAX-self.YMIN)/float(Y) |
|
self.default_vox_size_Z = (self.ZMAX-self.ZMIN)/float(Z) |
|
|
|
if pad: |
|
Z_pad, Y_pad, X_pad = pad |
|
self.ZMIN -= self.default_vox_size_Z * Z_pad |
|
self.ZMAX += self.default_vox_size_Z * Z_pad |
|
self.YMIN -= self.default_vox_size_Y * Y_pad |
|
self.YMAX += self.default_vox_size_Y * Y_pad |
|
self.XMIN -= self.default_vox_size_X * X_pad |
|
self.XMAX += self.default_vox_size_X * X_pad |
|
|
|
if assert_cube: |
|
|
|
if (not np.isclose(self.default_vox_size_X, self.default_vox_size_Y)) or (not np.isclose(self.default_vox_size_X, self.default_vox_size_Z)): |
|
print('Z, Y, X', Z, Y, X) |
|
print('bounds for this iter:', |
|
'X = %.2f to %.2f' % (self.XMIN, self.XMAX), |
|
'Y = %.2f to %.2f' % (self.YMIN, self.YMAX), |
|
'Z = %.2f to %.2f' % (self.ZMIN, self.ZMAX), |
|
) |
|
print('self.default_vox_size_X', self.default_vox_size_X) |
|
print('self.default_vox_size_Y', self.default_vox_size_Y) |
|
print('self.default_vox_size_Z', self.default_vox_size_Z) |
|
assert(np.isclose(self.default_vox_size_X, self.default_vox_size_Y)) |
|
assert(np.isclose(self.default_vox_size_X, self.default_vox_size_Z)) |
|
|
|
def Ref2Mem(self, xyz, Z, Y, X, assert_cube=False): |
|
|
|
|
|
B, N, C = list(xyz.shape) |
|
device = xyz.device |
|
assert(C==3) |
|
mem_T_ref = self.get_mem_T_ref(B, Z, Y, X, assert_cube=assert_cube, device=device) |
|
xyz = utils.geom.apply_4x4(mem_T_ref, xyz) |
|
return xyz |
|
|
|
def Mem2Ref(self, xyz_mem, Z, Y, X, assert_cube=False): |
|
|
|
|
|
B, N, C = list(xyz_mem.shape) |
|
ref_T_mem = self.get_ref_T_mem(B, Z, Y, X, assert_cube=assert_cube, device=xyz_mem.device) |
|
xyz_ref = utils.geom.apply_4x4(ref_T_mem, xyz_mem) |
|
return xyz_ref |
|
|
|
def get_mem_T_ref(self, B, Z, Y, X, assert_cube=False, device='cuda'): |
|
vox_size_X = (self.XMAX-self.XMIN)/float(X) |
|
vox_size_Y = (self.YMAX-self.YMIN)/float(Y) |
|
vox_size_Z = (self.ZMAX-self.ZMIN)/float(Z) |
|
|
|
if assert_cube: |
|
if (not np.isclose(vox_size_X, vox_size_Y)) or (not np.isclose(vox_size_X, vox_size_Z)): |
|
print('Z, Y, X', Z, Y, X) |
|
print('bounds for this iter:', |
|
'X = %.2f to %.2f' % (self.XMIN, self.XMAX), |
|
'Y = %.2f to %.2f' % (self.YMIN, self.YMAX), |
|
'Z = %.2f to %.2f' % (self.ZMIN, self.ZMAX), |
|
) |
|
print('vox_size_X', vox_size_X) |
|
print('vox_size_Y', vox_size_Y) |
|
print('vox_size_Z', vox_size_Z) |
|
assert(np.isclose(vox_size_X, vox_size_Y)) |
|
assert(np.isclose(vox_size_X, vox_size_Z)) |
|
|
|
|
|
|
|
center_T_ref = utils.geom.eye_4x4(B, device=device) |
|
center_T_ref[:,0,3] = -self.XMIN-vox_size_X/2.0 |
|
center_T_ref[:,1,3] = -self.YMIN-vox_size_Y/2.0 |
|
center_T_ref[:,2,3] = -self.ZMIN-vox_size_Z/2.0 |
|
|
|
|
|
|
|
mem_T_center = utils.geom.eye_4x4(B, device=device) |
|
mem_T_center[:,0,0] = 1./vox_size_X |
|
mem_T_center[:,1,1] = 1./vox_size_Y |
|
mem_T_center[:,2,2] = 1./vox_size_Z |
|
mem_T_ref = utils.geom.matmul2(mem_T_center, center_T_ref) |
|
|
|
return mem_T_ref |
|
|
|
def get_ref_T_mem(self, B, Z, Y, X, assert_cube=False, device='cuda'): |
|
mem_T_ref = self.get_mem_T_ref(B, Z, Y, X, assert_cube=assert_cube, device=device) |
|
|
|
|
|
ref_T_mem = mem_T_ref.inverse() |
|
return ref_T_mem |
|
|
|
def get_inbounds(self, xyz, Z, Y, X, already_mem=False, padding=0.0, assert_cube=False): |
|
|
|
|
|
if not already_mem: |
|
xyz = self.Ref2Mem(xyz, Z, Y, X, assert_cube=assert_cube) |
|
|
|
x = xyz[:,:,0] |
|
y = xyz[:,:,1] |
|
z = xyz[:,:,2] |
|
|
|
x_valid = ((x-padding)>-0.5).byte() & ((x+padding)<float(X-0.5)).byte() |
|
y_valid = ((y-padding)>-0.5).byte() & ((y+padding)<float(Y-0.5)).byte() |
|
z_valid = ((z-padding)>-0.5).byte() & ((z+padding)<float(Z-0.5)).byte() |
|
nonzero = (~(z==0.0)).byte() |
|
|
|
inbounds = x_valid & y_valid & z_valid & nonzero |
|
return inbounds.bool() |
|
|
|
def voxelize_xyz(self, xyz_ref, Z, Y, X, already_mem=False, assert_cube=False, clean_eps=0): |
|
B, N, D = list(xyz_ref.shape) |
|
assert(D==3) |
|
if already_mem: |
|
xyz_mem = xyz_ref |
|
else: |
|
xyz_mem = self.Ref2Mem(xyz_ref, Z, Y, X, assert_cube=assert_cube) |
|
xyz_zero = self.Ref2Mem(xyz_ref[:,0:1]*0, Z, Y, X, assert_cube=assert_cube) |
|
vox = self.get_occupancy(xyz_mem, Z, Y, X, clean_eps=clean_eps, xyz_zero=xyz_zero) |
|
return vox |
|
|
|
def voxelize_xyz_and_feats(self, xyz_ref, feats, Z, Y, X, already_mem=False, assert_cube=False, clean_eps=0): |
|
B, N, D = list(xyz_ref.shape) |
|
B2, N2, D2 = list(feats.shape) |
|
assert(D==3) |
|
assert(B==B2) |
|
assert(N==N2) |
|
if already_mem: |
|
xyz_mem = xyz_ref |
|
else: |
|
xyz_mem = self.Ref2Mem(xyz_ref, Z, Y, X, assert_cube=assert_cube) |
|
xyz_zero = self.Ref2Mem(xyz_ref[:,0:1]*0, Z, Y, X, assert_cube=assert_cube) |
|
feats = self.get_feat_occupancy(xyz_mem, feats, Z, Y, X, clean_eps=clean_eps, xyz_zero=xyz_zero) |
|
return feats |
|
|
|
def get_occupancy(self, xyz, Z, Y, X, clean_eps=0, xyz_zero=None): |
|
|
|
|
|
B, N, C = list(xyz.shape) |
|
assert(C==3) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inbounds = self.get_inbounds(xyz, Z, Y, X, already_mem=True) |
|
x, y, z = xyz[:,:,0], xyz[:,:,1], xyz[:,:,2] |
|
mask = torch.zeros_like(x) |
|
mask[inbounds] = 1.0 |
|
|
|
if xyz_zero is not None: |
|
|
|
dist = torch.norm(xyz_zero-xyz, dim=2) |
|
mask[dist < 0.1] = 0 |
|
|
|
if clean_eps > 0: |
|
|
|
xyz_round = torch.round(xyz) |
|
dist = torch.norm(xyz_round - xyz, dim=2) |
|
mask[dist > clean_eps] = 0 |
|
|
|
|
|
|
|
|
|
x = x*mask |
|
y = y*mask |
|
z = z*mask |
|
|
|
x = torch.round(x) |
|
y = torch.round(y) |
|
z = torch.round(z) |
|
x = torch.clamp(x, 0, X-1).int() |
|
y = torch.clamp(y, 0, Y-1).int() |
|
z = torch.clamp(z, 0, Z-1).int() |
|
|
|
x = x.view(B*N) |
|
y = y.view(B*N) |
|
z = z.view(B*N) |
|
|
|
dim3 = X |
|
dim2 = X * Y |
|
dim1 = X * Y * Z |
|
|
|
base = torch.arange(0, B, dtype=torch.int32, device=xyz.device)*dim1 |
|
base = torch.reshape(base, [B, 1]).repeat([1, N]).view(B*N) |
|
|
|
vox_inds = base + z * dim2 + y * dim3 + x |
|
voxels = torch.zeros(B*Z*Y*X, device=xyz.device).float() |
|
voxels[vox_inds.long()] = 1.0 |
|
|
|
voxels[base.long()] = 0.0 |
|
voxels = voxels.reshape(B, 1, Z, Y, X) |
|
|
|
return voxels |
|
|
|
def get_feat_occupancy(self, xyz, feat, Z, Y, X, clean_eps=0, xyz_zero=None): |
|
|
|
|
|
|
|
B, N, C = list(xyz.shape) |
|
B2, N2, D2 = list(feat.shape) |
|
assert(C==3) |
|
assert(B==B2) |
|
assert(N==N2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inbounds = self.get_inbounds(xyz, Z, Y, X, already_mem=True) |
|
x, y, z = xyz[:,:,0], xyz[:,:,1], xyz[:,:,2] |
|
mask = torch.zeros_like(x) |
|
mask[inbounds] = 1.0 |
|
|
|
if xyz_zero is not None: |
|
|
|
dist = torch.norm(xyz_zero-xyz, dim=2) |
|
mask[dist < 0.1] = 0 |
|
|
|
if clean_eps > 0: |
|
|
|
xyz_round = torch.round(xyz) |
|
dist = torch.norm(xyz_round - xyz, dim=2) |
|
mask[dist > clean_eps] = 0 |
|
|
|
|
|
|
|
|
|
x = x*mask |
|
y = y*mask |
|
z = z*mask |
|
feat = feat*mask.unsqueeze(-1) |
|
|
|
x = torch.round(x) |
|
y = torch.round(y) |
|
z = torch.round(z) |
|
x = torch.clamp(x, 0, X-1).int() |
|
y = torch.clamp(y, 0, Y-1).int() |
|
z = torch.clamp(z, 0, Z-1).int() |
|
|
|
|
|
perm = torch.randperm(N) |
|
x = x[:, perm] |
|
y = y[:, perm] |
|
z = z[:, perm] |
|
feat = feat[:, perm] |
|
|
|
x = x.view(B*N) |
|
y = y.view(B*N) |
|
z = z.view(B*N) |
|
feat = feat.view(B*N, -1) |
|
|
|
dim3 = X |
|
dim2 = X * Y |
|
dim1 = X * Y * Z |
|
|
|
base = torch.arange(0, B, dtype=torch.int32, device=xyz.device)*dim1 |
|
base = torch.reshape(base, [B, 1]).repeat([1, N]).view(B*N) |
|
|
|
vox_inds = base + z * dim2 + y * dim3 + x |
|
feat_voxels = torch.zeros((B*Z*Y*X, D2), device=xyz.device).float() |
|
feat_voxels[vox_inds.long()] = feat |
|
|
|
feat_voxels[base.long()] = 0.0 |
|
feat_voxels = feat_voxels.reshape(B, Z, Y, X, D2).permute(0, 4, 1, 2, 3) |
|
|
|
return feat_voxels |
|
|
|
def unproject_image_to_mem(self, rgb_camB, pixB_T_camA, camB_T_camA, Z, Y, X, assert_cube=False, xyz_camA=None): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B, C, H, W = list(rgb_camB.shape) |
|
|
|
if xyz_camA is None: |
|
xyz_memA = utils.basic.gridcloud3d(B, Z, Y, X, norm=False, device=pixB_T_camA.device) |
|
xyz_camA = self.Mem2Ref(xyz_memA, Z, Y, X, assert_cube=assert_cube) |
|
|
|
xyz_camB = utils.geom.apply_4x4(camB_T_camA, xyz_camA) |
|
z = xyz_camB[:,:,2] |
|
|
|
xyz_pixB = utils.geom.apply_4x4(pixB_T_camA, xyz_camA) |
|
normalizer = torch.unsqueeze(xyz_pixB[:,:,2], 2) |
|
EPS=1e-6 |
|
|
|
xy_pixB = xyz_pixB[:,:,:2]/torch.clamp(normalizer, min=EPS) |
|
|
|
|
|
x, y = xy_pixB[:,:,0], xy_pixB[:,:,1] |
|
|
|
|
|
x_valid = (x>-0.5).bool() & (x<float(W-0.5)).bool() |
|
y_valid = (y>-0.5).bool() & (y<float(H-0.5)).bool() |
|
z_valid = (z>0.0).bool() |
|
valid_mem = (x_valid & y_valid & z_valid).reshape(B, 1, Z, Y, X).float() |
|
|
|
if (0): |
|
|
|
values = torch.zeros([B, C, Z*Y*X], dtype=torch.float32) |
|
for b in list(range(B)): |
|
values[b] = utils.samp.bilinear_sample_single(rgb_camB[b], x_pixB[b], y_pixB[b]) |
|
else: |
|
|
|
y_pixB, x_pixB = utils.basic.normalize_grid2d(y, x, H, W) |
|
|
|
z_pixB = torch.zeros_like(x) |
|
xyz_pixB = torch.stack([x_pixB, y_pixB, z_pixB], axis=2) |
|
rgb_camB = rgb_camB.unsqueeze(2) |
|
xyz_pixB = torch.reshape(xyz_pixB, [B, Z, Y, X, 3]) |
|
values = F.grid_sample(rgb_camB, xyz_pixB, align_corners=False) |
|
|
|
values = torch.reshape(values, (B, C, Z, Y, X)) |
|
values = values * valid_mem |
|
return values |
|
|
|
def warp_tiled_to_mem(self, rgb_tileB, pixB_T_camA, camB_T_camA, Z, Y, X, DMIN, DMAX, assert_cube=False): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B, C, D, H, W = list(rgb_tileB.shape) |
|
|
|
xyz_memA = utils.basic.gridcloud3d(B, Z, Y, X, norm=False, device=pixB_T_camA.device) |
|
|
|
xyz_camA = self.Mem2Ref(xyz_memA, Z, Y, X, assert_cube=assert_cube) |
|
|
|
xyz_camB = utils.geom.apply_4x4(camB_T_camA, xyz_camA) |
|
z_camB = xyz_camB[:,:,2] |
|
|
|
|
|
z_tileB = (D-1.0) * (z_camB-float(DMIN)) / float(DMAX-DMIN) |
|
|
|
xyz_pixB = utils.geom.apply_4x4(pixB_T_camA, xyz_camA) |
|
normalizer = torch.unsqueeze(xyz_pixB[:,:,2], 2) |
|
EPS=1e-6 |
|
|
|
xy_pixB = xyz_pixB[:,:,:2]/torch.clamp(normalizer, min=EPS) |
|
|
|
|
|
x, y = xy_pixB[:,:,0], xy_pixB[:,:,1] |
|
|
|
|
|
x_valid = (x>-0.5).bool() & (x<float(W-0.5)).bool() |
|
y_valid = (y>-0.5).bool() & (y<float(H-0.5)).bool() |
|
z_valid = (z_camB>0.0).bool() |
|
valid_mem = (x_valid & y_valid & z_valid).reshape(B, 1, Z, Y, X).float() |
|
|
|
z_tileB, y_pixB, x_pixB = utils.basic.normalize_grid3d(z_tileB, y, x, D, H, W) |
|
xyz_pixB = torch.stack([x_pixB, y_pixB, z_tileB], axis=2) |
|
xyz_pixB = torch.reshape(xyz_pixB, [B, Z, Y, X, 3]) |
|
values = F.grid_sample(rgb_tileB, xyz_pixB, align_corners=False) |
|
|
|
values = torch.reshape(values, (B, C, Z, Y, X)) |
|
values = values * valid_mem |
|
return values |
|
|
|
|
|
def apply_mem_T_ref_to_lrtlist(self, lrtlist_cam, Z, Y, X, assert_cube=False): |
|
|
|
|
|
B, N, C = list(lrtlist_cam.shape) |
|
assert(C==19) |
|
mem_T_cam = self.get_mem_T_ref(B, Z, Y, X, assert_cube=assert_cube, device=lrtlist_cam.device) |
|
|
|
def xyz2circles(self, xyz, radius, Z, Y, X, soft=True, already_mem=True, also_offset=False, grid=None): |
|
|
|
|
|
|
|
B, N, D = list(xyz.shape) |
|
assert(D==3) |
|
if not already_mem: |
|
xyz = self.Ref2Mem(xyz, Z, Y, X) |
|
|
|
if grid is None: |
|
grid_z, grid_y, grid_x = utils.basic.meshgrid3d(B, Z, Y, X, stack=False, norm=False, device=xyz.device) |
|
|
|
grid = torch.stack([grid_x, grid_y, grid_z], dim=1) |
|
|
|
|
|
xyz = xyz.reshape(B, N, 3, 1, 1, 1) |
|
grid = grid.reshape(B, 1, 3, Z, Y, X) |
|
|
|
|
|
|
|
|
|
xyz = xyz.round() |
|
|
|
if torch.is_tensor(radius): |
|
radius = radius.clamp(min=0.01) |
|
|
|
if soft: |
|
off = grid - xyz |
|
|
|
dist_grid = torch.sum(off**2, dim=2, keepdim=False) |
|
|
|
if torch.is_tensor(radius): |
|
radius = radius.reshape(B, N, 1, 1, 1) |
|
mask = torch.exp(-dist_grid/(2*radius*radius)) |
|
|
|
mask[mask < 0.001] = 0.0 |
|
|
|
|
|
|
|
if also_offset: |
|
return mask, off |
|
else: |
|
return mask |
|
else: |
|
assert(False) |
|
|
|
dist_grid = torch.norm(grid - xyz, dim=2, keepdim=False) |
|
|
|
|
|
radius = radius.reshape(B, N, 1, 1, 1) |
|
|
|
within_radius_mask = (dist_grid < radius).float() |
|
within_radius_mask = torch.sum(within_radius_mask, dim=1, keepdim=True).clamp(0, 1) |
|
return within_radius_mask |
|
|
|
def xyz2circles_bev(self, xyz, radius, Z, Y, X, already_mem=True, also_offset=False): |
|
|
|
|
|
|
|
B, N, D = list(xyz.shape) |
|
assert(D==3) |
|
if not already_mem: |
|
xyz = self.Ref2Mem(xyz, Z, Y, X) |
|
|
|
xz = torch.stack([xyz[:,:,0], xyz[:,:,2]], dim=2) |
|
|
|
grid_z, grid_x = utils.basic.meshgrid2d(B, Z, X, stack=False, norm=False, device=xyz.device) |
|
|
|
grid = torch.stack([grid_x, grid_z], dim=1) |
|
|
|
|
|
xz = xz.reshape(B, N, 2, 1, 1) |
|
grid = grid.reshape(B, 1, 2, Z, X) |
|
|
|
|
|
|
|
|
|
xz = xz.round() |
|
|
|
if torch.is_tensor(radius): |
|
radius = radius.clamp(min=0.01) |
|
|
|
off = grid - xz |
|
|
|
dist_grid = torch.sum(off**2, dim=2, keepdim=False) |
|
|
|
if torch.is_tensor(radius): |
|
radius = radius.reshape(B, N, 1, 1, 1) |
|
mask = torch.exp(-dist_grid/(2*radius*radius)) |
|
|
|
mask[mask < 0.001] = 0.0 |
|
|
|
|
|
mask = mask.unsqueeze(-2) |
|
off = off.unsqueeze(-2) |
|
|
|
|
|
if also_offset: |
|
return mask, off |
|
else: |
|
return mask |
|
|
|
|