# Copyright 2016-present, Facebook, Inc. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. import torch, glob, os, numpy as np, math from .sparseConvNetTensor import SparseConvNetTensor from .metadata import Metadata def toLongTensor(dimension, x): if hasattr(x, 'type') and x.type() == 'torch.LongTensor': return x elif isinstance(x, (list, tuple)): assert len(x) == dimension return torch.LongTensor(x) else: return torch.LongTensor(dimension).fill_(x) def optionalTensor(a, b): return getattr(a, b) if hasattr(a, b) else torch.Tensor() def optionalTensorReturn(a): return a if a.numel() else None def threadDatasetIterator(d): try: import queue except BaseException: import Queue as queue import threading def iterator(): def worker(i): for k in range(i, len(d), 8): q.put(d[k]) q = queue.Queue(16) for i in range(8): t = threading.Thread(target=worker, args=(i,)) t.start() for _ in range(len(d)): item = q.get() yield item q.task_done() q.join() return iterator def concatenate_feature_planes(input): output = SparseConvNetTensor() output.metadata = input[0].metadata output.spatial_size = input[0].spatial_size output.features = torch.cat([i.features for i in input], 1) return output def add_feature_planes(input): output = SparseConvNetTensor() output.metadata = input[0].metadata output.spatial_size = input[0].spatial_size output.features = sum([i.features for i in input]) return output def append_tensors(tensors): spatial_size=tensors[0].spatial_size dimension=len(spatial_size) x=SparseConvNetTensor( features=torch.cat([t.features for t in tensors],0), metadata=Metadata(dimension), spatial_size=spatial_size) for t in tensors: x.metadata.appendMetadata(t.metadata,spatial_size) return x class AddCoords(torch.nn.Module): def forward(self, input): output = SparseConvNetTensor() if input.features.numel(): with torch.no_grad(): coords = input.get_spatial_locations() d = (input.spatial_size.type_as(input.features)-1)/2 coords=coords[:,:-1].type_as(input.features)/ d[None,:] - 1 output.features = torch.cat([input.features,coords],1) else: output.features = input.features output.metadata = input.metadata output.spatial_size = input.spatial_size return output def compare_sparse(x, y): cL,cR,L,R = x.metadata.compareSparseHelper(y.metadata, x.spatial_size) if x.features.is_cuda: cL=cL.cuda() cR=cR.cuda() L=L.cuda() R=R.cuda() e = 0 if cL.numel(): e += (x.features[cL]-y.features[cR]).pow(2).sum() if L.numel(): e += x.features[L].pow(2).sum() if R.numel(): e += y.features[R].pow(2).sum() return e / (cL.numel() + L.numel() + R.numel()) def spectral_norm_svd(module): w=module.weight if w.ndimension()==3: w=w.view(-1,w.size(2)) _,s,_=torch.svd(w) return s[0] def pad_with_batch_idx(x,idx): #add a batch index to the list of coordinates return torch.cat([x,torch.LongTensor(x.size(0),1).fill_(idx)],1) def batch_location_tensors(location_tensors): a=[] for batch_idx, lt in enumerate(location_tensors): if lt.numel(): a.append(pad_with_batch_idx(lt,batch_idx)) return torch.cat(a,0) def prepare_BLInput(batch): with torch.no_grad(): n=max([l.size(0) for l,f in batch]) l,f=batch[0] L=torch.empty(len(batch),n,l.size(1),dtype=torch.int64).fill_(-1) F=torch.zeros(len(batch),n,f.size(1)) for i, (l, f) in enumerate(batch): L[i,:l.size(0),:].copy_(l) F[i,:f.size(0),:].copy_(f) return [L,F] def checkpoint_restore(model,exp_name,name2,use_cuda=True,epoch=0): if use_cuda: model.cpu() if epoch>0: f=exp_name+'-%09d-'%epoch+name2+'.pth' assert os.path.isfile(f) print('Restore from ' + f) model.load_state_dict(torch.load(f)) else: f=sorted(glob.glob(exp_name+'-*-'+name2+'.pth')) if len(f)>0: f=f[-1] print('Restore from ' + f) model.load_state_dict(torch.load(f)) epoch=int(f[len(exp_name)+1:-len(name2)-5]) if use_cuda: model.cuda() return epoch+1 def is_power2(num): return num != 0 and ((num & (num - 1)) == 0) def is_square(num): return int(num**0.5+0.5)**2==num def has_only_one_nonzero_digit(num): #https://oeis.org/A037124 return num != 0 and (num/10**math.floor(math.log(num,10))).is_integer() def checkpoint_save(model,exp_name,name2,epoch, use_cuda=True): f=exp_name+'-%09d-'%epoch+name2+'.pth' model.cpu() torch.save(model.state_dict(),f) if use_cuda: model.cuda() #remove previous checkpoints unless they are a power of 2 to save disk space epoch=epoch-1 f=exp_name+'-%09d-'%epoch+name2+'.pth' if os.path.isfile(f): if not is_power2(epoch): os.remove(f) def random_rotation(dimension=3,allow_mirror=False): r=torch.qr(torch.randn(dimension,dimension))[0] f=torch.randint(2,(3,)) if f.sum()%2==0 and not allow_mirror: f=1-f return r*(2*f-1).float() def squareroot_rotation(a): import scipy.spatial b=scipy.spatial.transform.Slerp( [0,1], scipy.spatial.transform.Rotation.from_dcm(torch.stack([torch.eye(3),a])))([0.5]).as_dcm() return torch.from_numpy(b).float()[0] def voxelize_pointcloud(xyz,rgb,average=True,accumulate=False,return_inverse=False,return_counts=False): if xyz.numel()==0: return xyz, rgb if average or accumulate: xyz,inv,counts=torch.unique(xyz,dim=0,return_inverse=True,return_counts=True) rgb_out=torch.zeros(xyz.size(0),rgb.size(1),dtype=torch.float32,device=xyz.device) rgb_out.index_add_(0,inv,rgb) if average: rgb_out=rgb_out/counts[:,None].float() rgb=rgb_out else: #https://github.com/pytorch/pytorch/issues/36748 xyz, inv = torch.unique(xyz, sorted=True, return_inverse=True, dim=0) perm = torch.arange(inv.size(0), dtype=inv.dtype, device=inv.device) inv, perm = inv.flip([0]), perm.flip([0]) rgb=rgb[inv.new_empty(xyz.size(0)).scatter_(0, inv, perm)] r=[xyz,rgb_out] if return_inverse: r+=[inv] if return_counts: r+=[counts] return r class checkpointFunction(torch.autograd.Function): @staticmethod def forward(ctx, run_function, x_features, x_metadata, x_spatial_size): ctx.run_function = run_function ctx.save_for_backward(x_features, x_spatial_size) ctx.x_metadata=x_metadata with torch.no_grad(): y = run_function( SparseConvNetTensor (x_features, x_metadata, x_spatial_size)) return y.features @staticmethod def backward(ctx, grad_y_features): x_features, x_spatial_size = ctx.saved_tensors x_features = x_features.detach() x_features.requires_grad = True with torch.enable_grad(): y = ctx.run_function( SparseConvNetTensor (x_features, ctx.x_metadata, x_spatial_size)) torch.autograd.backward(y.features, grad_y_features,retain_graph=False) return None, x_features.grad, None, None def checkpoint101(run_function, x, down=1): x.features.requires_grad = True f=checkpointFunction.apply(run_function, x.features, x.metadata, x.spatial_size) s=x.spatial_size//down return SparseConvNetTensor(f, x.metadata, s) def matplotlib_cubes(ax, positions,colors): from mpl_toolkits.mplot3d import Axes3D from mpl_toolkits.mplot3d.art3d import Poly3DCollection """ import matplotlib.pyplot as plt fig = plt.figure(figsize=(15,15)) ax = fig.gca(projection='3d') ... plt.show() """ try: positions=positions.numpy() colors=colors.numpy() X = np.array([[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]], [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]], [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]], [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]], [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]], [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]).astype(np.float32)[None]-0.5 X=X+positions[:,None,None,:] X.resize(X.shape[0]*6,4,3) m=positions.min(0) M=positions.max(0)+1 ax.set_xlim([m[0],M[0]]) ax.set_ylim([m[1],M[1]]) ax.set_zlim([m[2],M[2]]) ax.add_collection3d(Poly3DCollection(X, facecolors=np.repeat(colors,6, axis=0))) except: print('matplotlibcubes fail!?!') pass ax.set_axis_off() def matplotlib_planes(ax, positions,colors): from mpl_toolkits.mplot3d import Axes3D from mpl_toolkits.mplot3d.art3d import Poly3DCollection """ import matplotlib.pyplot as plt fig = plt.figure(figsize=(15,15)) ax = fig.gca(projection='3d') ... plt.show() """ try: positions=positions.numpy() colors=colors.numpy() X = np.array([[[0, -0.5, 0.5], [0, -0.5, -0.5], [0, 0.5, -0.5], [0, 0.5, 0.5]]]).astype(np.float32)[None] X=X+positions[:,None,None,:] X.resize(X.shape[0]*1,4,3) m=positions.min(0) M=positions.max(0)+1 ax.set_xlim([m[0],M[0]]) ax.set_ylim([m[1],M[1]]) ax.set_zlim([m[2],M[2]]) ax.add_collection3d(Poly3DCollection(X, facecolors=np.repeat(colors,1, axis=0))) except: pass ax.set_axis_off() def visdom_scatter(vis, xyz, rgb, win='3d', markersize=3, title=''): rgb=rgb.detach() rgb-=rgb.min() rgb/=rgb.max()/255+1e-10 rgb=rgb.floor().cpu().numpy() vis.scatter( xyz.detach().cpu().numpy(), opts={'markersize': markersize,'markercolor': rgb, 'title': title}, win=win) def ply_scatter(name, xyz, rgb): rgb=rgb.detach() rgb-=rgb.min() rgb/=rgb.max()/255+1e-10 rgb=rgb.floor().cpu().numpy() with open(name+'.ply','w') as f: print("""ply format ascii 1.0 element vertex %d property float x property float y property float z property uchar red property uchar green property uchar blue end_header"""%(xyz.size(0)), file = f) for (x,y,z),(r,g,b) in zip(xyz,rgb): print('%d %d %d %d %d %d'%(x,y,z,r,g,b),file=f) class VerboseIdentity(torch.nn.Module): def forward(self, x): print(x) return x