├── DeepMLS_Generation.py ├── LICENSE ├── Octree ├── ocnn-tf │ └── libs │ │ ├── ReadMe.txt │ │ └── __init__.py └── ocnn.py ├── Pretrained ├── Config_d6_1p_pretrained.json ├── Config_d7_1p_pretrained.json ├── d6_1p_pretrained.zip ├── d7_1p_pretrained.zip └── download_models.py ├── README.md ├── environment.yml ├── examples ├── Config_g2_bs32_1p_d6.json ├── d0fa70e45dee680fa45b742ddc5add59.ply ├── d3380ee3db68aefb3f214ef9c53ac06.ply └── fcfc935c2ff7c66c858699aaad4acee4.ply ├── media └── teaser.png ├── mls_marching_cubes.py ├── network_architecture.py ├── points3d-tf ├── points3d │ ├── __init__.py │ ├── build.sh │ ├── get_neighbor_points_spatial_grid_local_self.cc │ ├── get_neighbor_points_spatial_grid_local_self.cu.cc │ ├── get_neighbor_points_spatial_grid_radius_weighting_voting.cc │ └── get_neighbor_points_spatial_grid_radius_weighting_voting.cu.cc └── pointsdataset │ └── points_dataset_octreed7.py ├── scripts ├── Readme.md └── generate_tf_records.py ├── utils.py └── vdb_tsdf ├── ReadMe.MD └── main.cpp /DeepMLS_Generation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | import tensorflow as tf 5 | import argparse 6 | from utils import * 7 | import tqdm 8 | 9 | sys.path.append("points3d-tf") 10 | from pointsdataset.points_dataset_octreed7 import * 11 | from network_architecture import * 12 | from points3d import get_neighbor_spatial_grid_local_self 13 | from points3d import get_neighbor_spatial_grid_radius_v_voting 14 | 15 | parser = argparse.ArgumentParser(description='Point-based Shape Generation') 16 | parser.add_argument('config', type=str, metavar='N', help='config json file') 17 | parser.add_argument('--test', action='store_true', help='forward the test data(inference)') 18 | args = parser.parse_args() 19 | config = config_reader(args.config) 20 | 21 | DBL_EPSILON = 1E-22 22 | 23 | class param: 24 | def __init__(self, config): 25 | self.train_data = config['train_data'] 26 | self.train_batch_size = config['train_batch_size'] 27 | self.learning_rate = config['learning_rate'] 28 | if('learning_rate_lower_bound' in config): 29 | self.learning_rate_lower_bound = config['learning_rate_lower_bound'] 30 | else: 31 | self.learning_rate_lower_bound = 1e-4 32 | self.lr_decay_epochs = config['lr_decay_epochs'] 33 | 34 | #choose different sdf samples for training(generate on depth-6 grids or depth-7 grids) 35 | self.sdf_data_sources = config['sdf_data_sources'] 36 | assert(self.sdf_data_sources == 6 or self.sdf_data_sources == 7) 37 | if(self.sdf_data_sources == 6): 38 | self.max_training_epochs = config['max_training_epochs_d6'] 39 | elif(self.sdf_data_sources == 7): 40 | self.max_training_epochs = config['max_training_epochs_d7'] 41 | 42 | self.exp_folder = config['exp_name'] 43 | self.ckpt = config['ckpt'] 44 | 45 | self.test = args.test 46 | self.gpu = config['gpu'] 47 | self.num_of_gpus = len(self.gpu.split(",")) 48 | self.num_of_input_points = config['num_of_input_points'] 49 | self.num_neighbors_to_search = config['num_neighbors_to_search'] 50 | 51 | #loss weighting 52 | self.sdf_loss_weight = config['sdf_loss_weight'] 53 | self.sdf_grad_loss_weight = config['sdf_grad_loss_weight'] 54 | self.geo_reg = config['geo_reg'] 55 | self.repulsion_weight = config['repulsion_weight'] 56 | self.normal_norm_reg_weight = config['normal_norm_reg_weight'] 57 | self.patch_radius_smoothness = config['patch_radius_smoothness'] 58 | self.octree_split_loss_weighting = config['octree_split_loss_weighting'] 59 | self.weight_decay = config['weight_decay'] 60 | 61 | #for octree data 62 | self.input_normal_signals = config['input_normal_signals'] 63 | if(self.input_normal_signals): 64 | self.channel = 4 65 | else: 66 | self.channel = 3 67 | self.depth = config['octree_depth'] 68 | if("decoder_octree_depth" in config): 69 | self.decoder_octree_depth = config['decoder_octree_depth'] 70 | else: 71 | self.decoder_octree_depth = 6 72 | print("decoder_octree_depth setting to {}".format(self.decoder_octree_depth)) 73 | 74 | #predict how many mls points in each non-empty octree leaf node 75 | self.points_per_node = config['points_per_node'] 76 | self.constant_radius = config['constant_radius'] 77 | assert(self.sdf_data_sources == 6 or self.sdf_data_sources == 7) 78 | if(self.sdf_data_sources == 7): 79 | self.sdf_loss_weight *= 4 80 | 81 | self.sdf_samples_each_iter = config['sdf_samples_each_iter'] 82 | print("using {} sdf samples for evaluation in each iteration".format(self.sdf_samples_each_iter)) 83 | 84 | self.node_receptive_field = config['node_receptive_field'] 85 | print("node receptive field times: {}".format(self.node_receptive_field)) 86 | 87 | self.radius_range = config['radius_range'] 88 | print("radius range = {}".format(self.radius_range)) 89 | 90 | if("noise_stddev" in config): 91 | self.noise_stddev = config['noise_stddev'] 92 | else: 93 | #our model is normaled to [-1, 1]^3 bounding box with 5% padding 94 | #to achieve same noise level with conv-onet 95 | self.noise_stddev = 0.0095 96 | 97 | FLAGS = param(config) 98 | 99 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' 100 | os.environ['CUDA_VISIBLE_DEVICES'] = FLAGS.gpu 101 | KNN_PREFERRED = FLAGS.num_neighbors_to_search 102 | print("KNN={}".format(KNN_PREFERRED)) 103 | assert(KNN_PREFERRED >= 2) 104 | 105 | train_batch_size_gpu = FLAGS.train_batch_size // FLAGS.num_of_gpus 106 | assert(train_batch_size_gpu * FLAGS.num_of_gpus == FLAGS.train_batch_size) 107 | print("utilize {} gpus with total training batch size={}".format(FLAGS.num_of_gpus, FLAGS.train_batch_size)) 108 | 109 | lr_placeholder = tf.placeholder(tf.float32) 110 | print('=====') 111 | sys.stdout.flush() 112 | 113 | #normal unit norm regularization to avoid degenerated normal (should be very gentle) 114 | def normal_unit_norm_regularization(normals, points_segment, points_num): 115 | #input shape [batch_size*n,3] 116 | per_vertex_loss = tf.math.square(tf.reduce_sum(tf.math.square(normals), axis=1) - 1) 117 | loss = tf.segment_sum(per_vertex_loss, points_segment) / tf.cast(points_num, normals.dtype) 118 | return tf.reduce_mean(loss) 119 | 120 | def self_regularization_loss(predict_position_matrix, predict_normal_normalized, points_segment, points_num, per_point_squared_ball_radius): 121 | #first get neighbor info and weights 122 | predict_points = tf.concat([tf.reshape(predict_position_matrix, [-1, 3]), tf.reshape(predict_normal_normalized, [-1, 3])], axis=1) 123 | 124 | p2p_neighbor = tf.reshape(get_neighbor_spatial_grid_local_self(predict_points, tf.cumsum(points_num), knn=KNN_PREFERRED), [-1,KNN_PREFERRED]) 125 | invalid_index_mask = tf.cast(tf.less(p2p_neighbor, 0), dtype=p2p_neighbor.dtype) 126 | #make -1 to 0 127 | p2p_neighbor += invalid_index_mask 128 | p2p_neighbor = tf.expand_dims(p2p_neighbor, axis=-1) 129 | p2p_neighbor = tf.stop_gradient(p2p_neighbor) 130 | 131 | p2p_patch_pos = tf.gather_nd(predict_position_matrix, p2p_neighbor) 132 | p2p_patch_normal = tf.gather_nd(predict_normal_normalized, p2p_neighbor) 133 | per_point_radius = tf.math.sqrt(per_point_squared_ball_radius + 1e-19) 134 | p2p_patch_radius = tf.gather_nd(tf.reshape(per_point_radius, [-1]), p2p_neighbor) 135 | 136 | p2p_patch_pos_diff = tf.tile(tf.reshape(predict_position_matrix, [-1,1,3]), multiples=[1,KNN_PREFERRED,1]) - p2p_patch_pos 137 | p2p_patch_pos_diff_norm_squared = rowwise_l2_norm_squared(p2p_patch_pos_diff) 138 | p2p_patch_pos_diff_norm = tf.math.sqrt(p2p_patch_pos_diff_norm_squared + 1e-10) 139 | 140 | #using dot product [batch_size, n] 141 | p2p_patch_normal_dot = tf.reduce_sum(tf.tile(tf.reshape(predict_normal_normalized, [-1,1,3]),multiples=[1,KNN_PREFERRED,1])*p2p_patch_normal, axis=-1) 142 | p2p_patch_distance = rowwise_l2_norm_squared(p2p_patch_pos_diff) 143 | 144 | if(FLAGS.constant_radius): 145 | squared_ball_radius = per_point_squared_ball_radius[0] 146 | else: 147 | squared_ball_radius = tf.gather_nd(per_point_squared_ball_radius, p2p_neighbor) 148 | 149 | valid_neighbor_mask = tf.cast(1 - invalid_index_mask, dtype=p2p_patch_distance.dtype) 150 | p2p_patch_distance += squared_ball_radius * (1 - p2p_patch_normal_dot) 151 | 152 | #mls weight is stored in matrix with shape=[batch_size*n, KNN] 153 | p2p_mls_weight = tf.math.exp(-p2p_patch_distance / squared_ball_radius) 154 | p2p_mls_weight = p2p_mls_weight*valid_neighbor_mask 155 | #mls weights should also be normalized 156 | #p2p_mls_weight = p2p_mls_weight / tf.tile(tf.reshape(tf.reduce_sum(p2p_mls_weight,axis=-1),[-1,1]),multiples=[1,KNN_PREFERRED]) 157 | 158 | stop_regularization_weight_gradient = True 159 | if(stop_regularization_weight_gradient): 160 | p2p_mls_weight = tf.stop_gradient(p2p_mls_weight) 161 | p2p_mls_weight_sum = tf.reduce_sum(p2p_mls_weight, axis=-1) 162 | p2p_mls_weight_sum_dim3 = tf.tile(tf.expand_dims(p2p_mls_weight_sum, axis=-1), multiples=[1,3]) 163 | 164 | Laplacian_radius = tf.math.square(p2p_mls_weight_sum*tf.reshape(per_point_radius, [-1]) - tf.reduce_sum(p2p_mls_weight*p2p_patch_radius, axis=-1)) / tf.math.square(p2p_mls_weight_sum + 1e-6) 165 | 166 | #local geometry regularization(planar shape) 167 | #get neighbor point distance to tangent plane in shape[batch_size*n_point, KNN_PREFERRED] 168 | p2p_patch_pos_diff_normal = tf.reduce_sum(p2p_patch_pos_diff*tf.tile(tf.reshape(predict_normal_normalized, [-1, 1, 3]), multiples=[1, KNN_PREFERRED, 1]), axis=-1) 169 | p2p_patch_pos_diff_tangent = p2p_patch_pos_diff - tf.tile(tf.expand_dims(p2p_patch_pos_diff_normal, axis=-1), multiples=[1,1,3])*tf.tile(tf.reshape(predict_normal_normalized, [-1, 1, 3]), multiples=[1, KNN_PREFERRED, 1]) 170 | p2p_patch_pos_diff_tangent_norm_squared = rowwise_l2_norm_squared(p2p_patch_pos_diff_tangent) 171 | p2p_patch_pos_diff_tangent_norm = tf.math.sqrt(p2p_patch_pos_diff_tangent_norm_squared + 1e-10) 172 | 173 | tangent_distance = tf.math.square(p2p_patch_pos_diff_normal) 174 | if(stop_regularization_weight_gradient): 175 | p2p_mls_weight_plane = tf.stop_gradient(p2p_mls_weight*tf.math.exp(-tangent_distance/squared_ball_radius)) 176 | else: 177 | p2p_mls_weight_plane = p2p_mls_weight*tf.math.exp(-tangent_distance/squared_ball_radius) 178 | 179 | tangent_distance *= p2p_mls_weight_plane 180 | 181 | #-d repulsion 182 | if(True): 183 | #repulsion in tangent direction 184 | repulsion_vec = - p2p_patch_pos_diff_tangent_norm / (octree_mls_points_squared_radius()**0.5) 185 | else: 186 | repulsion_vec = - p2p_patch_pos_diff_norm / (octree_mls_points_squared_radius()**0.5) 187 | 188 | #then we get a tensor with shape [\sum(all_mls_points)] 189 | repulsion_force = tf.reduce_mean(p2p_mls_weight * repulsion_vec, axis=-1) 190 | repulsion_force = tf.segment_sum(repulsion_force, points_segment) / tf.cast(points_num, repulsion_force.dtype) 191 | 192 | radius_smoothness_loss = tf.segment_sum(Laplacian_radius, points_segment) / tf.cast(points_num, Laplacian_radius.dtype) 193 | local_plane_regularization = tf.segment_sum(tf.reduce_sum(tangent_distance, axis=-1), points_segment) / tf.cast(points_num, repulsion_force.dtype) 194 | 195 | repulsion_force = tf.reduce_mean(repulsion_force) 196 | local_plane_regularization = tf.reduce_mean(local_plane_regularization) 197 | radius_smoothness_loss = tf.reduce_mean(radius_smoothness_loss) 198 | 199 | return repulsion_force, local_plane_regularization, radius_smoothness_loss 200 | 201 | def eval_sdf_from_mls(src_position, target_position, target_normal_normalized, per_point_squared_ball_radius, s2t_neighbor): 202 | invalid_index_mask = tf.cast(tf.less(s2t_neighbor, 0), dtype=s2t_neighbor.dtype) 203 | 204 | #make -1 to 0(index -1 indicates invalid neighbor index, which means we cannot find up to K neighbors) 205 | s2t_neighbor += invalid_index_mask 206 | s2t_neighbor = tf.expand_dims(s2t_neighbor, axis=-1) 207 | 208 | #get per vertex patch vertices position & normal [batch_size*n, KNN, 3]+[batch_size*n, KNN, 3] 209 | s2t_patch_pos = tf.gather_nd(target_position, s2t_neighbor) 210 | s2t_patch_normal = tf.gather_nd(target_normal_normalized, s2t_neighbor) 211 | #compute mls weights 212 | s2t_patch_pos_diff = tf.tile(tf.reshape(src_position, [-1,1,3]),multiples=[1,KNN_PREFERRED,1]) - s2t_patch_pos 213 | s2t_patch_distance = rowwise_l2_norm_squared(s2t_patch_pos_diff) 214 | 215 | valid_neighbor_mask = tf.cast(1 - invalid_index_mask, dtype=s2t_patch_distance.dtype) 216 | 217 | #avoid divide by zero error 218 | if(FLAGS.constant_radius): 219 | s2t_mls_weight = -s2t_patch_distance / per_point_squared_ball_radius[0] 220 | else: 221 | s2t_mls_weight = -s2t_patch_distance / tf.gather_nd(tf.reshape(per_point_squared_ball_radius, [-1]), s2t_neighbor) 222 | 223 | s2t_mls_weight -= tf.stop_gradient(tf.tile(tf.expand_dims(s2t_mls_weight[:,0], axis=-1), multiples=[1, KNN_PREFERRED])) 224 | 225 | #mls weight is stored in matrix with shape=[batch_size*n, KNN] 226 | s2t_mls_weight = s2t_mls_weight*valid_neighbor_mask 227 | s2t_mls_weight = tf.math.exp(s2t_mls_weight) 228 | s2t_mls_weight = s2t_mls_weight*valid_neighbor_mask 229 | 230 | #mls weights should also be normalized 231 | s2t_mls_weight = s2t_mls_weight / tf.tile(tf.reshape(tf.reduce_sum(s2t_mls_weight,axis=-1),[-1,1]),multiples=[1,KNN_PREFERRED]) 232 | 233 | s2t_sdf = tf.reduce_sum(s2t_mls_weight*tf.reduce_sum(s2t_patch_pos_diff*s2t_patch_normal, axis=-1), axis=-1) 234 | #get weighted average patch points 235 | s2t_patch_pos_mean = tf.reduce_sum(tf.tile(tf.expand_dims(s2t_mls_weight, axis=-1),multiples=[1,1,3])*s2t_patch_pos, axis=1) 236 | #get weighted average normal: gradient 237 | s2t_patch_normal_mean = tf.reduce_sum(tf.tile(tf.expand_dims(s2t_mls_weight, axis=-1),multiples=[1,1,3])*s2t_patch_normal, axis=1) 238 | s2t_sdf_grad = tf.math.l2_normalize(s2t_patch_normal_mean, axis=1) 239 | 240 | return s2t_sdf_grad, s2t_sdf 241 | 242 | def MLS_sdf_Loss_Pack(evaluate_position_matrix, predict_position_matrix, predict_normal_normalized, evaluate_points_num, ps2p_neighbor_index, \ 243 | per_point_squared_ball_radius, sdf_precompute): 244 | 245 | evaluate_position_matrix = tf.reshape(evaluate_position_matrix, [-1, 3]) 246 | sdf_precompute = tf.reshape(sdf_precompute, [-1, 4]) 247 | 248 | #select valid grid points out of padded data 249 | valid_data_mask = tf.cast(tf.greater(evaluate_position_matrix[:,0], -5), dtype=evaluate_position_matrix.dtype) 250 | 251 | predict_sdf_grad, predict_sdf = \ 252 | eval_sdf_from_mls(evaluate_position_matrix, predict_position_matrix, predict_normal_normalized, per_point_squared_ball_radius, ps2p_neighbor_index) 253 | 254 | valid_data_mask = tf.reshape(valid_data_mask, [-1, evaluate_points_num]) 255 | valid_data_mask = tf.stop_gradient(valid_data_mask) 256 | #select the normal for grid points 257 | predict_sdf_grad = tf.reshape(predict_sdf_grad, [-1, evaluate_points_num, 3]) 258 | predict_sdf = tf.reshape(predict_sdf, [-1, evaluate_points_num]) 259 | 260 | #using precomputed sdf value and gradients 261 | gt_sdf = tf.reshape(sdf_precompute[:,0], [-1, evaluate_points_num]) 262 | gt_sdf_grad = tf.reshape(sdf_precompute[:,1:], [-1, evaluate_points_num, 3]) 263 | 264 | gt_sdf = tf.stop_gradient(gt_sdf) 265 | gt_sdf_grad = tf.stop_gradient(tf.nn.l2_normalize(gt_sdf_grad, axis=-1)) 266 | 267 | #L2 loss of difference between predict sdf and gt sdf (as well as gradients) 268 | grid_sdf_squared_diff = valid_data_mask * tf.math.square(gt_sdf - predict_sdf) 269 | 270 | if(False): 271 | #l2 loss 272 | grid_sdf_grad_squared_diff = valid_data_mask*rowwise_l2_norm_squared(gt_sdf_grad - predict_sdf_grad) 273 | else: 274 | #using dot product 275 | grid_sdf_grad_squared_diff = valid_data_mask*(1 - tf.reduce_sum(gt_sdf_grad*predict_sdf_grad, axis=-1)) 276 | 277 | valid_count = tf.reduce_sum(tf.reshape(valid_data_mask, [-1, evaluate_points_num]), axis=-1) 278 | 279 | predict_sdf_diff_loss = tf.reduce_sum(grid_sdf_squared_diff, axis=-1) / valid_count 280 | predict_sdf_grad_loss = tf.reduce_sum(grid_sdf_grad_squared_diff, axis=-1) / valid_count 281 | 282 | predict_sdf_diff_loss = tf.reduce_mean(predict_sdf_diff_loss) 283 | predict_sdf_grad_loss = tf.reduce_mean(predict_sdf_grad_loss) 284 | 285 | return predict_sdf_diff_loss, predict_sdf_grad_loss 286 | 287 | def network_loss(predict_points, points_segment, points_num, per_point_squared_ball_radius, sampled_position_matrix, sdf_precompute, name="network_loss"): 288 | #prepare ingredients for cooking 289 | predict_position_matrix = predict_points[:,:3] 290 | predict_normal_matrix = predict_points[:,3:] 291 | predict_normal_normalized = tf.math.l2_normalize(predict_normal_matrix, axis=1) 292 | 293 | loss_dict = {} 294 | train_loss = 0 295 | 296 | sampled_points = sampled_position_matrix 297 | evaluated_points_num = tf.cast(tf.shape(sampled_points)[1], tf.int32) 298 | sampled_points = tf.reshape(tf.transpose(sampled_points, perm=[0,2,1]), [-1, evaluated_points_num*3]) 299 | 300 | #compute neighbor mls points for sdf samples 301 | ps2p_neighbor_index = tf.reshape(get_neighbor_spatial_grid_radius_v_voting(sampled_points, predict_points, tf.cumsum(points_num),\ 302 | per_point_squared_ball_radius, knn=KNN_PREFERRED),[-1, KNN_PREFERRED]) 303 | 304 | if(FLAGS.sdf_loss_weight > DBL_EPSILON or FLAGS.sdf_grad_loss_weight > DBL_EPSILON): 305 | mls_sdf_loss, mls_sdf_grad_loss = MLS_sdf_Loss_Pack(tf.reshape(sampled_position_matrix, [-1,3]), predict_position_matrix, predict_normal_normalized, evaluated_points_num, ps2p_neighbor_index, \ 306 | per_point_squared_ball_radius, sdf_precompute=sdf_precompute) 307 | mls_sdf_loss *= FLAGS.sdf_loss_weight 308 | mls_sdf_grad_loss *= FLAGS.sdf_grad_loss_weight 309 | 310 | if(FLAGS.sdf_loss_weight > DBL_EPSILON): 311 | loss_dict[name + "_mls_sdf_loss"] = mls_sdf_loss 312 | 313 | if(FLAGS.sdf_grad_loss_weight > DBL_EPSILON): 314 | loss_dict[name + "_mls_sdf_grad_loss"] = mls_sdf_grad_loss 315 | 316 | train_loss += mls_sdf_loss + mls_sdf_grad_loss 317 | 318 | wLop_repulsion_loss, local_plane_regularization, radius_smoothness_loss = \ 319 | self_regularization_loss(predict_position_matrix, predict_normal_normalized, points_segment, points_num, per_point_squared_ball_radius) 320 | 321 | if(FLAGS.repulsion_weight > DBL_EPSILON): 322 | LOP_regularization_loss = FLAGS.repulsion_weight * wLop_repulsion_loss 323 | train_loss += LOP_regularization_loss 324 | loss_dict[name + '_repulsion'] = LOP_regularization_loss 325 | 326 | if(FLAGS.normal_norm_reg_weight > DBL_EPSILON): 327 | normal_unit_norm_loss = FLAGS.normal_norm_reg_weight*normal_unit_norm_regularization(predict_normal_matrix, points_segment, points_num) 328 | loss_dict[name +'_normal_unit_norm_reg'] = normal_unit_norm_loss 329 | train_loss += normal_unit_norm_loss 330 | 331 | if(FLAGS.geo_reg > DBL_EPSILON): 332 | local_plane_regularization *= FLAGS.geo_reg 333 | train_loss += local_plane_regularization 334 | loss_dict[name + "_local_plane_regularization"] = local_plane_regularization 335 | 336 | if(not FLAGS.constant_radius and FLAGS.patch_radius_smoothness > DBL_EPSILON): 337 | radius_smoothness_loss *= FLAGS.patch_radius_smoothness 338 | train_loss += radius_smoothness_loss 339 | loss_dict["radius_smoothness"] = radius_smoothness_loss 340 | 341 | if(FLAGS.weight_decay > DBL_EPSILON): 342 | network_weights_decay_loss = l2_regularizer("ocnn", FLAGS.weight_decay) 343 | loss_dict['network_weight_decay_loss'] = network_weights_decay_loss 344 | train_loss += network_weights_decay_loss 345 | 346 | loss_dict[name + "_total_loss"] = train_loss 347 | return train_loss, loss_dict 348 | 349 | def train_data_loader(): 350 | data_list = points_dataset_AE_multiple_GPU(FLAGS.train_data, FLAGS.train_batch_size, points_num=FLAGS.num_of_input_points, depth=FLAGS.depth, \ 351 | gpu_num=FLAGS.num_of_gpus, sample_grid_points_number=FLAGS.sdf_samples_each_iter, data_sources=FLAGS.sdf_data_sources, noise_stddev=FLAGS.noise_stddev) 352 | 353 | assert(len(data_list) == FLAGS.num_of_gpus*2+5) 354 | train_record_num = data_list[0] 355 | octree_all_gpu = data_list[1:(FLAGS.num_of_gpus+1)] 356 | gt_octree_all_gpu = data_list[(FLAGS.num_of_gpus+1):(FLAGS.num_of_gpus*2+1)] 357 | points, filenames, sampled_points, sampled_sdf = data_list[(FLAGS.num_of_gpus*2+1):] 358 | 359 | points_all_gpu = [] 360 | sampled_points_all_gpu = [] 361 | sampled_sdf_all_gpu = [] 362 | for i in range(FLAGS.num_of_gpus): 363 | points_all_gpu.append(points[i*train_batch_size_gpu:(i+1)*train_batch_size_gpu]) 364 | sampled_points_all_gpu.append(sampled_points[i*train_batch_size_gpu:(i+1)*train_batch_size_gpu]) 365 | sampled_sdf_all_gpu.append(sampled_sdf[i*train_batch_size_gpu:(i+1)*train_batch_size_gpu]) 366 | return train_record_num, octree_all_gpu, gt_octree_all_gpu, points_all_gpu, sampled_points_all_gpu, sampled_sdf_all_gpu, filenames 367 | 368 | def train_network(): 369 | train_record_num, octree_all_gpu, gt_octree_all_gpu, points_all_gpu, sampled_points_all_gpu, sampled_sdf_all_gpu, filenames = train_data_loader() 370 | loss_all_gpu = [] 371 | gradients_all_gpu=[] 372 | solver = tf.train.AdamOptimizer(learning_rate=lr_placeholder) 373 | all_loss_dict = [] 374 | 375 | for g_id in range(FLAGS.num_of_gpus): 376 | with tf.device('/gpu:{}'.format(g_id)): 377 | with tf.name_scope('GPU_{}'.format(g_id)) as scope: 378 | if(g_id == 0): 379 | g_loss, predict_points, sampled_position_matrix, loss_dict = build_graph_gpu_octree_local(train_batch_size_gpu, octree_all_gpu[g_id], None if points_all_gpu is None else points_all_gpu[g_id], 380 | sampled_points_all_gpu[g_id], sampled_sdf_all_gpu[g_id], reuse=None, gt_octree= gt_octree_all_gpu[g_id]) 381 | else: 382 | g_loss, _, _, loss_dict = build_graph_gpu_octree_local(train_batch_size_gpu, octree_all_gpu[g_id], None if points_all_gpu is None else points_all_gpu[g_id], 383 | sampled_points_all_gpu[g_id], sampled_sdf_all_gpu[g_id], reuse=True, gt_octree= gt_octree_all_gpu[g_id]) 384 | loss_all_gpu.append(g_loss) 385 | gradients_all_gpu.append(solver.compute_gradients(g_loss)) 386 | all_loss_dict.append(loss_dict) 387 | #use last tower statistics to update the moving mean/variance 388 | if(g_id == 0): 389 | batchnorm_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, scope=scope) 390 | 391 | with tf.device('/cpu:0'): 392 | train_summary = tf_summary_from_dict(all_loss_dict, True) 393 | 394 | with tf.device('/gpu:0'): 395 | total_loss = sum(loss_all_gpu) / FLAGS.num_of_gpus 396 | with tf.control_dependencies(batchnorm_update_ops): 397 | if(FLAGS.num_of_gpus == 1): 398 | apply_gradient_op = solver.apply_gradients(gradients_all_gpu[0]) 399 | else: 400 | grad_avg = average_gradient(gradients_all_gpu) 401 | apply_gradient_op = solver.apply_gradients(grad_avg) 402 | 403 | points_all_gpu[0] = tf.transpose(tf.reshape(points_all_gpu[0],[-1, 6, FLAGS.num_of_input_points]), perm=[0,2,1]) 404 | 405 | return train_record_num, train_summary, total_loss, apply_gradient_op, predict_points, sampled_position_matrix, points_all_gpu[0], filenames 406 | 407 | def octree_mls_points_squared_radius(): 408 | return 0.25**(FLAGS.decoder_octree_depth - 1) / FLAGS.points_per_node 409 | 410 | def build_graph_gpu_octree_local(input_batch_size, octree, points, grid_position, grid_sdf, reuse, is_training=True, gt_octree=None): 411 | assert(gt_octree is not None) 412 | 413 | split_loss, split_acc, mls_points, points_segment, octree_node_xyz_valid = \ 414 | octree_network_unet(octree, FLAGS.depth, FLAGS.decoder_octree_depth, FLAGS.channel, FLAGS.points_per_node, training=is_training, reuse=reuse, node_receptive_field=FLAGS.node_receptive_field, predict_radius=not FLAGS.constant_radius, radius_range=FLAGS.radius_range, gt_octree=gt_octree) 415 | 416 | points_num = tf.cast(tf.segment_sum(tf.ones_like(points_segment, dtype=tf.float64), points_segment), tf.int32) 417 | constant_squared_radius = octree_mls_points_squared_radius() 418 | 419 | if(not FLAGS.constant_radius): 420 | per_point_squared_radius = constant_squared_radius* tf.math.square(mls_points[:,6]) 421 | mls_points = mls_points[:,:6] 422 | else: 423 | per_point_squared_radius = constant_squared_radius* tf.ones(tf.shape(mls_points)[0]) 424 | 425 | sampled_position_matrix = tf.reshape(grid_position, [-1, FLAGS.sdf_samples_each_iter, 3]) 426 | grid_sdf = tf.reshape(grid_sdf, [-1, FLAGS.sdf_samples_each_iter, 4]) 427 | 428 | sampled_position_matrix = tf.stop_gradient(sampled_position_matrix) 429 | grid_sdf = tf.stop_gradient(grid_sdf) 430 | 431 | final_loss, loss_dict = network_loss(mls_points, points_segment, points_num, per_point_squared_radius, sampled_position_matrix, grid_sdf, name="final_geometry") 432 | 433 | loss_dict['split_accuracy'] = tf.add_n(split_acc) / len(split_acc) 434 | loss_dict['octree_split_loss'] = FLAGS.octree_split_loss_weighting*tf.add_n(split_loss) 435 | train_loss = final_loss + FLAGS.octree_split_loss_weighting*tf.add_n(split_loss) 436 | 437 | predict_points_list = [mls_points, points_num, per_point_squared_radius] 438 | loss_dict['final_geometry_total_loss'] = train_loss 439 | 440 | return train_loss, predict_points_list, sampled_position_matrix, loss_dict 441 | 442 | def build_graph_gpu_octree_local_inference(octree, reuse=None, is_training=False, test_batch_size=1): 443 | assert(not is_training) 444 | print("inference using prediction octree") 445 | mls_points, points_segment, octree_node_xyz_valid = octree_network_unet_completion_decode_shape(octree, FLAGS.depth, FLAGS.decoder_octree_depth, FLAGS.channel, FLAGS.points_per_node, test_batch_size, training=is_training, reuse=reuse, node_receptive_field=FLAGS.node_receptive_field, predict_radius=not FLAGS.constant_radius, radius_range=FLAGS.radius_range) 446 | 447 | points_num = tf.cast(tf.segment_sum(tf.ones_like(points_segment, dtype=tf.float64), points_segment), tf.int32) 448 | constant_squared_radius = octree_mls_points_squared_radius() 449 | 450 | if(not FLAGS.constant_radius): 451 | per_point_squared_radius = constant_squared_radius* tf.math.square(mls_points[:,6]) 452 | mls_points = mls_points[:,:6] 453 | else: 454 | per_point_squared_radius = constant_squared_radius* tf.ones(tf.shape(mls_points)[0]) 455 | 456 | predict_points_list = [mls_points, points_num, per_point_squared_radius] 457 | 458 | return predict_points_list 459 | 460 | def write_visualization_results(predict_points, sampled_position_matrix, input_points, dir, iter, filenames=None): 461 | final_prediction = predict_points[0] 462 | assert(len(predict_points) == 3) 463 | per_point_radius = np.reshape(predict_points[2], [-1]) 464 | per_point_radius = np.split(per_point_radius, np.cumsum(predict_points[1])) 465 | per_point_radius.pop() 466 | final_prediction_ = np.split(np.reshape(final_prediction, [-1]), 6*np.cumsum(predict_points[1])) 467 | final_prediction = [np.reshape(item, [-1,6]) for item in final_prediction_] 468 | final_prediction.pop() 469 | batch_size = len(final_prediction) 470 | assert(len(per_point_radius) == batch_size) 471 | 472 | input_points = np.reshape(input_points, [-1, FLAGS.num_of_input_points, 6]) 473 | assert(batch_size == input_points.shape[0]) 474 | 475 | sampled_position_matrix = np.reshape(sampled_position_matrix, [batch_size, -1, 3]) 476 | 477 | if(filenames is not None): 478 | filename_text_file = open(os.path.join(dir, "filenames_{:06d}.txt".format(iter)), "w") 479 | for train_batch_idx in range(batch_size): 480 | words_arr = filenames[train_batch_idx].decode("utf8")#.split("\\") 481 | #filename_text_file.write("{}\t{}\n".format(words_arr[1], words_arr[3][:-4])) 482 | filename_text_file.write("{}\n".format(words_arr)) 483 | filename_text_file.close() 484 | 485 | for i in range(batch_size): 486 | np.savetxt(os.path.join(dir, 'input_{:06d}_{:04d}.xyz'.format(iter, i)), input_points[i], fmt='%0.4f') 487 | np.savetxt(os.path.join(dir, 'predict_pc_{:06d}_{:04d}.xyz'.format(iter, i)), final_prediction[i], fmt='%0.4f') 488 | if(iter == 0): 489 | cur_sample_position_matrix = sampled_position_matrix[i] 490 | cur_sample_position_matrix = cur_sample_position_matrix[np.where(cur_sample_position_matrix[:,0] > -50)[0]] 491 | np.savetxt(os.path.join(dir, 'sampled_{:06d}_{:04d}.xyz'.format(iter, i)), cur_sample_position_matrix) 492 | 493 | if(per_point_radius is not None): 494 | np.savetxt(os.path.join(dir, 'predict_pc_{:06d}_{:04d}_radius.txt'.format(iter, i)), per_point_radius[i]) 495 | 496 | def inference_from_inputs(): 497 | #placeholder for input pointcloud, should in shape [-1, 3] 498 | input_points = tf.placeholder(tf.float32) 499 | #convert input_points to octree 500 | input_points_str = points_new(input_points, [], input_points, []) 501 | input_octree = octree_batch([points2octree(input_points_str, depth=FLAGS.depth, full_depth=2, node_dis=False, split_label=True)]) 502 | mls_points = build_graph_gpu_octree_local_inference(input_octree) 503 | 504 | #finish build the graph and next we load checkpoint 505 | gvars = tf.global_variables() 506 | print("{} global variables".format(len(gvars))) 507 | print("{} model parameters total".format(get_num_params())) 508 | 509 | ckpt = tf.train.latest_checkpoint(FLAGS.ckpt) 510 | start_iters = 0 if not ckpt else int(ckpt[ckpt.find('iter') + 4:-5]) + 1 511 | if(ckpt): 512 | tf_restore_saver = tf.train.Saver(var_list=gvars, max_to_keep=100) 513 | else: 514 | print("Cannot load checkpoint from {}".format(FLAGS.ckpt)) 515 | raise NotImplementedError 516 | 517 | config = tf.ConfigProto() 518 | config.gpu_options.allow_growth = True 519 | with tf.Session(config=config) as sess: 520 | # initialize 521 | init = tf.compat.v1.global_variables_initializer() 522 | sess.run(init) 523 | 524 | print("===========================") 525 | print("restore training from iteration %d: %s" %(start_iters, ckpt)) 526 | print("===========================") 527 | tf_restore_saver.restore(sess, ckpt) 528 | 529 | if(True): 530 | #forward single ShapeNet object 531 | import plyfile 532 | #inference 533 | input_file = "examples/d0fa70e45dee680fa45b742ddc5add59.ply" 534 | assert(os.path.exists(input_file)) 535 | plydata = plyfile.PlyData.read(input_file) 536 | noisy_points = np.stack([plydata['vertex']['x'], plydata['vertex']['y'], plydata['vertex']['z']], axis=1) 537 | assert(noisy_points.shape[1] == 3) 538 | #first normalize noisy points to [-0.95, 0.95] 539 | scale = 0.95 / np.abs(noisy_points).max() 540 | noisy_points *= scale 541 | assert(noisy_points.min() >= -1 and noisy_points.max() <= 1) 542 | mls_points_prediction = sess.run(mls_points, feed_dict={input_points:noisy_points}) 543 | assert(len(mls_points_prediction) == 3) 544 | 545 | mls_points_geometry = mls_points_prediction[0] 546 | mls_points_radius = mls_points_prediction[2] 547 | 548 | mls_points_position = mls_points_geometry[:,:3] 549 | mls_points_normal = mls_points_geometry[:,3:] / np.linalg.norm(mls_points_geometry[:,3:], axis=1, keepdims=True) 550 | mls_points_geometry = np.concatenate([mls_points_position, mls_points_normal], axis=1) 551 | 552 | shape_scale = np.ones(1)*scale 553 | np.savetxt(input_file+".xyz", mls_points_geometry, fmt="%0.6f") 554 | np.savetxt(input_file+"_radius.txt", mls_points_radius, fmt="%0.6f") 555 | np.savetxt(input_file+"_scale.txt", shape_scale) 556 | return 557 | 558 | #Shape Completion of ShapeNet:on ShapeNet 13 classes 559 | import plyfile 560 | if not os.path.exists(FLAGS.exp_folder): 561 | os.mkdir(FLAGS.exp_folder) 562 | test_output_dir = os.path.join(FLAGS.exp_folder, 'test') 563 | if not os.path.exists(test_output_dir): os.makedirs(test_output_dir, exist_ok=True) 564 | 565 | #where input pointcloud lies 566 | input_dir = "your_directory/convolutional_occupancy_networks-master/out/pointcloud/shapenet_3plane/generation_pretrained/input" 567 | categories = os.listdir(input_dir) 568 | 569 | for cat in categories: 570 | cat_input_dir = os.path.join(input_dir, cat) 571 | filelist = os.listdir(cat_input_dir) 572 | cat_output_dir = os.path.join(test_output_dir, cat) 573 | if(not os.path.exists(cat_output_dir)): os.mkdir(cat_output_dir) 574 | for point_file in filelist: 575 | if(not os.path.isdir(point_file) and point_file.endswith(".ply")): 576 | sample_id = point_file.replace(".ply", "") 577 | print("{}:{}".format(cat, point_file)) 578 | #do inference 579 | plydata = plyfile.PlyData.read(os.path.join(cat_input_dir, point_file)) 580 | noisy_points = np.stack([plydata['vertex']['x'], plydata['vertex']['y'], plydata['vertex']['z']], axis=1) 581 | assert(noisy_points.shape[1] == 3) 582 | #first normalize noisy points to [-0.95, 0.95] 583 | scale = 0.95 / np.abs(noisy_points).max() 584 | noisy_points *= scale 585 | assert(noisy_points.min() >= -1 and noisy_points.max() <= 1) 586 | mls_points_prediction = sess.run(mls_points, feed_dict={input_points:noisy_points}) 587 | assert(len(mls_points_prediction) == 3) 588 | 589 | mls_points_geometry = mls_points_prediction[0] 590 | mls_points_radius = mls_points_prediction[2] 591 | 592 | mls_points_position = mls_points_geometry[:,:3] 593 | mls_points_normal = mls_points_geometry[:,3:] / np.linalg.norm(mls_points_geometry[:,3:], axis=1, keepdims=True) 594 | mls_points_geometry = np.concatenate([mls_points_position, mls_points_normal], axis=1) 595 | 596 | shape_scale = np.ones(1)*scale 597 | np.savetxt(os.path.join(cat_output_dir, sample_id+".xyz"), mls_points_geometry, fmt="%0.6f") 598 | np.savetxt(os.path.join(cat_output_dir, sample_id+"_radius.txt"), mls_points_radius, fmt="%0.6f") 599 | np.savetxt(os.path.join(cat_output_dir, sample_id+"_scale.txt"), shape_scale) 600 | 601 | def training_pipeline(): 602 | if not os.path.exists(FLAGS.exp_folder): 603 | os.mkdir(FLAGS.exp_folder) 604 | train_record_num, train_summary, total_loss, train_op, predict_points, sampled_position_matrix, input_points, train_filenames = train_network() 605 | 606 | # checkpoint 607 | ckpt = tf.train.latest_checkpoint(FLAGS.ckpt) 608 | start_iters = 0 if not ckpt else int(ckpt[ckpt.find('iter') + 4:-5]) + 1 609 | 610 | # saver 611 | gvars = tf.global_variables() 612 | print("{} global variables".format(len(gvars))) 613 | print("{} model parameters total".format(get_num_params())) 614 | 615 | save_vars = gvars 616 | save_vars_wo_adam = [var for var in gvars if 'Adam' not in var.name] 617 | tf_saver_model = tf.train.Saver(var_list = save_vars_wo_adam, max_to_keep=100) 618 | restore_vars = save_vars_wo_adam 619 | 620 | print("restore {} vars".format(len(restore_vars))) 621 | print("save {} vars".format(len(save_vars))) 622 | 623 | tf_saver = tf.train.Saver(var_list=save_vars, max_to_keep=100) 624 | if ckpt: 625 | tf_restore_saver = tf.train.Saver(var_list=restore_vars, max_to_keep=100) 626 | 627 | #train_record_num = sum(1 for _ in tf.python_io.tf_record_iterator(FLAGS.train_data)) 628 | print("train_record_num: {}".format(train_record_num)) 629 | iter_100_epoch = int(100.0 * train_record_num / FLAGS.train_batch_size) 630 | 631 | config = tf.ConfigProto() 632 | config.gpu_options.allow_growth = True 633 | with tf.Session(config=config) as sess: 634 | # initialize 635 | init = tf.compat.v1.global_variables_initializer() 636 | sess.run(init) 637 | 638 | if ckpt: 639 | print("===========================") 640 | print("restore training from iteration %d: %s" %(start_iters, ckpt)) 641 | print("===========================") 642 | tf_restore_saver.restore(sess, ckpt) 643 | 644 | #folders for training outputs 645 | obj_dir = os.path.join(FLAGS.exp_folder, 'obj') 646 | if not os.path.exists(obj_dir): os.makedirs(obj_dir) 647 | 648 | #folders for checkpoint 649 | if not os.path.exists(os.path.join(FLAGS.exp_folder, "model")): os.makedirs(os.path.join(FLAGS.exp_folder, "model"), exist_ok=True) 650 | 651 | # tf summary 652 | summary_writer = tf.summary.FileWriter(FLAGS.exp_folder, sess.graph) 653 | lr_decay_step = int(FLAGS.lr_decay_epochs*iter_100_epoch/100.0) 654 | print("lr decay 20% per {} iterations({} epochs)".format(lr_decay_step, FLAGS.lr_decay_epochs)) 655 | cur_lr = max(FLAGS.learning_rate_lower_bound, FLAGS.learning_rate*0.8**int(start_iters / lr_decay_step)) 656 | print("initial lr setting to {}".format(cur_lr)) 657 | 658 | max_iter = int(1.0 * FLAGS.max_training_epochs * train_record_num / FLAGS.train_batch_size) + 1 659 | print("max training iterations: {}".format(max_iter)) 660 | last_loss = 0 661 | # start training 662 | for i in tqdm.tqdm(range(start_iters, max_iter)): 663 | #cur_epochs = 1.0 * i * FLAGS.train_batch_size / train_record_num 664 | if(i % lr_decay_step == 0): 665 | cur_lr = max(FLAGS.learning_rate_lower_bound, FLAGS.learning_rate*0.8**int(i / lr_decay_step)) 666 | print("lr setting to {}".format(cur_lr)) 667 | 668 | if(i % 5000 == 0): 669 | #write out visualization results 670 | _, train_summary_fetch, train_loss_eval, predict_points_eval, sampled_position_matrix_eval, input_points_fetch, train_filenames_fetch = \ 671 | sess.run([train_op, train_summary, total_loss, predict_points, sampled_position_matrix, input_points, train_filenames], feed_dict={lr_placeholder: cur_lr}) 672 | write_visualization_results(predict_points_eval, sampled_position_matrix_eval, input_points_fetch, obj_dir, i, filenames=train_filenames_fetch) 673 | else: 674 | _, train_summary_fetch, train_loss_eval = sess.run([train_op, train_summary, total_loss], feed_dict={lr_placeholder: cur_lr}) 675 | 676 | #write summary to tfevents 677 | summary_writer.add_summary(train_summary_fetch, i) 678 | #save checkpoint 679 | if (i % 5000 == 0): 680 | tf_saver.save(sess, os.path.join(FLAGS.exp_folder, 'model/iter{:06d}.ckpt'.format(i))) 681 | #print logs to stdout 682 | if(i % 500 == 0): 683 | print("iteration {}: train loss = {}".format(i, train_loss_eval)) 684 | sys.stdout.flush() 685 | 686 | print("Program terminated.") 687 | 688 | def main(): 689 | if(FLAGS.test): 690 | return inference_from_inputs() 691 | return training_pipeline() 692 | 693 | if __name__ == '__main__': 694 | print('Program running...') 695 | main() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andy97 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Octree/ocnn-tf/libs/ReadMe.txt: -------------------------------------------------------------------------------- 1 | Please copy compiled "libocnn.so" here. -------------------------------------------------------------------------------- /Octree/ocnn-tf/libs/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tensorflow as tf 3 | from tensorflow.python.framework import ops 4 | 5 | if 'OCTREE_KEY' in os.environ and os.environ['OCTREE_KEY'] == '64': 6 | print('INFO from ocnn: The octree key is 64 bits') 7 | octree_key64 = True 8 | tf_uintk = tf.uint64 9 | tf_uints = tf.uint16 10 | tf_intk = tf.int64 11 | else: 12 | print('INFO from ocnn: The octree key is 32 bits, ' 13 | 'the octree depth should be smaller than 8.') 14 | octree_key64 = False 15 | tf_uintk = tf.uint32 16 | tf_uints = tf.uint8 17 | tf_intk = tf.int32 18 | 19 | _current_path = os.path.dirname(os.path.realpath(__file__)) 20 | _tf_ocnn_module = tf.load_op_library(os.path.join(_current_path, 'libocnn.so')) 21 | 22 | bounding_sphere = _tf_ocnn_module.bounding_sphere 23 | points_property = _tf_ocnn_module.points_property 24 | transform_points = _tf_ocnn_module.transform_points 25 | normalize_points = _tf_ocnn_module.normalize_points 26 | points_new = _tf_ocnn_module.points_new 27 | points_set_property = _tf_ocnn_module.points_set_property 28 | octree_drop = _tf_ocnn_module.octree_drop 29 | octree_scan = _tf_ocnn_module.octree_scan 30 | octree_cast = _tf_ocnn_module.octree_cast 31 | octree_batch = _tf_ocnn_module.octree_batch 32 | points2octree = _tf_ocnn_module.points_to_octree 33 | octree_property = _tf_ocnn_module.octree_property 34 | octree_pad = _tf_ocnn_module.octree_pad 35 | octree_depad = _tf_ocnn_module.octree_depad 36 | octree2col = _tf_ocnn_module.octree_to_col 37 | col2octree = _tf_ocnn_module.col_to_octree 38 | octree_grow = _tf_ocnn_module.octree_grow 39 | octree_new = _tf_ocnn_module.octree_new 40 | octree_update = _tf_ocnn_module.octree_update 41 | octree_align = _tf_ocnn_module.octree_align 42 | octree_mask = _tf_ocnn_module.octree_mask 43 | octree_samples = _tf_ocnn_module.octree_samples 44 | octree_search = _tf_ocnn_module.octree_search 45 | octree_key2xyz = _tf_ocnn_module.octree_key_to_xyz 46 | octree_xyz2key = _tf_ocnn_module.octree_xyz_to_key 47 | octree_decode_key = _tf_ocnn_module.octree_decode_key 48 | octree_encode_key = _tf_ocnn_module.octree_encode_key 49 | octree_search_key = _tf_ocnn_module.octree_search_key 50 | octree_set_property = _tf_ocnn_module.octree_set_property 51 | octree_gather = _tf_ocnn_module.octree_gather 52 | octree_gatherbk = _tf_ocnn_module.octree_gatherbk 53 | _octree_max_pool = _tf_ocnn_module.octree_max_pool 54 | _octree_mask_pool = _tf_ocnn_module.octree_mask_pool 55 | _octree_max_unpool = _tf_ocnn_module.octree_max_unpool 56 | _octree_conv = _tf_ocnn_module.octree_conv 57 | _octree_deconv = _tf_ocnn_module.octree_deconv 58 | _octree_conv_grad = _tf_ocnn_module.octree_conv_grad 59 | _octree_deconv_grad = _tf_ocnn_module.octree_deconv_grad 60 | _octree_align_grad = _tf_ocnn_module.octree_align_grad 61 | _octree_bilinear = _tf_ocnn_module.octree_bilinear 62 | 63 | 64 | ops.NotDifferentiable('BoundingSphere') 65 | ops.NotDifferentiable('OctreeSetProperty') 66 | ops.NotDifferentiable('OctreeBatch') 67 | ops.NotDifferentiable('TransformPoints') 68 | ops.NotDifferentiable('NormalizePoints') 69 | ops.NotDifferentiable('PointsNew') 70 | ops.NotDifferentiable('PointsSetProperty') 71 | ops.NotDifferentiable('PointsToOctree') 72 | ops.NotDifferentiable('OctreeProperty') 73 | ops.NotDifferentiable('OctreeNew') 74 | ops.NotDifferentiable('OctreeUpdate') 75 | ops.NotDifferentiable('OctreeGrow') 76 | ops.NotDifferentiable('OctreeSamples') 77 | ops.NotDifferentiable('OctreeBilinear') 78 | ops.NotDifferentiable('OctreeKeyToXyz') 79 | ops.NotDifferentiable('OctreeXyzToKey') 80 | ops.NotDifferentiable('OctreeDecodeKey') 81 | ops.NotDifferentiable('OctreeEncodeKey') 82 | ops.NotDifferentiable('OctreeSearchKey') 83 | ops.NotDifferentiable('OctreeSearch') 84 | ops.NotDifferentiable('PointsProperty') 85 | ops.NotDifferentiable('OctreeScan') 86 | ops.NotDifferentiable('OctreeCast') 87 | ops.NotDifferentiable('OctreeDrop') 88 | 89 | 90 | @ops.RegisterGradient('OctreePad') 91 | def _OctreePadGrad(op, grad): 92 | grad_out = octree_depad(grad, op.inputs[1], op.get_attr('depth')) 93 | return [grad_out, None] 94 | 95 | 96 | @ops.RegisterGradient('OctreeDepad') 97 | def _OctreeDepadGrad(op, grad): 98 | grad_out = octree_pad(grad, op.inputs[1], op.get_attr('depth')) 99 | return [grad_out, None] 100 | 101 | 102 | @ops.RegisterGradient('OctreeToCol') 103 | def _OctreeToColGrad(op, grad): 104 | grad_out = col2octree(grad, op.inputs[1], op.get_attr('depth'), 105 | op.get_attr('kernel_size'), op.get_attr('stride')) 106 | return [grad_out, None] 107 | 108 | 109 | @ops.RegisterGradient('ColToOctree') 110 | def _ColToOctreeGrad(op, grad): 111 | grad_out = octree2col(grad, op.inputs[1], op.get_attr('depth'), 112 | op.get_attr('kernel_size'), op.get_attr('stride')) 113 | return [grad_out, None] 114 | 115 | 116 | @ops.RegisterGradient('OctreeMaxPool') 117 | def _OctreeMaxPoolGrad(op, *grad): 118 | grad_out = _octree_max_unpool(grad[0], op.outputs[1], op.inputs[1], 119 | op.get_attr('depth')) 120 | return [grad_out, None] 121 | 122 | 123 | @ops.RegisterGradient('OctreeMaxUnpool') 124 | def _OctreeMaxUnpoolGrad(op, grad): 125 | grad_out = _octree_mask_pool(grad, op.inputs[1], op.inputs[2], 126 | op.get_attr('depth')) 127 | return [grad_out, None, None] 128 | 129 | 130 | @ops.RegisterGradient('OctreeMaskPool') 131 | def _OctreeMaskPoolGrad(op, grad): 132 | grad_out = _octree_max_unpool(grad, op.inputs[1], op.inputs[2], 133 | op.get_attr('depth')) 134 | return [grad_out, None, None] 135 | 136 | 137 | @ops.RegisterGradient('OctreeConv') 138 | def _OctreeConvGrad(op, grad): 139 | grad_out = _octree_conv_grad(op.inputs[0], op.inputs[1], op.inputs[2], grad, 140 | op.get_attr('depth'), op.get_attr('num_output'), 141 | op.get_attr('kernel_size'), op.get_attr('stride')) 142 | return grad_out + (None, ) 143 | 144 | 145 | @ops.RegisterGradient('OctreeDeconv') 146 | def _OctreeDeconvGrad(op, grad): 147 | grad_out = _octree_deconv_grad(op.inputs[0], op.inputs[1], op.inputs[2], grad, 148 | op.get_attr('depth'), op.get_attr('num_output'), 149 | op.get_attr('kernel_size'), op.get_attr('stride')) 150 | return grad_out + (None, ) 151 | 152 | 153 | @ops.RegisterGradient('OctreeAlign') 154 | def _OctreeAlignGrad(op, *grad): 155 | grad_out = _octree_align_grad(grad[0], op.outputs[1]) 156 | return [grad_out, None, None] 157 | 158 | 159 | @ops.RegisterGradient('OctreeMask') 160 | def _OctreeMaskGrad(op, grad): 161 | grad_out = octree_mask(grad, op.inputs[1], op.get_attr('mask')) 162 | return [grad_out, None] 163 | 164 | 165 | @ops.RegisterGradient('OctreeGather') 166 | def _OctreeGatherGrad(op, grad): 167 | shape = tf.shape(op.inputs[0]) 168 | grad_out = octree_gatherbk(grad, op.inputs[1], shape) 169 | return [grad_out, None] 170 | 171 | 172 | def octree_max_pool(data, octree, depth): 173 | with tf.variable_scope('octree_max_pool'): 174 | data, mask = _octree_max_pool(data, octree, depth) # the bottom data depth 175 | data = octree_pad(data, octree, depth-1) # !!! depth-1 176 | return data, mask 177 | 178 | 179 | def octree_max_unpool(data, mask, octree, depth): 180 | with tf.variable_scope('octree_max_unpool'): 181 | data = octree_depad(data, octree, depth) # !!! depth 182 | data = _octree_max_unpool(data, mask, octree, depth) # the bottom data depth 183 | return data 184 | 185 | 186 | def octree_avg_pool(data, octree, depth): 187 | with tf.variable_scope('octree_avg_pool'): 188 | data = tf.reshape(data, [1, int(data.shape[1]), -1, 8]) 189 | data = tf.reduce_mean(data, axis=3, keepdims=True) 190 | data = octree_pad(data, octree, depth-1) # !!! depth-1 191 | return data 192 | 193 | 194 | # todo: merge octree_conv_fast and octree_conv_memory to reduce code redundancy 195 | def octree_conv_fast(data, octree, depth, channel, kernel_size=[3], stride=1): 196 | assert(type(kernel_size) is list and len(kernel_size) < 4) 197 | for i in range(len(kernel_size), 3): 198 | kernel_size.append(kernel_size[-1]) 199 | 200 | with tf.variable_scope('octree_conv'): 201 | dim = int(data.shape[1]) * kernel_size[0] * kernel_size[1] * kernel_size[2] 202 | kernel = tf.get_variable('weights', shape=[channel, dim], dtype=tf.float32, 203 | initializer=tf.contrib.layers.xavier_initializer()) 204 | col = octree2col(data, octree, depth, kernel_size, stride) 205 | col = tf.reshape(col, [dim, -1]) 206 | conv = tf.matmul(kernel, col) 207 | conv = tf.expand_dims(tf.expand_dims(conv, 0), -1) # [C, H] -> [1, C, H, 1] 208 | if stride == 2: 209 | conv = octree_pad(conv, octree, depth-1, 0) 210 | return conv 211 | 212 | 213 | def octree_conv_memory(data, octree, depth, channel, kernel_size=[3], stride=1): 214 | assert(type(kernel_size) is list and len(kernel_size) < 4) 215 | for i in range(len(kernel_size), 3): 216 | kernel_size.append(kernel_size[-1]) 217 | 218 | with tf.variable_scope('octree_conv'): 219 | dim = int(data.shape[1]) * kernel_size[0] * kernel_size[1] * kernel_size[2] 220 | kernel = tf.get_variable('weights', shape=[channel, dim], dtype=tf.float32, 221 | initializer=tf.contrib.layers.xavier_initializer()) 222 | conv = _octree_conv(data, kernel, octree, depth, channel, kernel_size, stride) 223 | if stride == 2: 224 | conv = octree_pad(conv, octree, depth-1) 225 | return conv 226 | 227 | 228 | def octree_deconv_fast(data, octree, depth, channel, kernel_size=[3], stride=1): 229 | assert(type(kernel_size) is list and len(kernel_size) < 4) 230 | for i in range(len(kernel_size), 3): 231 | kernel_size.append(kernel_size[-1]) 232 | 233 | with tf.variable_scope('octree_deconv'): 234 | kernel_sdim = kernel_size[0] * kernel_size[1] * kernel_size[2] 235 | dim = channel * kernel_sdim 236 | kernel = tf.get_variable('weights', shape=[int(data.shape[1]), dim], dtype=tf.float32, 237 | initializer=tf.contrib.layers.xavier_initializer()) 238 | if stride == 2: 239 | data = octree_depad(data, octree, depth) 240 | depth = depth + 1 241 | data = tf.squeeze(data, [0, 3]) 242 | deconv = tf.matmul(kernel, data, transpose_a=True, transpose_b=False) 243 | deconv = tf.reshape(deconv, [channel, kernel_sdim, -1]) 244 | col = col2octree(deconv, octree, depth, kernel_size, stride) 245 | return col 246 | 247 | 248 | def octree_deconv_memory(data, octree, depth, channel, kernel_size=[3], stride=1): 249 | assert(type(kernel_size) is list and len(kernel_size) < 4) 250 | for i in range(len(kernel_size), 3): 251 | kernel_size.append(kernel_size[-1]) 252 | 253 | with tf.variable_scope('octree_deconv'): 254 | kernel_sdim = kernel_size[0] * kernel_size[1] * kernel_size[2] 255 | dim = channel * kernel_sdim 256 | kernel = tf.get_variable('weights', shape=[int(data.shape[1]), dim], dtype=tf.float32, 257 | initializer=tf.contrib.layers.xavier_initializer()) 258 | if stride == 2: 259 | data = octree_depad(data, octree, depth) 260 | deconv = _octree_deconv(data, kernel, octree, depth, channel, kernel_size, stride) 261 | return deconv 262 | 263 | 264 | def octree_full_voxel(data, depth): 265 | height = 2 ** (3 * depth) 266 | channel = int(data.shape[1]) 267 | with tf.variable_scope('octree_full_voxel'): 268 | data = tf.reshape(data, [channel, -1, height]) # (1, C, H, 1) -> (C, batch_size, H1) 269 | data = tf.transpose(data, perm=[1, 0, 2]) 270 | return data 271 | 272 | 273 | def octree_tile(data, octree, depth): 274 | with tf.variable_scope('octree_tile'): 275 | data = octree_depad(data, octree, depth) # (1, C, H, 1) 276 | data = tf.tile(data, [1, 1, 1, 8]) # (1, C, H, 8) 277 | channel = int(data.shape[1]) 278 | output = tf.reshape(data, [1, channel, -1, 1]) 279 | return output 280 | 281 | 282 | def octree_global_pool(data, octree, depth): 283 | with tf.variable_scope('octree_global_pool'): 284 | segment_ids = octree_property(octree, property_name='index', dtype=tf.int32, 285 | depth=depth, channel=1) 286 | segment_ids = tf.reshape(segment_ids, [-1]) 287 | data = tf.squeeze(data, axis=[0, 3]) # (1, C, H, 1) -> (C, H) 288 | data = tf.transpose(data) # (C, H) -> (H, C) 289 | output = tf.math.segment_mean(data, segment_ids) # (H, C) -> (batch_size, C) 290 | return output 291 | 292 | 293 | def octree_bilinear_legacy(data, octree, depth, target_depth): 294 | with tf.variable_scope('octree_bilinear'): 295 | mask = tf.constant( 296 | [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], 297 | [0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) 298 | index, fracs = _octree_bilinear(octree, depth, target_depth) 299 | feat = tf.transpose(tf.squeeze(data, [0, 3])) # (1, C, H, 1) -> (H, C) 300 | output = tf.zeros([tf.shape(index)[0], tf.shape(feat)[1]], dtype=tf.float32) 301 | norm = tf.zeros([tf.shape(index)[0], 1], dtype=tf.float32) 302 | for i in range(8): 303 | idxi = index[:, i] 304 | weight = tf.abs(tf.reduce_prod(mask[i, :] - fracs, axis=1, keepdims=True)) 305 | output += weight * tf.gather(feat, idxi) 306 | norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) 307 | output = tf.div(output, norm) 308 | output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) 309 | return output 310 | 311 | 312 | # pts: (N, 4), i.e. N x (x, y, z, id) 313 | # data: (1, C, H, 1) 314 | def octree_bilinear_v1(pts, data, octree, depth): 315 | with tf.variable_scope('octree_bilinear'): 316 | mask = tf.constant( 317 | [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], 318 | [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) 319 | 320 | xyzf, ids = tf.split(pts, [3, 1], 1) 321 | xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel 322 | xyzi = tf.floor(xyzf) # the integer part 323 | frac = xyzf - xyzi # the fraction part 324 | 325 | feat = tf.transpose(tf.squeeze(data, [0, 3])) # (1, C, H, 1) -> (H, C) 326 | output = tf.zeros([tf.shape(xyzi)[0], tf.shape(feat)[1]], dtype=tf.float32) 327 | norm = tf.zeros([tf.shape(xyzi)[0], 1], dtype=tf.float32) 328 | 329 | for i in range(8): 330 | maski = mask[i, :] 331 | maskc = 1.0 - maski 332 | xyzm = xyzi + maski 333 | xyzm = tf.cast(tf.concat([xyzm, ids], axis=1), dtype=tf_uints) 334 | idxi = octree_search_key(octree_encode_key(xyzm), octree, depth, is_xyz=True) 335 | 336 | weight = tf.abs(tf.reduce_prod(maskc - frac, axis=1, keepdims=True)) 337 | output += weight * tf.gather(feat, idxi) 338 | norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) 339 | output = tf.div(output, norm) 340 | 341 | output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) 342 | frac = tf.expand_dims(tf.expand_dims(tf.transpose(frac), 0), -1) 343 | 344 | return output, frac 345 | 346 | # pts: (N, 4), i.e. N x (x, y, z, id) 347 | # data: (1, C, H, 1) 348 | def octree_bilinear_v2(pts, data, octree, depth): 349 | with tf.variable_scope('octree_bilinear'): 350 | mask = tf.constant( 351 | [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], 352 | [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) 353 | 354 | xyzf, ids = tf.split(pts, [3, 1], 1) 355 | xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel 356 | xyzi = tf.floor(xyzf) # the integer part 357 | frac = xyzf - xyzi # the fraction part 358 | 359 | output = tf.zeros([1, tf.shape(data)[1], tf.shape(xyzi)[0], 1], dtype=tf.float32) 360 | norm = tf.zeros([tf.shape(xyzi)[0], 1], dtype=tf.float32) 361 | 362 | for i in range(8): 363 | maski = mask[i, :] 364 | maskc = 1.0 - maski 365 | xyzm = xyzi + maski 366 | xyzm = tf.cast(tf.concat([xyzm, ids], axis=1), dtype=tf_uints) 367 | # !!! Note some elements of idxi may be -1 368 | idxi = octree_search_key(octree_encode_key(xyzm), octree, depth, is_xyz=True) 369 | 370 | weight = tf.abs(tf.reduce_prod(maskc - frac, axis=1, keepdims=True)) 371 | # output += weight * tf.gather(data, idxi, axis=2) 372 | output += weight * octree_gather(data, idxi) 373 | norm += weight * tf.expand_dims(tf.cast(idxi > -1, dtype=tf.float32), -1) 374 | output = tf.div(output, norm) 375 | return output 376 | 377 | 378 | # pts: (N, 4), i.e. N x (x, y, z, id). 379 | # data: (1, C, H, 1) 380 | # !!! Note: the pts should be scaled into [0, 2^depth] 381 | def octree_bilinear_v3(pts, data, octree, depth): 382 | with tf.variable_scope('octree_linear'): 383 | mask = tf.constant( 384 | [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], 385 | [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], dtype=tf.float32) 386 | if octree_key64: 387 | masku = tf.constant([0, 4294967296, 65536, 4295032832, 388 | 1, 4294967297, 65537, 4295032833], dtype=tf.int64) 389 | else: 390 | masku = tf.constant([0, 65536, 256, 65792, 391 | 1, 65537, 257, 65793], dtype=tf.int32) 392 | 393 | maskc = 1 - mask 394 | 395 | xyzf, ids = tf.split(pts, [3, 1], 1) 396 | xyzf = xyzf - 0.5 # since the value is defined on the center of each voxel 397 | xyzi = tf.floor(xyzf) # the integer part (N, 3) 398 | frac = xyzf - xyzi # the fraction part (N, 3) 399 | 400 | key = tf.cast(tf.concat([xyzi, ids], axis=1), dtype=tf_uints) 401 | key = tf.cast(octree_encode_key(key), dtype=tf_intk) 402 | # Cast the key to `int32` since the `add` below does not support `uint64` 403 | # The size effect is that the batch_size must be smaller than 128 404 | key = tf.expand_dims(key, 1) + masku # (N, 8), 405 | key = tf.cast(tf.reshape(key, [-1]), dtype=tf_uintk) 406 | 407 | idx = octree_search_key(key, octree, depth) # (N*8,) 408 | flgs = idx > -1 # filtering flags 409 | idx = tf.boolean_mask(idx, flgs) 410 | 411 | npt = tf.shape(xyzi)[0] 412 | ids = tf.reshape(tf.range(npt), [-1, 1]) 413 | ids = tf.reshape(tf.tile(ids, [1, 8]), [-1]) # (N*8,) 414 | ids = tf.boolean_mask(ids, flgs) 415 | 416 | frac = maskc - tf.expand_dims(frac, axis=1) 417 | weight = tf.abs(tf.reshape(tf.reduce_prod(frac, axis=2), [-1])) 418 | weight = tf.boolean_mask(weight, flgs) 419 | 420 | indices = tf.concat([tf.expand_dims(ids, 1), tf.expand_dims(idx, 1)], 1) 421 | indices = tf.cast(indices, tf.int64) 422 | data = tf.squeeze(data, [0, 3]) # (C, H) 423 | h = tf.shape(data)[1] 424 | mat = tf.SparseTensor(indices=indices, values=weight, dense_shape=[npt, h]) 425 | 426 | # channel, max_channel = int(data.shape[0]), 512 427 | # if channel > max_channel: 428 | # num = channel // max_channel 429 | # remain = channel % max_channel 430 | # splits = [max_channel] * num 431 | # if remain != 0: 432 | # splits.append(remain) 433 | # num += 1 434 | # output_split = [None] * num 435 | # data_split = tf.split(data, splits, axis=0) 436 | # for i in range(num): 437 | # with tf.name_scope('mat_%d' % i): 438 | # output_split[i] = tf.sparse.sparse_dense_matmul( 439 | # mat, data_split[i], adjoint_a=False, adjoint_b=True) 440 | # output = tf.concat(output_split, axis=1) 441 | # else: 442 | # output = tf.sparse.sparse_dense_matmul(mat, data, adjoint_a=False, adjoint_b=True) 443 | 444 | output = tf.sparse.sparse_dense_matmul(mat, data, adjoint_a=False, adjoint_b=True) 445 | norm = tf.sparse.sparse_dense_matmul(mat, tf.ones([h, 1])) 446 | output = tf.div(output, norm + 1.0e-10) # avoid dividing by zeros 447 | output = tf.expand_dims(tf.expand_dims(tf.transpose(output), 0), -1) 448 | return output 449 | 450 | 451 | def octree_bilinear(data, octree, depth, target_depth, mask=None): 452 | with tf.name_scope('Octree_bilinear'): 453 | xyz = octree_property(octree, property_name='xyz', depth=target_depth, 454 | channel=1, dtype=tf_uintk) 455 | xyz = tf.reshape(xyz, [-1]) 456 | if mask is not None: 457 | xyz = tf.boolean_mask(xyz, mask) 458 | xyz = tf.cast(octree_decode_key(xyz), dtype=tf.float32) 459 | 460 | # Attention: displacement 0.5, scale 461 | scale = 2.0**(depth-target_depth) 462 | xyz += tf.constant([0.5, 0.5, 0.5, 0.0], dtype=tf.float32) 463 | xyz *= tf.constant([scale, scale, scale, 1.0], dtype=tf.float32) 464 | 465 | output = octree_bilinear_v3(xyz, data, octree, depth) 466 | return output 467 | 468 | 469 | # pts: (N, 4), i.e. N x (x, y, z, id) 470 | # data: (1, C, H, 1) 471 | def octree_nearest_interp(pts, data, octree, depth): 472 | with tf.variable_scope('octree_nearest_interp'): 473 | # The value is defined on the center of each voxel, 474 | # so we can get the closest grid point by simply casting the value to tf_uints 475 | pts = tf.cast(pts, dtype=tf_uints) 476 | key = tf.reshape(octree_encode_key(pts), [-1]) 477 | 478 | idx = octree_search_key(key, octree, depth) 479 | # !!! Note that some of idx may be -1 or over-bound 480 | # Use tf.gather may be problematic with some version of tensorflow 481 | # according to my experiments. So I implemented octree_gather to 482 | # replace the original tf.gather. If you encounter errors, please 483 | # use the octree_gather 484 | # output = tf.gather(data, idx, axis=2) 485 | output = octree_gather(data, idx) 486 | return output 487 | 488 | 489 | 490 | def octree_signal(octree, depth, channel): 491 | with tf.name_scope('octree_signal'): 492 | signal = octree_property(octree, property_name='feature', dtype=tf.float32, 493 | depth=depth, channel=channel) 494 | signal = tf.reshape(signal, [1, channel, -1, 1]) 495 | return signal 496 | 497 | 498 | def octree_xyz(octree, depth, decode=True): 499 | with tf.name_scope('octree_xyz'): 500 | xyz = octree_property(octree, property_name='xyz', dtype=tf_uintk, 501 | depth=depth, channel=1) 502 | xyz = tf.cast(xyz, tf.int64) 503 | xyz = tf.reshape(xyz, [-1]) # uint32, N 504 | xyz = tf.cast(xyz, tf_uintk) 505 | if decode: 506 | xyz = octree_decode_key(xyz) # uint8, Nx4 507 | return xyz 508 | 509 | 510 | def octree_child(octree, depth): 511 | with tf.name_scope('octree_child'): 512 | child = octree_property(octree, property_name='child', dtype=tf.int32, 513 | depth=depth, channel=1) 514 | child = tf.reshape(child, [-1]) 515 | return child 516 | 517 | 518 | def octree_split(octree, depth): 519 | with tf.name_scope('octree_split'): 520 | split = octree_property(octree, property_name='split', dtype=tf.float32, 521 | depth=depth, channel=1) 522 | split = tf.reshape(split, [-1]) 523 | return split -------------------------------------------------------------------------------- /Octree/ocnn.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tensorflow as tf 3 | sys.path.append("..") 4 | from libs import * 5 | 6 | 7 | def get_variables_with_name(name=None, without=None, train_only=True, verbose=False): 8 | if name is None: 9 | raise Exception("please input a name") 10 | 11 | t_vars = tf.trainable_variables() if train_only else tf.all_variables() 12 | d_vars = [var for var in t_vars if name in var.name] 13 | 14 | if without is not None: 15 | d_vars = [var for var in d_vars if without not in var.name] 16 | 17 | if verbose: 18 | print(" [*] geting variables with %s" % name) 19 | for idx, v in enumerate(d_vars): 20 | print(" got {:3}: {:15} {}".format(idx, v.name, str(v.get_shape()))) 21 | 22 | return d_vars 23 | 24 | 25 | def dense(inputs, nout, use_bias=False): 26 | inputs = tf.layers.flatten(inputs) 27 | fc = tf.layers.dense(inputs, nout, use_bias=use_bias, 28 | kernel_initializer=tf.contrib.layers.xavier_initializer()) 29 | return fc 30 | 31 | 32 | def batch_norm(inputs, training, axis=1): 33 | return tf.layers.batch_normalization(inputs, axis=axis, training=training) 34 | 35 | 36 | def fc_bn_relu(inputs, nout, training): 37 | fc = dense(inputs, nout, use_bias=False) 38 | bn = batch_norm(fc, training) 39 | return tf.nn.relu(bn) 40 | 41 | 42 | def conv2d(inputs, nout, kernel_size, stride, padding='SAME', data_format='channels_first'): 43 | return tf.layers.conv2d(inputs, nout, kernel_size=kernel_size, strides=stride, 44 | padding=padding, data_format=data_format, use_bias=False, 45 | kernel_initializer=tf.contrib.layers.xavier_initializer()) 46 | 47 | 48 | def octree_conv1x1(inputs, nout, use_bias=False): 49 | outputs = tf.layers.conv2d(inputs, nout, kernel_size=1, strides=1, 50 | data_format='channels_first', use_bias=use_bias, 51 | kernel_initializer=tf.contrib.layers.xavier_initializer()) 52 | return outputs 53 | 54 | 55 | def octree_conv1x1(inputs, nout, use_bias=False): 56 | with tf.variable_scope('conv2d_1x1'): 57 | inputs = tf.squeeze(inputs, axis=[0, 3]) # (1, C, H, 1) -> (C, H) 58 | weights = tf.get_variable('weights', shape=[nout, int(inputs.shape[0])], 59 | dtype=tf.float32, initializer=tf.contrib.layers.xavier_initializer()) 60 | outputs = tf.matmul(weights, inputs) # (C, H) -> (nout, H) 61 | if use_bias: 62 | bias = tf.get_variable('bias', shape=[nout, 1], dtype=tf.float32, 63 | initializer=tf.contrib.layers.xavier_initializer()) 64 | outputs = bias + outputs 65 | outputs = tf.expand_dims(tf.expand_dims(outputs, axis=0), axis=-1) 66 | return outputs 67 | 68 | 69 | def octree_conv1x1_bn(inputs, nout, training): 70 | conv = octree_conv1x1(inputs, nout, use_bias=False) 71 | return batch_norm(conv, training) 72 | 73 | 74 | def octree_conv1x1_bn_relu(inputs, nout, training): 75 | conv = octree_conv1x1_bn(inputs, nout, training) 76 | return tf.nn.relu(conv) 77 | 78 | 79 | def conv2d_bn(inputs, nout, kernel_size, stride, training): 80 | conv = conv2d(inputs, nout, kernel_size, stride) 81 | return batch_norm(conv, training) 82 | 83 | 84 | def conv2d_bn_relu(inputs, nout, kernel_size, stride, training): 85 | conv = conv2d_bn(inputs, nout, kernel_size, stride, training) 86 | return tf.nn.relu(conv) 87 | 88 | 89 | def upsample(data, channel, training): 90 | deconv = tf.layers.conv2d_transpose(data, channel, kernel_size=[8, 1], 91 | strides=[8, 1], data_format='channels_first', use_bias=False, 92 | kernel_initializer=tf.contrib.layers.xavier_initializer()) 93 | bn = tf.layers.batch_normalization(deconv, axis=1, training=training) 94 | return tf.nn.relu(bn) 95 | 96 | 97 | def downsample(data, channel, training): 98 | deconv = tf.layers.conv2d(data, channel, kernel_size=[8, 1], 99 | strides=[8, 1], data_format='channels_first', use_bias=False, 100 | kernel_initializer=tf.contrib.layers.xavier_initializer()) 101 | bn = tf.layers.batch_normalization(deconv, axis=1, training=training) 102 | return tf.nn.relu(bn) 103 | 104 | 105 | def avg_pool2d(inputs, data_format='NCHW'): 106 | return tf.nn.avg_pool2d(inputs, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], 107 | padding='SAME', data_format=data_format) 108 | 109 | 110 | def global_pool(inputs, data_format='channels_first'): 111 | axis = [2, 3] if data_format == 'channels_first' else [1, 2] 112 | return tf.reduce_mean(inputs, axis=axis) 113 | 114 | 115 | # !!! Deprecated 116 | def octree_upsample(data, octree, depth, channel, training): 117 | with tf.variable_scope('octree_upsample'): 118 | depad = octree_depad(data, octree, depth) 119 | up = upsample(depad, channel, training) 120 | return up 121 | 122 | 123 | def octree_upsample(data, octree, depth, channel, training): 124 | up = octree_deconv_bn_relu(data, octree, depth, channel, training, 125 | kernel_size=[2], stride=2, fast_mode=False) 126 | return up 127 | 128 | 129 | def octree_downsample(data, octree, depth, channel, training): 130 | with tf.variable_scope('octree_downsample'): 131 | down = downsample(data, channel, training) 132 | pad = octree_pad(down, octree, depth) 133 | return pad 134 | 135 | 136 | def octree_conv_bn(data, octree, depth, channel, training, kernel_size=[3], 137 | stride=1, fast_mode=False): 138 | if fast_mode == True: 139 | conv = octree_conv_fast(data, octree, depth, channel, kernel_size, stride) 140 | else: 141 | conv = octree_conv_memory( 142 | data, octree, depth, channel, kernel_size, stride) 143 | return tf.layers.batch_normalization(conv, axis=1, training=training) 144 | 145 | 146 | def octree_conv_bn_relu(data, octree, depth, channel, training, kernel_size=[3], 147 | stride=1, fast_mode=False): 148 | with tf.variable_scope('conv_bn_relu'): 149 | conv_bn = octree_conv_bn(data, octree, depth, channel, training, kernel_size, 150 | stride, fast_mode) 151 | rl = tf.nn.relu(conv_bn) 152 | return rl 153 | 154 | 155 | def octree_conv_bn_leakyrelu(data, octree, depth, channel, training): 156 | cb = octree_conv_bn(data, octree, depth, channel, training) 157 | return tf.nn.leaky_relu(cb, alpha=0.2) 158 | 159 | 160 | def octree_deconv_bn(data, octree, depth, channel, training, kernel_size=[3], 161 | stride=1, fast_mode=False): 162 | if fast_mode == True: 163 | conv = octree_deconv_fast( 164 | data, octree, depth, channel, kernel_size, stride) 165 | else: 166 | conv = octree_deconv_memory( 167 | data, octree, depth, channel, kernel_size, stride) 168 | return tf.layers.batch_normalization(conv, axis=1, training=training) 169 | 170 | 171 | def octree_deconv_bn_relu(data, octree, depth, channel, training, kernel_size=[3], 172 | stride=1, fast_mode=False): 173 | with tf.variable_scope('deconv_bn_relu'): 174 | conv_bn = octree_deconv_bn(data, octree, depth, channel, training, kernel_size, 175 | stride, fast_mode) 176 | rl = tf.nn.relu(conv_bn) 177 | return rl 178 | 179 | 180 | def octree_resblock(data, octree, depth, num_out, stride, training, bottleneck=4): 181 | num_in = int(data.shape[1]) 182 | channelb = int(num_out / bottleneck) 183 | if stride == 2: 184 | data, mask = octree_max_pool(data, octree, depth=depth) 185 | depth = depth - 1 186 | 187 | with tf.variable_scope("1x1x1_a"): 188 | block1 = octree_conv1x1_bn_relu(data, channelb, training=training) 189 | 190 | with tf.variable_scope("3x3x3"): 191 | block2 = octree_conv_bn_relu(block1, octree, depth, channelb, training) 192 | 193 | with tf.variable_scope("1x1x1_b"): 194 | block3 = octree_conv1x1_bn(block2, num_out, training=training) 195 | 196 | block4 = data 197 | if num_in != num_out: 198 | with tf.variable_scope("1x1x1_c"): 199 | block4 = octree_conv1x1_bn(data, num_out, training=training) 200 | 201 | return tf.nn.relu(block3 + block4) 202 | 203 | 204 | def octree_resblock2(data, octree, depth, num_out, training): 205 | num_in = int(data.shape[1]) 206 | with tf.variable_scope("conv_1"): 207 | conv = octree_conv_bn_relu(data, octree, depth, num_out/4, training) 208 | with tf.variable_scope("conv_2"): 209 | conv = octree_conv_bn(conv, octree, depth, num_out, training) 210 | 211 | link = data 212 | if num_in != num_out: 213 | with tf.variable_scope("conv_1x1"): 214 | link = octree_conv1x1_bn(data, num_out, training=training) 215 | 216 | out = tf.nn.relu(conv + link) 217 | return out 218 | 219 | 220 | def predict_module(data, num_output, num_hidden, training): 221 | # MLP with one hidden layer 222 | with tf.variable_scope('conv1'): 223 | conv = octree_conv1x1_bn_relu(data, num_hidden, training) 224 | with tf.variable_scope('conv2'): 225 | logit = octree_conv1x1(conv, num_output, use_bias=True) 226 | return logit 227 | 228 | 229 | def predict_label(data, num_output, num_hidden, training): 230 | logit = predict_module(data, num_output, num_hidden, training) 231 | # prob = tf.nn.softmax(logit, axis=1) # logit (1, num_output, ?, 1) 232 | label = tf.argmax(logit, axis=1, output_type=tf.int32) # predict (1, ?, 1) 233 | label = tf.reshape(label, [-1]) # flatten 234 | return logit, label 235 | 236 | 237 | def predict_signal(data, num_output, num_hidden, training): 238 | return tf.nn.tanh(predict_module(data, num_output, num_hidden, training)) 239 | 240 | 241 | def softmax_loss(logit, label_gt, num_class, label_smoothing=0.0): 242 | with tf.name_scope('softmax_loss'): 243 | label_gt = tf.cast(label_gt, tf.int32) 244 | onehot = tf.one_hot(label_gt, depth=num_class) 245 | loss = tf.losses.softmax_cross_entropy( 246 | onehot, logit, label_smoothing=label_smoothing) 247 | return loss 248 | 249 | 250 | def l2_regularizer(name, weight_decay): 251 | with tf.name_scope('l2_regularizer'): 252 | var = get_variables_with_name(name) 253 | regularizer = tf.add_n([tf.nn.l2_loss(v) for v in var]) * weight_decay 254 | return regularizer 255 | 256 | 257 | def label_accuracy(label, label_gt): 258 | label_gt = tf.cast(label_gt, tf.int32) 259 | accuracy = tf.reduce_mean(tf.to_float(tf.equal(label, label_gt))) 260 | return accuracy 261 | 262 | 263 | def softmax_accuracy(logit, label): 264 | with tf.name_scope('softmax_accuracy'): 265 | predict = tf.argmax(logit, axis=1, output_type=tf.int32) 266 | accu = label_accuracy(predict, tf.cast(label, tf.int32)) 267 | return accu 268 | 269 | 270 | def regress_loss(signal, signal_gt): 271 | return tf.reduce_mean(tf.reduce_sum(tf.square(signal-signal_gt), 1)) 272 | 273 | 274 | def normalize_signal(data): 275 | channel = data.shape[1] 276 | assert(channel == 3 or channel == 4) 277 | with tf.variable_scope("normalize"): 278 | if channel == 4: 279 | normals = tf.slice(data, [0, 0, 0, 0], [1, 3, -1, 1]) 280 | displacement = tf.slice(data, [0, 3, 0, 0], [1, 1, -1, 1]) 281 | normals = tf.nn.l2_normalize(normals, axis=1) 282 | output = tf.concat([normals, displacement], axis=1) 283 | else: 284 | output = tf.nn.l2_normalize(data, axis=1) 285 | return output 286 | 287 | 288 | def average_tensors(tower_tensors): 289 | avg_tensors = [] 290 | with tf.name_scope('avg_tensors'): 291 | for tensors in tower_tensors: 292 | tensors = [tf.expand_dims(tensor, 0) for tensor in tensors] 293 | avg_tensor = tf.concat(tensors, axis=0) 294 | avg_tensor = tf.reduce_mean(avg_tensor, 0) 295 | avg_tensors.append(avg_tensor) 296 | return avg_tensors 297 | 298 | 299 | def solver_single_gpu(total_loss, learning_rate_handle, gpu_num=1): 300 | with tf.variable_scope('solver'): 301 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) 302 | with tf.control_dependencies(update_ops): 303 | global_step = tf.Variable(0, trainable=False, name='global_step') 304 | lr = learning_rate_handle(global_step) 305 | solver = tf.train.MomentumOptimizer(lr, 0.9) \ 306 | .minimize(total_loss, global_step=global_step) 307 | return solver, lr 308 | 309 | 310 | def solver_multiple_gpus(total_loss, learning_rate_handle, gpu_num): 311 | tower_grads, variables = [], [] 312 | with tf.device('/cpu:0'): 313 | with tf.variable_scope('solver'): 314 | global_step = tf.Variable(0, trainable=False, name='global_step') 315 | lr = learning_rate_handle(global_step) 316 | opt = tf.train.MomentumOptimizer(lr, 0.9) 317 | 318 | for i in range(gpu_num): 319 | with tf.device('/gpu:%d' % i): 320 | with tf.name_scope('device_b%d' % i): 321 | grads_and_vars = opt.compute_gradients(total_loss[i]) 322 | grads, variables = zip(*grads_and_vars) 323 | tower_grads.append(grads) 324 | 325 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) 326 | # !!! Only get the update_ops defined on `device_0` to avoid the sync 327 | # between different GPUs to speed up the training process. !!! 328 | update_ops = [op for op in update_ops if 'device_0' in op.name] 329 | assert update_ops, 'The update ops of BN are empty, check the namescope \'device_0\'' 330 | with tf.device('/cpu:0'): 331 | with tf.name_scope('sync_and_apply_grad'): 332 | with tf.control_dependencies(update_ops): 333 | tower_grads = list(zip(*tower_grads)) 334 | avg_grads = average_tensors(tower_grads) 335 | grads_and_vars = list(zip(avg_grads, variables)) 336 | solver = opt.apply_gradients(grads_and_vars, global_step=global_step) 337 | return solver, lr 338 | 339 | 340 | def build_solver(total_loss, learning_rate_handle, gpu_num=1): 341 | assert (gpu_num > 0) 342 | the_solver = solver_single_gpu if gpu_num == 1 else solver_multiple_gpus 343 | return the_solver(total_loss, learning_rate_handle, gpu_num) 344 | 345 | 346 | def summary_train(names, tensors): 347 | with tf.name_scope('summary_train'): 348 | summaries = [] 349 | for it in zip(names, tensors): 350 | summaries.append(tf.summary.scalar(it[0], it[1])) 351 | summ = tf.summary.merge(summaries) 352 | return summ 353 | 354 | 355 | def summary_test(names): 356 | with tf.name_scope('summary_test'): 357 | summaries = [] 358 | summ_placeholder = [] 359 | for name in names: 360 | summ_placeholder.append(tf.placeholder(tf.float32)) 361 | summaries.append(tf.summary.scalar(name, summ_placeholder[-1])) 362 | summ = tf.summary.merge(summaries) 363 | return summ, summ_placeholder 364 | 365 | 366 | def loss_functions(logit, label_gt, num_class, weight_decay, var_name, label_smoothing=0.0): 367 | with tf.name_scope('loss'): 368 | loss = softmax_loss(logit, label_gt, num_class, label_smoothing) 369 | accu = softmax_accuracy(logit, label_gt) 370 | regularizer = l2_regularizer(var_name, weight_decay) 371 | return [loss, accu, regularizer] 372 | 373 | 374 | def loss_functions_seg(logit, label_gt, num_class, weight_decay, var_name, mask=-1): 375 | with tf.name_scope('loss_seg'): 376 | label_mask = label_gt > mask # filter label -1 377 | masked_logit = tf.boolean_mask(logit, label_mask) 378 | masked_label = tf.boolean_mask(label_gt, label_mask) 379 | loss = softmax_loss(masked_logit, masked_label, num_class) 380 | 381 | accu = softmax_accuracy(masked_logit, masked_label) 382 | regularizer = l2_regularizer(var_name, weight_decay) 383 | return [loss, accu, regularizer] 384 | 385 | 386 | def get_seg_label(octree, depth): 387 | with tf.name_scope('seg_label'): 388 | label = octree_property(octree, property_name='label', dtype=tf.float32, 389 | depth=depth, channel=1) 390 | label = tf.reshape(tf.cast(label, tf.int32), [-1]) 391 | return label 392 | 393 | 394 | def run_k_iterations(sess, k, tensors): 395 | num = len(tensors) 396 | avg_results = [0] * num 397 | for _ in range(k): 398 | iter_results = sess.run(tensors) 399 | for j in range(num): 400 | avg_results[j] += iter_results[j] 401 | 402 | for j in range(num): 403 | avg_results[j] /= k 404 | return avg_results 405 | 406 | 407 | def tf_IoU_per_shape(pred, label, class_num, mask=-1): 408 | with tf.name_scope('IoU'): 409 | label_mask = label > mask # filter label -1 410 | pred = tf.boolean_mask(pred, label_mask) 411 | label = tf.boolean_mask(label, label_mask) 412 | pred = tf.argmax(pred, axis=1, output_type=tf.int32) 413 | IoU, valid_part_num, esp = 0.0, 0.0, 1.0e-10 414 | for k in range(class_num): 415 | pk, lk = tf.equal(pred, k), tf.equal(label, k) 416 | # pk, lk = pred == k, label == k # why can this not output the right results? 417 | intsc = tf.reduce_sum(tf.cast(pk & lk, dtype=tf.float32)) 418 | union = tf.reduce_sum(tf.cast(pk | lk, dtype=tf.float32)) 419 | valid = tf.cast(tf.reduce_any(lk), dtype=tf.float32) 420 | valid_part_num += valid 421 | IoU += valid * intsc / (union + esp) 422 | IoU /= valid_part_num + esp 423 | return IoU, valid_part_num 424 | 425 | 426 | class Optimizer: 427 | def __init__(self, stype='SGD', var_list=None, mul=1.0): 428 | self.stype = stype # TODO: support more optimizers 429 | self.mul = mul # used to modulate the global learning rate 430 | self.var_list = var_list 431 | 432 | def __call__(self, total_loss, learning_rate): 433 | with tf.name_scope('solver'): 434 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) 435 | with tf.control_dependencies(update_ops): 436 | global_step = tf.Variable(0, trainable=False, name='global_step') 437 | lr = learning_rate(global_step) * self.mul 438 | solver = tf.train.MomentumOptimizer(lr, 0.9) \ 439 | .minimize(total_loss, global_step=global_step, 440 | var_list=self.var_list) 441 | return solver, lr 442 | 443 | 444 | 445 | def octree2points(octree, depth, pts_channel=4, output_normal=False): 446 | with tf.name_scope('octree2points'): 447 | signal = octree_signal(octree, depth, 4) # normal and displacement 448 | signal = tf.transpose(tf.squeeze(signal, [0, 3])) # (1, C, H, 1) -> (H, C) 449 | xyz = octree_xyz(octree, depth) 450 | xyz = tf.cast(xyz, dtype=tf.float32) 451 | 452 | mask = octree_child(octree, depth) > -1 453 | signal = tf.boolean_mask(signal, mask) 454 | xyz = tf.boolean_mask(xyz, mask) 455 | 456 | c = 3.0 ** 0.5 / 2.0 457 | normal, dis = tf.split(signal, [3, 1], axis=1) 458 | pts, idx = tf.split(xyz, [3, 1], axis=1) 459 | pts = (pts + 0.5) + normal * (dis * c) 460 | if pts_channel == 4: 461 | pts = tf.concat([pts, idx], axis=1) 462 | output = pts if not output_normal else (pts, normal) 463 | return output 464 | 465 | -------------------------------------------------------------------------------- /Pretrained/Config_d6_1p_pretrained.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment":"=========================================", 3 | "_comment":" Parameters for point-based shape generation r=e", 4 | "_comment":"=========================================", 5 | "exp_name":"Pretrained/ShapeNet_bs32_g2_all_1p_d6_pretrained", 6 | "train_data":"USE_YOUR_DATA", 7 | "train_batch_size":32, 8 | "lr_decay_epochs":10, 9 | "learning_rate":0.001, 10 | "max_training_epochs_d6":200, 11 | "max_training_epochs_d7":200, 12 | "ckpt":"Pretrained/d6_1p_pretrained", 13 | "gpu":"0", 14 | "num_of_input_points":3000, 15 | "sdf_data_sources":6, 16 | "input_normal_signals":false, 17 | 18 | "_comment":"=========================================", 19 | "_comment":" Parameters for network architecture", 20 | "_comment":"=========================================", 21 | "octree_depth":7, 22 | "points_per_node":1, 23 | "weight_decay":0.00005, 24 | "decoder_octree_depth":6, 25 | 26 | "_comment":"=========================================", 27 | "_comment":" Parameters for loss design", 28 | "_comment":"=========================================", 29 | "sdf_loss_weight":200.0, 30 | "sdf_grad_loss_weight":0.05, 31 | "geo_reg":10.0, 32 | "repulsion_weight":0.05, 33 | "normal_norm_reg_weight":0.0001, 34 | "num_neighbors_to_search":10, 35 | "stop_regularization_weight_gradient":true, 36 | "sdf_samples_each_iter":20000, 37 | "octree_split_loss_weighting":0.1, 38 | "node_receptive_field":2.0, 39 | 40 | "_comment":"=========================================", 41 | "_comment":" Radius parameters", 42 | "_comment":"=========================================", 43 | "constant_radius":false, 44 | "radius_range":2.0, 45 | "patch_radius_smoothness":10.0 46 | } -------------------------------------------------------------------------------- /Pretrained/Config_d7_1p_pretrained.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment":"=========================================", 3 | "_comment":" Parameters for point-based shape generation r=e", 4 | "_comment":"=========================================", 5 | "exp_name":"Pretrained/ShapeNet_bs32_g2_all_1p_d7_pretrained", 6 | "train_data":"USE_YOUR_DATA", 7 | "train_batch_size":32, 8 | "lr_decay_epochs":10, 9 | "learning_rate":0.001, 10 | "max_training_epochs_d6":200, 11 | "max_training_epochs_d7":200, 12 | "ckpt":"Pretrained/d7_1p_pretrained", 13 | "gpu":"0", 14 | "num_of_input_points":3000, 15 | "sdf_data_sources":6, 16 | "input_normal_signals":false, 17 | 18 | "_comment":"=========================================", 19 | "_comment":" Parameters for network architecture", 20 | "_comment":"=========================================", 21 | "octree_depth":7, 22 | "points_per_node":1, 23 | "weight_decay":0.00005, 24 | "decoder_octree_depth":7, 25 | 26 | "_comment":"=========================================", 27 | "_comment":" Parameters for loss design", 28 | "_comment":"=========================================", 29 | "sdf_loss_weight":200.0, 30 | "sdf_grad_loss_weight":0.05, 31 | "geo_reg":10.0, 32 | "repulsion_weight":0.05, 33 | "normal_norm_reg_weight":0.0001, 34 | "num_neighbors_to_search":10, 35 | "stop_regularization_weight_gradient":true, 36 | "sdf_samples_each_iter":20000, 37 | "octree_split_loss_weighting":0.1, 38 | "node_receptive_field":2.0, 39 | 40 | "_comment":"=========================================", 41 | "_comment":" Radius parameters", 42 | "_comment":"=========================================", 43 | "constant_radius":false, 44 | "radius_range":2.0, 45 | "patch_radius_smoothness":10.0 46 | } -------------------------------------------------------------------------------- /Pretrained/d6_1p_pretrained.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy97/DeepMLS/551c51606a586f7ab3633621b9e4f57bb80c2345/Pretrained/d6_1p_pretrained.zip -------------------------------------------------------------------------------- /Pretrained/d7_1p_pretrained.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy97/DeepMLS/551c51606a586f7ab3633621b9e4f57bb80c2345/Pretrained/d7_1p_pretrained.zip -------------------------------------------------------------------------------- /Pretrained/download_models.py: -------------------------------------------------------------------------------- 1 | import wget 2 | import zipfile 3 | import os 4 | 5 | if __name__ == '__main__': 6 | print("Downloading Pretrained Models...") 7 | if(not os.path.exists("d7_1p_pretrained.zip")): 8 | print("Downloading d7_1p_pretrained model...") 9 | wget.download("http://home.ustc.edu.cn/~freelin/DeepMLS/Pretrained_Models/d7_1p_pretrained.zip", 'd7_1p_pretrained.zip') 10 | print("\n") 11 | if(not os.path.exists("d6_1p_pretrained.zip")): 12 | print("Downloading d6_1p_pretrained model...") 13 | wget.download("http://home.ustc.edu.cn/~freelin/DeepMLS/Pretrained_Models/d6_1p_pretrained.zip", 'd6_1p_pretrained.zip') 14 | print("\n") 15 | 16 | if(not os.path.exists("d7_1p_pretrained")): 17 | with zipfile.ZipFile("d7_1p_pretrained.zip", 'r') as zip_ref: 18 | zip_ref.extractall("") 19 | 20 | if(not os.path.exists("d6_1p_pretrained")): 21 | with zipfile.ZipFile("d6_1p_pretrained.zip", 'r') as zip_ref: 22 | zip_ref.extractall("") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepMLS: Deep Implicit Moving Least-Squares Functions for 3D Reconstruction 2 | 3 | This repository contains the implementation of the paper: 4 | 5 | **Deep Implicit Moving Least-Squares Functions for 3D Reconstruction** [\[arXiv\]](https://arxiv.org/abs/2103.12266) 6 | Shi-Lin Liu, [Hao-Xiang Guo](https://haoxiangguo.cn/), [Hao Pan](https://haopan.github.io/), [Pengshuai Wang](https://wang-ps.github.io/), [Xin Tong](http://www.xtong.info/), [Yang Liu](https://xueyuhanlang.github.io/). 7 | 8 |
9 | 10 |
11 | 12 | 13 | If you find our code or paper useful, please consider citing 14 | 15 | ```bibtex 16 | @inproceedings{Liu2021MLS, 17 | author = {Shi-Lin Liu, Hao-Xiang Guo, Hao Pan, Pengshuai Wang, Xin Tong, Yang Liu}, 18 | title = {Deep Implicit Moving Least-Squares Functions for 3D Reconstruction}, 19 | booktitle = {IEEE/CVF Conference on Computer Vision and Pattern Recognition}, 20 | year = {2021}} 21 | ``` 22 | 23 | ## Installation 24 | First you have to make sure that you have all dependencies in place. 25 | The simplest way to do so, is to use [anaconda](https://www.anaconda.com/). 26 | 27 | You can create an anaconda environment called `deep_mls` using 28 | ``` 29 | conda env create -f environment.yml 30 | conda activate deep_mls 31 | ``` 32 | Next, a few customized tensorflow modules should be installed: 33 | #### [O-CNN](https://github.com/microsoft/O-CNN) Module 34 | [O-CNN](https://github.com/microsoft/O-CNN) is an octree-based convolution module, please take the following steps to install: 35 | ``` 36 | cd Octree && git clone https://github.com/microsoft/O-CNN/ 37 | cd O-CNN/octree/external && git clone --recursive https://github.com/wang-ps/octree-ext.git 38 | cd .. && mkdir build && cd build 39 | cmake .. && cmake --build . --config Release 40 | export PATH=`pwd`:$PATH 41 | cd ../../tensorflow/libs && python build.py --cuda /usr/local/cuda-10.0 42 | cp libocnn.so ../../../ocnn-tf/libs 43 | ``` 44 | 45 | #### Efficient Neighbor Searching Ops 46 | Neighbor searching is intensively used in DeepMLS. For efficiency reasons, we provide several customized neighbor searching ops: 47 | ``` 48 | cd points3d-tf/points3d 49 | bash build.sh 50 | ``` 51 | In this step, some errors like this may occur: 52 | ``` 53 | tensorflow_core/include/tensorflow/core/util/gpu_kernel_helper.h:22:10: fatal error: third_party/gpus/cuda/include/cuda_fp16.h: No such file or directory 54 | #include "third_party/gpus/cuda/include/cuda_fp16.h" 55 | ``` 56 | For solving this, please refer to [issue](https://github.com/tensorflow/tensorflow/issues/31349). 57 | Basically, We need to edit the codes in tensorflow framework, please modify 58 | ``` C 59 | #include "third_party/gpus/cuda/include/cuda_fp16.h" 60 | ``` 61 | in "site-packages/tensorflow_core/include/tensorflow/core/util/gpu_kernel_helper.h" to 62 | ``` C 63 | #include "cuda_fp16.h" 64 | ``` 65 | and 66 | ``` C 67 | #include "third_party/gpus/cuda/include/cuComplex.h" 68 | #include "third_party/gpus/cuda/include/cuda.h" 69 | ``` 70 | in "site-packages/tensorflow_core/include/tensorflow/core/util/gpu_device_functions.h" to 71 | ``` C 72 | #include "cuComplex.h" 73 | #include "cuda.h" 74 | ``` 75 | 76 | #### Modified Marching Cubes Module 77 | We have modified the [PyMCubes](https://github.com/pmneila/PyMCubes) to get a more efficient marching cubes method for extract 0-isosurface defined by mls points. 78 | To install: 79 | ``` 80 | git clone https://github.com/Andy97/PyMCubes 81 | cd PyMCubes && python setup.py install 82 | ``` 83 | 84 | ## Datasets 85 | 86 | ### Preprocessed ShapeNet Dataset 87 | We have provided the processed tfrecords file. This can be used directly. 88 | 89 | Our training data is **available** now! (total 130G+) 90 | Please download all zip files for extraction. 91 | [ShapeNet_points_all_train.zip.001](https://drive.google.com/file/d/1tP2y_lx8QIbxP9emRejpwQSkZ1x-MW5S/view?usp=sharing) 92 | [ShapeNet_points_all_train.zip.002](https://drive.google.com/file/d/1NrgLarnDAgvZ0aipKGYXmgQHFK887-XM/view?usp=sharing) 93 | [ShapeNet_points_all_train.zip.003](https://drive.google.com/file/d/1D49XjrkoH7ftuWKdXAKef1kRGRNxsU3I/view?usp=sharing) 94 | After extraction, please modify the "train_data" field in experiment config json file with this tfrecords name. 95 | 96 | #### Update(20220228) 97 | Since the training data in google drive is down, we provide download links in baidu netdisk: 98 | https://pan.baidu.com/s/1V70-VphJcN1fZaBdYNUN0g 99 | 100 | Please feel free to email freelin AT mail.ustc.edu.cn for access code. 101 | 102 | 103 | ### Build the Dataset 104 | If you want to build the dataset from your own data, please follow: 105 | 106 | #### Step 1: Get Watertight Meshes 107 | To acquire a watertight mesh, we first preprocess each mesh follow the [preprocess steps](https://github.com/autonomousvision/occupancy_networks#Building-the-dataset) of [Occupancy Networks](https://github.com/autonomousvision/occupancy_networks). 108 | 109 | #### Step 2: Get the groundtruth sdf pair 110 | From step 1, we have already gotten the watertight version of each model. Then, we utilize [OpenVDB](https://www.openvdb.org/) library to get the sdf values and gradients for training. 111 | For details, please refer to [here](vdb_tsdf). 112 | 113 | #### Update (2021-09-30): The script for generating training tfrecords is released, for details please refer to [Readme.md](https://github.com/Andy97/DeepMLS/tree/master/scripts). 114 | 115 | ## Usage 116 | 117 | #### Inference using pre-trained model 118 | We have provided pretrained models which can be used to inference: 119 | ``` 120 | #first download the pretrained models 121 | cd Pretrained && python download_models.py 122 | #then we can use either of the pretrained model to do the inference 123 | cd .. && python DeepMLS_Generation.py Pretrained/Config_d7_1p_pretrained.json --test 124 | ``` 125 | 126 | The input for the inference is defined in [here](https://github.com/Andy97/DeepMLS/blob/master/DeepMLS_Generation.py#L533). 127 | Your can replace it with other point cloud files in [examples](https://github.com/Andy97/DeepMLS/tree/master/examples) or your own data. 128 | 129 | #### Extract Isosurface from MLS Points 130 | After inference, now we have network predicted mls points. The next step is to extract the surface: 131 | ``` 132 | python mls_marching_cubes.py --i examples/d0fa70e45dee680fa45b742ddc5add59.ply.xyz --o examples/d0fa70e45dee680fa45b742ddc5add59_mc.obj --scale 133 | ``` 134 | 135 | #### Training 136 | Our code supports single and multiple gpu training. For details, please refer to the config json file. 137 | ``` 138 | python DeepMLS_Generation.py examples/Config_g2_bs32_1p_d6.json 139 | ``` 140 | #### Evaluation 141 | For evaluation of results, [ConvONet](https://github.com/autonomousvision/convolutional_occupancy_networks) has provided a great script. Please refer to [here](https://github.com/autonomousvision/convolutional_occupancy_networks#evaluation). 142 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: deep_mls 2 | dependencies: 3 | - _libgcc_mutex=0.1=main 4 | - ca-certificates=2021.1.19=h06a4308_1 5 | - certifi=2020.12.5=py37h06a4308_0 6 | - ld_impl_linux-64=2.33.1=h53a641e_7 7 | - libffi=3.3=he6710b0_2 8 | - libgcc-ng=9.1.0=hdf63c60_0 9 | - libstdcxx-ng=9.1.0=hdf63c60_0 10 | - ncurses=6.2=he6710b0_1 11 | - openssl=1.1.1j=h27cfd23_0 12 | - pip=21.0.1=py37h06a4308_0 13 | - python=3.7.10=hdb3f193_0 14 | - readline=8.1=h27cfd23_0 15 | - setuptools=52.0.0=py37h06a4308_0 16 | - sqlite=3.35.1=hdfb4753_0 17 | - tk=8.6.10=hbc83047_0 18 | - wheel=0.36.2=pyhd3eb1b0_0 19 | - xz=5.2.5=h7b6447c_0 20 | - zlib=1.2.11=h7b6447c_3 21 | - pip: 22 | - absl-py==0.12.0 23 | - astor==0.8.1 24 | - cached-property==1.5.2 25 | - gast==0.2.2 26 | - google-pasta==0.2.0 27 | - grpcio==1.36.1 28 | - h5py==3.2.1 29 | - importlib-metadata==3.7.3 30 | - keras-applications==1.0.8 31 | - keras-preprocessing==1.1.2 32 | - markdown==3.3.4 33 | - numpy==1.17.2 34 | - opt-einsum==3.3.0 35 | - pandas==1.2.3 36 | - plydata==0.4.3 37 | - plyfile==0.7.3 38 | - protobuf==3.15.6 39 | - python-dateutil==2.8.1 40 | - pytz==2021.1 41 | - six==1.15.0 42 | - tensorboard==1.15.0 43 | - tensorflow-estimator==1.15.1 44 | - tensorflow-gpu==1.15.0 45 | - termcolor==1.1.0 46 | - tqdm==4.59.0 47 | - typing-extensions==3.7.4.3 48 | - werkzeug==1.0.1 49 | - wget==3.2 50 | - wrapt==1.12.1 51 | - zipp==3.4.1 52 | 53 | -------------------------------------------------------------------------------- /examples/Config_g2_bs32_1p_d6.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment":"=========================================", 3 | "_comment":" Parameters for point-based shape generation r=e", 4 | "_comment":"=========================================", 5 | "exp_name":"ShapeNet_local_bs32_g2_all_1p_d6", 6 | "train_data":"../PointShape/data/ShapeNet_points_all_train_w_octree_grid_occupancy_compress.tfrecords", 7 | "train_batch_size":32, 8 | "lr_decay_epochs":10, 9 | "learning_rate":0.001, 10 | "max_training_epochs_d6":200, 11 | "max_training_epochs_d7":200, 12 | "ckpt":"ShapeNet_local_bs32_g2_all_1p_d6/model", 13 | "gpu":"0,1", 14 | "num_of_input_points":3000, 15 | "sdf_data_sources":6, 16 | "input_normal_signals":false, 17 | 18 | "_comment":"=========================================", 19 | "_comment":" Parameters for network architecture", 20 | "_comment":"=========================================", 21 | "octree_depth":7, 22 | "points_per_node":1, 23 | "weight_decay":0.00005, 24 | "decoder_octree_depth":6, 25 | 26 | "_comment":"=========================================", 27 | "_comment":" Parameters for loss design", 28 | "_comment":"=========================================", 29 | "sdf_loss_weight":200.0, 30 | "sdf_grad_loss_weight":0.05, 31 | "geo_reg":10.0, 32 | "repulsion_weight":0.05, 33 | "normal_norm_reg_weight":0.0001, 34 | "num_neighbors_to_search":10, 35 | "sdf_samples_each_iter":20000, 36 | "octree_split_loss_weighting":0.1, 37 | "node_receptive_field":2.0, 38 | 39 | "_comment":"=========================================", 40 | "_comment":" Radius parameters", 41 | "_comment":"=========================================", 42 | "constant_radius":false, 43 | "radius_range":2.0, 44 | "patch_radius_smoothness":10.0 45 | } -------------------------------------------------------------------------------- /examples/fcfc935c2ff7c66c858699aaad4acee4.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy97/DeepMLS/551c51606a586f7ab3633621b9e4f57bb80c2345/examples/fcfc935c2ff7c66c858699aaad4acee4.ply -------------------------------------------------------------------------------- /media/teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andy97/DeepMLS/551c51606a586f7ab3633621b9e4f57bb80c2345/media/teaser.png -------------------------------------------------------------------------------- /mls_marching_cubes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | import tensorflow as tf 5 | from scipy import ndimage 6 | import mcubes 7 | import math 8 | import time 9 | 10 | sys.path.append("points3d-tf") 11 | 12 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' 13 | os.environ['CUDA_VISIBLE_DEVICES'] = '0' 14 | KNN_PREFERRED = 20 15 | print("KNN={}".format(KNN_PREFERRED)) 16 | assert(KNN_PREFERRED >= 2) 17 | print('====') 18 | sys.stdout.flush() 19 | 20 | struct2 = ndimage.generate_binary_structure(3, 3) 21 | 22 | import argparse 23 | parser = argparse.ArgumentParser(description='Marching Cube Input Output Arguments') 24 | parser.add_argument('--i', type=str, help='input grid sdf file or points file or directory') #if args.i is dir, we will process all points file under the folder 25 | parser.add_argument('--o', type=str, help='output marching cube file name') 26 | parser.add_argument('--res', type=int, default=7, help='grid resolution') 27 | parser.add_argument('--gpu', type=str, help='used gpu for program') 28 | parser.add_argument('--scale', action='store_true', help='scale mesh back') 29 | parser.add_argument('--overwrite', action='store_true', help='overwrite results') 30 | 31 | args = parser.parse_args() 32 | if(args.gpu is not None): 33 | os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu 34 | 35 | mls_points_ph = tf.placeholder(tf.float32) 36 | mls_radius_ph = tf.placeholder(tf.float32) 37 | query_points_ph = tf.placeholder(tf.float32) 38 | 39 | from points3d import get_neighbor_spatial_grid_radius_v_voting 40 | 41 | def rowwise_l2_norm_squared(feature): 42 | #assum input with size[n,f] out shape = [n,1] 43 | return tf.reduce_sum(tf.math.square(feature), axis=-1) 44 | 45 | def write_obj(obj_filename, vertices, triangles, output_max_component=True, scale=None): 46 | #normalize to -1,1 47 | resolution = 2**args.res 48 | print("mesh resolution in {}".format(resolution)) 49 | 50 | vertices /= (resolution / 2.0) 51 | vertices -= 1 52 | 53 | if(scale is not None): 54 | vertices *= scale 55 | 56 | with open(obj_filename,"w") as wf: 57 | for i in range(vertices.shape[0]): 58 | wf.write("v %lf %lf %lf\n" %(vertices[i][0], vertices[i][1], vertices[i][2])) 59 | for i in range(triangles.shape[0]): 60 | wf.write("f %d %d %d\n" %(triangles[i][0]+1, triangles[i][1]+1, triangles[i][2]+1)) 61 | 62 | def eval_sdf_from_mls(cur_projection, target_position, target_normal_normalized, src_points_num, target_points_num, \ 63 | per_point_squared_ball_radius, s2t_neighbor=None): 64 | #here we assert batch size=1 65 | if(s2t_neighbor == None): 66 | projection_points = tf.reshape(tf.transpose(tf.reshape(cur_projection, [1, -1, 3]), perm=[0,2,1]), [1, -1]) 67 | target_points = tf.concat([tf.reshape(target_position, [-1, 3]), tf.reshape(target_normal_normalized, [-1, 3])], axis=1) 68 | #fetch corresponding patch, now we get indices matrix [batch_size*n, KNN] 69 | s2t_neighbor = tf.reshape(get_neighbor_spatial_grid_radius_v_voting(projection_points, target_points, [target_points_num],\ 70 | tf.reshape(per_point_squared_ball_radius, [-1]), knn=KNN_PREFERRED), [-1,KNN_PREFERRED]) 71 | 72 | invalid_index_mask = tf.cast(tf.less(s2t_neighbor, 0), dtype=s2t_neighbor.dtype) 73 | #make -1 to 0 74 | s2t_neighbor += invalid_index_mask 75 | s2t_neighbor = tf.expand_dims(s2t_neighbor, axis=-1) 76 | 77 | #get per vertex patch vertices position & normal [batch_size*n, KNN, 3]+[batch_size*n, KNN, 3] 78 | s2t_patch_pos = tf.gather_nd(target_position, s2t_neighbor) 79 | s2t_patch_normal = tf.gather_nd(target_normal_normalized, s2t_neighbor) 80 | s2t_patch_squared_radius = tf.gather_nd(tf.reshape(per_point_squared_ball_radius, [-1]), s2t_neighbor) 81 | #compute mls weights 82 | s2t_patch_pos_diff = tf.tile(tf.reshape(cur_projection, [-1,1,3]),multiples=[1,KNN_PREFERRED,1]) - s2t_patch_pos 83 | s2t_patch_distance = rowwise_l2_norm_squared(s2t_patch_pos_diff) 84 | 85 | valid_neighbor_mask = tf.cast(1 - invalid_index_mask, dtype=s2t_patch_distance.dtype) 86 | #mls weight is stored in matrix with shape=[batch_size*n, KNN] 87 | s2t_mls_weight = -s2t_patch_distance / s2t_patch_squared_radius 88 | s2t_mls_weight -= tf.tile(tf.expand_dims(s2t_mls_weight[:,0], axis=-1), multiples=[1, KNN_PREFERRED]) 89 | s2t_mls_weight = tf.math.exp(s2t_mls_weight) 90 | 91 | #mask the patch verts out of ball 92 | s2t_mls_weight = s2t_mls_weight*valid_neighbor_mask 93 | #select projection points which are far away from surface 94 | s2t_mls_weight += tf.pad(tf.expand_dims(1 - valid_neighbor_mask[:,0], axis=-1), [[0,0],[0,KNN_PREFERRED-1]]) 95 | #mls weights should also be normalized 96 | s2t_mls_weight = s2t_mls_weight / tf.tile(tf.reshape(tf.reduce_sum(s2t_mls_weight,axis=-1),[-1,1]),multiples=[1,KNN_PREFERRED]) 97 | s2t_sdf = tf.reduce_sum(s2t_mls_weight*tf.reduce_sum(s2t_patch_pos_diff*s2t_patch_normal, axis=-1), axis=-1) 98 | return s2t_sdf 99 | 100 | def get_sdf(mls_points, mls_radius, query_points): 101 | #assume mls_points in shape [n,6] and query_points in shape[q,3] 102 | n_mls_points = tf.shape(mls_points)[0] 103 | n_query_points = query_points.shape[0] 104 | mls_points = tf.cast(mls_points, tf.float64) 105 | query_points = tf.cast(query_points, tf.float64) 106 | assert(mls_radius is not None) 107 | mls_radius = tf.cast(mls_radius, tf.float64) 108 | return eval_sdf_from_mls(query_points, mls_points[:,:3], mls_points[:,3:], \ 109 | n_query_points, n_mls_points, mls_radius) 110 | 111 | class MLS_Surface: 112 | def __init__(self, mls_points, mls_radius, sess=None, sdf_node=None, scale=None): 113 | self.mls_points = mls_points 114 | self.mls_radius = mls_radius 115 | if(sess is None): 116 | config = tf.ConfigProto() 117 | config.gpu_options.allow_growth = True 118 | self.sess = tf.Session(config=config) 119 | else: 120 | self.sess = sess 121 | 122 | self.sdf_node = sdf_node 123 | self.scale = scale 124 | 125 | def marching_cubes(self, output_obj_filename, voxel_resolution): 126 | voxel_dim = 2**voxel_resolution 127 | voxel_length = 2.0 / voxel_dim 128 | voxel_dim += 1 129 | 130 | grids = np.ones([voxel_dim, voxel_dim, voxel_dim])*1000000 #which means initial values are all not available (will be considered in our implemented marching_cubes_partial) 131 | assert(self.mls_radius is not None) 132 | 133 | mls_points_id = np.round((self.mls_points[:,:3] + 1) / voxel_length) 134 | 135 | np.clip(mls_points_id, 0, voxel_dim - 1, out = mls_points_id) 136 | mls_points_id = mls_points_id.astype(int) 137 | active_grids = np.zeros([voxel_dim, voxel_dim, voxel_dim]) 138 | active_grids[mls_points_id[:,0], mls_points_id[:, 1], mls_points_id[:, 2]] = 1.0; 139 | 140 | #might use larger dilation_layer_num 141 | max_radius = np.sqrt(np.max(self.mls_radius)) * 2 142 | dilation_layer_num = (int)(round(max_radius / voxel_length)) + 1 143 | print ("dilation layer number: ", dilation_layer_num) 144 | active_grids_large = ndimage.binary_dilation(active_grids, structure=struct2, iterations = dilation_layer_num) 145 | 146 | nonzeros = np.nonzero(active_grids_large) 147 | evaluated_points = np.stack(nonzeros, axis=1)*voxel_length - 1 148 | print("active number: ", nonzeros[0].shape) 149 | 150 | max_num_per_iter = 200000 151 | num_iter = math.ceil(nonzeros[0].shape[0] / max_num_per_iter) 152 | for i in range(0, num_iter): 153 | startid = i * max_num_per_iter 154 | endid = (i + 1) * max_num_per_iter 155 | if i == num_iter-1: 156 | endid = nonzeros[0].shape[0] 157 | if(self.sdf_node == None): 158 | sdf = self.sess.run(get_sdf(self.mls_points, self.mls_radius, evaluated_points[startid:endid])) 159 | else: 160 | sdf = self.sess.run(self.sdf_node, feed_dict={mls_points_ph:self.mls_points, mls_radius_ph:self.mls_radius, query_points_ph:evaluated_points[startid:endid]}) 161 | grids[nonzeros[0][startid:endid], nonzeros[1][startid:endid], nonzeros[2][startid:endid]] = sdf 162 | 163 | print('outputfilename: ', output_obj_filename) 164 | vertices, triangles = mcubes.marching_cubes_partial(-grids, 0.0) 165 | write_obj(output_obj_filename, vertices, triangles, scale=self.scale) 166 | 167 | def get_mls_points_radius_from_file(points_filename): 168 | mls_points = np.loadtxt(points_filename) 169 | if(mls_points.shape[1] != 6): 170 | #the xyz file does not contain normals: indicate this is not a mls file 171 | if(args.scale): 172 | return mls_points, None, None 173 | else: 174 | return mls_points, None 175 | if(os.path.exists(points_filename.replace(".xyz", "_radius.txt"))): 176 | mls_radius = np.loadtxt(points_filename.replace(".xyz", "_radius.txt")) 177 | else: 178 | print("radius of mls points not found") 179 | raise NotImplementedError 180 | mls_radius = None 181 | 182 | if(mls_radius.shape == ()): 183 | mls_radius = mls_radius*np.ones([mls_points.shape[0]]) 184 | assert(mls_points.shape[0] == mls_radius.shape[0]) 185 | 186 | if(args.scale): 187 | scale_file = points_filename.replace(".xyz", "_scale.txt") 188 | assert(os.path.exists(scale_file)) 189 | scale = 1.0 / np.loadtxt(scale_file) 190 | return mls_points, mls_radius, scale 191 | 192 | #normal of mls points should be normalized 193 | mls_postion = mls_points[:,:3] 194 | mls_normal = mls_points[:,3:] / np.linalg.norm(mls_points[:,3:], axis=1, keepdims=True) 195 | mls_points = np.concatenate([mls_postion, mls_normal], axis=1) 196 | 197 | return mls_points, mls_radius 198 | 199 | def process_folder(sess): 200 | #build graph in advance 201 | sdf_node = get_sdf(mls_points_ph, mls_radius_ph, query_points_ph) 202 | list = os.listdir(args.i) 203 | for file in list: 204 | if(file.endswith(".xyz")): 205 | points_filename = os.path.join(args.i, file) 206 | output_file_name = points_filename.replace(".xyz", "_mc.obj") 207 | if(not args.overwrite and os.path.exists(output_file_name)): 208 | continue 209 | if(args.scale): 210 | mls_points, mls_radius, scale = get_mls_points_radius_from_file(points_filename) 211 | else: 212 | mls_points, mls_radius = get_mls_points_radius_from_file(points_filename) 213 | scale = None 214 | if(mls_points.shape[1] != 6): 215 | continue 216 | mls_object = MLS_Surface(mls_points, mls_radius, sess=sess, sdf_node=sdf_node, scale=scale) 217 | 218 | if(args.overwrite or not os.path.exists(output_file_name)): 219 | mls_object.marching_cubes(output_file_name, args.res) 220 | 221 | def main(): 222 | config = tf.ConfigProto() 223 | config.gpu_options.allow_growth = True 224 | sess = tf.Session(config=config) 225 | 226 | start = time.time() 227 | if(args.i is not None and os.path.isdir(args.i)): 228 | #marching cube for all files under directory 229 | return process_folder(sess) 230 | 231 | #process single input file 232 | assert(args.i is not None and args.i.endswith(".xyz")) 233 | points_filename = args.i 234 | 235 | if(args.scale): 236 | mls_points, mls_radius, scale = get_mls_points_radius_from_file(points_filename) 237 | else: 238 | mls_points, mls_radius = get_mls_points_radius_from_file(points_filename) 239 | scale = None 240 | assert(mls_points.shape[1] == 6) 241 | mls_object = MLS_Surface(mls_points, mls_radius, sess=sess, scale=scale) 242 | 243 | #marching cube from implicit distance fields defined by mls control points 244 | mls_object.marching_cubes("marching_cube_128.obj" if args.o is None else args.o, args.res) 245 | 246 | end = time.time() 247 | print("{}s elapsed".format(end - start)) 248 | 249 | if __name__ == '__main__': 250 | print('Program running...') 251 | main() -------------------------------------------------------------------------------- /network_architecture.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import sys 3 | sys.path.append("Octree") 4 | from ocnn import * 5 | 6 | def preprocess_octree_data_from_unoriented_points(data, octree, depth): 7 | #subtract octree node mean position 8 | #print("=======================\ninput octree feature with shape:{}\n==========================".format(data.shape)) 9 | data = tf.reshape(data, [3, -1]) 10 | octree_node_xyz = tf.cast(octree_xyz(octree, depth), tf.int32) #we get nx4 11 | octree_node_center = tf.cast(octree_node_xyz[:,:3], dtype=tf.float32)*(0.5**(depth-1)) - 1 + (0.5**depth) 12 | #normalize to [-1, 1] 13 | data = (data - tf.transpose(octree_node_center))*(2**depth) 14 | 15 | #concat node occupancy indicator 16 | data = tf.concat([data, tf.ones([1, tf.shape(data)[1]], dtype=data.dtype)], axis=0) 17 | print("=======================\ninput octree feature with shape:{}\n==========================".format(data.shape)) 18 | 19 | #mask empty nodes to zero 20 | input_node_mask = tf.reshape(tf.greater(octree_property(octree, property_name="split", dtype=tf.float32, depth=depth, channel=1), 0), [1, -1]) 21 | input_node_mask = tf.cast(tf.tile(input_node_mask, multiples = [4,1]), data.dtype) 22 | data = tf.stop_gradient(data*input_node_mask) 23 | 24 | return tf.reshape(data, [1, 4, -1, 1]) 25 | 26 | def octree_network_unet(octree, input_octree_depth, output_octree_depth, octree_feature_channel, points_per_node, training, reuse=False, node_receptive_field=1.0, predict_radius=False, radius_range=2.0, gt_octree=None): 27 | #if gt_octree is None, this is ShapeAE Architecture else Shape Completion 28 | channels = [4, 64, 128, 128, 128, 64, 32, 16, 8] 29 | resblock_num = 3 30 | depth = input_octree_depth 31 | with tf.variable_scope('ocnn_encoder', reuse=reuse): 32 | with tf.variable_scope('signal_gt'): 33 | data = octree_property(octree, property_name="feature", dtype=tf.float32, 34 | depth=depth, channel=octree_feature_channel) 35 | if(octree_feature_channel == 3): 36 | #which means input signal does not include normal 37 | data = preprocess_octree_data_from_unoriented_points(data, octree, depth) 38 | else: 39 | data = tf.reshape(data, [1, octree_feature_channel, -1, 1]) 40 | 41 | with tf.variable_scope("front"): 42 | data = octree_conv_bn_relu(data, octree, depth, channels[depth], training) 43 | 44 | convd = [None]*10 45 | for d in range(depth, 1, -1): 46 | for i in range(0, resblock_num): 47 | with tf.variable_scope('resblock_%d_%d' % (d, i)): 48 | data = octree_resblock(data, octree, d, channels[d], 1, training) 49 | convd[d] = data #for skip connections 50 | if(d != 2): 51 | with tf.variable_scope('down_%d' % d): 52 | data = octree_conv_bn_relu(data, octree, d, channels[d-1], training, 53 | stride=2, kernel_size=[2]) 54 | 55 | code = data 56 | 57 | #decoder 58 | depth = output_octree_depth 59 | with tf.variable_scope('ocnn_decoder', reuse=reuse): 60 | data = code 61 | loss, accu = [], [] 62 | for d in range(2, depth + 1): 63 | for i in range(0, resblock_num): 64 | with tf.variable_scope('resblock_%d_%d' % (d, i)): 65 | data = octree_resblock(data, octree if gt_octree is None else gt_octree, d, channels[d], 1, training) 66 | 67 | with tf.variable_scope('predict_%d' % d): 68 | logit, label = predict_label(data, 2, 32, training) 69 | logit = tf.transpose(tf.squeeze(logit, [0,3])) # (1, C, H, 1) -> (H, C) 70 | 71 | with tf.variable_scope('octree_loss_%d' % d): 72 | with tf.variable_scope('label_gt'): 73 | label_gt = octree_property(octree if gt_octree is None else gt_octree, property_name="split", 74 | dtype=tf.float32, depth=d, channel=1) 75 | label_gt = tf.reshape(tf.cast(label_gt, dtype=tf.int32), [-1]) 76 | loss.append(softmax_loss(logit, label_gt, num_class=2)) 77 | accu.append(label_accuracy(label, label_gt)) 78 | 79 | signals_per_point = 6 80 | if(predict_radius): 81 | signals_per_point += 1 82 | 83 | if d == depth: 84 | with tf.variable_scope('regress_%d' % d): 85 | mls_points_local = predict_signal(data, points_per_node*signals_per_point, 128, training) #axis-angle 86 | #from signal to mls points, merge points 87 | mls_points_local = tf.reshape(tf.transpose(tf.squeeze(mls_points_local, [0,3])), [-1, signals_per_point]) # (1, C, H, 1) -> (H, C) 88 | position = tf.nn.tanh(mls_points_local[:,:3])*(0.5**depth)*node_receptive_field 89 | if(predict_radius): 90 | normal = mls_points_local[:,3:6] 91 | radius = tf.expand_dims(tf.math.pow(radius_range, tf.nn.tanh(mls_points_local[:,6])), axis=-1) 92 | normal = tf.concat([normal, radius], axis=-1) 93 | else: 94 | normal = mls_points_local[:,3:] 95 | 96 | octree_node_xyz = tf.cast(octree_xyz(octree if gt_octree is None else gt_octree, depth), tf.int32) #we get nx4 97 | octree_node_center = tf.cast(octree_node_xyz[:,:3], dtype=position.dtype)*(0.5**(depth-1)) - 1 + (0.5**depth) 98 | position += tf.reshape(tf.tile(tf.expand_dims(octree_node_center, axis=1), multiples = [1, points_per_node, 1]), [-1,3]) 99 | 100 | mls_points = tf.concat([position, normal], axis=-1) 101 | #points_nums = tf.segment_sum(tf.ones_like(point_segment, dtype=tf.int32), point_segment) #tf.cumsum() 102 | 103 | #mask empty octree node 104 | if(True): 105 | #using groundtruth supervision in last depth 106 | node_mask = tf.greater(label_gt, 0) 107 | else: 108 | print("with real split in unet decoder output") 109 | #using the real split supervision 110 | node_mask = tf.greater(label, 0) 111 | octree_node_xyz_valid = tf.boolean_mask(octree_node_xyz, node_mask) 112 | point_segment = octree_node_xyz_valid[:,3] 113 | 114 | #point_segment = tf.boolean_mask(octree_node_xyz[:,3], node_mask) 115 | point_segment = tf.cast(tf.reshape(tf.tile(tf.expand_dims(point_segment, axis=1), multiples=[1, points_per_node]), [-1]), tf.int32) 116 | points_mask = tf.reshape(tf.tile(tf.expand_dims(node_mask, axis=-1), multiples=[1, points_per_node]), [-1]) 117 | mls_points = tf.boolean_mask(mls_points, points_mask) 118 | 119 | if d < depth: 120 | with tf.variable_scope('up_%d' % d): 121 | data = octree_deconv_bn_relu(data, octree if gt_octree is None else gt_octree, d, channels[d-1], training, 122 | stride=2, kernel_size=[2]) 123 | #skip connections 124 | if(gt_octree is None): 125 | data = tf.concat([data, convd[d+1]], axis=1) 126 | else: 127 | skip, _ = octree_align(convd[d+1], octree, gt_octree, d+1) 128 | data = tf.concat([data, skip], axis=1) 129 | if(gt_octree is None): 130 | return mls_points, point_segment, octree_node_xyz_valid 131 | else: 132 | return loss, accu, mls_points, point_segment, octree_node_xyz_valid 133 | 134 | def octree_network_unet_completion_decode_shape(octree, input_octree_depth, output_octree_depth, octree_feature_channel, points_per_node, shape_batch_size, training, reuse=False, node_receptive_field=1.0, predict_radius=False, radius_range=2.0): 135 | assert(not training) 136 | #Shape Completion decode shape at test phase 137 | channels = [4, 64, 128, 128, 128, 64, 32, 16, 8] 138 | resblock_num = 3 139 | depth = input_octree_depth 140 | with tf.variable_scope('ocnn_encoder', reuse=reuse): 141 | with tf.variable_scope('signal_gt'): 142 | data = octree_property(octree, property_name="feature", dtype=tf.float32, 143 | depth=depth, channel=octree_feature_channel) 144 | if(octree_feature_channel == 3): 145 | #which means input signal does not include normal 146 | data = preprocess_octree_data_from_unoriented_points(data, octree, depth) 147 | else: 148 | data = tf.reshape(data, [1, octree_feature_channel, -1, 1]) 149 | 150 | with tf.variable_scope("front"): 151 | data = octree_conv_bn_relu(data, octree, depth, channels[depth], training) 152 | 153 | convd = [None]*10 154 | input_split_label = [None]*10 155 | 156 | for d in range(depth, 1, -1): 157 | input_split_label[d] = octree_property(octree, property_name="split", dtype=tf.float32, depth=d, channel=1) 158 | print("input split label with shape in depth {}: {}".format(d, input_split_label[d].shape)) 159 | for i in range(0, resblock_num): 160 | with tf.variable_scope('resblock_%d_%d' % (d, i)): 161 | data = octree_resblock(data, octree, d, channels[d], 1, training) 162 | convd[d] = data #for skip connections 163 | if(d != 2): 164 | with tf.variable_scope('down_%d' % d): 165 | data = octree_conv_bn_relu(data, octree, d, channels[d-1], training, 166 | stride=2, kernel_size=[2]) 167 | 168 | code = data 169 | 170 | #decoder 171 | depth = output_octree_depth 172 | with tf.variable_scope('ocnn_decoder', reuse=reuse): 173 | # init the octree 174 | with tf.variable_scope('octree_0'): 175 | #dis = False if flags.channel < 4 else True 176 | octree_prediction = octree_new(batch_size=shape_batch_size, channel=4, has_displace=False) 177 | with tf.variable_scope('octree_1'): 178 | octree_prediction = octree_grow(octree_prediction, target_depth=1, full_octree=True) 179 | with tf.variable_scope('octree_2'): 180 | octree_prediction = octree_grow(octree_prediction, target_depth=2, full_octree=True) 181 | 182 | data = code 183 | for d in range(2, depth + 1): 184 | for i in range(0, resblock_num): 185 | with tf.variable_scope('resblock_%d_%d' % (d, i)): 186 | data = octree_resblock(data, octree_prediction, d, channels[d], 1, training) 187 | 188 | with tf.variable_scope('predict_%d' % d): 189 | logit, label = predict_label(data, 2, 32, training) 190 | logit = tf.transpose(tf.squeeze(logit, [0,3])) # (1, C, H, 1) -> (H, C) 191 | 192 | #utilize input octree info 193 | #skip_label, _ = octree_align(tf.reshape(input_split_label[d], [1,1,-1,1]), octree, octree_prediction, d) 194 | #skip_label = tf.reshape(skip_label, [-1]) 195 | #print("label shape {}".format(label.shape)) 196 | #label = tf.cast(tf.greater(tf.cast(label, tf.float32)+skip_label, 0), tf.int32) 197 | 198 | with tf.variable_scope('octree_%d' % d): 199 | octree_prediction = octree_update(octree_prediction, label, depth=d, mask=1) 200 | 201 | if d == depth: 202 | signals_per_point = 6 203 | if(predict_radius): 204 | signals_per_point += 1 205 | with tf.variable_scope('regress_%d' % d): 206 | mls_points_local = predict_signal(data, points_per_node*signals_per_point, 128, training) #axis-angle 207 | #from signal to mls points, merge points 208 | mls_points_local = tf.reshape(tf.transpose(tf.squeeze(mls_points_local, [0,3])), [-1, signals_per_point]) # (1, C, H, 1) -> (H, C) 209 | position = tf.nn.tanh(mls_points_local[:,:3])*(0.5**depth)*node_receptive_field 210 | if(predict_radius): 211 | normal = mls_points_local[:,3:6] 212 | radius = tf.expand_dims(tf.math.pow(radius_range, tf.nn.tanh(mls_points_local[:,6])), axis=-1) 213 | normal = tf.concat([normal, radius], axis=-1) 214 | else: 215 | normal = mls_points_local[:,3:] 216 | 217 | octree_node_xyz = tf.cast(octree_xyz(octree_prediction, depth), tf.int32) #we get nx4 218 | octree_node_center = tf.cast(octree_node_xyz[:,:3], dtype=position.dtype)*(0.5**(depth-1)) - 1 + (0.5**depth) 219 | position += tf.reshape(tf.tile(tf.expand_dims(octree_node_center, axis=1), multiples = [1, points_per_node, 1]), [-1,3]) 220 | 221 | mls_points = tf.concat([position, normal], axis=-1) 222 | #points_nums = tf.segment_sum(tf.ones_like(point_segment, dtype=tf.int32), point_segment) #tf.cumsum() 223 | 224 | #mask empty octree node 225 | node_mask = tf.greater(label, 0) 226 | octree_node_xyz_valid = tf.boolean_mask(octree_node_xyz, node_mask) 227 | point_segment = octree_node_xyz_valid[:,3] 228 | 229 | #point_segment = tf.boolean_mask(octree_node_xyz[:,3], node_mask) 230 | point_segment = tf.cast(tf.reshape(tf.tile(tf.expand_dims(point_segment, axis=1), multiples=[1, points_per_node]), [-1]), tf.int32) 231 | points_mask = tf.reshape(tf.tile(tf.expand_dims(node_mask, axis=-1), multiples=[1, points_per_node]), [-1]) 232 | mls_points = tf.boolean_mask(mls_points, points_mask) 233 | 234 | if d < depth: 235 | with tf.variable_scope('octree_%d' % (d+1)): 236 | octree_prediction = octree_grow(octree_prediction, target_depth=d+1, full_octree=False) 237 | with tf.variable_scope('up_%d' % d): 238 | data = octree_deconv_bn_relu(data, octree_prediction, d, channels[d-1], training, 239 | stride=2, kernel_size=[2]) 240 | #skip connections 241 | skip, _ = octree_align(convd[d+1], octree, octree_prediction, d+1) 242 | data = tf.concat([data, skip], axis=1) 243 | 244 | return mls_points, point_segment, octree_node_xyz_valid 245 | -------------------------------------------------------------------------------- /points3d-tf/points3d/__init__.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import os 3 | from tensorflow.python.framework import ops 4 | 5 | _current_path = os.path.dirname(os.path.realpath(__file__)) 6 | _tf_points_module = tf.load_op_library(os.path.join(_current_path, 'libpoints3d.so')) 7 | 8 | get_neighbor_spatial_grid_local_self = _tf_points_module.get_neighbor_spatial_grid_local_self 9 | get_neighbor_spatial_grid_radius_v_voting = _tf_points_module.get_neighbor_spatial_grid_radius_v_voting 10 | 11 | ops.NotDifferentiable("GetNeighborSpatialGridRadiusVVoting") 12 | ops.NotDifferentiable("GetNeighborSpatialGridLocalSelf") -------------------------------------------------------------------------------- /points3d-tf/points3d/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | TF_CFLAGS=$(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_compile_flags()))') 3 | TF_LFLAGS=$(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_link_flags()))') 4 | 5 | nvcc -std=c++11 -c get_neighbor_points_spatial_grid_local_self.cu.cc $TF_CFLAGS -I /usr/local/cuda-10.0/include -x cu -Xcompiler -fPIC -D GOOGLE_CUDA=1 -I /usr/local -expt-relaxed-constexpr -DNDEBUG --expt-relaxed-constexpr --ptxas-options=--verbose -maxrregcount=55 6 | 7 | nvcc -std=c++11 -c get_neighbor_points_spatial_grid_radius_weighting_voting.cu.cc $TF_CFLAGS -I /usr/local/cuda-10.0/include -x cu -Xcompiler -fPIC -D GOOGLE_CUDA=1 -I /usr/local -expt-relaxed-constexpr -DNDEBUG --expt-relaxed-constexpr --ptxas-options=--verbose -maxrregcount=55 8 | 9 | g++ -std=c++11 -shared -o libpoints3d.so get_neighbor_points_spatial_grid_local_self.cc get_neighbor_points_spatial_grid_radius_weighting_voting.cc get_neighbor_points_spatial_grid_local_self.cu.o get_neighbor_points_spatial_grid_radius_weighting_voting.cu.o $TF_CFLAGS -I /usr/local/cuda-10.0/include -L /usr/local/cuda-10.0/lib64 -D GOOGLE_CUDA=1 $TF_LFLAGS -fPIC -lcudart 10 | 11 | rm *.o -------------------------------------------------------------------------------- /points3d-tf/points3d/get_neighbor_points_spatial_grid_local_self.cc: -------------------------------------------------------------------------------- 1 | #include "tensorflow/core/framework/op.h" 2 | #include "tensorflow/core/framework/op_kernel.h" 3 | #include "tensorflow/core/framework/shape_inference.h" 4 | #include "tensorflow/core/framework/common_shape_fns.h" 5 | 6 | namespace tensorflow { 7 | 8 | template 9 | void neighbor_points_spatial_grid_local_self(OpKernelContext* context, 10 | const int* n_source_points, const int batch_size, const T* in_source_points, 11 | const int knn, const int batch_points_total, int* neighbor_points_id_ptr); 12 | 13 | REGISTER_OP("GetNeighborSpatialGridLocalSelf") 14 | .Attr("T: {float, double}") 15 | .Input("source_points: T") 16 | .Input("source_points_num: int32") 17 | .Attr("knn: int = 10") 18 | .Output("neighbor_points: int32") 19 | .SetShapeFn([](shape_inference::InferenceContext* c) { 20 | c->set_output(0, c->MakeShape({c->Dim(c->input(0),0), c->UnknownDim()})); 21 | return Status::OK(); 22 | }) 23 | .Doc(R"doc( 24 | Find the neighbor points for each points using spatial partition and efficient insertion sort. 25 | )doc"); 26 | 27 | template 28 | class GetNeighborSpatialGridLocalSelfOp : public OpKernel { 29 | public: 30 | explicit GetNeighborSpatialGridLocalSelfOp(OpKernelConstruction* context) 31 | : OpKernel(context) 32 | { 33 | OP_REQUIRES_OK(context, context->GetAttr("knn", 34 | &this->knn_)); 35 | } 36 | 37 | void Compute(OpKernelContext* context) override { 38 | // in source points [\sum(n_source_points), 6] 39 | const Tensor& in_source_points = context->input(0); 40 | auto in_source_points_ptr = in_source_points.flat().data(); 41 | batch_points_total = in_source_points.dim_size(0); 42 | 43 | const Tensor& in_source_num = context->input(1); 44 | auto in_source_num_ptr = in_source_num.flat().data(); 45 | batch_size_ = in_source_num.dim_size(0); 46 | 47 | // out loss 48 | Tensor* out_vec = nullptr; 49 | TensorShape out_shape({batch_points_total, knn_}); 50 | OP_REQUIRES_OK(context, context->allocate_output("neighbor_points", out_shape, &out_vec)); 51 | auto out_ptr = out_vec->flat().data(); 52 | 53 | // find neighbor point 54 | neighbor_points_spatial_grid_local_self(context, in_source_num_ptr, batch_size_, in_source_points_ptr, knn_, batch_points_total, out_ptr); 55 | } 56 | 57 | private: 58 | int batch_points_total; 59 | int batch_size_; 60 | int knn_; 61 | }; 62 | 63 | REGISTER_KERNEL_BUILDER(Name("GetNeighborSpatialGridLocalSelf").Device(DEVICE_GPU).TypeConstraint("T"), 64 | GetNeighborSpatialGridLocalSelfOp); 65 | 66 | REGISTER_KERNEL_BUILDER(Name("GetNeighborSpatialGridLocalSelf").Device(DEVICE_GPU).TypeConstraint("T"), 67 | GetNeighborSpatialGridLocalSelfOp); 68 | } // namespace tensorflow 69 | -------------------------------------------------------------------------------- /points3d-tf/points3d/get_neighbor_points_spatial_grid_local_self.cu.cc: -------------------------------------------------------------------------------- 1 | #define EIGEN_USE_THREADS 2 | #define EIGEN_USE_GPU 3 | 4 | #include "tensorflow/core/util/gpu_kernel_helper.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // Macro definition 16 | #define CudaAssert( X ) if ( !(X) ) {printf( "Thread %d:%d failed assert at %s:%d!\n", blockIdx.x, threadIdx.x, __FILE__, __LINE__ ); return;} 17 | 18 | namespace tensorflow { 19 | 20 | typedef Eigen::GpuDevice GPUDevice; 21 | 22 | template 23 | static __global__ void find_knn_grids_local_self( 24 | const int nthreads, const int* n_source_points, const T* in_source_points, const int batch_size, const int knn, const int CUBES_TOTAL, const int MAX_POINTS_IN_CUBE, 25 | const int *cube_points_num_ptr, const int *cube_points_indices_ptr, int *neighbor_points_id_ptr, T *neighbor_points_dist_ptr) { 26 | // 0 1 2 3 4 5 6 7 10 14 18 19 21 23 25 27 | __shared__ int disp_x[27];// = {0, -1, 0, 0, 0, 0, 1, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1}; 28 | __shared__ int disp_y[27];// = {0, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, -1, -1, 1, 1, 0, 0, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1}; 29 | __shared__ int disp_z[27];// = {0, 0, 0, -1, 1, 0, 0, 0, -1, 1, 0, -1, 1, -1, 1, -1, 1, 0, 0, -1, 1, -1, 1, -1, 1, -1, 1}; 30 | 31 | //fill the shared memory 32 | if(threadIdx.x < 27) 33 | { 34 | int i = threadIdx.x; 35 | 36 | if(i < 9) 37 | disp_x[i] = 0; 38 | else if (i < 18) 39 | disp_x[i] = -1; 40 | else 41 | disp_x[i] = 1; 42 | 43 | if ((i/3) % 3 == 0) 44 | disp_y[i] = 0; 45 | else if ((i/3) % 3 == 1) 46 | disp_y[i] = -1; 47 | else 48 | disp_y[i] = 1; 49 | 50 | if(i % 3 == 0) 51 | disp_z[i] = 0; 52 | else if(i % 3 == 1) 53 | disp_z[i] = -1; 54 | else 55 | disp_z[i] = 1; 56 | } 57 | __syncthreads(); 58 | 59 | int batch_index = 0; 60 | 61 | CUDA_1D_KERNEL_LOOP(index, nthreads) { 62 | 63 | for(int ii = batch_index; ii < batch_size; ii++) 64 | if(index < n_source_points[ii]) 65 | { 66 | batch_index = ii; 67 | break; 68 | } 69 | 70 | int* cur_neighbor_points_id_ptr = neighbor_points_id_ptr + index*knn; 71 | T *cur_neighbor_points_dist_ptr = neighbor_points_dist_ptr + index*knn; 72 | 73 | for(int ii = 0; ii < knn; ii++) 74 | cur_neighbor_points_id_ptr[ii] = -1; 75 | 76 | T sx = in_source_points[6 * index]; 77 | T sy = in_source_points[6 * index + 1]; 78 | T sz = in_source_points[6 * index + 2]; 79 | 80 | //find current cube and nearby cubes 81 | int scx = int((sx + 1) * 5); 82 | int scy = int((sy + 1) * 5); 83 | int scz = int((sz + 1) * 5); 84 | 85 | if(scx < 0) 86 | scx = 0; 87 | else if(scx > 9) 88 | scx = 9; 89 | 90 | if(scy < 0) 91 | scy = 0; 92 | else if (scy > 9) 93 | scy = 9; 94 | 95 | if(scz < 0) 96 | scz = 0; 97 | else if(scz > 9) 98 | scz = 9; 99 | 100 | CudaAssert(scx>=0 && scy>=0 && scz>=0 && scx<10 && scy<10 && scz<10); 101 | 102 | int k = 0; 103 | //T tnx,tny,tnz,tn_norm; 104 | T dx,dy,dz,cur_distance; 105 | //T dot_product; 106 | 107 | for (int j = 0; j < 27; j++) 108 | { 109 | int tcx = scx + disp_x[j]; 110 | int tcy = scy + disp_y[j]; 111 | int tcz = scz + disp_z[j]; 112 | if(tcx < 0 || tcx >=10) 113 | continue; 114 | if(tcy < 0 || tcy >=10) 115 | continue; 116 | if(tcz < 0 || tcz >=10) 117 | continue; 118 | int cube_index = 100*tcx + 10*tcy + tcz; 119 | const int points_in_cube = cube_points_num_ptr[batch_index*CUBES_TOTAL + cube_index]; 120 | const int *cube_indices = cube_points_indices_ptr + batch_index*CUBES_TOTAL*MAX_POINTS_IN_CUBE + cube_index * MAX_POINTS_IN_CUBE; 121 | 122 | for (int pt = 0; pt < points_in_cube; pt++) 123 | { 124 | int tid = cube_indices[pt]; 125 | if(tid == index) 126 | continue; 127 | 128 | dx = sx - in_source_points[6 * tid]; 129 | dy = sy - in_source_points[6 * tid + 1]; 130 | dz = sz - in_source_points[6 * tid + 2]; 131 | 132 | cur_distance = dx * dx + dy * dy + dz * dz; 133 | 134 | int iii; 135 | 136 | if(k < knn) 137 | { 138 | //do a insertion less than knn 139 | iii = k; 140 | } 141 | else if(cur_distance < cur_neighbor_points_dist_ptr[knn-1]) 142 | { 143 | //do the insertion 144 | iii = knn-1; 145 | } 146 | else 147 | continue; 148 | 149 | //the actual comparison 150 | for(; iii > 0; iii--) 151 | { 152 | if(cur_distance < cur_neighbor_points_dist_ptr[iii-1]) 153 | { 154 | cur_neighbor_points_dist_ptr[iii] = cur_neighbor_points_dist_ptr[iii-1]; 155 | cur_neighbor_points_id_ptr[iii] = cur_neighbor_points_id_ptr[iii-1]; 156 | } 157 | else 158 | break; 159 | } 160 | cur_neighbor_points_dist_ptr[iii] = cur_distance; 161 | cur_neighbor_points_id_ptr[iii] = tid; 162 | k++; 163 | } 164 | } 165 | 166 | //CudaAssert(k>=knn); 167 | if(k == 0) 168 | { 169 | cur_neighbor_points_dist_ptr[0] = 1e20; 170 | cur_neighbor_points_id_ptr[0] = -1; 171 | 172 | int start_index = 0; 173 | if(batch_index != 0) 174 | start_index = n_source_points[batch_index - 1]; 175 | 176 | //continue search until find nearest point at least 177 | for (int pt = start_index; pt < n_source_points[batch_index]; pt++) 178 | { 179 | if(pt == index) 180 | continue; 181 | 182 | dx = sx - in_source_points[6 * pt]; 183 | dy = sy - in_source_points[6 * pt + 1]; 184 | dz = sz - in_source_points[6 * pt + 2]; 185 | 186 | cur_distance = dx * dx + dy * dy + dz * dz; 187 | 188 | if(cur_distance < cur_neighbor_points_dist_ptr[0]) 189 | { 190 | cur_neighbor_points_dist_ptr[0] = cur_distance; 191 | cur_neighbor_points_id_ptr[0] = pt; 192 | } 193 | } 194 | } 195 | CudaAssert(cur_neighbor_points_id_ptr[0] >= 0); 196 | } 197 | } 198 | 199 | template 200 | static __global__ void gather_cube_pointsb_local( 201 | const int nthreads, const int* n_target_points, const T* in_target_points, const int MAX_POINTS_IN_CUBE, 202 | const int CUBES_TOTAL, int *cube_points_num_ptr, int *cube_points_indices_ptr) { 203 | CUDA_1D_KERNEL_LOOP(batch_index, nthreads) { 204 | int *cube_indices = cube_points_indices_ptr + batch_index * MAX_POINTS_IN_CUBE * CUBES_TOTAL; 205 | int *batch_cube_points_num_ptr = cube_points_num_ptr + batch_index*CUBES_TOTAL; 206 | 207 | int start_index = 0; 208 | if(batch_index != 0) 209 | start_index = n_target_points[batch_index - 1]; 210 | 211 | for (int n_target_index = start_index; n_target_index < n_target_points[batch_index]; n_target_index++) 212 | { 213 | T tx = in_target_points[6*n_target_index]; 214 | T ty = in_target_points[6*n_target_index + 1]; 215 | T tz = in_target_points[6*n_target_index + 2]; 216 | //find if point is inside cube 217 | int x = int((tx + 1) * 5); 218 | int y = int((ty + 1) * 5); 219 | int z = int((tz + 1) * 5); 220 | if(x < 0) 221 | x = 0; 222 | else if(x > 9) 223 | x = 9; 224 | 225 | if(y < 0) 226 | y = 0; 227 | else if (y > 9) 228 | y = 9; 229 | 230 | if(z < 0) 231 | z = 0; 232 | else if(z > 9) 233 | z = 9; 234 | 235 | int cube_index = 100*x+10*y+z; 236 | cube_indices[cube_index*MAX_POINTS_IN_CUBE + batch_cube_points_num_ptr[cube_index]] = n_target_index; 237 | batch_cube_points_num_ptr[cube_index]++; 238 | CudaAssert(batch_cube_points_num_ptr[cube_index] <= MAX_POINTS_IN_CUBE); 239 | } 240 | } 241 | } 242 | 243 | template 244 | void neighbor_points_spatial_grid_local_self(OpKernelContext* context, 245 | const int* n_source_points, const int batch_size, const T* in_source_points, 246 | const int knn, const int batch_points_total, int* neighbor_points_id_ptr) { 247 | // get GPU device 248 | GPUDevice d = context->eigen_device(); 249 | CudaLaunchConfig config; 250 | int nthreads; 251 | 252 | const int CUBES_TOTAL = 1000; 253 | const int MAX_POINTS_IN_CUBE = 4096; 254 | 255 | //assume input points are bounded by a unit sphere, and we partition it to 10x10x10 grids 256 | const TensorShape cube_index_shape({batch_size, CUBES_TOTAL, MAX_POINTS_IN_CUBE}); 257 | Tensor cube_points_indices; 258 | OP_REQUIRES_OK(context, context->allocate_temp(DT_INT32, cube_index_shape, &cube_points_indices)); 259 | auto cube_points_indices_ptr = cube_points_indices.flat().data(); 260 | 261 | const TensorShape cube_points_num_shape({batch_size, CUBES_TOTAL}); 262 | Tensor cube_points_num; 263 | OP_REQUIRES_OK(context, context->allocate_temp(DT_INT32, cube_points_num_shape, &cube_points_num)); 264 | auto cube_points_num_ptr = cube_points_num.flat().data(); 265 | cudaMemset(cube_points_num_ptr, 0, sizeof(int)*batch_size*CUBES_TOTAL); 266 | 267 | const TensorShape neighbor_points_distance_shape({batch_points_total, knn}); 268 | Tensor neighbor_points_distance; 269 | OP_REQUIRES_OK(context, context->allocate_temp(sizeof(T)==4?DT_FLOAT:DT_DOUBLE, neighbor_points_distance_shape, &neighbor_points_distance)); 270 | auto neighbor_points_distance_ptr = neighbor_points_distance.flat().data(); 271 | 272 | nthreads = batch_size; 273 | config = GetCudaLaunchConfig(nthreads, d); 274 | gather_cube_pointsb_local 275 | <<>>( 276 | nthreads, n_source_points, in_source_points, MAX_POINTS_IN_CUBE, CUBES_TOTAL, cube_points_num_ptr, cube_points_indices_ptr); 277 | 278 | nthreads = batch_points_total; 279 | config = GetCudaLaunchConfig(nthreads, d); 280 | //printf("%d block and %d threads per block\n", config.block_count, config.thread_per_block); 281 | find_knn_grids_local_self 282 | <<>>( 283 | nthreads, n_source_points, in_source_points, batch_size, knn, CUBES_TOTAL, MAX_POINTS_IN_CUBE, 284 | cube_points_num_ptr, cube_points_indices_ptr, neighbor_points_id_ptr, neighbor_points_distance_ptr); 285 | cudaDeviceSynchronize(); 286 | 287 | cudaError_t err2 = cudaGetLastError(); 288 | if (err2 != cudaSuccess) 289 | printf("Cuda error: %s\n", cudaGetErrorString(err2)); 290 | 291 | } 292 | 293 | //explicit instantiation 294 | template void neighbor_points_spatial_grid_local_self(OpKernelContext* context, 295 | const int* n_source_points, const int batch_size, 296 | const float* in_source_points, const int knn, const int batch_points_total, int* neighbor_points_id_ptr); 297 | 298 | template void neighbor_points_spatial_grid_local_self(OpKernelContext* context, 299 | const int* n_source_points, const int batch_size, 300 | const double* in_source_points, const int knn, const int batch_points_total, int* neighbor_points_id_ptr); 301 | 302 | } // namespace tensorflow 303 | -------------------------------------------------------------------------------- /points3d-tf/points3d/get_neighbor_points_spatial_grid_radius_weighting_voting.cc: -------------------------------------------------------------------------------- 1 | #include "tensorflow/core/framework/op.h" 2 | #include "tensorflow/core/framework/op_kernel.h" 3 | #include "tensorflow/core/framework/shape_inference.h" 4 | #include "tensorflow/core/framework/common_shape_fns.h" 5 | 6 | namespace tensorflow { 7 | 8 | template 9 | void neighbor_points_spatial_grid_no_normal_weight_local_varying_radius_voting(OpKernelContext* context, 10 | const int n_source_points, const int* n_target_points, const int batch_size, 11 | const T* in_source_points, const T* in_target_points, const T* squared_radius, const int knn, 12 | int* neighbor_points_id_ptr); 13 | 14 | REGISTER_OP("GetNeighborSpatialGridRadiusVVoting") 15 | .Attr("T: {float, double}") 16 | .Input("source_points: T") 17 | .Input("target_points: T") 18 | .Input("target_point_num: int32") 19 | .Input("squared_radius: T") 20 | .Attr("knn: int = 10") 21 | .Output("neighbor_points: int32") 22 | .SetShapeFn([](shape_inference::InferenceContext* c) { 23 | c->set_output(0, c->MakeShape({c->Dim(c->input(0),0),c->UnknownDim(),c->UnknownDim()})); 24 | return Status::OK(); 25 | }) 26 | .Doc(R"doc( 27 | Find the neighbor points for each source points in target pointset using spatial partition and efficient insertion sort. 28 | )doc"); 29 | 30 | template 31 | class GetNeighborSpatialGridRadiusVVotingOp : public OpKernel { 32 | public: 33 | explicit GetNeighborSpatialGridRadiusVVotingOp(OpKernelConstruction* context) 34 | : OpKernel(context) 35 | { 36 | OP_REQUIRES_OK(context, context->GetAttr("knn", 37 | &this->knn_)); 38 | } 39 | 40 | void Compute(OpKernelContext* context) override { 41 | // in source points [bs, 3*n_source_points] 42 | const Tensor& in_source_points = context->input(0); 43 | auto in_source_points_ptr = in_source_points.flat().data(); 44 | batch_size_ = in_source_points.dim_size(0); 45 | n_source_points_ = in_source_points.dim_size(1)/3; 46 | 47 | // in target points [\sum(n_target_points), 6] 48 | const Tensor& in_target_points = context->input(1); 49 | auto in_target_points_ptr = in_target_points.flat().data(); 50 | CHECK_EQ(in_target_points.dim_size(1), 6); 51 | 52 | const Tensor& in_target_num = context->input(2); 53 | auto in_target_num_ptr = in_target_num.flat().data(); 54 | CHECK_EQ(in_target_num.dim_size(0), batch_size_); 55 | 56 | //in shape [\sum(n_target_points)] 57 | const Tensor& in_squared_radius = context->input(3); 58 | auto in_squared_radius_ptr = in_squared_radius.flat().data(); 59 | CHECK_EQ(in_squared_radius.dim_size(0), in_target_points.dim_size(0)); 60 | 61 | // out tensor 62 | Tensor* out_vec = nullptr; 63 | TensorShape out_shape({batch_size_, n_source_points_, knn_}); 64 | OP_REQUIRES_OK(context, context->allocate_output("neighbor_points", out_shape, &out_vec)); 65 | auto out_ptr = out_vec->flat().data(); 66 | 67 | // find neighbor points 68 | neighbor_points_spatial_grid_no_normal_weight_local_varying_radius_voting(context, n_source_points_, in_target_num_ptr, 69 | batch_size_, in_source_points_ptr, in_target_points_ptr, in_squared_radius_ptr, knn_, out_ptr); 70 | } 71 | 72 | private: 73 | int n_source_points_; 74 | int batch_size_; 75 | int knn_; 76 | }; 77 | 78 | REGISTER_KERNEL_BUILDER(Name("GetNeighborSpatialGridRadiusVVoting").Device(DEVICE_GPU).TypeConstraint("T"), 79 | GetNeighborSpatialGridRadiusVVotingOp); 80 | 81 | REGISTER_KERNEL_BUILDER(Name("GetNeighborSpatialGridRadiusVVoting").Device(DEVICE_GPU).TypeConstraint("T"), 82 | GetNeighborSpatialGridRadiusVVotingOp); 83 | } // namespace tensorflow 84 | -------------------------------------------------------------------------------- /points3d-tf/points3d/get_neighbor_points_spatial_grid_radius_weighting_voting.cu.cc: -------------------------------------------------------------------------------- 1 | #define EIGEN_USE_THREADS 2 | #define EIGEN_USE_GPU 3 | 4 | #include "tensorflow/core/util/gpu_kernel_helper.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // Macro definition 16 | #define CudaAssert( X ) if ( !(X) ) {printf( "Thread %d:%d failed assert at %s:%d!\n", blockIdx.x, threadIdx.x, __FILE__, __LINE__ ); return;} 17 | 18 | //assume mls weighting function is exp(-3d^2/r^2) 19 | 20 | namespace tensorflow { 21 | 22 | typedef Eigen::GpuDevice GPUDevice; 23 | 24 | template 25 | static __global__ void find_knn_grids_no_normalw_local_varying_radius_voting( 26 | const int nthreads, const int n_source_points, const int* n_target_points, const T* in_source_points, const T* in_target_points, 27 | const T* in_squared_radius, const int knn, const int CUBES_TOTAL, const int MAX_POINTS_IN_CUBE, 28 | const int *cube_points_num_ptr, const int *cube_points_indices_ptr, int *neighbor_points_id_ptr, T *neighbor_points_dist_ptr) { 29 | // 0 1 2 3 4 5 6 7 10 14 18 19 21 23 25 30 | __shared__ int disp_x[27];// = {0, -1, 0, 0, 0, 0, 1, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1}; 31 | __shared__ int disp_y[27];// = {0, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, -1, -1, 1, 1, 0, 0, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1}; 32 | __shared__ int disp_z[27];// = {0, 0, 0, -1, 1, 0, 0, 0, -1, 1, 0, -1, 1, -1, 1, -1, 1, 0, 0, -1, 1, -1, 1, -1, 1, -1, 1}; 33 | 34 | 35 | 36 | //fill the shared memory 37 | if(threadIdx.x < 27) 38 | { 39 | int i = threadIdx.x; 40 | 41 | if(i < 9) 42 | disp_x[i] = 0; 43 | else if (i < 18) 44 | disp_x[i] = -1; 45 | else 46 | disp_x[i] = 1; 47 | 48 | if ((i/3) % 3 == 0) 49 | disp_y[i] = 0; 50 | else if ((i/3) % 3 == 1) 51 | disp_y[i] = -1; 52 | else 53 | disp_y[i] = 1; 54 | 55 | if(i % 3 == 0) 56 | disp_z[i] = 0; 57 | else if(i % 3 == 1) 58 | disp_z[i] = -1; 59 | else 60 | disp_z[i] = 1; 61 | 62 | //printf("%d %d %d\n", threadIdx.x, blockIdx.x, gridDim.x); 63 | } 64 | __syncthreads(); 65 | 66 | CUDA_1D_KERNEL_LOOP(index, nthreads) { 67 | int batch_index = index / n_source_points; 68 | int n_source_index = index % n_source_points; 69 | 70 | int* cur_neighbor_points_id_ptr = neighbor_points_id_ptr + index*knn; 71 | T *cur_neighbor_points_dist_ptr = neighbor_points_dist_ptr + index*knn; 72 | 73 | for(int ii = 0; ii < knn; ii++) 74 | cur_neighbor_points_id_ptr[ii] = -1; 75 | 76 | T sx = in_source_points[(batch_index*3 + 0)*n_source_points + n_source_index]; 77 | T sy = in_source_points[(batch_index*3 + 1)*n_source_points + n_source_index]; 78 | T sz = in_source_points[(batch_index*3 + 2)*n_source_points + n_source_index]; 79 | 80 | //filter out padded data 81 | if(sx < -10) 82 | { 83 | cur_neighbor_points_id_ptr[0] = 0; 84 | continue; 85 | } 86 | 87 | //find current cube and nearby cubes 88 | int scx = int((sx + 1) * 5); 89 | int scy = int((sy + 1) * 5); 90 | int scz = int((sz + 1) * 5); 91 | 92 | if(scx < 0) 93 | scx = 0; 94 | else if(scx > 9) 95 | scx = 9; 96 | 97 | if(scy < 0) 98 | scy = 0; 99 | else if (scy > 9) 100 | scy = 9; 101 | 102 | if(scz < 0) 103 | scz = 0; 104 | else if(scz > 9) 105 | scz = 9; 106 | 107 | CudaAssert(scx>=0 && scy>=0 && scz>=0 && scx<10 && scy<10 && scz<10); 108 | 109 | int k = 0; 110 | 111 | T dx, dy, dz, cur_distance; 112 | for (int j = 0; j < 27; j++) 113 | { 114 | int tcx = scx + disp_x[j]; 115 | int tcy = scy + disp_y[j]; 116 | int tcz = scz + disp_z[j]; 117 | if(tcx < 0 || tcx >=10) 118 | continue; 119 | if(tcy < 0 || tcy >=10) 120 | continue; 121 | if(tcz < 0 || tcz >=10) 122 | continue; 123 | int cube_index = 100*tcx + 10*tcy + tcz; 124 | const int points_in_cube = cube_points_num_ptr[batch_index*CUBES_TOTAL + cube_index]; 125 | const int *cube_indices = cube_points_indices_ptr + batch_index*CUBES_TOTAL*MAX_POINTS_IN_CUBE + cube_index * MAX_POINTS_IN_CUBE; 126 | 127 | for (int pt = 0; pt < points_in_cube; pt++) 128 | { 129 | int tid = cube_indices[pt]; 130 | 131 | dx = sx - in_target_points[6*tid]; 132 | dy = sy - in_target_points[6*tid + 1]; 133 | dz = sz - in_target_points[6*tid + 2]; 134 | 135 | T squared_radius = in_squared_radius[tid]; 136 | cur_distance = dx * dx + dy * dy + dz * dz; 137 | //compute mls weights 138 | cur_distance = cur_distance / squared_radius; //sort in ascent order 139 | 140 | int iii; 141 | 142 | if(k < knn) 143 | { 144 | //do a insertion less than knn 145 | iii = k; 146 | } 147 | else if(cur_distance < cur_neighbor_points_dist_ptr[knn-1]) 148 | { 149 | //do the insertion 150 | iii = knn-1; 151 | } 152 | else 153 | continue; 154 | 155 | //the actual comparison 156 | for(; iii > 0; iii--) 157 | { 158 | if(cur_distance < cur_neighbor_points_dist_ptr[iii-1]) 159 | { 160 | cur_neighbor_points_dist_ptr[iii] = cur_neighbor_points_dist_ptr[iii-1]; 161 | cur_neighbor_points_id_ptr[iii] = cur_neighbor_points_id_ptr[iii-1]; 162 | } 163 | else 164 | break; 165 | } 166 | cur_neighbor_points_dist_ptr[iii] = cur_distance; 167 | cur_neighbor_points_id_ptr[iii] = tid; 168 | k++; 169 | } 170 | } 171 | 172 | if(k <= 1) 173 | { 174 | //first try another ring 175 | for(int dx1 = -2; dx1 <= 2; dx1++) 176 | for(int dx2 = -2; dx2 <= 2; dx2++) 177 | for(int dx3 = -2; dx3 <= 2; dx3++) 178 | { 179 | if(abs(dx1) != 2 && abs(dx2) != 2 && abs(dx3) != 2) 180 | continue; 181 | int tcx = scx + dx1; 182 | int tcy = scy + dx2; 183 | int tcz = scz + dx3; 184 | if(tcx < 0 || tcx >=10) 185 | continue; 186 | if(tcy < 0 || tcy >=10) 187 | continue; 188 | if(tcz < 0 || tcz >=10) 189 | continue; 190 | int cube_index = 100*tcx + 10*tcy + tcz; 191 | const int points_in_cube = cube_points_num_ptr[batch_index*CUBES_TOTAL + cube_index]; 192 | const int *cube_indices = cube_points_indices_ptr + batch_index*CUBES_TOTAL*MAX_POINTS_IN_CUBE + cube_index * MAX_POINTS_IN_CUBE; 193 | 194 | for (int pt = 0; pt < points_in_cube; pt++) 195 | { 196 | int tid = cube_indices[pt]; 197 | 198 | dx = sx - in_target_points[6*tid]; 199 | dy = sy - in_target_points[6*tid + 1]; 200 | dz = sz - in_target_points[6*tid + 2]; 201 | 202 | T squared_radius = in_squared_radius[tid];//[batch_index*n_target_points + tid]; 203 | cur_distance = dx * dx + dy * dy + dz * dz; 204 | //compute mls weights 205 | cur_distance = cur_distance / squared_radius; //sort in ascent order 206 | 207 | int iii; 208 | 209 | if(k < knn) 210 | { 211 | //do a insertion less than knn 212 | iii = k; 213 | } 214 | else if(cur_distance < cur_neighbor_points_dist_ptr[knn-1]) 215 | { 216 | //do the insertion 217 | iii = knn-1; 218 | } 219 | else 220 | continue; 221 | 222 | //the actual comparison 223 | for(; iii > 0; iii--) 224 | { 225 | if(cur_distance < cur_neighbor_points_dist_ptr[iii-1]) 226 | { 227 | cur_neighbor_points_dist_ptr[iii] = cur_neighbor_points_dist_ptr[iii-1]; 228 | cur_neighbor_points_id_ptr[iii] = cur_neighbor_points_id_ptr[iii-1]; 229 | } 230 | else 231 | break; 232 | } 233 | cur_neighbor_points_dist_ptr[iii] = cur_distance; 234 | cur_neighbor_points_id_ptr[iii] = tid; 235 | k++; 236 | } 237 | } 238 | } 239 | 240 | //CudaAssert(k>=knn); 241 | if(k <= 1) 242 | { 243 | //rare case 244 | k = 0; 245 | cur_neighbor_points_dist_ptr[0] = 1e20; 246 | cur_neighbor_points_id_ptr[0] = -1; 247 | 248 | int start_index = 0; 249 | if(batch_index != 0) 250 | start_index = n_target_points[batch_index - 1]; 251 | 252 | //continue search until find nearest point at least 253 | for (int pt = start_index; pt < n_target_points[batch_index]; pt++) 254 | { 255 | dx = sx - in_target_points[6 * pt]; 256 | dy = sy - in_target_points[6 * pt + 1]; 257 | dz = sz - in_target_points[6 * pt + 2]; 258 | 259 | cur_distance = dx * dx + dy * dy + dz * dz; 260 | T squared_radius = in_squared_radius[pt];//[batch_index*n_target_points + pt]; 261 | //compute mls weights 262 | cur_distance = cur_distance / squared_radius; //sort in ascent order 263 | 264 | int iii; 265 | 266 | if(k < knn) 267 | { 268 | //do a insertion less than knn 269 | iii = k; 270 | } 271 | else if(cur_distance < cur_neighbor_points_dist_ptr[knn-1]) 272 | { 273 | //do the insertion 274 | iii = knn-1; 275 | } 276 | else 277 | continue; 278 | 279 | //the actual comparison 280 | for(; iii > 0; iii--) 281 | { 282 | if(cur_distance < cur_neighbor_points_dist_ptr[iii-1]) 283 | { 284 | cur_neighbor_points_dist_ptr[iii] = cur_neighbor_points_dist_ptr[iii-1]; 285 | cur_neighbor_points_id_ptr[iii] = cur_neighbor_points_id_ptr[iii-1]; 286 | } 287 | else 288 | break; 289 | } 290 | cur_neighbor_points_dist_ptr[iii] = cur_distance; 291 | cur_neighbor_points_id_ptr[iii] = pt; 292 | k++; 293 | } 294 | } 295 | 296 | CudaAssert(cur_neighbor_points_id_ptr[0] >= 0); 297 | //voting according to normal 298 | if(k > 1) 299 | { 300 | if(k > knn) 301 | k = knn; 302 | T tnx,tny,tnz, tn_norm; 303 | T sn_norm; 304 | //filter according to normal 305 | tnx = 0; 306 | tny = 0; 307 | tnz = 0; 308 | //get average normal 309 | for(int iii = 0; iii < k; iii++) 310 | { 311 | int tid = cur_neighbor_points_id_ptr[iii]; 312 | T mls_weight = exp(cur_neighbor_points_dist_ptr[0] - cur_neighbor_points_dist_ptr[iii]); 313 | tnx += mls_weight * in_target_points[6 * tid + 3]; 314 | tny += mls_weight * in_target_points[6 * tid + 4]; 315 | tnz += mls_weight * in_target_points[6 * tid + 5]; 316 | } 317 | //normalize normal 318 | tn_norm = sqrt(tnx*tnx+tny*tny+tnz*tnz); 319 | CudaAssert(tn_norm > 1e-20); 320 | tnx /= tn_norm; 321 | tny /= tn_norm; 322 | tnz /= tn_norm; 323 | 324 | //remove neighbors with opposite normal direction 325 | for(int iii = k - 1; iii >= 0; iii--) 326 | { 327 | int tid = cur_neighbor_points_id_ptr[iii]; 328 | T dot_product = tnx*in_target_points[6 * tid + 3]; 329 | dot_product += tny*in_target_points[6 * tid + 4]; 330 | dot_product += tnz*in_target_points[6 * tid + 5]; 331 | sn_norm = in_target_points[6 * tid + 3]*in_target_points[6 * tid + 3] + in_target_points[6 * tid + 4]*in_target_points[6 * tid + 4] + in_target_points[6 * tid + 5]*in_target_points[6 * tid + 5]; 332 | 333 | if(dot_product < -0.5*sqrt(sn_norm)) 334 | { 335 | //remove point from neighbor list 336 | //cur_neighbor_points_id_ptr[iii] = cur_neighbor_points_id_ptr[k - 1]; 337 | for(int jjj = iii; jjj < k - 1; jjj++) 338 | cur_neighbor_points_id_ptr[jjj] = cur_neighbor_points_id_ptr[jjj + 1]; 339 | cur_neighbor_points_id_ptr[k - 1] = -1; 340 | k--; 341 | } 342 | } 343 | } 344 | 345 | } 346 | } 347 | 348 | 349 | template 350 | static __global__ void gather_cube_pointsb_local( 351 | const int nthreads, const int* n_target_points, const T* in_target_points, const int MAX_POINTS_IN_CUBE, 352 | const int CUBES_TOTAL, int *cube_points_num_ptr, int *cube_points_indices_ptr) { 353 | CUDA_1D_KERNEL_LOOP(batch_index, nthreads) { 354 | int *cube_indices = cube_points_indices_ptr + batch_index * MAX_POINTS_IN_CUBE * CUBES_TOTAL; 355 | int *batch_cube_points_num_ptr = cube_points_num_ptr + batch_index*CUBES_TOTAL; 356 | 357 | int start_index = 0; 358 | if(batch_index != 0) 359 | start_index = n_target_points[batch_index - 1]; 360 | 361 | for (int n_target_index = start_index; n_target_index < n_target_points[batch_index]; n_target_index++) 362 | { 363 | T tx = in_target_points[6*n_target_index]; 364 | T ty = in_target_points[6*n_target_index + 1]; 365 | T tz = in_target_points[6*n_target_index + 2]; 366 | //find if point is inside cube 367 | int x = int((tx + 1) * 5); 368 | int y = int((ty + 1) * 5); 369 | int z = int((tz + 1) * 5); 370 | if(x < 0) 371 | x = 0; 372 | else if(x > 9) 373 | x = 9; 374 | 375 | if(y < 0) 376 | y = 0; 377 | else if (y > 9) 378 | y = 9; 379 | 380 | if(z < 0) 381 | z = 0; 382 | else if(z > 9) 383 | z = 9; 384 | 385 | int cube_index = 100*x+10*y+z; 386 | cube_indices[cube_index*MAX_POINTS_IN_CUBE + batch_cube_points_num_ptr[cube_index]] = n_target_index; 387 | batch_cube_points_num_ptr[cube_index]++; 388 | CudaAssert(batch_cube_points_num_ptr[cube_index] <= MAX_POINTS_IN_CUBE); 389 | } 390 | } 391 | } 392 | 393 | template 394 | void neighbor_points_spatial_grid_no_normal_weight_local_varying_radius_voting(OpKernelContext* context, 395 | const int n_source_points, const int* n_target_points, const int batch_size, 396 | const T* in_source_points, const T* in_target_points, 397 | const T* in_squared_radius, const int knn, 398 | int* neighbor_points_id_ptr) { 399 | 400 | //time_t start_time = clock(); 401 | // get GPU device 402 | GPUDevice d = context->eigen_device(); 403 | CudaLaunchConfig config; 404 | int nthreads; 405 | 406 | const int CUBES_TOTAL = 1000; 407 | const int MAX_POINTS_IN_CUBE = 4096; 408 | 409 | //assume input points are bounded by a unit sphere, and we partition it to 10x10x10 grids 410 | const TensorShape cube_index_shape({batch_size, CUBES_TOTAL, MAX_POINTS_IN_CUBE}); 411 | Tensor cube_points_indices; 412 | OP_REQUIRES_OK(context, context->allocate_temp(DT_INT32, cube_index_shape, &cube_points_indices)); 413 | auto cube_points_indices_ptr = cube_points_indices.flat().data(); 414 | 415 | const TensorShape cube_points_num_shape({batch_size, CUBES_TOTAL}); 416 | Tensor cube_points_num; 417 | OP_REQUIRES_OK(context, context->allocate_temp(DT_INT32, cube_points_num_shape, &cube_points_num)); 418 | auto cube_points_num_ptr = cube_points_num.flat().data(); 419 | cudaMemset(cube_points_num_ptr, 0, sizeof(int)*batch_size*CUBES_TOTAL); 420 | 421 | const TensorShape neighbor_points_distance_shape({batch_size, n_source_points, knn}); 422 | Tensor neighbor_points_distance; 423 | OP_REQUIRES_OK(context, context->allocate_temp(sizeof(T)==4?DT_FLOAT:DT_DOUBLE, neighbor_points_distance_shape, &neighbor_points_distance)); 424 | auto neighbor_points_distance_ptr = neighbor_points_distance.flat().data(); 425 | 426 | nthreads = batch_size; 427 | config = GetCudaLaunchConfig(nthreads, d); 428 | gather_cube_pointsb_local 429 | <<>>( 430 | nthreads, n_target_points, in_target_points, MAX_POINTS_IN_CUBE, CUBES_TOTAL, cube_points_num_ptr, cube_points_indices_ptr); 431 | 432 | nthreads = batch_size * n_source_points; 433 | config = GetCudaLaunchConfig(nthreads, d); 434 | //printf("%d block and %d threads per block\n", config.block_count, config.thread_per_block); 435 | find_knn_grids_no_normalw_local_varying_radius_voting 436 | <<>>( 437 | nthreads, n_source_points, n_target_points, in_source_points, in_target_points, in_squared_radius, knn, CUBES_TOTAL, MAX_POINTS_IN_CUBE, 438 | cube_points_num_ptr, cube_points_indices_ptr, neighbor_points_id_ptr, neighbor_points_distance_ptr); 439 | cudaDeviceSynchronize(); 440 | 441 | cudaError_t err2 = cudaGetLastError(); 442 | if (err2 != cudaSuccess) 443 | printf("Cuda error: %s\n", cudaGetErrorString(err2)); 444 | 445 | } 446 | 447 | //explicit instantiation 448 | template void neighbor_points_spatial_grid_no_normal_weight_local_varying_radius_voting(OpKernelContext* context, 449 | const int n_source_points, const int* n_target_points, const int batch_size, 450 | const float* in_source_points, const float* in_target_points, 451 | const float* in_squared_radius, const int knn, 452 | int* neighbor_points_id_ptr); 453 | 454 | template void neighbor_points_spatial_grid_no_normal_weight_local_varying_radius_voting(OpKernelContext* context, 455 | const int n_source_points, const int* n_target_points, const int batch_size, 456 | const double* in_source_points, const double* in_target_points, 457 | const double* in_squared_radius, const int knn, 458 | int* neighbor_points_id_ptr); 459 | 460 | } // namespace tensorflow 461 | -------------------------------------------------------------------------------- /points3d-tf/pointsdataset/points_dataset_octreed7.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tensorflow as tf 3 | import numpy as np 4 | sys.path.append("Octree/ocnn-tf") 5 | from libs import * 6 | 7 | def IntList_to_Bytes(int_list): 8 | #list_bytes = struct.pack('i'*len(int_list), *int_list) 9 | x = np.array(int_list, dtype=np.int32) 10 | list_bytes = x.tobytes() 11 | return list_bytes 12 | 13 | def DoubleList_to_Bytes(float_list): 14 | #list_bytes = struct.pack('d'*len(float_list), *float_list) 15 | x = np.array(float_list, dtype=np.float64) 16 | list_bytes = x.tobytes() 17 | return list_bytes 18 | 19 | def Float32List_to_Bytes(float_list): 20 | #list_bytes = struct.pack('f'*len(float_list), *float_list) 21 | x = np.array(float_list, dtype=np.float32) 22 | list_bytes = x.tobytes() 23 | return list_bytes 24 | 25 | class PointsPreprocessor: 26 | def __init__(self, depth, points_num, full_depth=2, sample_grid_points_number=None, data_sources=-1, return_gt_points=False, noise_stddev=0.0095): 27 | self._depth = depth 28 | self._full_depth = full_depth 29 | self._points_num = points_num 30 | self._sample_grid_points_number = sample_grid_points_number 31 | self._data_sources = data_sources 32 | self._return_gt_points = return_gt_points 33 | self._noise_stddev = noise_stddev 34 | 35 | 36 | def __call__(self, record): 37 | points_array_f16, filename, sdf_samples_depth6, sdf_samples_depth7, depth6_coord, depth7_coord, octree_d7_points_idx = self.parse_example_array(record) 38 | 39 | octree_points_d7_x = octree_d7_points_idx // 65536 40 | octree_points_d7_y = (octree_d7_points_idx // 256) % 256 41 | octree_points_d7_z = octree_d7_points_idx % 256 42 | octree_points_d7 = tf.cast(tf.stack([octree_points_d7_x, octree_points_d7_y, octree_points_d7_z], axis=1), tf.float32) / 128.0 - 1 43 | 44 | gt_points = tf.cast(tf.transpose(tf.reshape(points_array_f16, [6, 100000])), tf.float32) 45 | 46 | #sample 3000 points and add noise 47 | selected_points_idx = tf.random.uniform(shape=[3000], minval=0, maxval=100000, dtype=tf.int32) 48 | selected_points = tf.gather(gt_points, selected_points_idx) 49 | #add noise 50 | assert(self._points_num == 3000) 51 | if(self._noise_stddev > 1e-6): 52 | selected_points_position = selected_points[:,:3] + tf.random.normal([3000,3], stddev=self._noise_stddev)#0.0095 53 | else: 54 | selected_points_position = selected_points[:,:3] 55 | #selected_points_normal = selected_points[:,3:] 56 | #selected_points = tf.concat([selected_points_position, selected_points_normal], axis=-1) 57 | 58 | points = points_new(selected_points_position, [], selected_points_position, []) 59 | input_octree = points2octree(points, depth=self._depth, full_depth=self._full_depth, 60 | node_dis=False, split_label=True) #node_feature=True, 61 | 62 | gt_octree = points2octree(points_new(octree_points_d7, [], tf.ones(tf.shape(octree_points_d7_x)), []), depth=self._depth, full_depth=self._full_depth, 63 | node_dis=False, split_label=True) #node_feature=True, 64 | if(self._data_sources == 6): 65 | sdf_samples = sdf_samples_depth6 66 | sdf_samples_id = depth6_coord 67 | elif(self._data_sources == 7): 68 | sdf_samples = sdf_samples_depth7 69 | sdf_samples_id = depth7_coord 70 | else: 71 | raise NotImplementedError 72 | 73 | if(self._sample_grid_points_number is None): 74 | raise NotImplementedError 75 | else: 76 | padding_limit = self._sample_grid_points_number 77 | #random select sample_grid_points_number samples 78 | sdf_samples = tf.reshape(sdf_samples, [-1,4]) 79 | num_valid_samples = tf.reduce_sum(tf.cast(tf.greater_equal(sdf_samples_id, 0), tf.int32)) 80 | 81 | #randomly select points(uniformly) 82 | selected_idx = tf.random.shuffle(tf.range(num_valid_samples))[:padding_limit] 83 | selected_samples = tf.gather(sdf_samples, selected_idx) 84 | selected_samples_coord = tf.gather(sdf_samples_id, selected_idx) 85 | 86 | selected_samples_coordx = selected_samples_coord // 66049 87 | selected_samples_coordy = (selected_samples_coord // 257) % 257 88 | selected_samples_coordz = selected_samples_coord % 257 89 | 90 | selected_position = tf.cast(tf.stack([selected_samples_coordx, selected_samples_coordy, selected_samples_coordz], axis=1), tf.float32) / 128.0 - 1.0 91 | selected_sdf = selected_samples 92 | #pad if needed 93 | selected_position = tf.pad(selected_position, [[0, padding_limit - tf.shape(selected_position)[0]],[0,0]], constant_values=-100) 94 | selected_sdf = tf.pad(selected_sdf, [[0, padding_limit - tf.shape(selected_sdf)[0]],[0,0]], constant_values=0) 95 | if(self._return_gt_points): 96 | return input_octree, gt_octree, tf.transpose(selected_points), filename, selected_position, selected_sdf, gt_points 97 | else: 98 | return input_octree, gt_octree, tf.transpose(selected_points), filename, selected_position, selected_sdf 99 | 100 | def parse_example_array(self, record): 101 | features = { 'points': tf.FixedLenFeature([], tf.string), 102 | "filename": tf.FixedLenFeature([], tf.string)} 103 | SeqFeatures = {"sdf_samples_depth6": tf.FixedLenSequenceFeature([4000], tf.float32), 104 | "sdf_samples_depth7": tf.FixedLenSequenceFeature([4000], tf.float32), 105 | "depth6_coord": tf.FixedLenSequenceFeature([1000], tf.int64), 106 | "depth7_coord": tf.FixedLenSequenceFeature([1000], tf.int64), 107 | "octree_points_idx": tf.FixedLenSequenceFeature([2000], tf.int64)} 108 | parsed_all = tf.parse_single_sequence_example(record, context_features=features, sequence_features=SeqFeatures) 109 | parsed = parsed_all[0] 110 | feature_parsed = parsed_all[1] 111 | return tf.decode_raw(parsed['points'], tf.float16), parsed['filename'], feature_parsed['sdf_samples_depth6'], feature_parsed['sdf_samples_depth7'], tf.reshape(feature_parsed['depth6_coord'], [-1]), tf.reshape(feature_parsed['depth7_coord'], [-1]), tf.reshape(feature_parsed['octree_points_idx'], [-1]) 112 | 113 | def points_dataset_AE(record_name, batch_size, points_num, depth=5, full_depth=2, shuffle_data=True, sample_grid_points_number=None, data_sources=-1, return_gt_points=False, noise_stddev=0.0095): 114 | def merge_octrees(input_octrees, gt_octrees, points_arrays, filenames, grid_points, grid_fvalue): 115 | input_octree = octree_batch(input_octrees) 116 | gt_octree = octree_batch(gt_octrees) 117 | return input_octree, gt_octree, points_arrays, filenames, grid_points, grid_fvalue 118 | 119 | def merge_octrees_gtpoints(input_octrees, gt_octrees, points_arrays, filenames, grid_points, grid_fvalue, gt_points): 120 | input_octree = octree_batch(input_octrees) 121 | gt_octree = octree_batch(gt_octrees) 122 | return input_octree, gt_octree, points_arrays, filenames, grid_points, grid_fvalue, gt_points 123 | 124 | if("all_test_1000" in record_name): 125 | record_num = 1000 126 | else: 127 | record_num = sum(1 for _ in tf.python_io.tf_record_iterator(record_name)) 128 | shuffle_buffer_size = min(1000, 2*record_num) 129 | print("dataset using data sources:{}".format(data_sources)) 130 | print("{} records in {}".format(record_num, record_name)) 131 | if(shuffle_data): 132 | print("shuffle buffer size = {}".format(shuffle_buffer_size)) 133 | with tf.name_scope('points_dataset'): 134 | preprocess = PointsPreprocessor(depth, points_num, full_depth, sample_grid_points_number=sample_grid_points_number, data_sources=data_sources, return_gt_points=return_gt_points, noise_stddev=noise_stddev) 135 | if(return_gt_points): 136 | merge_octrees_op = merge_octrees_gtpoints 137 | else: 138 | merge_octrees_op = merge_octrees 139 | if(shuffle_data): 140 | return (record_num,) + tf.data.TFRecordDataset([record_name]).repeat().shuffle(shuffle_buffer_size) \ 141 | .map(preprocess, num_parallel_calls=8) \ 142 | .batch(batch_size).map(merge_octrees_op, num_parallel_calls=8) \ 143 | .prefetch(8).make_one_shot_iterator().get_next() 144 | else: 145 | return (record_num,) + tf.data.TFRecordDataset([record_name]).repeat() \ 146 | .map(preprocess, num_parallel_calls=8) \ 147 | .batch(batch_size).map(merge_octrees_op, num_parallel_calls=8) \ 148 | .prefetch(8).make_one_shot_iterator().get_next() 149 | 150 | def points_dataset_AE_multiple_GPU(record_name, batch_size, points_num, depth=5, full_depth=2, gpu_num=1, sample_grid_points_number=None, data_sources=-1, noise_stddev=0.0095): 151 | def merge_octrees(input_octrees, gt_octrees, points_arrays, filenames, grid_points, grid_fvalue): 152 | gpu_batch_size = batch_size // gpu_num 153 | return_list = [] 154 | for i in range(gpu_num): 155 | return_list.append(octree_batch(input_octrees[i*gpu_batch_size:(i+1)*gpu_batch_size])) 156 | for i in range(gpu_num): 157 | return_list.append(octree_batch(gt_octrees[i*gpu_batch_size:(i+1)*gpu_batch_size])) 158 | return_list += [points_arrays, filenames, grid_points, grid_fvalue] 159 | return return_list 160 | 161 | if("all_train" in record_name): 162 | record_num = 30661 163 | else: 164 | record_num = sum(1 for _ in tf.python_io.tf_record_iterator(record_name)) 165 | shuffle_buffer_size = min(1000, 2*record_num) 166 | print("multi-GPU dataset using data sources:{}".format(data_sources)) 167 | print("{} records in {}".format(record_num, record_name)) 168 | print("shuffle buffer size = {}".format(shuffle_buffer_size)) 169 | with tf.name_scope('points_dataset'): 170 | preprocess = PointsPreprocessor(depth, points_num, full_depth, sample_grid_points_number=sample_grid_points_number, data_sources=data_sources, noise_stddev=noise_stddev) 171 | return (record_num,) + tf.data.TFRecordDataset([record_name]).repeat().shuffle(shuffle_buffer_size) \ 172 | .map(preprocess, num_parallel_calls=8) \ 173 | .batch(batch_size).map(merge_octrees, num_parallel_calls=8) \ 174 | .prefetch(8).make_one_shot_iterator().get_next() -------------------------------------------------------------------------------- /scripts/Readme.md: -------------------------------------------------------------------------------- 1 | # Script for tfrecords generation 2 | 3 | Before using this script, please first compile [main.cpp](https://github.com/Andy97/DeepMLS/tree/master/vdb_tsdf) and get an executable file named "vdb_tsdf". 4 | 5 | The executable will be used to generate groudtruth sdf values and gradients as supervision for training, which will be used in script. ([here](https://github.com/Andy97/DeepMLS/blob/master/scripts/generate_tf_records.py#L79)) 6 | 7 | For each Shapenet model, there will be **2 input files** to this script: 8 | 9 | #### **1.Watertight mesh file**:the watertight version of each original shapenet model, generated using scripts from occupancy networks 10 | We assume that each mesh file has already been normalized to [-0.95, 0.95]^3 (which equals to [-1,1]^3 with 5% padding). 11 | 12 | This input is declared [here](https://github.com/Andy97/DeepMLS/blob/master/scripts/generate_tf_records.py#L53). 13 | 14 | #### **2.Sampled points file**: sampled 100k points with normal from input #1 15 | This input is the groundtruth points sampled from surface, we will draw 3k points from these points plus gaussian noise as network input. 16 | 17 | Please also note that since these points are sampled from input #1, all these points should also be bounded by bounding box [-0.95, 0.95]^3. 18 | 19 | This input is declared [here](https://github.com/Andy97/DeepMLS/blob/master/scripts/generate_tf_records.py#L42). 20 | -------------------------------------------------------------------------------- /scripts/generate_tf_records.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import struct 4 | 5 | import numpy as np 6 | import tensorflow as tf 7 | import trimesh 8 | 9 | def _float_feature(value): 10 | return tf.train.Feature(float_list=tf.train.FloatList(value=value)) 11 | 12 | def _bytes_feature(value): 13 | return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) 14 | 15 | def _int_feature(value): 16 | return tf.train.Feature(int64_list=tf.train.Int64List(value=value)) 17 | 18 | def get_octree_from_points(points, octree_depth=8): 19 | #get representative points for building the groundtruth octree 20 | assert(points.shape[1] == 6 or points.shape[1] == 3) 21 | points_pos = points[:,:3] 22 | octree_node_length = 2.0 / (2**octree_depth) 23 | octree_node_index = np.unique(np.clip(np.floor((points_pos + 1) / octree_node_length).astype(np.int32), a_min = 0, a_max = (2**octree_depth) - 1), axis=0) 24 | 25 | return octree_node_index 26 | 27 | def write_data_to_tfrecords(sample_list, tfrecords_filename): 28 | #options = tf.python_io.TFRecordOptions(compression_type=tf.python_io.TFRecordCompressionType.ZLIB) 29 | writer = tf.python_io.TFRecordWriter(tfrecords_filename)#, options=options 30 | n_data = len(sample_list) 31 | for i in range(n_data): 32 | if i % 100 == 0 or i == n_data-1: 33 | print('=========================\n') 34 | print('data loaded: {} / {}\n'.format(i+1, n_data)) 35 | print('=========================\n') 36 | sample_category = sample_list[i][0] 37 | sample_id = sample_list[i][2] 38 | #print("{}:{}".format(sample_category, sample_id)) 39 | signature = "{}:{}".format(sample_category, sample_id) 40 | 41 | #points_files contains the 100k points sampled from groundtruth surface, 3k input points to the network will be drawn from this set 42 | points_file = os.path.join("occupancy_networks-master\\data\\ShapeNet.build", sample_category, "4_pointcloud", sample_id+".npz") 43 | assert(os.path.exists(points_file)) 44 | points_dict = np.load(points_file) 45 | points_position = points_dict['points'] 46 | points_normal = points_dict['normals'] 47 | assert(points_position.shape == points_normal.shape) 48 | print("points in bbox:{} {}".format(points_position.min(), points_position.max())) 49 | assert(points_position.min() >= -1 and points_position.max() <= 1) 50 | points = np.concatenate((points_position, points_normal), axis=-1).astype(np.float32) 51 | assert(points.shape[0] == 100000 and points.shape[1] ==6) 52 | 53 | mesh_file = sample_list[i][1] + ".obj" 54 | assert(os.path.exists(mesh_file)) 55 | #prepare ground truth octree for decoder supervision 56 | #sample 5M points to build octree as gt octree for decode shape 57 | mesh = trimesh.load(mesh_file) 58 | sampled_points = trimesh.sample.sample_surface(mesh, 5000000)[0] 59 | #scale to [-1,1] with 5% padding 60 | #sampled_points *= 1.9 61 | assert(sampled_points.min() >= -1 and sampled_points.max() <= 1) 62 | octree = get_octree_from_points(sampled_points, 8) 63 | assert(octree.min() >=0 and octree.max() <= 255) 64 | octree_points = (octree*np.array([256*256, 256, 1])).sum(axis=-1) 65 | assert(octree_points.dtype == np.int) 66 | assert(octree_points.min() >=0 and octree_points.max() <= 16777215) 67 | #octree_points_file = sample_list[i][1] + "_octreep.npz" 68 | #np.savez_compressed(octree_points_file, octree_points=octree_points) 69 | #print("{} octree points".format(octree_points.shape)) 70 | 71 | octree_points_seq_length = 2000 72 | #pad octree_points 73 | octree_points = np.reshape(octree_points, [-1]) 74 | octree_points_pad_elements = (octree_points_seq_length - (octree_points.shape[0] % octree_points_seq_length)) % octree_points_seq_length 75 | octree_points = np.reshape(np.pad(octree_points, (0, octree_points_pad_elements), mode='constant', constant_values=octree_points[0]), [-1, octree_points_seq_length]) 76 | 77 | sdf_samples_file = sample_list[i][1] + ".mat.bin" 78 | if(not os.path.exists(sdf_samples_file)): 79 | os.system("vdb_tsdf {} {}".format(mesh_file, sdf_samples_file)) 80 | assert(os.path.exists(sdf_samples_file)) 81 | 82 | sdf_samples = np.reshape(np.fromfile(sdf_samples_file, dtype=np.float32), [-1, 7]) 83 | assert(sdf_samples.shape[1] == 7) 84 | sdf_points = sdf_samples[:,:3] 85 | assert(sdf_points.min() >= -1 and sdf_points.max() <= 1) 86 | 87 | #first get samples on 64^3 grid 88 | x = sdf_samples[:,0] 89 | y = sdf_samples[:,1] 90 | z = sdf_samples[:,2] 91 | coordx = np.round((x+1)*128).astype(int) 92 | coordy = np.round((y+1)*128).astype(int) 93 | coordz = np.round((z+1)*128).astype(int) 94 | depth6_ind = np.where(np.logical_and(np.logical_and(coordx % 4 == 0, coordy % 4 == 0), coordz % 4 == 0)) 95 | depth6_sdf_samples = sdf_samples[depth6_ind] 96 | #print("{} samples in depth 6".format(depth6_sdf_samples.shape)) 97 | 98 | #filter sdf samples with sdf > 2/32 99 | sdf_samples = sdf_samples[np.where(np.abs(sdf_samples[:,3]) < 2.0/32)] 100 | x = sdf_samples[:,0] 101 | y = sdf_samples[:,1] 102 | z = sdf_samples[:,2] 103 | coordx = np.round((x+1)*128).astype(int) 104 | coordy = np.round((y+1)*128).astype(int) 105 | coordz = np.round((z+1)*128).astype(int) 106 | depth7_ind = np.where(np.logical_and(np.logical_and(coordx % 2 == 0, coordy % 2 == 0), coordz % 2 == 0)) 107 | depth7_sdf_samples = sdf_samples[depth7_ind] 108 | #print("{} samples in depth 7".format(depth7_sdf_samples.shape)) 109 | 110 | def efficient_padding(sdf_samples): 111 | xyz_coord = np.round((sdf_samples[:,:3] + 1)*128).astype(np.int32) 112 | assert(xyz_coord.min() >= 0 and xyz_coord.max() <= 256) 113 | xyz_id = (xyz_coord*np.array([257*257, 257, 1])).sum(axis=-1) 114 | sdf_pad_elements = (1000 - (xyz_id.shape[0] % 1000)) % 1000 115 | 116 | xyz_id = np.pad(xyz_id, (0, sdf_pad_elements), mode='constant', constant_values=-100) #should be carefully dealt when parsing 117 | sdf_values = np.pad(np.reshape(sdf_samples[:,3:], [-1]), (0, 4*sdf_pad_elements), mode='constant', constant_values=-100) 118 | assert(xyz_id.shape[0] % 1000 == 0 and xyz_id.dtype == np.int32) 119 | assert(sdf_values.shape[0] == xyz_id.shape[0]*4 and sdf_values.dtype==np.float32) 120 | return np.reshape(xyz_id, [-1,1000]), np.reshape(sdf_values, [-1, 4000]) 121 | 122 | depth7_coord, depth7_sdf_samples = efficient_padding(depth7_sdf_samples) 123 | depth6_coord, depth6_sdf_samples = efficient_padding(depth6_sdf_samples) 124 | assert(depth7_coord.dtype == np.int32 and depth6_coord.dtype == np.int32) 125 | assert(depth7_sdf_samples.dtype == np.float32 and depth6_sdf_samples.dtype == np.float32) 126 | 127 | sequence_feature = {"depth7_coord":tf.train.FeatureList(feature=[_int_feature(coord_row.tolist()) for coord_row in depth7_coord]), 128 | "depth6_coord":tf.train.FeatureList(feature=[_int_feature(coord_row.tolist()) for coord_row in depth6_coord]), 129 | "sdf_samples_depth7":tf.train.FeatureList(feature=[_float_feature(sdf_row.tolist()) for sdf_row in depth7_sdf_samples]), 130 | "sdf_samples_depth6":tf.train.FeatureList(feature=[_float_feature(sdf_row.tolist()) for sdf_row in depth6_sdf_samples]), 131 | "octree_points_idx":tf.train.FeatureList(feature=[_int_feature(idx_row.tolist()) for idx_row in octree_points])} 132 | feature = {'points': _bytes_feature(np.reshape(np.transpose(points), [-1]).astype(np.float16).tobytes()), 133 | "filename":_bytes_feature(signature.encode('utf8')), 134 | } 135 | example = tf.train.SequenceExample(context=tf.train.Features(feature=feature), 136 | feature_lists=tf.train.FeatureLists(feature_list=sequence_feature)) 137 | writer.write(example.SerializeToString()) 138 | writer.close() 139 | 140 | def main(): 141 | cat = ['02691156', '02828884', '02933112', '02958343', '03001627', '03211117', '03636649', '03691459', '04090263', '04256520', '04379243', '04401088', '04530566'] 142 | rootpc = "occupancy_networks-master\\data\\ShapeNet.build\\" 143 | 144 | list=[] 145 | for item in cat: 146 | print("working on {}".format(item)) 147 | dir_point = os.path.join(rootpc, item, 'watertight_normalized') 148 | assert(os.path.exists(dir_point)) 149 | 150 | #training data split file follow occupancy networks 151 | split_file = os.path.join("convolutional_occupancy_networks-master\\data\\ShapeNet", item, "train.lst") 152 | assert(os.path.exists(split_file)) 153 | split_file = open(split_file, "r") 154 | fns = [line.replace("\r", "").replace("\n", "") for line in split_file] 155 | split_file.close() 156 | 157 | assert(len(fns) != 0) 158 | for fn in fns: 159 | list.append((item, os.path.join(dir_point, fn), fn)) 160 | 161 | 162 | print("{} samples total".format(len(list))) 163 | random.shuffle(list) 164 | 165 | tfrecords_filename = 'ShapeNet_points_{}_w_octree_grid_occupancy_compress.tfrecords'.format("all_train") 166 | write_data_to_tfrecords(list, tfrecords_filename) 167 | 168 | if __name__ == '__main__': 169 | main() 170 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import functools 3 | import tensorflow as tf 4 | import json 5 | 6 | def config_reader(config_path): 7 | json_data=open(config_path).read() 8 | config = json.loads(json_data) 9 | return config 10 | 11 | def get_num_params(): 12 | num_params = 0 13 | for variable in tf.trainable_variables(): 14 | shape = variable.get_shape() 15 | num_params += functools.reduce(operator.mul, [dim.value for dim in shape], 1) 16 | return num_params 17 | 18 | def average_gradient(tower_grads): 19 | average_grads = [] 20 | for grad_and_vars in zip(*tower_grads): 21 | grads = [] 22 | for g, var in grad_and_vars: 23 | if g is not None: 24 | expanded_g = tf.expand_dims(g, 0) 25 | grads.append(expanded_g) 26 | if(len(grads) == 0): 27 | continue 28 | grad = tf.concat(grads, axis=0) 29 | grad = tf.reduce_mean(grad, axis=0) 30 | 31 | v = grad_and_vars[0][1] 32 | grad_and_var = (grad, v) 33 | average_grads.append(grad_and_var) 34 | return average_grads 35 | 36 | def tf_summary_from_dict(loss_dict, is_training): 37 | if(is_training): 38 | summary_name = "train_summary" 39 | else: 40 | summary_name = "test_summary" 41 | #create summary 42 | with tf.name_scope(summary_name): 43 | summary_list = [] 44 | gpu0_loss_dict = loss_dict[0] 45 | for item in gpu0_loss_dict: 46 | scalar_acc = 0 47 | for i in range(len(loss_dict)): 48 | scalar_acc += loss_dict[i][item] 49 | scalar_acc /= len(loss_dict) 50 | summary_item = tf.summary.scalar(item, scalar_acc) 51 | summary_list.append(summary_item) 52 | return tf.summary.merge(summary_list) 53 | 54 | def rowwise_l2_norm_squared(feature): 55 | #assum input with size[n,f] out shape = [n] 56 | return tf.reduce_sum(tf.math.square(feature), axis=-1) -------------------------------------------------------------------------------- /vdb_tsdf/ReadMe.MD: -------------------------------------------------------------------------------- 1 | ## Groundtruth SDF Data Generation 2 | Given a watertight mesh, we use [OpenVDB](https://www.openvdb.org/) library to generate the tsdf field. 3 | As clarified in paper, more sdf samples near the surface will be chosen. 4 | For details, please refer to the source code. 5 | 6 | ### Dependencies 7 | ##### 1. [OpenVDB](https://www.openvdb.org/) 8 | ##### 2. [OpenMesh](https://www.openmesh.org/) 9 | -------------------------------------------------------------------------------- /vdb_tsdf/main.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning (disable: 4146) 2 | #define _USE_MATH_DEFINES 3 | #include 4 | #define NOMINMAX 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "OpenMesh\Core\IO\MeshIO.hh" 11 | #include "OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh" 12 | 13 | #include 14 | inline bool debug_output(const char *string) 15 | { 16 | std::cout << "=======================================================\n"; 17 | std::cout << "Assertion failed:" << string << "!!!\n"; 18 | std::cout << "=======================================================\n"; 19 | system("pause"); 20 | return false; 21 | } 22 | 23 | #define myassert(expression) (bool)( \ 24 | (!!(expression)) || \ 25 | (debug_output(#expression)) \ 26 | ) 27 | 28 | // Select mesh type (TriMesh) and kernel (ArrayKernel) 29 | // and define my personal mesh type (MyMesh) 30 | struct DPTraits : public OpenMesh::DefaultTraits 31 | { 32 | typedef OpenMesh::Vec3d Point; // use double-values points/normals 33 | typedef OpenMesh::Vec3d Normal; 34 | }; 35 | typedef OpenMesh::TriMesh_ArrayKernelT TriMesh; 36 | 37 | void fetch_sdf(const char *model_filename, const char *sdf_filename, int grid_resolution = 256) 38 | { 39 | auto mesh = std::make_shared(); 40 | if (!OpenMesh::IO::read_mesh(*mesh, model_filename)) 41 | { 42 | std::cerr << "Error: cannot read mesh file " << model_filename << std::endl; 43 | return; 44 | } 45 | std::cout << "Finished reading mesh.\n"; 46 | 47 | double bbox[6] = { 1,1,1,-1,-1,-1 }; 48 | std::vector verts; 49 | std::vector faces; 50 | for (auto vtx = mesh->vertices_begin(); vtx != mesh->vertices_end(); vtx++) 51 | { 52 | auto& pt = mesh->point(*vtx); 53 | verts.push_back(openvdb::Vec3f(pt[0], pt[1], pt[2])); 54 | 55 | //for (int i = 0; i < 3; i++) 56 | //{ 57 | // bbox[i] = std::max(pt[i], bbox[i]); 58 | // bbox[i + 3] = std::min(pt[i], bbox[i + 3]); 59 | //} 60 | } 61 | for (auto face = mesh->faces_begin(); face != mesh->faces_end(); face++) 62 | { 63 | openvdb::Vec3I face_idx; 64 | auto fvhandle = mesh->fv_begin(*face); 65 | for (int vitr = 0; vitr < 3; vitr++, fvhandle++) 66 | { 67 | face_idx[vitr] = fvhandle->idx(); 68 | } 69 | faces.push_back(face_idx); 70 | } 71 | 72 | double diag_len = (bbox[0] - bbox[3])*(bbox[0] - bbox[3]) + (bbox[1] - bbox[4])*(bbox[1] - bbox[4]) 73 | + (bbox[2] - bbox[5])*(bbox[2] - bbox[5]); 74 | diag_len = std::sqrt(diag_len); 75 | double center[3] = { bbox[0] + bbox[3], bbox[1] + bbox[4], bbox[2] + bbox[5] }; 76 | for (int i = 0; i < 3; i++) 77 | { 78 | center[i] /= 2.0; 79 | } 80 | double voxel_size = 2.0 / grid_resolution; 81 | 82 | printf("Model center: %f,%f,%f, diagonal length: %f, voxel size: %f\n", center[0], center[1], center[2], 83 | diag_len, voxel_size); 84 | 85 | auto grid = openvdb::tools::meshToSignedDistanceField( 86 | *openvdb::math::Transform::createLinearTransform(voxel_size), 87 | verts, faces, std::vector(), grid_resolution / 4.0, grid_resolution / 4.0 88 | ); 89 | openvdb::tools::signedFloodFill(grid->tree()); 90 | 91 | std::vector>> sdf_grids; 92 | std::vector>> sdf_grids_grad; 93 | sdf_grids.resize(grid_resolution + 1); 94 | sdf_grids_grad.resize(grid_resolution + 1); 95 | for (int i = 0; i < sdf_grids.size(); i++) 96 | { 97 | sdf_grids[i].resize(grid_resolution + 1); 98 | sdf_grids_grad[i].resize(grid_resolution + 1); 99 | for (int j = 0; j < sdf_grids.size(); j++) 100 | { 101 | sdf_grids[i][j].resize(grid_resolution + 1); 102 | sdf_grids_grad[i][j].resize(grid_resolution + 1); 103 | std::fill(sdf_grids[i][j].begin(), sdf_grids[i][j].end(), -10000); 104 | std::fill(sdf_grids_grad[i][j].begin(), sdf_grids_grad[i][j].end(), openvdb::math::Vec3s(-10000, -10000, -10000)); 105 | } 106 | } 107 | auto grad_field = openvdb::tools::gradient(*grid); 108 | //openvdb::tools::signedFloodFill(grad_field->tree()); 109 | myassert(grid_resolution % 2 == 0); 110 | int half_grid_resolution = grid_resolution / 2; 111 | 112 | int filled_value_count = 0; 113 | int maxx, maxy, maxz; 114 | int minx, miny, minz; 115 | maxx = maxy = maxz = -1; 116 | minx = miny = minz = 1; 117 | 118 | //extract sdf samples with fabs(sdf) < sdf_truncation 119 | const double sdf_truncation = 2.0 / 16; 120 | std::vector sampled_points; 121 | sampled_points.clear(); 122 | 123 | //iterate the active voxels of grid 124 | // Iterate over all active values but don't allow them to be changed. 125 | for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) { 126 | const float& value = *iter; 127 | if (iter.isVoxelValue()) { 128 | //if (value <= 0) 129 | // std::cout << iter.getCoord() << " value: " << value << std::endl; 130 | int x, y, z; 131 | x = iter.getCoord().x(); 132 | y = iter.getCoord().y(); 133 | z = iter.getCoord().z(); 134 | 135 | maxx = std::max(x, maxx); 136 | maxy = std::max(y, maxy); 137 | maxz = std::max(z, maxz); 138 | 139 | minx = std::min(x, minx); 140 | miny = std::min(y, miny); 141 | minz = std::min(z, minz); 142 | 143 | x += half_grid_resolution; 144 | y += half_grid_resolution; 145 | z += half_grid_resolution; 146 | 147 | if (x >= 0 && y >= 0 && z >= 0 && x <= grid_resolution && y <= grid_resolution && z <= grid_resolution) 148 | { 149 | filled_value_count++; 150 | sdf_grids[x][y][z] = value; 151 | if (fabs(value) < sdf_truncation) 152 | { 153 | //aggressive generate more samples near surface 154 | if(fabs(value) < sdf_truncation / 4) 155 | sampled_points.push_back(OpenMesh::Vec3i(x, y, z)); 156 | else if(x % 4 == 0 && y % 4 == 0 && z % 4 == 0) 157 | sampled_points.push_back(OpenMesh::Vec3i(x, y, z)); 158 | else if(fabs(value) < sdf_truncation / 2 && x % 2 == 0 && y % 2 == 0 && z % 2 == 0) 159 | sampled_points.push_back(OpenMesh::Vec3i(x, y, z)); 160 | } 161 | } 162 | } 163 | else 164 | { 165 | std::cout << "Not voxel node sdf value" << std::endl; 166 | //int junk; 167 | //std::cin >> junk; 168 | } 169 | } 170 | std::cout << maxx << "," << maxy << "," << maxz << "\n"; 171 | std::cout << minx << "," << miny << "," << minz << "\n"; 172 | std::cout << sampled_points.size() << " points with |sdf| < " << sdf_truncation << "\n"; 173 | 174 | int filled_grad_count = 0; 175 | for (openvdb::Vec3SGrid::ValueOnCIter iter = grad_field->cbeginValueOn(); iter.test(); ++iter) { 176 | const openvdb::math::Vec3s& value = *iter; 177 | if (iter.isVoxelValue()) { 178 | //if (value <= 0) 179 | // std::cout << iter.getCoord() << " value: " << value << std::endl; 180 | int x, y, z; 181 | x = iter.getCoord().x(); 182 | y = iter.getCoord().y(); 183 | z = iter.getCoord().z(); 184 | 185 | maxx = std::max(x, maxx); 186 | maxy = std::max(y, maxy); 187 | maxz = std::max(z, maxz); 188 | 189 | minx = std::min(x, minx); 190 | miny = std::min(y, miny); 191 | minz = std::min(z, minz); 192 | 193 | x += half_grid_resolution; 194 | y += half_grid_resolution; 195 | z += half_grid_resolution; 196 | 197 | if (x >= 0 && y >= 0 && z >= 0 && x <= grid_resolution && y <= grid_resolution && z <= grid_resolution) 198 | { 199 | filled_grad_count++; 200 | sdf_grids_grad[x][y][z] = value; 201 | } 202 | } 203 | else 204 | { 205 | //std::cout << "Not voxel node sdf grad" << std::endl; 206 | //int junk; 207 | //std::cin >> junk; 208 | } 209 | } 210 | 211 | auto valid_grid_coeff = [](int x, int y, int z, int max) 212 | { 213 | if (x < 0 || y < 0 || z < 0) 214 | return false; 215 | if (x > max || y > max || z > max) 216 | return false; 217 | return true; 218 | }; 219 | 220 | int coordx, coordy, coordz; 221 | FILE *wf = fopen(sdf_filename, "wb"); 222 | std::vector buffered_data; 223 | buffered_data.reserve(sampled_points.size() * 7); 224 | for (int i = 0; i < sampled_points.size(); i++) 225 | { 226 | OpenMesh::Vec3i point = sampled_points[i]; 227 | coordx = point[0]; 228 | coordy = point[1]; 229 | coordz = point[2]; 230 | myassert(fabs(sdf_grids[coordx][coordy][coordz]) < sdf_truncation); 231 | if (sdf_grids_grad[coordx][coordy][coordz].x() < -1000) 232 | { 233 | //openvdb does not generate gradient we want 234 | float gradx, grady, gradz; 235 | //center difference 236 | //x 237 | if (valid_grid_coeff(coordx - 1, coordy, coordz, grid_resolution)) 238 | { 239 | myassert(sdf_grids[coordx - 1][coordy][coordz] > -1000); 240 | if (valid_grid_coeff(coordx + 1, coordy, coordz, grid_resolution)) 241 | { 242 | myassert(sdf_grids[coordx + 1][coordy][coordz] > -1000); 243 | //center difference 244 | gradx = (sdf_grids[coordx + 1][coordy][coordz] - sdf_grids[coordx - 1][coordy][coordz]) / 2.0; 245 | } 246 | else 247 | gradx = sdf_grids[coordx][coordy][coordz] - sdf_grids[coordx - 1][coordy][coordz]; 248 | } 249 | else 250 | { 251 | gradx = sdf_grids[coordx + 1][coordy][coordz] - sdf_grids[coordx][coordy][coordz]; 252 | } 253 | 254 | //y 255 | if (valid_grid_coeff(coordx, coordy - 1, coordz, grid_resolution)) 256 | { 257 | myassert(sdf_grids[coordx][coordy - 1][coordz] > -1000); 258 | if (valid_grid_coeff(coordx, coordy + 1, coordz, grid_resolution)) 259 | { 260 | myassert(sdf_grids[coordx][coordy + 1][coordz] > -1000); 261 | //center difference 262 | grady = (sdf_grids[coordx][coordy + 1][coordz] - sdf_grids[coordx][coordy - 1][coordz]) / 2.0; 263 | } 264 | else 265 | grady = sdf_grids[coordx][coordy][coordz] - sdf_grids[coordx][coordy - 1][coordz]; 266 | } 267 | else 268 | { 269 | grady = sdf_grids[coordx][coordy + 1][coordz] - sdf_grids[coordx][coordy][coordz]; 270 | } 271 | 272 | //z 273 | if (valid_grid_coeff(coordx, coordy, coordz - 1, grid_resolution)) 274 | { 275 | myassert(sdf_grids[coordx][coordy][coordz - 1] > -1000); 276 | if (valid_grid_coeff(coordx, coordy, coordz + 1, grid_resolution)) 277 | { 278 | myassert(sdf_grids[coordx][coordy][coordz + 1] > -1000); 279 | //center difference 280 | gradz = (sdf_grids[coordx][coordy][coordz + 1] - sdf_grids[coordx][coordy][coordz - 1]) / 2.0; 281 | } 282 | else 283 | gradz = sdf_grids[coordx][coordy][coordz] - sdf_grids[coordx][coordy][coordz - 1]; 284 | } 285 | else 286 | { 287 | gradz = sdf_grids[coordx][coordy][coordz + 1] - sdf_grids[coordx][coordy][coordz]; 288 | } 289 | 290 | sdf_grids_grad[coordx][coordy][coordz] = openvdb::math::Vec3s(gradx, grady, gradz); 291 | sdf_grids_grad[coordx][coordy][coordz].normalize(); 292 | } 293 | myassert(sdf_grids_grad[coordx][coordy][coordz].x() > -1000); 294 | sdf_grids_grad[coordx][coordy][coordz].normalize(); 295 | buffered_data.push_back(2.0*coordx / grid_resolution - 1); 296 | buffered_data.push_back(2.0*coordy / grid_resolution - 1); 297 | buffered_data.push_back(2.0*coordz / grid_resolution - 1); 298 | buffered_data.push_back(sdf_grids[coordx][coordy][coordz]); 299 | buffered_data.push_back(sdf_grids_grad[coordx][coordy][coordz].x()); 300 | buffered_data.push_back(sdf_grids_grad[coordx][coordy][coordz].y()); 301 | buffered_data.push_back(sdf_grids_grad[coordx][coordy][coordz].z()); 302 | } 303 | fwrite(&(buffered_data[0]), sizeof(float), buffered_data.size(), wf); 304 | fclose(wf); 305 | 306 | printf("done\n"); 307 | } 308 | 309 | int main(int argc, char *argv[]) 310 | { 311 | if (argc != 3 && argc != 4) 312 | { 313 | printf("Usage: vdb_tsdf model_obj_filename output_sdf_filename [resolution=256]\n"); 314 | return 0; 315 | } 316 | int grid_resolution = 256; 317 | 318 | if (argc >= 4) 319 | { 320 | sscanf(argv[3], "%d", &grid_resolution); 321 | printf("grid resolution set to %d\n", grid_resolution); 322 | } 323 | 324 | openvdb::initialize(); 325 | fetch_sdf(argv[1], argv[2], grid_resolution); 326 | std::cout << "Finished converting mesh." << std::endl; 327 | return 0; 328 | } --------------------------------------------------------------------------------