├── .gitignore ├── README.md ├── _ext ├── __init__.py └── my_lib │ └── __init__.py ├── build.py ├── eval_img2pc.py ├── functions ├── __init__.py └── nnd.py ├── models ├── AutoEncoder.py ├── GAN.py ├── ImageToShape.py ├── MRTDecoder.py ├── Model.py ├── VoxUNet.py └── __init__.py ├── modules ├── __init__.py └── nnd.py ├── mr_train_4k.py ├── nndistance ├── .gitignore ├── README.md ├── _ext │ ├── __init__.py │ └── my_lib │ │ └── __init__.py ├── build.py ├── functions │ ├── __init__.py │ └── nnd.py ├── modules │ ├── __init__.py │ └── nnd.py ├── src │ ├── make.sh │ ├── my_lib.c │ ├── my_lib.h │ ├── my_lib_cuda.c │ ├── my_lib_cuda.h │ ├── nnd_cuda.cu │ └── nnd_cuda.h └── test.py ├── run_img2pc.py ├── sampler ├── README ├── cotSpectral.java └── vecmath.jar ├── src ├── emd_cuda.cu ├── emd_cuda.cu.o ├── emd_cuda.h ├── make.sh ├── my_lib.c ├── my_lib.h ├── my_lib_cuda.c ├── my_lib_cuda.h ├── nnd_cuda.cu ├── nnd_cuda.cu.o └── nnd_cuda.h ├── test.py ├── testchamfer.py ├── tools ├── .ImageToPCDataset.py.swp ├── .PointCloudDataset.py.swp ├── DataVis.py ├── ImageToPCDataset.py ├── Ops.py ├── PointCloudDataset.py ├── Trainer.py └── __init__.py └── train_img2pc.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiresolution Tree Networks for 3D Point Cloud Processing (ECCV 2018) 2 | 3 | 4 | This repository contains the source code for the ECCV 2018 paper Multiresolution Tree Networks for 3D Point Clout Processing. 5 | 6 | [Project page](http://mgadelha.me/mrt/) 7 | 8 |  9 | 10 | ## Dependencies 11 | 12 | * numpy 13 | * pytorch 14 | * tensorboardX 15 | * fxia22/pointGAN (optional - if you want faster Chamfer Distance) Thanks to Fei Xia'a for making the code publicly available. We have a version in this repo already, but might not be up-to-date. 16 | 17 | 18 | ## Train 19 | 20 | First, you need to change the dataset path in the file `train_img2pc.py`. We are using the rendered images from https://github.com/chrischoy/3D-R2N2. You will also need a path for the point clouds in .npy format. Finally, you can train a model by using the following command: 21 | ``` 22 | python train_img2pc.py --name experiment_name 23 | ``` 24 | 25 | If you want to run the model, change the folder name indicated in `run_img2pc.py` and use the following command: 26 | ``` 27 | python run_img2pc.py --n experiment_name 28 | ``` 29 | Notice that`experiment_name` should match in both cases. Similarly, we also have evaluation code to reproduce the paper's numbers. 30 | 31 | 32 | ## Point Sampling 33 | 34 | This repository also contains code for sampling the point clouds in the sampler folder. It is a single .java file and it contains a README with specific instructions. 35 | This code automatically sorts the points according to a kd-tree structure. 36 | 37 | 38 | ## Citation 39 | 40 | If you use any part of this code or data, consider citing this work: 41 | ``` 42 | @inProceedings{mrt18, 43 | title={Multiresolution Tree Networks for 3D Point Cloud Processing}, 44 | author = {Matheus Gadelha and Rui Wang and Subhransu Maji}, 45 | booktitle={ECCV}, 46 | year={2018} 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /_ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/_ext/__init__.py -------------------------------------------------------------------------------- /_ext/my_lib/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from torch.utils.ffi import _wrap_function 3 | from ._my_lib import lib as _lib, ffi as _ffi 4 | 5 | __all__ = [] 6 | def _import_symbols(locals): 7 | for symbol in dir(_lib): 8 | fn = getattr(_lib, symbol) 9 | if callable(fn): 10 | locals[symbol] = _wrap_function(fn, _ffi) 11 | else: 12 | locals[symbol] = fn 13 | __all__.append(symbol) 14 | 15 | _import_symbols(locals()) 16 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from torch.utils.ffi import create_extension 4 | 5 | this_file = os.path.dirname(__file__) 6 | 7 | sources = ['src/my_lib.c'] 8 | headers = ['src/my_lib.h'] 9 | defines = [] 10 | with_cuda = False 11 | 12 | if torch.cuda.is_available(): 13 | print('Including CUDA code.') 14 | sources += ['src/my_lib_cuda.c'] 15 | headers += ['src/my_lib_cuda.h'] 16 | defines += [('WITH_CUDA', None)] 17 | with_cuda = True 18 | 19 | this_file = os.path.dirname(os.path.realpath(__file__)) 20 | print(this_file) 21 | extra_objects = ['src/nnd_cuda.cu.o'] 22 | extra_objects = [os.path.join(this_file, fname) for fname in extra_objects] 23 | 24 | ffi = create_extension( 25 | '_ext.my_lib', 26 | headers=headers, 27 | sources=sources, 28 | define_macros=defines, 29 | relative_to=__file__, 30 | with_cuda=with_cuda, 31 | extra_objects=extra_objects 32 | ) 33 | 34 | if __name__ == '__main__': 35 | ffi.build() 36 | -------------------------------------------------------------------------------- /eval_img2pc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.optim as optim 4 | import torch.nn as nn 5 | 6 | import argparse 7 | import os 8 | 9 | from tools.Trainer import ImageToPCTrainer 10 | from tools.PointCloudDataset import ImageToPointCloudDataset 11 | from models.AutoEncoder import PointCloudVAE 12 | from models.AutoEncoder import ChamferLoss 13 | from models.AutoEncoder import ChamferWithNormalLoss 14 | from models.AutoEncoder import L2WithNormalLoss 15 | from models.ImageToShape import MultiResImageToShape 16 | from models.ImageToShape import SingleResImageToShape 17 | from models.ImageToShape import FCImageToShape 18 | 19 | parser = argparse.ArgumentParser(description='MultiResolution image to shape model.') 20 | parser.add_argument("-n", "--name", type=str, help="Name of the experiment.", default="MRI2PC") 21 | parser.add_argument("-a", "--arch", type=str, help="Encoder architecture.", default="vgg") 22 | parser.add_argument("-pt", "--pretrained", type=str, help="Use pretrained net", default="True") 23 | parser.add_argument("-c", "--category", type=str, help="Category code (all is possible)", default="all") 24 | parser.add_argument("--train", dest='train', action='store_true') 25 | parser.set_defaults(train=False) 26 | 27 | image_datapath = "/media/mgadelha/hd2/ShapenetRenderings" 28 | pc_datapath = "/media/mgadelha/hd2/shapenet_4k" 29 | 30 | if __name__ == '__main__': 31 | args = parser.parse_args() 32 | 33 | ptrain = None 34 | if args.pretrained == "False": 35 | ptrain = False 36 | elif args.pretrained == "True": 37 | ptrain = True 38 | 39 | full_name = "{}_{}_{}_{}".format(args.name, args.category, args.arch, ptrain) 40 | #full_name = args.name 41 | print full_name 42 | 43 | mri2pc = MultiResImageToShape(size=4096, dim=3, batch_size=1, 44 | name=full_name, pretrained=ptrain, arch=args.arch) 45 | mri2pc.load('checkpoint') 46 | optimizer = optim.Adam(mri2pc.parameters(), lr=0.001) 47 | 48 | train_dataset = ImageToPointCloudDataset(image_datapath, pc_datapath, 49 | category=args.category, train_mode=True) 50 | test_dataset = ImageToPointCloudDataset(image_datapath, pc_datapath, 51 | category=args.category, train_mode=False) 52 | train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=1, 53 | shuffle=True, num_workers=2) 54 | test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=1, 55 | shuffle=True, num_workers=2) 56 | 57 | log_dir = os.path.join("log", full_name) 58 | if not os.path.exists(log_dir): 59 | os.makedirs(log_dir) 60 | 61 | trainer = ImageToPCTrainer(mri2pc, train_loader, test_loader, 62 | optimizer, ChamferLoss(), log_dir=log_dir) 63 | trainer.evaluate() 64 | 65 | -------------------------------------------------------------------------------- /functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/functions/__init__.py -------------------------------------------------------------------------------- /functions/nnd.py: -------------------------------------------------------------------------------- 1 | # functions/add.py 2 | import torch 3 | from torch.autograd import Function 4 | from _ext import my_lib 5 | 6 | 7 | class NNDFunction(Function): 8 | def forward(self, xyz1, xyz2): 9 | batchsize, n, _ = xyz1.size() 10 | _, m, _ = xyz2.size() 11 | self.xyz1 = xyz1 12 | self.xyz2 = xyz2 13 | dist1 = torch.zeros(batchsize, n) 14 | dist2 = torch.zeros(batchsize, m) 15 | 16 | self.idx1 = torch.zeros(batchsize, n).type(torch.IntTensor) 17 | self.idx2 = torch.zeros(batchsize, m).type(torch.IntTensor) 18 | 19 | if not xyz1.is_cuda: 20 | my_lib.nnd_forward(xyz1, xyz2, dist1, dist2, self.idx1, self.idx2) 21 | else: 22 | dist1 = dist1.cuda() 23 | dist2 = dist2.cuda() 24 | self.idx1 = self.idx1.cuda() 25 | self.idx2 = self.idx2.cuda() 26 | my_lib.nnd_forward_cuda(xyz1, xyz2, dist1, dist2, self.idx1, self.idx2) 27 | 28 | self.dist1 = dist1 29 | self.dist2 = dist2 30 | 31 | #print(batchsize, n, m) 32 | 33 | return dist1, dist2 34 | 35 | def backward(self, graddist1, graddist2): 36 | #print(self.idx1, self.idx2) 37 | 38 | 39 | graddist1 = graddist1.contiguous() 40 | graddist2 = graddist2.contiguous() 41 | 42 | gradxyz1 = torch.zeros(self.xyz1.size()) 43 | gradxyz2 = torch.zeros(self.xyz2.size()) 44 | 45 | if not graddist1.is_cuda: 46 | my_lib.nnd_backward(self.xyz1, self.xyz2, gradxyz1, gradxyz2, graddist1, graddist2, self.idx1, self.idx2) 47 | else: 48 | gradxyz1 = gradxyz1.cuda() 49 | gradxyz2 = gradxyz2.cuda() 50 | my_lib.nnd_backward_cuda(self.xyz1, self.xyz2, gradxyz1, gradxyz2, graddist1, graddist2, self.idx1, self.idx2) 51 | 52 | return gradxyz1, gradxyz2 -------------------------------------------------------------------------------- /models/AutoEncoder.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from torch.autograd import Variable 7 | 8 | from Model import Model 9 | import tools.DataVis as DataVis 10 | from tools.PointCloudDataset import save_objs 11 | from tools import Ops 12 | from modules.nnd import NNDModule 13 | 14 | 15 | class PointCloudEncoder(nn.Module): 16 | 17 | def __init__(self, size, dim, batch_size=64, enc_size=100, kernel_size=16, 18 | init_channels=16): 19 | super(PointCloudEncoder, self).__init__() 20 | self.size = size 21 | self.dim = dim 22 | self.batch_size = batch_size 23 | self.kernel_size = kernel_size 24 | self.enc_size = enc_size 25 | self.init_channels = init_channels 26 | 27 | conv_enc = nn.Sequential() 28 | 29 | current_size = self.size 30 | in_channels = self.dim 31 | out_channels = self.init_channels 32 | layer_num = 1 33 | padding = (self.kernel_size - 1)/2 34 | while current_size > 16: 35 | conv_enc.add_module('conv{}'.format(layer_num), 36 | nn.Conv1d(in_channels, out_channels, self.kernel_size, 37 | stride=2, 38 | padding=padding)) 39 | conv_enc.add_module('bn{}'.format(layer_num), 40 | nn.BatchNorm1d(out_channels)) 41 | conv_enc.add_module('lrelu{}'.format(layer_num), 42 | nn.LeakyReLU(0.2, inplace=True)) 43 | 44 | current_size /= 2 45 | in_channels = out_channels 46 | out_channels *= 2 47 | layer_num += 1 48 | 49 | self.conv_enc = conv_enc 50 | 51 | self.fc = nn.Linear(16*in_channels, self.enc_size) 52 | 53 | def forward(self, x): 54 | t = self.conv_enc(x).view(self.batch_size, -1) 55 | out = self.fc(t) 56 | return out 57 | 58 | 59 | class PointCloudDecoder(nn.Module): 60 | 61 | def __init__(self, size, dim, batch_size=64, enc_size=100, kernel_size=16, 62 | init_channels=1024): 63 | super(PointCloudDecoder, self).__init__() 64 | self.size = size 65 | self.dim = dim 66 | self.batch_size = batch_size 67 | self.kernel_size = kernel_size 68 | self.enc_size = enc_size 69 | self.init_channels = init_channels 70 | 71 | self.fc = nn.Linear(self.enc_size, 16*self.init_channels) 72 | 73 | conv_dec = nn.Sequential() 74 | 75 | current_size = 16*2 76 | in_channels = self.init_channels 77 | out_channels = in_channels/2 78 | layer_num = 1 79 | padding = (self.kernel_size - 1)/2 80 | while current_size < self.size: 81 | conv_dec.add_module('conv{}'.format(layer_num), 82 | nn.ConvTranspose1d(in_channels, out_channels, self.kernel_size, 83 | stride=2, 84 | padding=padding)) 85 | conv_dec.add_module('bn{}'.format(layer_num), 86 | nn.BatchNorm1d(out_channels)) 87 | conv_dec.add_module('lrelu{}'.format(layer_num), 88 | nn.LeakyReLU(0.2, inplace=True)) 89 | 90 | current_size *= 2 91 | in_channels = out_channels 92 | out_channels /= 2 93 | layer_num += 1 94 | 95 | conv_dec.add_module('conv{}'.format(layer_num), 96 | nn.ConvTranspose1d(in_channels, self.dim, self.kernel_size, 97 | stride=2, 98 | padding=padding)) 99 | conv_dec.add_module('lrelu{}'.format(layer_num), 100 | nn.Tanh()) 101 | 102 | self.conv_dec = conv_dec 103 | 104 | 105 | def forward(self, x): 106 | t = self.fc(x).view(self.batch_size, self.init_channels, 16) 107 | out = self.conv_dec(t) 108 | return out 109 | 110 | 111 | class MultiResBlock1d(nn.Module): 112 | 113 | def __init__(self, name, in_channels, out_channels, blocktype, activation): 114 | super(MultiResBlock1d, self).__init__() 115 | 116 | self.upsample = Ops.NNUpsample1d() 117 | self.pool = nn.MaxPool1d(kernel_size=4, stride=4) 118 | self.in_channels = in_channels 119 | self.out_channels = out_channels 120 | self.name = name 121 | 122 | self.conv0 = nn.Sequential() 123 | self.conv0.add_module('{}_conv0'.format(self.name), 124 | blocktype(self.in_channels*2, 125 | self.out_channels, 126 | kernel_size=2, 127 | stride=2, 128 | padding=0)) 129 | self.conv0.add_module('{}_bn0'.format(self.name), 130 | nn.BatchNorm1d(self.out_channels)) 131 | self.conv0.add_module('{}_activation0'.format(self.name), 132 | activation) 133 | 134 | self.conv1 = nn.Sequential() 135 | self.conv1.add_module('{}_conv1'.format(self.name), 136 | blocktype(self.in_channels*3, 137 | self.out_channels, 138 | kernel_size=2, 139 | stride=2, 140 | padding=0)) 141 | self.conv1.add_module('{}_bn1'.format(self.name), 142 | nn.BatchNorm1d(self.out_channels)) 143 | self.conv1.add_module('{}_activation1'.format(self.name), 144 | activation) 145 | 146 | self.conv2 = nn.Sequential() 147 | self.conv2.add_module('{}_conv2'.format(self.name), 148 | blocktype(self.in_channels*2, 149 | self.out_channels, 150 | kernel_size=2, 151 | stride=2, 152 | padding=0)) 153 | self.conv2.add_module('{}_bn2'.format(self.name), 154 | nn.BatchNorm1d(self.out_channels)) 155 | self.conv2.add_module('{}_activation2'.format(self.name), 156 | activation) 157 | 158 | def forward(self, x): 159 | x0 = x[0] 160 | x1 = x[1] 161 | x2 = x[2] 162 | 163 | in0 = torch.cat((x0, self.upsample(x1)), 1) 164 | in1 = torch.cat((self.pool(x0), x1, self.upsample(x2)), 1) 165 | in2 = torch.cat((self.pool(x1), x2), 1) 166 | 167 | out0 = self.conv0(in0) 168 | out1 = self.conv1(in1) 169 | out2 = self.conv2(in2) 170 | 171 | return [out0, out1, out2] 172 | 173 | 174 | class MultiResConv1d(MultiResBlock1d): 175 | 176 | def __init__(self, name, in_channels, out_channels, activation=nn.ReLU(inplace=True)): 177 | super(MultiResConv1d, self).__init__( 178 | name, in_channels, out_channels, nn.Conv1d, activation=activation) 179 | 180 | 181 | class MultiResConvTranspose1d(MultiResBlock1d): 182 | 183 | def __init__(self, name, in_channels, out_channels, activation=nn.ReLU(inplace=True)): 184 | super(MultiResConvTranspose1d, self).__init__( 185 | name, in_channels, out_channels, nn.ConvTranspose1d, activation=activation) 186 | 187 | 188 | class ChamferLoss(nn.Module): 189 | 190 | def __init__(self, n_samples=1024, cuda_opt=True): 191 | super(ChamferLoss, self).__init__() 192 | self.n_samples = n_samples 193 | self.dist = NNDModule() 194 | self.cuda_opt = cuda_opt 195 | 196 | 197 | def chamfer(self, a, b): 198 | pcsize = a.size()[1] 199 | 200 | a = torch.t(a) 201 | b = torch.t(b) 202 | mma = torch.stack([a]*pcsize) 203 | mmb = torch.stack([b]*pcsize).transpose(0,1) 204 | d = torch.sum((mma-mmb)**2,2).squeeze() 205 | 206 | return torch.min(d, 1)[0].sum() + torch.min(d, 0)[0].sum() 207 | 208 | 209 | def chamfer_batch(self, a, b): 210 | pcsize = a.size()[-1] 211 | 212 | if pcsize != self.n_samples: 213 | indices = np.arange(pcsize).astype(int) 214 | np.random.shuffle(indices) 215 | indices = torch.from_numpy(indices[:self.n_samples]).cuda() 216 | a = a[:, :, indices] 217 | b = b[:, :, indices] 218 | 219 | a = torch.transpose(a, 1, 2).contiguous() 220 | b = torch.transpose(b, 1, 2).contiguous() 221 | 222 | if self.cuda_opt: 223 | d1, d2 = self.dist(a, b) 224 | out = torch.sum(d1) + torch.sum(d2) 225 | return out 226 | else: 227 | d = Ops.batch_pairwise_dist(a, b) 228 | return torch.min(d, dim=2)[0].sum() + torch.min(d, dim=1)[0].sum() 229 | 230 | #mma = torch.stack([a]*self.n_samples, dim=1) 231 | #mmb = torch.stack([b]*self.n_samples, dim=1).transpose(1,2) 232 | #d = torch.sum((mma-mmb)**2,3).squeeze() 233 | #d = pd 234 | 235 | 236 | 237 | def forward(self, a, b): 238 | batch_size = a.size()[0] 239 | assert(batch_size == b.size()[0]) 240 | loss = self.chamfer_batch(a, b) 241 | return loss/(float(batch_size) * 1) 242 | 243 | 244 | class MultiResChamferLoss(nn.Module): 245 | 246 | def __init__(self, n_samples=1024): 247 | super(MultiResChamferLoss, self).__init__() 248 | self.n_samples = n_samples 249 | self.pool = nn.MaxPool1d(kernel_size=8, stride=8) 250 | 251 | def chamfer_batch(self, a, b): 252 | pcsize = a.size()[-1] 253 | 254 | if pcsize > self.n_samples: 255 | pcsize = self.n_samples 256 | 257 | indices = np.arange(pcsize) 258 | np.random.shuffle(indices) 259 | indices = indices[:self.n_samples] 260 | 261 | a = a[:, :, indices] 262 | b = b[:, :, indices] 263 | 264 | a = torch.transpose(a, 1, 2) 265 | b = torch.transpose(b, 1, 2) 266 | #d = Ops.batch_pairwise_dist(a, b) 267 | mma = torch.stack([a]*self.n_samples, dim=1) 268 | mmb = torch.stack([b]*self.n_samples, dim=1).transpose(1,2) 269 | d = torch.sum((mma-mmb)**2,3).squeeze() 270 | #d = pd 271 | 272 | return (torch.min(d, dim=2)[0].sum() + torch.min(d, dim=1)[0].sum())/float(pcsize) 273 | 274 | 275 | def forward(self, a, b): 276 | batch_size = a[0].size()[0] 277 | 278 | b_samples = [] 279 | b_samples.append(b) 280 | b_samples.append(self.pool(b_samples[-1])) 281 | b_samples.append(self.pool(b_samples[-1])) 282 | 283 | loss = 0.0 284 | for i in [0]: 285 | loss += self.chamfer_batch(a[i], b_samples[i]) 286 | 287 | return 1e3*loss/float(batch_size) 288 | 289 | 290 | 291 | class ChamferWithNormalLoss(nn.Module): 292 | 293 | def __init__(self, normal_weight=0.001, n_samples=1024): 294 | super(ChamferWithNormalLoss, self).__init__() 295 | self.normal_weight = normal_weight 296 | self.nlogger = DataVis.LossLogger("normal component") 297 | self.n_samples = n_samples 298 | 299 | 300 | def forward(self, a, b): 301 | pcsize = a.size()[-1] 302 | 303 | if pcsize != self.n_samples: 304 | indices = np.arange(pcsize) 305 | np.random.shuffle(indices) 306 | indices = indices[:self.n_samples] 307 | a = a[:, :, indices] 308 | b = b[:, :, indices] 309 | 310 | a_points = torch.transpose(a, 1, 2)[:, :, 0:3] 311 | b_points = torch.transpose(b, 1, 2)[:, :, 0:3] 312 | pd = Ops.batch_pairwise_dist(a_points, b_points) 313 | #mma = torch.stack([a_points]*self.n_samples, dim=1) 314 | #mmb = torch.stack([b_points]*self.n_samples, dim=1).transpose(1,2) 315 | d = pd 316 | 317 | a_normals = torch.transpose(a, 1, 2)[:, :, 3:6] 318 | b_normals = torch.transpose(b, 1, 2)[:, :, 3:6] 319 | mma = torch.stack([a_normals]*self.n_samples, dim=1) 320 | mmb = torch.stack([b_normals]*self.n_samples, dim=1).transpose(1,2) 321 | d_norm = 1 - torch.sum(mma*mmb,3).squeeze() 322 | d += self.normal_weight * d_norm 323 | 324 | normal_min_mean = torch.min(d_norm, dim=2)[0].mean() 325 | self.nlogger.update(normal_min_mean) 326 | 327 | chamfer_sym = torch.min(d, dim=2)[0].sum() + torch.min(d, dim=1)[0].sum() 328 | chamfer_sym /= a.size()[0] 329 | 330 | return chamfer_sym 331 | 332 | 333 | class SampleChamfer(nn.Module): 334 | 335 | def __init__(self, normal_weight=0.001, n_samples=1024): 336 | super(SampleChamfer, self).__init__() 337 | self.normal_weight = normal_weight 338 | self.nlogger = DataVis.LossLogger("normal component") 339 | self.n_samples = n_samples 340 | 341 | 342 | def chamfer(self, a, b): 343 | 344 | a_indices = np.arange(a.size()[-1]) 345 | b_indices = np.arange(b.size()[-1]) 346 | np.random.shuffle(a_indices) 347 | np.random.shuffle(b_indices) 348 | a_indices = a_indices[:self.n_samples] 349 | b_indices = b_indices[:self.n_samples] 350 | a = a[:, a_indices] 351 | b = b[:, b_indices] 352 | 353 | a = torch.t(a) 354 | b = torch.t(b) 355 | mma = torch.stack([a[:, 0:3]]*self.n_samples) 356 | mmb = torch.stack([b[:, 0:3]]*self.n_samples).transpose(0,1) 357 | d = torch.sum((mma-mmb)**2,2).squeeze() 358 | 359 | #return torch.min(d, 1)[0].sum() + torch.min(d, 0)[0].sum() 360 | return torch.min(d, 0)[0].sum() 361 | 362 | def forward(self, a, b): 363 | # pcsize = a.size()[-1] 364 | # 365 | # if pcsize != self.n_samples: 366 | # indices = np.arange(pcsize) 367 | # np.random.shuffle(indices) 368 | # indices = indices[:self.n_samples] 369 | # a = a[:, :, indices] 370 | # b = b[:, :, indices] 371 | # 372 | # a_points = torch.transpose(a, 1, 2)[:, :, 0:3] 373 | # b_points = torch.transpose(b, 1, 2)[:, :, 0:3] 374 | # mma = torch.stack([a_points]*self.n_samples, dim=1) 375 | # mmb = torch.stack([b_points]*self.n_samples, dim=1).transpose(1,2) 376 | # d = torch.sum((mma-mmb)**2,3).squeeze() 377 | # 378 | # a_normals = torch.transpose(a, 1, 2)[:, :, 3:6] 379 | # b_normals = torch.transpose(b, 1, 2)[:, :, 3:6] 380 | # mma = torch.stack([a_normals]*self.n_samples, dim=1) 381 | # mmb = torch.stack([b_normals]*self.n_samples, dim=1).transpose(1,2) 382 | # d_norm = 1 - torch.sum(mma*mmb,3).squeeze() 383 | # d += self.normal_weight * d_norm 384 | # 385 | # normal_min_mean = torch.min(d_norm, dim=2)[0].mean() 386 | # self.nlogger.update(normal_min_mean) 387 | # 388 | # chamfer_sym = torch.min(d, dim=2)[0].sum() + torch.min(d, dim=1)[0].sum() 389 | # chamfer_sym /= a.size()[0] 390 | 391 | return self.chamfer(a, b) 392 | 393 | 394 | 395 | class SinkhornLoss(nn.Module): 396 | 397 | def __init__(self, n_iter=20, eps=1.0, batch_size=64, enc_size=512): 398 | super(SinkhornLoss, self).__init__() 399 | self.eps = eps 400 | self.n_iter = n_iter 401 | self.batch_size = batch_size 402 | self.normal_noise = torch.FloatTensor(batch_size, enc_size) 403 | 404 | 405 | def forward(self, x): 406 | bsize = x.size()[0] 407 | assert bsize == self.batch_size 408 | 409 | self.normal_noise.normal_() 410 | y = Variable(self.normal_noise.cuda()) 411 | 412 | #Computes MSE cost 413 | mmx = torch.stack([x]*bsize) 414 | mmy = torch.stack([x]*bsize).transpose(0, 1) 415 | c = torch.sum((mmx-mmy)**2,2).squeeze() 416 | 417 | k = (-c/self.eps).exp() 418 | b = Variable(torch.ones((bsize, 1))).cuda() 419 | a = Variable(torch.ones((bsize, 1))).cuda() 420 | 421 | #Sinkhorn iterations 422 | for l in range(self.n_iter): 423 | a = Variable(torch.ones((bsize, 1))).cuda() / (torch.mm(k, b)) 424 | b = Variable(torch.ones((bsize, 1))).cuda() / (torch.mm(k.t(), a)) 425 | 426 | loss = torch.mm(k * c, b) 427 | loss = torch.sum(loss*a) 428 | return loss 429 | 430 | 431 | 432 | class GeodesicChamferLoss(nn.Module): 433 | 434 | def __init__(self): 435 | super(GeodesicChamferLoss, self).__init__() 436 | 437 | 438 | def forward(self, a, b): 439 | pass 440 | 441 | 442 | class L2WithNormalLoss(nn.Module): 443 | 444 | def __init__(self): 445 | super(L2WithNormalLoss, self).__init__() 446 | self.nlogger = DataVis.LossLogger("normal w/ L2") 447 | self.L1 = nn.L1Loss() 448 | 449 | def forward(self, a, b): 450 | position_loss = self.L1(a[:, 0:3, :], b[:, 0:3, :]) 451 | normal_loss = torch.mean(1 - Ops.cosine_similarity(a[:, 3:6, :], b[:, 3:6, :])) 452 | self.nlogger.update(normal_loss) 453 | 454 | return normal_loss 455 | 456 | 457 | class PointCloudAutoEncoder(Model): 458 | 459 | def __init__(self, size, dim, batch_size=64, enc_size=100, kernel_size=16, 460 | noise=0, 461 | name="PCAutoEncoder"): 462 | super(PointCloudAutoEncoder, self).__init__(name) 463 | 464 | self.size = size 465 | self.dim = dim 466 | self.batch_size = batch_size 467 | self.kernel_size = kernel_size 468 | self.enc_size = enc_size 469 | self.noise_factor = noise 470 | self.enc_noise = torch.FloatTensor(self.batch_size, self.enc_size) 471 | 472 | self.encoder = PointCloudEncoder(self.size, self.dim, 473 | batch_size = self.batch_size, 474 | enc_size = self.enc_size, 475 | kernel_size = self.kernel_size) 476 | 477 | self.decoder = PointCloudDecoder(self.size, self.dim, 478 | batch_size = self.batch_size, 479 | enc_size = self.enc_size, 480 | kernel_size = self.kernel_size) 481 | 482 | # if self.dim == 6: 483 | # self.normal_decoder = PointCloudDecoder(self.size, 3, 484 | # batch_size = self.batch_size, 485 | # enc_size = self.enc_size, 486 | # kernel_size = self.kernel_size) 487 | 488 | 489 | def forward(self, x): 490 | encoding = self.encoder(x) 491 | self.enc_noise.normal_() 492 | 493 | added_noise = Variable(self.noise_factor*self.enc_noise.cuda()) 494 | 495 | encoding += added_noise 496 | x_prime = self.decoder(encoding) 497 | if self.dim == 6: 498 | x_normal = x_prime[:, 3:6, :] 499 | x_normal = F.normalize(x_normal) 500 | result = torch.cat((x_prime[:, 0:3, :], x_normal), dim=1) 501 | else: 502 | result = x_prime 503 | 504 | return result 505 | 506 | 507 | def save_results(self, path, data): 508 | results = data.cpu().data.numpy() 509 | results = results.transpose(0, 2, 1) 510 | save_objs(results, path) 511 | print "Points saved." 512 | 513 | 514 | class NormalReg(nn.Module): 515 | 516 | def __init__(self): 517 | super(NormalReg, self).__init__() 518 | 519 | def forward(self, x): 520 | 521 | mean = torch.mean(x, dim=0).pow(2) 522 | cov = Ops.cov(x) 523 | 524 | cov_loss = torch.mean( 525 | (Variable(torch.eye(cov.size()[0]).cuda())-cov) 526 | .pow(2)) 527 | 528 | return torch.mean(mean) + cov_loss 529 | 530 | 531 | class PointCloudVAE(PointCloudAutoEncoder): 532 | 533 | def __init__(self, size, dim, batch_size=64, enc_size=100, kernel_size=16, 534 | reg_fn=NormalReg(), 535 | noise = 0, 536 | name="PCVAE"): 537 | super(PointCloudVAE, self).__init__(size, dim, batch_size, enc_size, kernel_size, 538 | noise=noise, name=name) 539 | self.reg_fn = reg_fn 540 | self.noise = torch.FloatTensor(self.batch_size, self.enc_size) 541 | 542 | 543 | def encoding_regularizer(self, x): 544 | return self.reg_fn(self.encoder(x)) 545 | 546 | 547 | def sample(self): 548 | self.noise.normal_() 549 | return self.decoder(Variable(self.noise.cuda())) 550 | 551 | 552 | class MultiResVAE(Model): 553 | 554 | def __init__(self, size, dim, batch_size=64, enc_size=100, kernel_size=2, 555 | reg_fn=NormalReg(), 556 | noise = 0, 557 | name="MLVAE"): 558 | super(MultiResVAE, self).__init__(name) 559 | 560 | self.reg_fn = reg_fn 561 | 562 | self.size = size 563 | self.dim = dim 564 | self.enc_size = enc_size 565 | self.batch_size = batch_size 566 | self.kernel_size = kernel_size 567 | self.enc_modules = nn.ModuleList() 568 | self.dec_modules = nn.ModuleList() 569 | self.upsample = Ops.NNUpsample1d() 570 | self.pool = nn.MaxPool1d(kernel_size=4, stride=4) 571 | self.noise_factor = noise 572 | 573 | self.enc_noise = torch.FloatTensor(self.batch_size, self.enc_size) 574 | 575 | custom_nfilters = [3, 32, 64, 128, 256, 512, 512, 1024, 1024, 1024] 576 | custom_nfilters = np.array(custom_nfilters) 577 | custom_nfilters[1:] /= 2 578 | self.last_size = 16 579 | 580 | self.noise = torch.FloatTensor(self.batch_size, self.enc_size) 581 | 582 | current_size = self.size 583 | layer_num = 1 584 | padding = (self.kernel_size - 1)/2 585 | n_channels = [] 586 | n_channels.append(custom_nfilters[layer_num-1]) 587 | while current_size > self.last_size: 588 | in_channels = custom_nfilters[layer_num-1] 589 | out_channels = custom_nfilters[layer_num] 590 | conv_enc = MultiResConv1d('down{}'.format(layer_num), 591 | in_channels, out_channels) 592 | current_size /= 2 593 | in_channels = out_channels 594 | n_channels.append(out_channels) 595 | layer_num += 1 596 | 597 | self.enc_modules.append(conv_enc) 598 | 599 | self.enc_fc = nn.Linear(3*self.last_size*in_channels, self.enc_size) 600 | #self.enc_fc_mean = nn.Linear(3*self.last_size*in_channels, self.enc_size) 601 | #self.enc_fc_var = nn.Linear(3*self.last_size*in_channels, self.enc_size) 602 | self.dec_fc = nn.Linear(self.enc_size, self.last_size*n_channels[-1]) 603 | 604 | self.final_feature = 128 605 | n_channels.reverse() 606 | n_channels[-1] = self.final_feature 607 | current_size = self.last_size 608 | layer_num = 1 609 | padding = (self.kernel_size - 1)/2 610 | while current_size < self.size: 611 | in_channels = n_channels[layer_num-1] 612 | out_channels = n_channels[layer_num] 613 | conv_dec = MultiResConvTranspose1d('up{}'.format(layer_num), 614 | in_channels, out_channels) 615 | current_size *= 2 616 | in_channels = out_channels 617 | layer_num += 1 618 | 619 | self.dec_modules.append(conv_dec) 620 | 621 | self.final_conv = nn.Sequential() 622 | self.final_conv.add_module('final_conv1', 623 | nn.ConvTranspose1d(self.final_feature*3, 128, 1, stride=1, padding=0)) 624 | self.final_conv.add_module('bn_final', 625 | nn.BatchNorm1d(128)) 626 | self.final_conv.add_module('relu_final', 627 | nn.ReLU(inplace=True)) 628 | self.final_conv.add_module('final_conv2', 629 | nn.ConvTranspose1d(128, 3, 1, stride=1, padding=0)) 630 | self.final_conv.add_module('tanh_final', 631 | nn.Tanh()) 632 | 633 | 634 | def enc_forward(self, x): 635 | x0 = x 636 | x1 = self.pool(x) 637 | x2 = self.pool(x1) 638 | 639 | enc_tensors = [] 640 | enc_tensors.append([x0, x1, x2]) 641 | 642 | for enc_op in self.enc_modules: 643 | enc_tensors.append(enc_op(enc_tensors[-1])) 644 | 645 | t0 = enc_tensors[-1][0] 646 | t1 = self.upsample(enc_tensors[-1][1]) 647 | t2 = self.upsample(self.upsample(enc_tensors[-1][2])) 648 | t = torch.cat((t0, t1, t2), 1).view(self.batch_size, -1) 649 | 650 | encoding = self.enc_fc(t) 651 | return encoding, enc_tensors 652 | #encoding_mean = self.enc_fc_mean(t) 653 | #encoding_var = self.enc_fc_var(t) 654 | #return (encoding_mean, encoding_var) 655 | 656 | 657 | def dec_forward(self, x): 658 | 659 | mr_enc0 = self.dec_fc(x).view(self.batch_size, -1, self.last_size) 660 | mr_enc1 = self.pool(mr_enc0) 661 | mr_enc2 = self.pool(mr_enc1) 662 | mr_enc = [mr_enc0, mr_enc1, mr_enc2] 663 | 664 | dec_tensors = [] 665 | dec_tensors.append(mr_enc) 666 | 667 | for i in xrange(0, len(self.dec_modules)-1): 668 | dec_tensors.append(self.dec_modules[i](dec_tensors[-1])) 669 | 670 | conv_out = self.dec_modules[-1](dec_tensors[-1]) 671 | out0 = conv_out[0] 672 | out1 = self.upsample(conv_out[1]) 673 | out2 = self.upsample(self.upsample(conv_out[2])) 674 | out = torch.cat((out0, out1, out2), 1) 675 | return self.final_conv(out) 676 | 677 | # 678 | # def reparameterize(self, mu, logvar): 679 | # if self.training: 680 | # std = logvar.mul(0.5).exp_() 681 | # eps = Variable(std.data.new(std.size()).normal_()) 682 | # return eps.mul(std).add_(mu) 683 | # else: 684 | # return mu 685 | # 686 | 687 | def forward(self, x): 688 | encoding = self.enc_forward(x)[0] 689 | self.enc_noise.normal_() 690 | 691 | added_noise = Variable(self.noise_factor*self.enc_noise.cuda()) 692 | 693 | encoding += added_noise 694 | return self.dec_forward(encoding) 695 | 696 | 697 | def encoding_regularizer(self, x): 698 | return self.reg_fn(self.enc_forward(x)[0]) 699 | 700 | 701 | def sample(self): 702 | self.noise.normal_() 703 | return self.dec_forward(Variable(self.noise.cuda())) 704 | 705 | 706 | def save_results(self, path, data, start_idx=0): 707 | results = data.cpu().data.numpy() 708 | results = results.transpose(0, 2, 1) 709 | save_objs(results, path, start_idx) 710 | print "Points saved." 711 | 712 | 713 | class EncodingSVM(Model): 714 | 715 | def __init__(self, enc_size, n_classes, ae_model, batch_size, name="EncSVM"): 716 | super(EncodingSVM, self).__init__(name) 717 | 718 | self.batch_size = batch_size 719 | self.enc_size = enc_size 720 | self.n_classes = n_classes 721 | self.ae_model = ae_model 722 | 723 | self.upsample = Ops.NNUpsample1d() 724 | 725 | alpha = 32 726 | self.pools = [] 727 | self.pools.append(nn.MaxPool1d(kernel_size=alpha, stride=alpha)) 728 | self.pools.append(nn.MaxPool1d(kernel_size=alpha/2, stride=alpha/2)) 729 | self.pools.append(nn.MaxPool1d(kernel_size=alpha/4, stride=alpha/4)) 730 | #self.pools.append(nn.MaxPool1d(kernel_size=alpha/8, stride=alpha/8)) 731 | 732 | self.fc = nn.Linear(self.enc_size, self.n_classes) 733 | 734 | def forward(self, x): 735 | enc, features = self.ae_model.enc_forward(x) 736 | descriptor = [] 737 | for i, p in enumerate(self.pools): 738 | t0 = p(features[i][0]) 739 | t1 = self.upsample(p(features[i][1])) 740 | t2 = self.upsample(self.upsample(p(features[i][2]))) 741 | descriptor.append(torch.cat((t0, t1, t2), 1)) 742 | 743 | descriptor = torch.cat(descriptor, 1) 744 | descriptor = descriptor.view(self.batch_size, -1) 745 | 746 | return self.fc(descriptor) 747 | -------------------------------------------------------------------------------- /models/GAN.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from Model import Model 6 | from tools.PointCloudDataset import save_objs 7 | from tools import Ops 8 | 9 | 10 | class LinearDiscriminator(Model): 11 | 12 | def __init__(self, input_size=256, n_layers=3, layer_size=256, 13 | name="LinearGenerator"): 14 | super(LinearDiscriminator, self).__init__(name) 15 | self.n_layers = n_layers 16 | self.layer_size = layer_size 17 | self.activation = nn.LeakyReLU(0.2, inplace=True) 18 | self.bn = nn.BatchNorm1d(layer_size) 19 | 20 | self.layers = [] 21 | self.bns = [] 22 | for i in range(n_layers): 23 | if i == 0: 24 | self.layers.append(nn.Linear(input_size, layer_size)) 25 | else: 26 | self.layers.append(nn.Linear(input_size, layer_size)) 27 | self.bns.append(nn.BatchNorm1d(layer_size)) 28 | 29 | self.out = nn.Linear(layer_size, 1) 30 | 31 | 32 | def cuda(self): 33 | super(LinearDiscriminator, self).cuda() 34 | for l in self.layers: 35 | l.cuda() 36 | for b in self.bns: 37 | b.cuda() 38 | 39 | 40 | def forward(self, x): 41 | 42 | features = [] 43 | for i, layer in enumerate(self.layers): 44 | if i == 0: 45 | features.append(self.activation(self.bns[i](layer(x)))) 46 | else: 47 | features.append(self.activation(self.bns[i](layer(features[-1])))) 48 | output = self.out(features[-1]) 49 | return (output, features) 50 | 51 | 52 | class LinearGenerator(Model): 53 | 54 | def __init__(self, enc_size=100, output_size=256, n_layers=3, layer_size=256, 55 | name="LinearGenerator"): 56 | super(LinearGenerator, self).__init__(name) 57 | self.n_layers = n_layers 58 | self.layer_size = layer_size 59 | self.enc_size = enc_size 60 | self.activation = nn.LeakyReLU(0.2, inplace=True) 61 | self.bn = nn.BatchNorm1d(layer_size) 62 | 63 | self.net = nn.Sequential() 64 | for i in range(n_layers): 65 | if i == 0: 66 | self.net.add_module("linear{}".format(i), 67 | nn.Linear(enc_size, layer_size)) 68 | else: 69 | self.net.add_module("linear{}".format(i), 70 | nn.Linear(layer_size, layer_size)) 71 | self.net.add_module("bn{}".format(i), 72 | nn.BatchNorm1d(layer_size)) 73 | self.net.add_module("relu{}".format(i), 74 | nn.ReLU()) 75 | 76 | self.out = nn.Linear(layer_size, output_size) 77 | 78 | 79 | def forward(self, x): 80 | return self.out(self.net(x)) 81 | 82 | 83 | def save_results(self, path, data): 84 | results = data.cpu().data.numpy() 85 | results = results.transpose(0, 2, 1) 86 | save_objs(results, path) 87 | print "Points saved." 88 | 89 | 90 | class DiscriminatorBCELoss(nn.Module): 91 | 92 | def __init__(self): 93 | super(DiscriminatorBCELoss, self).__init__() 94 | self.BCE = nn.BCELoss() 95 | 96 | 97 | def forward(self, x): 98 | input_data, target = x 99 | input_logits, _ = input_data 100 | 101 | return self.BCE(F.sigmoid(input_logits), target) 102 | 103 | 104 | class GeneratorFeatureLoss(nn.Module): 105 | 106 | def __init__(self): 107 | super(GeneratorFeatureLoss, self).__init__() 108 | 109 | 110 | def forward(self, x): 111 | real, fake = x 112 | _, real_features = real 113 | _, fake_features = fake 114 | 115 | real_features = torch.cat(real_features, dim=1) 116 | fake_features = torch.cat(fake_features, dim=1) 117 | 118 | real_mean = torch.mean(real_features, dim=0) 119 | fake_mean = torch.mean(fake_features, dim=0) 120 | 121 | real_cov = Ops.cov(real_features) 122 | fake_cov = Ops.cov(fake_features) 123 | 124 | mean_loss = torch.sum((real_mean-fake_mean).pow(2)) 125 | cov_loss = torch.sum((real_cov-fake_cov).pow(2)) 126 | 127 | return mean_loss + cov_loss 128 | 129 | 130 | -------------------------------------------------------------------------------- /models/ImageToShape.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from torch.autograd import Variable 7 | import torchvision 8 | 9 | from Model import Model 10 | from tools.PointCloudDataset import save_objs 11 | from tools import Ops 12 | import tools.DataVis as DataVis 13 | 14 | from AutoEncoder import MultiResConvTranspose1d 15 | 16 | 17 | class MultiResImageToShape(Model): 18 | 19 | def __init__(self, size, dim, batch_size=64, kernel_size=2, name="MRI2S", 20 | pretrained=False, arch=True): 21 | super(MultiResImageToShape, self).__init__(name) 22 | 23 | self.size = size 24 | self.dim = dim 25 | self.kernel_size = kernel_size 26 | self.batch_size = batch_size 27 | if arch == 'vgg': 28 | self.encoder = torchvision.models.vgg11(pretrained=pretrained) 29 | elif arch == 'alexnet': 30 | self.encoder = torchvision.models.alexnet(pretrained=pretrained) 31 | self.encoder.classifier._modules['6'] = nn.Linear(4096, 16*1024) 32 | self.dec_modules = nn.ModuleList() 33 | self.base_size = 16 34 | 35 | self.upsample = Ops.NNUpsample1d() 36 | self.pool = nn.MaxPool1d(kernel_size=4, stride=4) 37 | 38 | custom_nfilters = [128, 128, 128, 256, 512, 512, 1024, 1024, 1024] 39 | custom_nfilters.reverse() 40 | custom_nfilters = np.array(custom_nfilters) 41 | 42 | current_size = self.base_size 43 | layer_num = 1 44 | padding = (self.kernel_size - 1)/2 45 | while current_size < self.size: 46 | in_channels = custom_nfilters[layer_num-1] 47 | print in_channels 48 | out_channels = custom_nfilters[layer_num] 49 | conv_dec = MultiResConvTranspose1d('up{}'.format(layer_num), 50 | in_channels, out_channels) 51 | current_size *= 2 52 | in_channels = out_channels 53 | layer_num += 1 54 | 55 | self.dec_modules.append(conv_dec) 56 | 57 | self.final_conv = nn.Sequential() 58 | self.final_conv.add_module('final_conv1', 59 | nn.ConvTranspose1d(custom_nfilters[-1]*3, 128, 1, stride=1, padding=0)) 60 | self.final_conv.add_module('bn_final', 61 | nn.BatchNorm1d(128)) 62 | self.final_conv.add_module('relu_final', 63 | nn.ReLU(inplace=True)) 64 | self.final_conv.add_module('final_conv2', 65 | nn.ConvTranspose1d(128, 3, 1, stride=1, padding=0)) 66 | self.final_conv.add_module('tanh_final', 67 | nn.Tanh()) 68 | 69 | 70 | def forward(self, x): 71 | mr_enc0 = self.encoder(x).view(self.batch_size, -1, self.base_size) 72 | mr_enc1 = self.pool(mr_enc0) 73 | mr_enc2 = self.pool(mr_enc1) 74 | mr_enc = [mr_enc0, mr_enc1, mr_enc2] 75 | 76 | dec_tensors = [] 77 | dec_tensors.append(mr_enc) 78 | 79 | for i in xrange(0, len(self.dec_modules)-1): 80 | dec_tensors.append(self.dec_modules[i](dec_tensors[-1])) 81 | 82 | conv_out = self.dec_modules[-1](dec_tensors[-1]) 83 | out0 = conv_out[0] 84 | out1 = self.upsample(conv_out[1]) 85 | out2 = self.upsample(self.upsample(conv_out[2])) 86 | out = torch.cat((out0, out1, out2), 1) 87 | return self.final_conv(out) 88 | 89 | 90 | def save_results(self, path, data): 91 | results = data.cpu().data.numpy() 92 | results = results.transpose(0, 2, 1) 93 | save_objs(results, path) 94 | print "Points saved." 95 | 96 | 97 | -------------------------------------------------------------------------------- /models/MRTDecoder.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from torch.autograd import Variable 7 | import torchvision 8 | 9 | from Model import Model 10 | from tools.PointCloudDataset import save_objs 11 | from tools import Ops 12 | import tools.DataVis as DataVis 13 | 14 | from AutoEncoder import MultiResConvTranspose1d 15 | from AutoEncoder import MultiResConv1d 16 | 17 | 18 | class FoldingNet(Model): 19 | def __init__(self, size, batch_size=64, name="FoldingNet"): 20 | super(FoldingNet, self).__init__(name) 21 | 22 | self.fold = nn.Sequential( 23 | nn.Conv1d(2+1024, 1024, 1, stride=1, padding=0), 24 | nn.BatchNorm1d(1024), 25 | nn.ReLU(inplace=True), 26 | 27 | nn.Conv1d(1024, 512, 1, stride=1, padding=0), 28 | nn.BatchNorm1d(512), 29 | nn.ReLU(inplace=True), 30 | 31 | nn.Conv1d(512, 256, 1, stride=1, padding=0), 32 | nn.BatchNorm1d(256), 33 | nn.ReLU(inplace=True), 34 | 35 | nn.Conv1d(256, 128, 1, stride=1, padding=0), 36 | nn.BatchNorm1d(128), 37 | nn.ReLU(inplace=True), 38 | 39 | nn.Conv1d(128, 3, 1, stride=1, padding=0), 40 | nn.Tanh()) 41 | 42 | #gd = int(np.sqrt(size)) 43 | #grid = np.indices((gd,gd)).T.reshape(-1, 2).T.astype('float32') 44 | #grid /= gd-1 45 | #self.z = Variable(torch.from_numpy(grid).unsqueeze(0).cuda()) 46 | self.z = Variable(torch.rand(1, 2, size).cuda()) 47 | self.global_feat = nn.Parameter(torch.rand(1, 1024, 1)) 48 | 49 | def forward(self): 50 | #self.z.data.uniform_(0, 1) 51 | inp = torch.cat((self.z, self.global_feat.expand(-1, -1, 1024)), 1) 52 | return self.fold(inp) 53 | 54 | 55 | class UNetMRTDecoder(Model): 56 | 57 | def __init__(self, size, dim, batch_size=64, enc_size=100, 58 | kernel_size=2, 59 | axis_file='rpt_axis.npy', 60 | name="PointSeg"): 61 | super(UNetMRTDecoder, self).__init__(name) 62 | 63 | self.init_channels = 128 64 | self.size = size 65 | self.dim = dim 66 | self.batch_size = batch_size 67 | self.kernel_size = kernel_size 68 | self.enc_size = enc_size 69 | self.enc_modules = nn.ModuleList() 70 | self.dec_modules = nn.ModuleList() 71 | self.upsample = Ops.NNUpsample1d() 72 | self.pool = nn.AvgPool1d(kernel_size=4, stride=4) 73 | #self.z = nn.Parameter(torch.randn(self.size*3).view(batch_size, 3, -1)) 74 | self.z = Variable(torch.randn(self.size*3).view(batch_size, 3, -1)).cuda() 75 | 76 | #custom_nfilters = [3, 128, 128, 128, 256, 265, 256, 77 | # 512, 512, 512, 1024, 1024, 2048] 78 | custom_nfilters = [3, 4, 8, 16, 32, 64, 128, 79 | 128, 128, 128, 256, 256, 256] 80 | custom_nfilters = np.array(custom_nfilters) 81 | #custom_nfilters[1:] /= 4 82 | 83 | current_size = self.size 84 | layer_num = 1 85 | padding = (self.kernel_size - 1)/2 86 | n_channels = [] 87 | while current_size > 64: 88 | in_channels = custom_nfilters[layer_num-1] 89 | out_channels = custom_nfilters[layer_num] 90 | conv_enc = MultiResConv1d('down{}'.format(layer_num), 91 | in_channels, out_channels) 92 | # conv_enc = nn.Sequential() 93 | # conv_enc.add_module('conv{}'.format(layer_num), 94 | # nn.Conv1d(in_channels, out_channels, self.kernel_size, 95 | # stride=2, 96 | # padding=padding)) 97 | # #axis=self.axis_on_level(layer_num-1))) 98 | # conv_enc.add_module('bn{}'.format(layer_num), 99 | # nn.BatchNorm1d(out_channels)) 100 | # conv_enc.add_module('lrelu{}'.format(layer_num), 101 | # nn.LeakyReLU(0.2, inplace=True)) 102 | current_size /= 2 103 | in_channels = out_channels 104 | n_channels.append(out_channels) 105 | if out_channels < 1024: 106 | out_channels *= 2 107 | layer_num += 1 108 | 109 | self.enc_modules.append(conv_enc) 110 | 111 | n_channels.reverse() 112 | current_size = 64 113 | layer_num = 1 114 | padding = (self.kernel_size - 1)/2 115 | while current_size < self.size//2: 116 | if layer_num == 1: 117 | in_channels = n_channels[layer_num-1] 118 | else: 119 | in_channels = n_channels[layer_num-1]*2 120 | out_channels = n_channels[layer_num] 121 | 122 | conv_dec = MultiResConvTranspose1d('up{}'.format(layer_num), 123 | in_channels, out_channels) 124 | # conv_dec = nn.Sequential() 125 | # conv_dec.add_module('conv{}'.format(layer_num), 126 | # nn.ConvTranspose1d(in_channels, 127 | # out_channels, 128 | # self.kernel_size, 129 | # stride=2, 130 | # padding=padding)) 131 | # conv_dec.add_module('bn{}'.format(layer_num), 132 | # nn.BatchNorm1d(out_channels)) 133 | # conv_dec.add_module('relu{}'.format(layer_num), 134 | # nn.ReLU(inplace=True)) 135 | 136 | current_size *= 2 137 | in_channels = out_channels 138 | layer_num += 1 139 | 140 | self.dec_modules.append(conv_dec) 141 | 142 | conv_dec = MultiResConvTranspose1d('up{}'.format(layer_num), 143 | in_channels, 256) 144 | self.dec_modules.append(conv_dec) 145 | 146 | # conv_dec = nn.Sequential() 147 | # conv_dec.add_module('conv{}'.format(layer_num), 148 | # nn.ConvTranspose1d(in_channels, 256, self.kernel_size, 149 | # stride=2, 150 | # padding=padding)) 151 | # conv_dec.add_module('bn{}'.format(layer_num), 152 | # nn.BatchNorm1d(256)) 153 | # conv_dec.add_module('relu{}'.format(layer_num), 154 | # nn.ReLU(inplace=True)) 155 | 156 | self.final_conv = nn.Sequential() 157 | self.final_conv.add_module('final_conv1', 158 | nn.ConvTranspose1d(256*3, 128, 1, stride=1, padding=0)) 159 | self.final_conv.add_module('bn_final', 160 | nn.BatchNorm1d(128)) 161 | self.final_conv.add_module('relu_final', 162 | nn.ReLU(inplace=True)) 163 | self.final_conv.add_module('final_conv2', 164 | nn.ConvTranspose1d(128, 3, 1, stride=1, padding=0)) 165 | self.final_conv.add_module('tanh_final', 166 | nn.Tanh()) 167 | 168 | def multires_cat(self, x, y): 169 | out0 = torch.cat((x[0], y[0]), 1) 170 | out1 = torch.cat((x[1], y[1]), 1) 171 | out2 = torch.cat((x[2], y[2]), 1) 172 | 173 | return [out0, out1, out2] 174 | 175 | def forward(self): 176 | x0 = self.z 177 | x1 = self.pool(x0) 178 | x2 = self.pool(x1) 179 | 180 | enc_tensors = [] 181 | enc_tensors.append([x0, x1, x2]) 182 | 183 | for enc_op in self.enc_modules: 184 | enc_tensors.append(enc_op(enc_tensors[-1])) 185 | # 186 | # for t in enc_tensors: 187 | # print t.size() 188 | # 189 | # print self.dec_modules 190 | # 191 | #t = enc_tensors[-1].view(self.batch_size, -1) 192 | #encoding = self.enc_fc(t) 193 | 194 | dec_tensors = [] 195 | #dec_tensors.append(self.dec_fc(encoding).view(self.batch_size, -1, 16)) 196 | dec_tensors.append(self.dec_modules[0](enc_tensors[-1])) 197 | 198 | for i in xrange(1, len(self.dec_modules)-1): 199 | in_tensor = enc_tensors[-(i+1)] 200 | #in_tensor = torch.cat((dec_tensors[-1], in_tensor), 1) 201 | in_tensor = self.multires_cat(in_tensor, dec_tensors[-1]) 202 | dec_tensors.append(self.dec_modules[i](in_tensor)) 203 | 204 | conv_out = self.dec_modules[-1](dec_tensors[-1]) 205 | 206 | out0 = conv_out[0] 207 | out1 = self.upsample(conv_out[1]) 208 | out2 = self.upsample(self.upsample(conv_out[2])) 209 | 210 | out = torch.cat((out0, out1, out2), 1) 211 | 212 | return self.final_conv(out) 213 | 214 | 215 | def axis_on_level(self, l): 216 | nlevels = np.log2(self.axis.shape[0]+1) 217 | level = nlevels - l - 1 218 | a = int(2**level - 1) 219 | b = int(2**(level+1) - 1) 220 | return self.axis[a:b, :] 221 | 222 | 223 | def save_results(self, path, data): 224 | results = data.cpu().data.numpy() 225 | results = results.transpose(0, 2, 1) 226 | save_segs(results, path) 227 | print "Segmentations saved." 228 | 229 | 230 | 231 | class MRTDecoder(Model): 232 | 233 | def __init__(self, size, dim, batch_size=64, kernel_size=2, name="MRTDecoder"): 234 | super(MRTDecoder, self).__init__(name) 235 | 236 | self.size = size 237 | self.dim = dim 238 | self.kernel_size = kernel_size 239 | self.batch_size = batch_size 240 | 241 | self.z = nn.Parameter(torch.randn(16*1024)) 242 | self.dec_modules = nn.ModuleList() 243 | self.base_size = 16 244 | 245 | self.upsample = Ops.NNUpsample1d() 246 | self.pool = nn.MaxPool1d(kernel_size=4, stride=4) 247 | 248 | custom_nfilters = [128, 128, 128, 256, 512, 512, 1024, 1024, 1024] 249 | custom_nfilters.reverse() 250 | custom_nfilters = np.array(custom_nfilters) 251 | custom_nfilters[1:] /= 2 252 | 253 | current_size = self.base_size 254 | layer_num = 1 255 | padding = (self.kernel_size - 1)/2 256 | while current_size < self.size: 257 | in_channels = custom_nfilters[layer_num-1] 258 | out_channels = custom_nfilters[layer_num] 259 | conv_dec = MultiResConvTranspose1d('up{}'.format(layer_num), 260 | in_channels, out_channels) 261 | current_size *= 2 262 | in_channels = out_channels 263 | layer_num += 1 264 | 265 | self.dec_modules.append(conv_dec) 266 | 267 | self.final_conv = nn.Sequential() 268 | self.final_conv.add_module('final_conv1', 269 | nn.ConvTranspose1d(custom_nfilters[-1]*3, 128, 1, stride=1, padding=0)) 270 | self.final_conv.add_module('bn_final', 271 | nn.BatchNorm1d(128)) 272 | self.final_conv.add_module('relu_final', 273 | nn.ReLU(inplace=True)) 274 | self.final_conv.add_module('final_conv2', 275 | nn.ConvTranspose1d(128, 3, 1, stride=1, padding=0)) 276 | self.final_conv.add_module('tanh_final', 277 | nn.Tanh()) 278 | 279 | 280 | def forward(self): 281 | mr_enc0 = self.z.view(self.batch_size, -1, self.base_size) 282 | mr_enc1 = self.pool(mr_enc0) 283 | mr_enc2 = self.pool(mr_enc1) 284 | mr_enc = [mr_enc0, mr_enc1, mr_enc2] 285 | 286 | dec_tensors = [] 287 | dec_tensors.append(mr_enc) 288 | 289 | for i in xrange(0, len(self.dec_modules)-1): 290 | dec_tensors.append(self.dec_modules[i](dec_tensors[-1])) 291 | 292 | conv_out = self.dec_modules[-1](dec_tensors[-1]) 293 | out0 = conv_out[0] 294 | out1 = self.upsample(conv_out[1]) 295 | out2 = self.upsample(self.upsample(conv_out[2])) 296 | out = torch.cat((out0, out1, out2), 1) 297 | return self.final_conv(out) 298 | 299 | 300 | def save_results(self, path, data): 301 | results = data.cpu().data.numpy() 302 | results = results.transpose(0, 2, 1) 303 | save_objs(results, path) 304 | print "Points saved." 305 | 306 | 307 | class SRTDecoder(Model): 308 | 309 | def __init__(self, size, dim, batch_size=64, kernel_size=2, name="SRTDecoder"): 310 | super(SRTDecoder, self).__init__(name) 311 | 312 | self.size = size 313 | self.dim = dim 314 | self.kernel_size = kernel_size 315 | self.batch_size = batch_size 316 | 317 | self.z = nn.Parameter(torch.randn(16*1024)) 318 | self.dec_modules = nn.ModuleList() 319 | self.base_size = 16 320 | 321 | self.upsample = Ops.NNUpsample1d() 322 | self.pool = nn.MaxPool1d(kernel_size=4, stride=4) 323 | 324 | custom_nfilters = [128, 128, 128, 256, 512, 512, 1024, 1024, 1024] 325 | custom_nfilters.reverse() 326 | custom_nfilters = np.array(custom_nfilters) 327 | custom_nfilters[1:] /= 2 328 | 329 | self.conv_dec = nn.Sequential() 330 | 331 | current_size = self.base_size 332 | layer_num = 1 333 | padding = (self.kernel_size - 1)/2 334 | while current_size < self.size: 335 | in_channels = custom_nfilters[layer_num-1] 336 | out_channels = custom_nfilters[layer_num] 337 | 338 | self.conv_dec.add_module('conv{}'.format(layer_num), 339 | nn.ConvTranspose1d(in_channels, out_channels, self.kernel_size, 340 | stride=2, 341 | padding=padding)) 342 | self.conv_dec.add_module('bn{}'.format(layer_num), 343 | nn.BatchNorm1d(out_channels)) 344 | self.conv_dec.add_module('lrelu{}'.format(layer_num), 345 | nn.LeakyReLU(0.2, inplace=True)) 346 | 347 | current_size *= 2 348 | in_channels = out_channels 349 | layer_num += 1 350 | 351 | self.final_conv = nn.Sequential() 352 | self.final_conv.add_module('final_conv1', 353 | nn.ConvTranspose1d(custom_nfilters[-1], 128, 1, stride=1, padding=0)) 354 | self.final_conv.add_module('bn_final', 355 | nn.BatchNorm1d(128)) 356 | self.final_conv.add_module('relu_final', 357 | nn.ReLU(inplace=True)) 358 | self.final_conv.add_module('final_conv2', 359 | nn.ConvTranspose1d(128, 3, 1, stride=1, padding=0)) 360 | self.final_conv.add_module('tanh_final', 361 | nn.Tanh()) 362 | 363 | 364 | def forward(self): 365 | feat = self.z.view(self.batch_size, -1, self.base_size) 366 | feat = self.conv_dec(feat) 367 | out = self.final_conv(feat) 368 | return out 369 | 370 | 371 | def save_results(self, path, data): 372 | results = data.cpu().data.numpy() 373 | results = results.transpose(0, 2, 1) 374 | save_objs(results, path) 375 | print "Points saved." 376 | 377 | 378 | -------------------------------------------------------------------------------- /models/Model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import os 4 | import glob 5 | 6 | 7 | class Model(nn.Module): 8 | 9 | def __init__(self, name): 10 | super(Model, self).__init__() 11 | self.name = name 12 | 13 | 14 | def save(self, path, epoch=0): 15 | complete_path = os.path.join(path, self.name) 16 | if not os.path.exists(complete_path): 17 | os.makedirs(complete_path) 18 | torch.save(self.state_dict(), 19 | os.path.join(complete_path, 20 | "model-{}.pth".format(str(epoch).zfill(5)))) 21 | 22 | 23 | def save_results(self, path, data): 24 | raise NotImplementedError("Model subclass must implement this method.") 25 | 26 | 27 | def load(self, path, modelfile=None): 28 | complete_path = os.path.join(path, self.name) 29 | if not os.path.exists(complete_path): 30 | raise IOError("{} directory does not exist in {}".format(self.name, path)) 31 | 32 | if modelfile is None: 33 | model_files = glob.glob(complete_path+"/*") 34 | mf = max(model_files) 35 | else: 36 | mf = os.path.join(complete_path, modelfile) 37 | 38 | self.load_state_dict(torch.load(mf)) 39 | 40 | 41 | -------------------------------------------------------------------------------- /models/VoxUNet.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from torch.autograd import Variable 7 | import torchvision 8 | 9 | from Model import Model 10 | from tools.PointCloudDataset import save_objs 11 | from tools import Ops 12 | import tools.DataVis as DataVis 13 | 14 | from AutoEncoder import MultiResConvTranspose1d 15 | 16 | 17 | class UpConv(nn.Module): 18 | 19 | def __init__(self, in_channels, out_channels, kernel_size=3): 20 | super(UpConv, self).__init__() 21 | padding = (kernel_size - 1)/2 22 | self.conv = nn.Conv3d(in_channels, out_channels, kernel_size, 23 | padding=padding) 24 | self.upsample = nn.Upsample(scale_factor=2, mode='trilinear') 25 | 26 | def forward(self, x): 27 | return self.conv(self.upsample(x)) 28 | 29 | 30 | class VoxUNet(Model): 31 | 32 | def __init__(self, size, batch_size=64, kernel_size=3, name="VoxUNet"): 33 | super(VoxUNet, self).__init__(name) 34 | 35 | self.size = size 36 | self.kernel_size = kernel_size 37 | self.batch_size = batch_size 38 | 39 | self.z = nn.Parameter(torch.randn(1,1,size,size,size)) 40 | self.enc_modules = nn.ModuleList() 41 | self.dec_modules = nn.ModuleList() 42 | 43 | custom_nfilters = [1, 16, 32, 64, 128, 256, 512, 1024, 1024, 1024] 44 | #custom_nfilters.reverse() 45 | #custom_nfilters = np.array(custom_nfilters) 46 | #custom_nfilters[1:] /= 2 47 | 48 | nfilters = [] 49 | 50 | #Encoder creation 51 | current_size = size 52 | layer_num = 1 53 | padding = (self.kernel_size - 1)/2 54 | while current_size > 2: 55 | in_channels = custom_nfilters[layer_num-1] 56 | out_channels = custom_nfilters[layer_num] 57 | conv_enc = nn.Sequential( 58 | nn.Conv3d(in_channels, out_channels, 59 | kernel_size=self.kernel_size, 60 | padding=padding, 61 | stride=2), 62 | nn.BatchNorm3d(out_channels), 63 | nn.ReLU(inplace=False)) 64 | current_size /= 2 65 | in_channels = out_channels 66 | layer_num += 1 67 | nfilters.append(out_channels) 68 | 69 | self.enc_modules.append(conv_enc) 70 | 71 | nfilters.reverse() 72 | 73 | #Decoder creation 74 | current_size = 2 75 | layer_num = 1 76 | padding = (self.kernel_size - 1)/2 77 | while current_size < self.size: 78 | if layer_num == 1: 79 | in_channels = nfilters[layer_num-1] 80 | else: 81 | in_channels = nfilters[layer_num-1]*2 82 | out_channels = nfilters[layer_num] 83 | conv_dec = nn.Sequential( 84 | UpConv(in_channels, out_channels, 85 | kernel_size=self.kernel_size), 86 | nn.BatchNorm3d(out_channels), 87 | nn.ReLU(inplace=False)) 88 | current_size *= 2 89 | in_channels = out_channels 90 | layer_num += 1 91 | nfilters.append(out_channels) 92 | 93 | self.dec_modules.append(conv_dec) 94 | 95 | self.final_conv = nn.Sequential( 96 | UpConv(nfilters[-2]*2, 1, 97 | kernel_size=self.kernel_size), 98 | nn.Sigmoid()) 99 | 100 | 101 | def forward(self): 102 | #Encoding 103 | enc_tensors = [] 104 | enc_tensors.append(self.z) 105 | 106 | for enc_op in self.enc_modules: 107 | enc_tensors.append(enc_op(enc_tensors[-1])) 108 | 109 | #Decoding 110 | dec_tensors = [] 111 | dec_tensors.append(self.dec_modules[0](enc_tensors[-1])) 112 | print dec_tensors[0].size() 113 | 114 | for i in xrange(1, len(self.dec_modules)-1): 115 | in_tensor = enc_tensors[-(i+1)] 116 | in_tensor = torch.cat((in_tensor, dec_tensors[-1]), 1) 117 | dec_tensors.append(self.dec_modules[i](in_tensor)) 118 | 119 | final_input = torch.cat((dec_tensors[-1], enc_tensors[1]), 1) 120 | out = self.final_conv(final_input) 121 | 122 | return out 123 | 124 | 125 | def save_results(self, path, data): 126 | results = data.cpu().data.numpy() 127 | results = results.transpose(0, 2, 1) 128 | save_objs(results, path) 129 | print "Points saved." 130 | 131 | if __name__ == '__main__': 132 | net = VoxUNet(256, batch_size=1).cuda() 133 | net() 134 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/models/__init__.py -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/modules/__init__.py -------------------------------------------------------------------------------- /modules/nnd.py: -------------------------------------------------------------------------------- 1 | from torch.nn.modules.module import Module 2 | from functions.nnd import NNDFunction 3 | 4 | class NNDModule(Module): 5 | def forward(self, input1, input2): 6 | return NNDFunction()(input1, input2) 7 | -------------------------------------------------------------------------------- /mr_train_4k.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.optim as optim 4 | import torch.nn as nn 5 | 6 | import argparse 7 | 8 | from tools.Trainer import VAETrainer 9 | from tools.PointCloudDataset import PointCloudDataset 10 | from models.AutoEncoder import MultiResVAE 11 | from models.AutoEncoder import ChamferLoss 12 | from models.AutoEncoder import ChamferWithNormalLoss 13 | from models.AutoEncoder import L2WithNormalLoss 14 | 15 | parser = argparse.ArgumentParser(description='Point Cloud Generator.') 16 | parser.add_argument("-d", "--datapath", type=str, help="Dataset path.", default="") 17 | parser.add_argument("-n", "--name", type=str, help="Name of the experiment", default="PointGen") 18 | parser.add_argument("-bs", "--batchSize", type=int, help="Batch size", default=64) 19 | parser.add_argument("-e", "--encSize", type=int, help="Encoding size", default=128) 20 | parser.add_argument("-f", "--factorNoise", type=float, help="Noise factor", default=0.0) 21 | parser.add_argument("--train", dest='train', action='store_true') 22 | parser.set_defaults(train=False) 23 | 24 | 25 | if __name__ == '__main__': 26 | args = parser.parse_args() 27 | 28 | vae = MultiResVAE(4096, 3, name=args.name, enc_size=args.encSize, 29 | noise=args.factorNoise, 30 | batch_size=args.batchSize) 31 | #vae.load('checkpoint') 32 | optimizer = optim.Adam(vae.parameters(), lr=1e-5) 33 | 34 | dataset = PointCloudDataset(args.datapath) 35 | loader = torch.utils.data.DataLoader(dataset, batch_size=args.batchSize, 36 | shuffle=True, num_workers=2) 37 | trainer = VAETrainer(vae, loader, optimizer, ChamferLoss()) 38 | trainer.train(2000) 39 | 40 | -------------------------------------------------------------------------------- /nndistance/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | -------------------------------------------------------------------------------- /nndistance/README.md: -------------------------------------------------------------------------------- 1 | # An example C extension for PyTorch 2 | 3 | This example showcases adding a neural network layer that adds two input Tensors 4 | 5 | - src: C source code 6 | - functions: the autograd functions 7 | - modules: code of the nn module 8 | - build.py: a small file that compiles your module to be ready to use 9 | - test.py: an example file that loads and uses the extension 10 | 11 | ```bash 12 | cd src 13 | nvcc -c -o nnd_cuda.cu.o nnd_cuda.cu -x cu -Xcompiler -fPIC -arch=sm_52 14 | cd .. 15 | python build.py 16 | python test.py 17 | ``` 18 | -------------------------------------------------------------------------------- /nndistance/_ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/nndistance/_ext/__init__.py -------------------------------------------------------------------------------- /nndistance/_ext/my_lib/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from torch.utils.ffi import _wrap_function 3 | from ._my_lib import lib as _lib, ffi as _ffi 4 | 5 | __all__ = [] 6 | def _import_symbols(locals): 7 | for symbol in dir(_lib): 8 | fn = getattr(_lib, symbol) 9 | if callable(fn): 10 | locals[symbol] = _wrap_function(fn, _ffi) 11 | else: 12 | locals[symbol] = fn 13 | __all__.append(symbol) 14 | 15 | _import_symbols(locals()) 16 | -------------------------------------------------------------------------------- /nndistance/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from torch.utils.ffi import create_extension 4 | 5 | this_file = os.path.dirname(__file__) 6 | 7 | sources = ['src/my_lib.c'] 8 | headers = ['src/my_lib.h'] 9 | defines = [] 10 | with_cuda = False 11 | 12 | if torch.cuda.is_available(): 13 | print('Including CUDA code.') 14 | sources += ['src/my_lib_cuda.c'] 15 | headers += ['src/my_lib_cuda.h'] 16 | defines += [('WITH_CUDA', None)] 17 | with_cuda = True 18 | 19 | this_file = os.path.dirname(os.path.realpath(__file__)) 20 | print(this_file) 21 | extra_objects = ['src/nnd_cuda.cu.o'] 22 | extra_objects = [os.path.join(this_file, fname) for fname in extra_objects] 23 | 24 | ffi = create_extension( 25 | '_ext.my_lib', 26 | headers=headers, 27 | sources=sources, 28 | define_macros=defines, 29 | relative_to=__file__, 30 | with_cuda=with_cuda, 31 | extra_objects=extra_objects 32 | ) 33 | 34 | if __name__ == '__main__': 35 | ffi.build() 36 | -------------------------------------------------------------------------------- /nndistance/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/nndistance/functions/__init__.py -------------------------------------------------------------------------------- /nndistance/functions/nnd.py: -------------------------------------------------------------------------------- 1 | # functions/add.py 2 | import torch 3 | from torch.autograd import Function 4 | from _ext import my_lib 5 | 6 | 7 | class NNDFunction(Function): 8 | def forward(self, xyz1, xyz2): 9 | batchsize, n, _ = xyz1.size() 10 | _, m, _ = xyz2.size() 11 | self.xyz1 = xyz1 12 | self.xyz2 = xyz2 13 | dist1 = torch.zeros(batchsize, n) 14 | dist2 = torch.zeros(batchsize, m) 15 | 16 | self.idx1 = torch.zeros(batchsize, n).type(torch.IntTensor) 17 | self.idx2 = torch.zeros(batchsize, m).type(torch.IntTensor) 18 | 19 | if not xyz1.is_cuda: 20 | my_lib.nnd_forward(xyz1, xyz2, dist1, dist2, self.idx1, self.idx2) 21 | else: 22 | dist1 = dist1.cuda() 23 | dist2 = dist2.cuda() 24 | self.idx1 = self.idx1.cuda() 25 | self.idx2 = self.idx2.cuda() 26 | my_lib.nnd_forward_cuda(xyz1, xyz2, dist1, dist2, self.idx1, self.idx2) 27 | 28 | self.dist1 = dist1 29 | self.dist2 = dist2 30 | 31 | #print(batchsize, n, m) 32 | 33 | return dist1, dist2 34 | 35 | def backward(self, graddist1, graddist2): 36 | #print(self.idx1, self.idx2) 37 | 38 | 39 | graddist1 = graddist1.contiguous() 40 | graddist2 = graddist2.contiguous() 41 | 42 | gradxyz1 = torch.zeros(self.xyz1.size()) 43 | gradxyz2 = torch.zeros(self.xyz2.size()) 44 | 45 | if not graddist1.is_cuda: 46 | my_lib.nnd_backward(self.xyz1, self.xyz2, gradxyz1, gradxyz2, graddist1, graddist2, self.idx1, self.idx2) 47 | else: 48 | gradxyz1 = gradxyz1.cuda() 49 | gradxyz2 = gradxyz2.cuda() 50 | my_lib.nnd_backward_cuda(self.xyz1, self.xyz2, gradxyz1, gradxyz2, graddist1, graddist2, self.idx1, self.idx2) 51 | 52 | return gradxyz1, gradxyz2 -------------------------------------------------------------------------------- /nndistance/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusgadelha/MRTNet/3b540189c56aec201c3423469cb497730f7797e2/nndistance/modules/__init__.py -------------------------------------------------------------------------------- /nndistance/modules/nnd.py: -------------------------------------------------------------------------------- 1 | from torch.nn.modules.module import Module 2 | from functions.nnd import NNDFunction 3 | 4 | class NNDModule(Module): 5 | def forward(self, input1, input2): 6 | return NNDFunction()(input1, input2) 7 | -------------------------------------------------------------------------------- /nndistance/src/make.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | nvcc nnd_cuda.cu -o nnd_cuda.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPIC 3 | -------------------------------------------------------------------------------- /nndistance/src/my_lib.c: -------------------------------------------------------------------------------- 1 | #include