diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/damaged-building data/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/damaged-building data/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/damaged-building data/download" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/damaged-building data/download" new file mode 100644 index 0000000000000000000000000000000000000000..0e817f93949e3050bbde61e84b565ed51c65a405 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/data/damaged-building data/download" @@ -0,0 +1,2 @@ +链接:https://pan.baidu.com/s/15n8BRLCaa-N3joRPzXZsTg +提取码:79d7 \ No newline at end of file diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/evaluate.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/evaluate.py" new file mode 100644 index 0000000000000000000000000000000000000000..abc5a521d56a45c2854e0e0fb34eaaa57f8852a7 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/evaluate.py" @@ -0,0 +1,166 @@ +''' + Evaluate classification performance with optional voting. + Will use H5 dataset in default. If using normal, will shift to the normal dataset. +''' +import tensorflow as tf +import numpy as np +import argparse +import socket +import importlib +import time +import os +import scipy.misc +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = BASE_DIR +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(ROOT_DIR, 'models')) +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import provider +import modelnet_dataset +import modelnet_h5_dataset + +parser = argparse.ArgumentParser() +parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') +parser.add_argument('--model', default='pointnet2_cls_ssg', help='Model name. [default: pointnet2_cls_ssg]') +parser.add_argument('--batch_size', type=int, default=4, help='Batch Size during training [default: 16]') + + +parser.add_argument('--num_point', type=int, default=1024, help='Point Number [256/512/1024/2048] [default: 1024]') +parser.add_argument('--model_path', default='log/model.ckpt', help='model checkpoint file path [default: log/model.ckpt]') +parser.add_argument('--dump_dir', default='dump', help='dump folder path [dump]') +parser.add_argument('--normal', action='store_true', help='Whether to use normal information') +parser.add_argument('--num_votes', type=int, default=1, help='Aggregate classification scores from multiple rotations [default: 1]') +FLAGS = parser.parse_args() + + +BATCH_SIZE = FLAGS.batch_size +NUM_POINT = FLAGS.num_point +MODEL_PATH = FLAGS.model_path +GPU_INDEX = FLAGS.gpu +MODEL = importlib.import_module(FLAGS.model) # import network module +DUMP_DIR = FLAGS.dump_dir +if not os.path.exists(DUMP_DIR): os.mkdir(DUMP_DIR) +LOG_FOUT = open(os.path.join(DUMP_DIR, 'log_evaluate.txt'), 'w') +LOG_FOUT.write(str(FLAGS)+'\n') + +NUM_CLASSES = 3 +SHAPE_NAMES = [line.rstrip() for line in \ + open(os.path.join(ROOT_DIR, 'data/modelnet40_ply_hdf5_2048/shape_names.txt'))] + +HOSTNAME = socket.gethostname() + +# Shapenet official train/test split +if FLAGS.normal: + assert(NUM_POINT<=10000) + DATA_PATH = os.path.join(ROOT_DIR, 'data/modelnet40_normal_resampled') + TRAIN_DATASET = modelnet_dataset.ModelNetDataset(root=DATA_PATH, npoints=NUM_POINT, split='train', normal_channel=FLAGS.normal, batch_size=BATCH_SIZE) + TEST_DATASET = modelnet_dataset.ModelNetDataset(root=DATA_PATH, npoints=NUM_POINT, split='test', normal_channel=FLAGS.normal, batch_size=BATCH_SIZE) +else: + assert(NUM_POINT<=2048) + TRAIN_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt'), batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=True) + TEST_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt'), batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=False) + +def log_string(out_str): + LOG_FOUT.write(out_str+'\n') + LOG_FOUT.flush() + print(out_str) + +def evaluate(num_votes): + is_training = False + + with tf.device('/gpu:'+str(GPU_INDEX)): + pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) + is_training_pl = tf.placeholder(tf.bool, shape=()) + + # simple model + pred, end_points = MODEL.get_model(pointclouds_pl, is_training_pl) + MODEL.get_loss(pred, labels_pl, end_points) + losses = tf.get_collection('losses') + total_loss = tf.add_n(losses, name='total_loss') + + # Add ops to save and restore all the variables. + saver = tf.train.Saver() + + # Create a session + config = tf.ConfigProto() + config.gpu_options.allow_growth = True + config.allow_soft_placement = True + config.log_device_placement = False + sess = tf.Session(config=config) + + # Restore variables from disk. + saver.restore(sess, MODEL_PATH) + log_string("Model restored.") + + ops = {'pointclouds_pl': pointclouds_pl, + 'labels_pl': labels_pl, + 'is_training_pl': is_training_pl, + 'pred': pred, + 'loss': total_loss} + + eval_one_epoch(sess, ops, num_votes) + +def eval_one_epoch(sess, ops, num_votes=1, topk=1): + is_training = False + + # Make sure batch data is of same size + cur_batch_data = np.zeros((BATCH_SIZE,NUM_POINT,TEST_DATASET.num_channel())) + cur_batch_label = np.zeros((BATCH_SIZE), dtype=np.int32) + + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx = 0 + shape_ious = [] + total_seen_class = [0 for _ in range(NUM_CLASSES)] + total_correct_class = [0 for _ in range(NUM_CLASSES)] + + while TEST_DATASET.has_next_batch(): + batch_data, batch_label = TEST_DATASET.next_batch(augment=False) + bsize = batch_data.shape[0] + print('Batch: %03d, batch size: %d'%(batch_idx, bsize)) + # for the last batch in the epoch, the bsize:end are from last batch + cur_batch_data[0:bsize,...] = batch_data + cur_batch_label[0:bsize] = batch_label + + batch_pred_sum = np.zeros((BATCH_SIZE, NUM_CLASSES)) # score for classes + for vote_idx in range(num_votes): + # Shuffle point order to achieve different farthest samplings + shuffled_indices = np.arange(NUM_POINT) + np.random.shuffle(shuffled_indices) + if FLAGS.normal: + rotated_data = provider.rotate_point_cloud_by_angle_with_normal(cur_batch_data[:, shuffled_indices, :], + vote_idx/float(num_votes) * np.pi * 2) + else: + rotated_data = provider.rotate_point_cloud_by_angle(cur_batch_data[:, shuffled_indices, :], + vote_idx/float(num_votes) * np.pi * 2) + feed_dict = {ops['pointclouds_pl']: rotated_data, + ops['labels_pl']: cur_batch_label, + ops['is_training_pl']: is_training} + loss_val, pred_val = sess.run([ops['loss'], ops['pred']], feed_dict=feed_dict) + batch_pred_sum += pred_val + pred_val = np.argmax(batch_pred_sum, 1) + correct = np.sum(pred_val[0:bsize] == batch_label[0:bsize]) + total_correct += correct + total_seen += bsize + loss_sum += loss_val + batch_idx += 1 + for i in range(bsize): + l = batch_label[i] + total_seen_class[l] += 1 + total_correct_class[l] += (pred_val[i] == l) + + log_string('eval mean loss: %f' % (loss_sum / float(batch_idx))) + log_string('eval accuracy: %f'% (total_correct / float(total_seen))) + log_string('eval avg class acc: %f' % (np.mean(np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float)))) + + class_accuracies = np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float) + for i, name in enumerate(SHAPE_NAMES): + log_string('%10s:\t%0.3f' % (name, class_accuracies[i])) + + +if __name__=='__main__': + with tf.Graph().as_default(): + evaluate(num_votes=FLAGS.num_votes) + LOG_FOUT.close() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_dataset.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_dataset.py" new file mode 100644 index 0000000000000000000000000000000000000000..78f326e080724ef2735fc52a57639bd42bf65b55 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_dataset.py" @@ -0,0 +1,144 @@ +''' + ModelNet dataset. Support ModelNet40, ModelNet10, XYZ and normal channels. Up to 10000 points. +''' + +import os +import os.path +import json +import numpy as np +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = BASE_DIR +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import provider + +def pc_normalize(pc): + l = pc.shape[0] + centroid = np.mean(pc, axis=0) + pc = pc - centroid + m = np.max(np.sqrt(np.sum(pc**2, axis=1))) + pc = pc / m + return pc + +class ModelNetDataset(): + def __init__(self, root, batch_size = 32, npoints = 1024, split='train', normalize=True, normal_channel=False, modelnet10=False, cache_size=15000, shuffle=None): + self.root = root + self.batch_size = batch_size + self.npoints = npoints + self.normalize = normalize + if modelnet10: + self.catfile = os.path.join(self.root, 'modelnet10_shape_names.txt') + else: + self.catfile = os.path.join(self.root, 'shape_names.txt') + self.cat = [line.rstrip() for line in open(self.catfile)] + self.classes = dict(zip(self.cat, range(len(self.cat)))) + self.normal_channel = normal_channel + + shape_ids = {} + if modelnet10: + shape_ids['train'] = [line.rstrip() for line in open(os.path.join(self.root, 'modelnet10_train.txt'))] + shape_ids['test']= [line.rstrip() for line in open(os.path.join(self.root, 'modelnet10_test.txt'))] + else: + shape_ids['train'] = [line.rstrip() for line in open(os.path.join(self.root, 'modelnet40_train.txt'))] + shape_ids['test']= [line.rstrip() for line in open(os.path.join(self.root, 'modelnet40_test.txt'))] + assert(split=='train' or split=='test') + shape_names = ['_'.join(x.split('_')[0:-1]) for x in shape_ids[split]] + # list of (shape_name, shape_txt_file_path) tuple + self.datapath = [(shape_names[i], os.path.join(self.root, shape_names[i], shape_ids[split][i])+'.txt') for i in range(len(shape_ids[split]))] + + self.cache_size = cache_size # how many data points to cache in memory + self.cache = {} # from index to (point_set, cls) tuple + + if shuffle is None: + if split == 'train': self.shuffle = True + else: self.shuffle = False + else: + self.shuffle = shuffle + + self.reset() + + def _augment_batch_data(self, batch_data): + if self.normal_channel: + rotated_data = provider.rotate_point_cloud_with_normal(batch_data) + rotated_data = provider.rotate_perturbation_point_cloud_with_normal(rotated_data) + else: + rotated_data = provider.rotate_point_cloud(batch_data) + rotated_data = provider.rotate_perturbation_point_cloud(rotated_data) + + jittered_data = provider.random_scale_point_cloud(rotated_data[:,:,0:3]) + jittered_data = provider.shift_point_cloud(jittered_data) + jittered_data = provider.jitter_point_cloud(jittered_data) + rotated_data[:,:,0:3] = jittered_data + return provider.shuffle_points(rotated_data) + + + def _get_item(self, index): + if index in self.cache: + point_set, cls = self.cache[index] + else: + fn = self.datapath[index] + cls = self.classes[self.datapath[index][0]] + cls = np.array([cls]).astype(np.int32) + point_set = np.loadtxt(fn[1],delimiter=',').astype(np.float32) + # Take the first npoints + point_set = point_set[0:self.npoints,:] + if self.normalize: + point_set[:,0:3] = pc_normalize(point_set[:,0:3]) + if not self.normal_channel: + point_set = point_set[:,0:3] + if len(self.cache) < self.cache_size: + self.cache[index] = (point_set, cls) + return point_set, cls + + def __getitem__(self, index): + return self._get_item(index) + + def __len__(self): + return len(self.datapath) + + def num_channel(self): + if self.normal_channel: + return 6 + else: + return 3 + + def reset(self): + self.idxs = np.arange(0, len(self.datapath)) + if self.shuffle: + np.random.shuffle(self.idxs) + self.num_batches = (len(self.datapath)+self.batch_size-1) // self.batch_size + self.batch_idx = 0 + + def has_next_batch(self): + return self.batch_idx < self.num_batches + + def next_batch(self, augment=False): + ''' returned dimension may be smaller than self.batch_size ''' + start_idx = self.batch_idx * self.batch_size + end_idx = min((self.batch_idx+1) * self.batch_size, len(self.datapath)) + bsize = end_idx - start_idx + batch_data = np.zeros((bsize, self.npoints, self.num_channel())) + batch_label = np.zeros((bsize), dtype=np.int32) + for i in range(bsize): + ps,cls = self._get_item(self.idxs[i+start_idx]) + batch_data[i] = ps + batch_label[i] = cls + self.batch_idx += 1 + if augment: batch_data = self._augment_batch_data(batch_data) + return batch_data, batch_label + +if __name__ == '__main__': + d = ModelNetDataset(root = '../data/modelnet40_normal_resampled', split='test') + print(d.shuffle) + print(len(d)) + import time + tic = time.time() + for i in range(10): + ps, cls = d[i] + print(time.time() - tic) + print(ps.shape, type(ps), cls) + + print(d.has_next_batch()) + ps_batch, cls_batch = d.next_batch(True) + print(ps_batch.shape) + print(cls_batch.shape) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_h5_dataset.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_h5_dataset.py" new file mode 100644 index 0000000000000000000000000000000000000000..ecd6e166e8cbbc27dd5d9d052760f8ce6efc6d79 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/modelnet_h5_dataset.py" @@ -0,0 +1,126 @@ +''' + ModelNet dataset. Support ModelNet40, XYZ channels. Up to 2048 points. + Faster IO than ModelNetDataset in the first epoch. +''' + +import os +import sys +import numpy as np +import h5py +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) +ROOT_DIR = BASE_DIR +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import provider + + +# Download dataset for point cloud classification +DATA_DIR = os.path.join(ROOT_DIR, 'data') +if not os.path.exists(DATA_DIR): + os.mkdir(DATA_DIR) +if not os.path.exists(os.path.join(DATA_DIR, 'modelnet40_ply_hdf5_2048')): + www = 'https://shapenet.cs.stanford.edu/media/modelnet40_ply_hdf5_2048.zip' + zipfile = os.path.basename(www) + os.system('wget %s; unzip %s' % (www, zipfile)) + os.system('mv %s %s' % (zipfile[:-4], DATA_DIR)) + os.system('rm %s' % (zipfile)) + + +def shuffle_data(data, labels): + """ Shuffle data and labels. + Input: + data: B,N,... numpy array + label: B,... numpy array + Return: + shuffled data, label and shuffle indices + """ + idx = np.arange(len(labels)) + np.random.shuffle(idx) + return data[idx, ...], labels[idx], idx + +def getDataFiles(list_filename): + return [line.rstrip() for line in open(list_filename)] + +def load_h5(h5_filename): + f = h5py.File(h5_filename) + data = f['data'][:] + label = f['label'][:] + return (data, label) + +def loadDataFile(filename): + return load_h5(filename) + + +class ModelNetH5Dataset(object): + def __init__(self, list_filename, batch_size = 32, npoints = 1024, shuffle=True): + self.list_filename = list_filename + self.batch_size = batch_size + self.npoints = npoints + self.shuffle = shuffle + self.h5_files = getDataFiles(self.list_filename) + self.reset() + + def reset(self): + ''' reset order of h5 files ''' + self.file_idxs = np.arange(0, len(self.h5_files)) + if self.shuffle: np.random.shuffle(self.file_idxs) + self.current_data = None + self.current_label = None + self.current_file_idx = 0 + self.batch_idx = 0 + + def _augment_batch_data(self, batch_data): + rotated_data = provider.rotate_point_cloud(batch_data) + rotated_data = provider.rotate_perturbation_point_cloud(rotated_data) + jittered_data = provider.random_scale_point_cloud(rotated_data[:,:,0:3]) + jittered_data = provider.shift_point_cloud(jittered_data) + jittered_data = provider.jitter_point_cloud(jittered_data) + rotated_data[:,:,0:3] = jittered_data + return provider.shuffle_points(rotated_data) + + + def _get_data_filename(self): + return self.h5_files[self.file_idxs[self.current_file_idx]] + + def _load_data_file(self, filename): + self.current_data,self.current_label = load_h5(filename) + self.current_label = np.squeeze(self.current_label) + self.batch_idx = 0 + if self.shuffle: + self.current_data, self.current_label, _ = shuffle_data(self.current_data,self.current_label) + + def _has_next_batch_in_file(self): + return self.batch_idx*self.batch_size < self.current_data.shape[0] + + def num_channel(self): + return 3 + + def has_next_batch(self): + # TODO: add backend thread to load data + if (self.current_data is None) or (not self._has_next_batch_in_file()): + if self.current_file_idx >= len(self.h5_files): + return False + self._load_data_file(self._get_data_filename()) + self.batch_idx = 0 + self.current_file_idx += 1 + return self._has_next_batch_in_file() + + def next_batch(self, augment=False): + ''' returned dimension may be smaller than self.batch_size ''' + start_idx = self.batch_idx * self.batch_size + end_idx = min((self.batch_idx+1) * self.batch_size, self.current_data.shape[0]) + bsize = end_idx - start_idx + batch_label = np.zeros((bsize), dtype=np.int32) + data_batch = self.current_data[start_idx:end_idx, 0:self.npoints, :].copy() + label_batch = self.current_label[start_idx:end_idx].copy() + self.batch_idx += 1 + if augment: data_batch = self._augment_batch_data(data_batch) + return data_batch, label_batch + +if __name__=='__main__': + d = ModelNetH5Dataset('data/modelnet40_ply_hdf5_2048/train_files.txt') + print(d.shuffle) + print(d.has_next_batch()) + ps_batch, cls_batch = d.next_batch(True) + print(ps_batch.shape) + print(cls_batch.shape) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/models/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/models/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/models/pointnet2_cls_ssg.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/models/pointnet2_cls_ssg.py" new file mode 100644 index 0000000000000000000000000000000000000000..1e845ff342bd7c101cb04c06a6aeebaa74cdffed --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/models/pointnet2_cls_ssg.py" @@ -0,0 +1,62 @@ +""" + PointNet++ Model for point clouds classification +""" + +import os +import sys +BASE_DIR = os.path.dirname(__file__) +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(BASE_DIR, '../utils')) +import tensorflow as tf +import numpy as np +import tf_util +from pointnet_util import pointnet_sa_module + +def placeholder_inputs(batch_size, num_point): + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) + labels_pl = tf.placeholder(tf.int32, shape=(batch_size)) + return pointclouds_pl, labels_pl + +def get_model(point_cloud, is_training, bn_decay=None): + """ Classification PointNet, input is BxNx3, output Bx40 """ + batch_size = point_cloud.get_shape()[0].value + num_point = point_cloud.get_shape()[1].value + end_points = {} + l0_xyz = point_cloud + l0_points = None + end_points['l0_xyz'] = l0_xyz + + # Set abstraction layers + # Note: When using NCHW for layer 2, we see increased GPU memory usage (in TF1.4). + # So we only use NCHW for layer 1 until this issue can be resolved. + l1_xyz, l1_points, l1_indices = pointnet_sa_module(l0_xyz, l0_points, npoint=512, radius=0.2, nsample=32, mlp=[64,64,128], mlp2=None, group_all=False, is_training=is_training, bn_decay=bn_decay, scope='layer1', use_nchw=True) + l2_xyz, l2_points, l2_indices = pointnet_sa_module(l1_xyz, l1_points, npoint=128, radius=0.4, nsample=64, mlp=[128,128,256], mlp2=None, group_all=False, is_training=is_training, bn_decay=bn_decay, scope='layer2') + l3_xyz, l3_points, l3_indices = pointnet_sa_module(l2_xyz, l2_points, npoint=None, radius=None, nsample=None, mlp=[256,512,1024], mlp2=None, group_all=True, is_training=is_training, bn_decay=bn_decay, scope='layer3') + + # Fully connected layers + net = tf.reshape(l3_points, [batch_size, -1]) + net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, scope='fc1', bn_decay=bn_decay) + net = tf_util.dropout(net, keep_prob=0.5, is_training=is_training, scope='dp1') + net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training, scope='fc2', bn_decay=bn_decay) + net = tf_util.dropout(net, keep_prob=0.5, is_training=is_training, scope='dp2') + net = tf_util.fully_connected(net, 40, activation_fn=None, scope='fc3') + + return net, end_points + + +def get_loss(pred, label, end_points): + """ pred: B*NUM_CLASSES, + label: B, """ + loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=pred, labels=label) + classify_loss = tf.reduce_mean(loss) + tf.summary.scalar('classify loss', classify_loss) + tf.add_to_collection('losses', classify_loss) + return classify_loss + + +if __name__=='__main__': + with tf.Graph().as_default(): + inputs = tf.zeros((32,1024,3)) + output, _ = get_model(inputs, tf.constant(True)) + print(output) + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/interpolate.cpp" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/interpolate.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..b7d0dd022860ed87378d8a8ac35c3005cb165d50 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/interpolate.cpp" @@ -0,0 +1,169 @@ +#include +#include +#include // memset +#include // rand, RAND_MAX +#include // sqrtf +#include +#include +using namespace std; +float randomf(){ + return (rand()+0.5)/(RAND_MAX+1.0); +} +static double get_time(){ + timespec tp; + clock_gettime(CLOCK_MONOTONIC,&tp); + return tp.tv_sec+tp.tv_nsec*1e-9; +} + +// Find three nearest neigbors with square distance +// input: xyz1 (b,n,3), xyz2(b,m,3) +// output: dist (b,n,3), idx (b,n,3) +void threenn_cpu(int b, int n, int m, const float *xyz1, const float *xyz2, float *dist, int *idx) { + for (int i=0;i +#include +#include // memset +#include // rand, RAND_MAX +#include // sqrtf +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/framework/common_shape_fns.h" + +#pragma GCC diagnostic ignored "-Wunused-result" + +using namespace tensorflow; + +REGISTER_OP("ThreeNN") + .Input("xyz1: float32") + .Input("xyz2: float32") + .Output("dist: float32") + .Output("idx: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + c->set_output(1, c->input(0)); + return Status::OK(); + }); +REGISTER_OP("ThreeInterpolate") + .Input("points: float32") + .Input("idx: int32") + .Input("weight: float32") + .Output("out: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims1; // (b,m,c) + c->WithRank(c->input(0), 3, &dims1); + ::tensorflow::shape_inference::ShapeHandle dims2; // (b,n,3) + c->WithRank(c->input(1), 3, &dims2); + // (b,n,c) + ::tensorflow::shape_inference::ShapeHandle output = c->MakeShape({c->Dim(dims1, 0), c->Dim(dims2, 1), c->Dim(dims1, 2)}); + c->set_output(0, output); + return Status::OK(); + }); +REGISTER_OP("ThreeInterpolateGrad") + .Input("points: float32") + .Input("idx: int32") + .Input("weight: float32") + .Input("grad_out: float32") + .Output("grad_points: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }); + +float randomf(){ + return (rand()+0.5)/(RAND_MAX+1.0); +} +static double get_time(){ + timespec tp; + clock_gettime(CLOCK_MONOTONIC,&tp); + return tp.tv_sec+tp.tv_nsec*1e-9; +} + +// Find three nearest neigbors with square distance +// input: xyz1 (b,n,3), xyz2(b,m,3) +// output: dist (b,n,3), idx (b,n,3) +void threenn_cpu(int b, int n, int m, const float *xyz1, const float *xyz2, float *dist, int *idx) { + for (int i=0;iinput(0); + OP_REQUIRES(context, xyz1_tensor.dims()==3 && xyz1_tensor.shape().dim_size(2)==3, errors::InvalidArgument("ThreeNN expects (b,n,3) xyz1 shape.")); + int b = xyz1_tensor.shape().dim_size(0); + int n = xyz1_tensor.shape().dim_size(1); + + const Tensor& xyz2_tensor = context->input(1); + OP_REQUIRES(context, xyz2_tensor.dims()==3 && xyz2_tensor.shape().dim_size(2)==3, errors::InvalidArgument("ThreeNN expects (b,m,3) xyz2 shape.")); + int m = xyz2_tensor.shape().dim_size(1); + + Tensor *dist_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape{b,n,3}, &dist_tensor)); + Tensor *idx_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(1, TensorShape{b,n,3}, &idx_tensor)); + + auto xyz1_flat = xyz1_tensor.flat(); + const float *xyz1 = &(xyz1_flat(0)); + auto xyz2_flat = xyz2_tensor.flat(); + const float *xyz2 = &(xyz2_flat(0)); + auto dist_flat = dist_tensor->flat(); + float *dist = &(dist_flat(0)); + auto idx_flat = idx_tensor->flat(); + int *idx = &(idx_flat(0)); + threenn_cpu(b,n,m,xyz1,xyz2,dist,idx); + } +}; +REGISTER_KERNEL_BUILDER(Name("ThreeNN").Device(DEVICE_CPU), ThreeNNOp); + + + +class ThreeInterpolateOp: public OpKernel{ + public: + explicit ThreeInterpolateOp(OpKernelConstruction * context):OpKernel(context){} + + void Compute(OpKernelContext * context) override { + const Tensor& points_tensor=context->input(0); + OP_REQUIRES(context, points_tensor.dims()==3, errors::InvalidArgument("ThreeInterpolate expects (b,m,c) points shape")); + int b = points_tensor.shape().dim_size(0); + int m = points_tensor.shape().dim_size(1); + int c = points_tensor.shape().dim_size(2); + + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==3 && idx_tensor.shape().dim_size(0)==b && idx_tensor.shape().dim_size(2)==3, errors::InvalidArgument("ThreeInterpolate expects (b,n,3) idx shape")); + int n = idx_tensor.shape().dim_size(1); + const Tensor& weight_tensor=context->input(2); + OP_REQUIRES(context,weight_tensor.dims()==3 && weight_tensor.shape().dim_size(0)==b && weight_tensor.shape().dim_size(1)==n && weight_tensor.shape().dim_size(2)==3, errors::InvalidArgument("ThreeInterpolate expects (b,n,3) weight shape")); + + Tensor * out_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0,TensorShape{b,n,c}, &out_tensor)); + + auto points_flat = points_tensor.flat(); + const float *points = &(points_flat(0)); + auto idx_flat = idx_tensor.flat(); + const int *idx = &(idx_flat(0)); + auto weight_flat = weight_tensor.flat(); + const float *weight = &(weight_flat(0)); + auto out_flat = out_tensor->flat(); + float *out = &(out_flat(0)); + threeinterpolate_cpu(b,m,c,n,points,idx,weight,out); + } +}; +REGISTER_KERNEL_BUILDER(Name("ThreeInterpolate").Device(DEVICE_CPU),ThreeInterpolateOp); + + +class ThreeInterpolateGradOp: public OpKernel{ + public: + explicit ThreeInterpolateGradOp(OpKernelConstruction * context):OpKernel(context){} + + void Compute(OpKernelContext * context) override { + const Tensor& points_tensor=context->input(0); + OP_REQUIRES(context, points_tensor.dims()==3, errors::InvalidArgument("ThreeInterpolateGrad expects (b,m,c) points shape")); + int b = points_tensor.shape().dim_size(0); + int m = points_tensor.shape().dim_size(1); + int c = points_tensor.shape().dim_size(2); + + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==3 && idx_tensor.shape().dim_size(0)==b, errors::InvalidArgument("ThreeInterpolateGrad expects (b,n,3) idx shape")); + int n = idx_tensor.shape().dim_size(1); + const Tensor& weight_tensor=context->input(2); + OP_REQUIRES(context,weight_tensor.dims()==3 && weight_tensor.shape().dim_size(0)==b && weight_tensor.shape().dim_size(1)==n && weight_tensor.shape().dim_size(2)==3, errors::InvalidArgument("ThreeInterpolateGrad expects (b,n,3) weight shape")); + + const Tensor& grad_out_tensor=context->input(3); + OP_REQUIRES(context,grad_out_tensor.dims()==3 && grad_out_tensor.shape().dim_size(0)==b && grad_out_tensor.shape().dim_size(1)==n && grad_out_tensor.shape().dim_size(2)==c, errors::InvalidArgument("ThreeInterpolateGrad expects (b,n,c) grad_out shape")); + + Tensor * grad_points_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0,TensorShape{b,m,c}, &grad_points_tensor)); + + auto points_flat = points_tensor.flat(); + const float *points = &(points_flat(0)); + auto idx_flat = idx_tensor.flat(); + const int *idx = &(idx_flat(0)); + auto weight_flat = weight_tensor.flat(); + const float *weight = &(weight_flat(0)); + auto grad_out_flat = grad_out_tensor.flat(); + const float *grad_out = &(grad_out_flat(0)); + auto grad_points_flat = grad_points_tensor->flat(); + float *grad_points = &(grad_points_flat(0)); + memset(grad_points, 0, sizeof(float)*b*m*c); + threeinterpolate_grad_cpu(b,n,c,m,grad_out,idx,weight,grad_points); + } +}; +REGISTER_KERNEL_BUILDER(Name("ThreeInterpolateGrad").Device(DEVICE_CPU),ThreeInterpolateGradOp); + + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate.py" new file mode 100644 index 0000000000000000000000000000000000000000..2ef1eddd98f251d4942215ca4212a1717257f4e7 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate.py" @@ -0,0 +1,59 @@ +import tensorflow as tf +from tensorflow.python.framework import ops +import sys +import os +BASE_DIR = os.path.dirname(__file__) +sys.path.append(BASE_DIR) +interpolate_module=tf.load_op_library(os.path.join(BASE_DIR, 'tf_interpolate_so.so')) +def three_nn(xyz1, xyz2): + ''' + Input: + xyz1: (b,n,3) float32 array, unknown points + xyz2: (b,m,3) float32 array, known points + Output: + dist: (b,n,3) float32 array, distances to known points + idx: (b,n,3) int32 array, indices to known points + ''' + return interpolate_module.three_nn(xyz1, xyz2) +ops.NoGradient('ThreeNN') +def three_interpolate(points, idx, weight): + ''' + Input: + points: (b,m,c) float32 array, known points + idx: (b,n,3) int32 array, indices to known points + weight: (b,n,3) float32 array, weights on known points + Output: + out: (b,n,c) float32 array, interpolated point values + ''' + return interpolate_module.three_interpolate(points, idx, weight) +@tf.RegisterGradient('ThreeInterpolate') +def _three_interpolate_grad(op, grad_out): + points = op.inputs[0] + idx = op.inputs[1] + weight = op.inputs[2] + return [interpolate_module.three_interpolate_grad(points, idx, weight, grad_out), None, None] + +if __name__=='__main__': + import numpy as np + import time + np.random.seed(100) + pts = np.random.random((32,128,64)).astype('float32') + tmp1 = np.random.random((32,512,3)).astype('float32') + tmp2 = np.random.random((32,128,3)).astype('float32') + with tf.device('/cpu:0'): + points = tf.constant(pts) + xyz1 = tf.constant(tmp1) + xyz2 = tf.constant(tmp2) + dist, idx = three_nn(xyz1, xyz2) + weight = tf.ones_like(dist)/3.0 + interpolated_points = three_interpolate(points, idx, weight) + with tf.Session('') as sess: + now = time.time() + for _ in range(100): + ret = sess.run(interpolated_points) + print (time.time() - now) + print (ret.shape, ret.dtype) + #print ret + + + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_compile.sh" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_compile.sh" new file mode 100644 index 0000000000000000000000000000000000000000..8af3cf74fce6f9120611754be9a3a6385dd21005 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_compile.sh" @@ -0,0 +1,7 @@ +# TF1.2 +#g++ -std=c++11 tf_interpolate.cpp -o tf_interpolate_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -lcudart -L /usr/local/cuda-8.0/lib64/ -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +# TF1.4 +#g++ -std=c++11 tf_interpolate.cpp -o tf_interpolate_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -I /usr/local/lib/python2.7/dist-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-8.0/lib64/ -L/usr/local/lib/python2.7/dist-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +g++ -std=c++11 tf_interpolate.cpp -o tf_interpolate_so.so -shared -fPIC -I /home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include -I /usr/local/cuda-9.0/include -I /home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-9.0/lib64/ -L/home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow -l:libtensorflow_framework.so -O2 -D_GLIBCXX_USE_CXX11_ABI=0 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_op_test.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_op_test.py" new file mode 100644 index 0000000000000000000000000000000000000000..030456df8bfcd291eee308b90494921b74d56e56 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_op_test.py" @@ -0,0 +1,24 @@ +import tensorflow as tf +import numpy as np +from tf_interpolate import three_nn, three_interpolate + +class GroupPointTest(tf.test.TestCase): + def test(self): + pass + + def test_grad(self): + with self.test_session(): + points = tf.constant(np.random.random((1,8,16)).astype('float32')) + print (points) + xyz1 = tf.constant(np.random.random((1,128,3)).astype('float32')) + xyz2 = tf.constant(np.random.random((1,8,3)).astype('float32')) + dist, idx = three_nn(xyz1, xyz2) + weight = tf.ones_like(dist)/3.0 + interpolated_points = three_interpolate(points, idx, weight) + print(interpolated_points) + err = tf.test.compute_gradient_error(points, (1,8,16), interpolated_points, (1,128,16)) + print (err) + self.assertLess(err, 1e-4) + +if __name__=='__main__': + tf.test.main() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_so.so" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_so.so" new file mode 100644 index 0000000000000000000000000000000000000000..66d67ca3c5d2c52602144e24fc4a95565690b9be Binary files /dev/null and "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/tf_interpolate_so.so" differ diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/visu_interpolation.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/visu_interpolation.py" new file mode 100644 index 0000000000000000000000000000000000000000..bc828200760092cd9578213f8bd103b9856579c7 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/3d_interpolation/visu_interpolation.py" @@ -0,0 +1,44 @@ +''' Visualize part segmentation ''' +import os +import sys +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append('/home/rqi/Projects/toolkits/visualization') +from show3d_balls import showpoints +import numpy as np +from tf_interpolate import three_nn, three_interpolate +import tensorflow as tf + + +pts2 = np.array([[0,0,1],[1,0,0],[0,1,0],[1,1,0]]).astype('float32') +xyz1 = np.random.random((100,3)).astype('float32') +xyz2 = np.array([[0,0,0],[1,0,0],[0,1,0],[1,1,1]]).astype('float32') + +def fun(xyz1,xyz2,pts2): + with tf.device('/cpu:0'): + points = tf.constant(np.expand_dims(pts2,0)) + xyz1 = tf.constant(np.expand_dims(xyz1,0)) + xyz2 = tf.constant(np.expand_dims(xyz2,0)) + dist, idx = three_nn(xyz1, xyz2) + #weight = tf.ones_like(dist)/3.0 + dist = tf.maximum(dist, 1e-10) + norm = tf.reduce_sum((1.0/dist),axis=2,keep_dims=True) + norm = tf.tile(norm, [1,1,3]) + print (norm) + weight = (1.0/dist) / norm + interpolated_points = three_interpolate(points, idx, weight) + with tf.Session('') as sess: + tmp,pts1,d,w = sess.run([xyz1, interpolated_points, dist, weight]) + #print w + pts1 = pts1.squeeze() + return pts1 + +pts1 = fun(xyz1,xyz2,pts2) +all_pts = np.zeros((104,3)) +all_pts[0:100,:] = pts1 +all_pts[100:,:] = pts2 +all_xyz = np.zeros((104,3)) +all_xyz[0:100,:]=xyz1 +all_xyz[100:,:]=xyz2 +showpoints(xyz2, pts2, ballradius=8) +showpoints(xyz1, pts1, ballradius=8) +showpoints(all_xyz, all_pts, ballradius=8) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/.gitignore" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/.gitignore" new file mode 100644 index 0000000000000000000000000000000000000000..2f08276060038482fce8e43233c26760b0032b46 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/.gitignore" @@ -0,0 +1,10 @@ +a.out +query_ball_point +query_ball_point_block +query_ball_point_cuda +query_ball_point_grid +tf_grouping_g.cu.o +tf_grouping_so.so +selection_sort +selection_sort_cuda +selection_sort_const_cuda diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.cpp" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..924be5fe4f8f8745d4c4b65ba7e75b5d56924c2f --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.cpp" @@ -0,0 +1,213 @@ +#include +#include +#include // memset +#include // rand, RAND_MAX +#include // sqrtf +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/framework/common_shape_fns.h" +#include + +#pragma GCC diagnostic ignored "-Wunused-result" + +using namespace tensorflow; + +REGISTER_OP("QueryBallPoint") + .Attr("radius: float") + .Attr("nsample: int") + .Input("xyz1: float32") + .Input("xyz2: float32") + .Output("idx: int32") + .Output("pts_cnt: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims2; // batch_size * npoint * 3 + c->WithRank(c->input(1), 3, &dims2); + int nsample; + TF_RETURN_IF_ERROR(c->GetAttr("nsample", &nsample)); + ::tensorflow::shape_inference::ShapeHandle output1 = c->MakeShape({c->Dim(dims2, 0), c->Dim(dims2, 1), nsample}); + c->set_output(0, output1); + ::tensorflow::shape_inference::ShapeHandle output2 = c->MakeShape({c->Dim(dims2, 0), c->Dim(dims2, 1)}); + c->set_output(1, output2); + return Status::OK(); + }); +REGISTER_OP("SelectionSort") + .Attr("k: int") + .Input("dist: float32") + .Output("outi: int32") + .Output("out: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + c->set_output(1, c->input(0)); + return Status::OK(); + }); +REGISTER_OP("GroupPoint") + .Input("points: float32") + .Input("idx: int32") + .Output("out: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims1; // batch_size * ndataset * channels + c->WithRank(c->input(0), 3, &dims1); + ::tensorflow::shape_inference::ShapeHandle dims2; // batch_size * npoints * nsample + c->WithRank(c->input(1), 3, &dims2); + // batch_size * npoints * nsample * channels + ::tensorflow::shape_inference::ShapeHandle output = c->MakeShape({c->Dim(dims2, 0), c->Dim(dims2, 1), c->Dim(dims2, 2), c->Dim(dims1, 2)}); + c->set_output(0, output); + return Status::OK(); + }); +REGISTER_OP("GroupPointGrad") + .Input("points: float32") + .Input("idx: int32") + .Input("grad_out: float32") + .Output("grad_points: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }); + + +void queryBallPointLauncher(int b, int n, int m, float radius, int nsample, const float *xyz1, const float *xyz2, int *idx, int *pts_cnt); +class QueryBallPointGpuOp : public OpKernel { + public: + explicit QueryBallPointGpuOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("radius", &radius_)); + OP_REQUIRES(context, radius_ > 0, errors::InvalidArgument("QueryBallPoint expects positive radius")); + + OP_REQUIRES_OK(context, context->GetAttr("nsample", &nsample_)); + OP_REQUIRES(context, nsample_ > 0, errors::InvalidArgument("QueryBallPoint expects positive nsample")); + } + + void Compute(OpKernelContext* context) override { + const Tensor& xyz1_tensor = context->input(0); + OP_REQUIRES(context, xyz1_tensor.dims()==3 && xyz1_tensor.shape().dim_size(2)==3, errors::InvalidArgument("QueryBallPoint expects (batch_size, ndataset, 3) xyz1 shape.")); + int b = xyz1_tensor.shape().dim_size(0); + int n = xyz1_tensor.shape().dim_size(1); + + const Tensor& xyz2_tensor = context->input(1); + OP_REQUIRES(context, xyz2_tensor.dims()==3 && xyz2_tensor.shape().dim_size(2)==3, errors::InvalidArgument("QueryBallPoint expects (batch_size, npoint, 3) xyz2 shape.")); + int m = xyz2_tensor.shape().dim_size(1); + + Tensor *idx_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape{b,m,nsample_}, &idx_tensor)); + Tensor *pts_cnt_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(1, TensorShape{b,m}, &pts_cnt_tensor)); + + auto xyz1_flat = xyz1_tensor.flat(); + const float *xyz1 = &(xyz1_flat(0)); + auto xyz2_flat = xyz2_tensor.flat(); + const float *xyz2 = &(xyz2_flat(0)); + auto idx_flat = idx_tensor->flat(); + int *idx = &(idx_flat(0)); + auto pts_cnt_flat = pts_cnt_tensor->flat(); + int *pts_cnt = &(pts_cnt_flat(0)); + queryBallPointLauncher(b,n,m,radius_,nsample_,xyz1,xyz2,idx,pts_cnt); + } + private: + float radius_; + int nsample_; +}; +REGISTER_KERNEL_BUILDER(Name("QueryBallPoint").Device(DEVICE_GPU), QueryBallPointGpuOp); + +void selectionSortLauncher(int b, int n, int m, int k, const float *dist, int *outi, float *out); +class SelectionSortGpuOp : public OpKernel { + public: + explicit SelectionSortGpuOp(OpKernelConstruction* context) : OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("k", &k_)); + OP_REQUIRES(context, k_ > 0, errors::InvalidArgument("SelectionSort expects positive k")); + } + + void Compute(OpKernelContext* context) override { + const Tensor& dist_tensor = context->input(0); + OP_REQUIRES(context, dist_tensor.dims()==3, errors::InvalidArgument("SelectionSort expects (b,m,n) dist shape.")); + int b = dist_tensor.shape().dim_size(0); + int m = dist_tensor.shape().dim_size(1); + int n = dist_tensor.shape().dim_size(2); + + Tensor *outi_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0, TensorShape{b,m,n}, &outi_tensor)); + Tensor *out_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(1, TensorShape{b,m,n}, &out_tensor)); + + auto dist_flat = dist_tensor.flat(); + const float *dist = &(dist_flat(0)); + auto outi_flat = outi_tensor->flat(); + int *outi = &(outi_flat(0)); + auto out_flat = out_tensor->flat(); + float *out = &(out_flat(0)); + selectionSortLauncher(b,n,m,k_,dist,outi,out); + } + private: + int k_; +}; +REGISTER_KERNEL_BUILDER(Name("SelectionSort").Device(DEVICE_GPU), SelectionSortGpuOp); + + +void groupPointLauncher(int b, int n, int c, int m, int nsample, const float *points, const int *idx, float *out); +class GroupPointGpuOp: public OpKernel{ + public: + explicit GroupPointGpuOp(OpKernelConstruction * context):OpKernel(context){} + + void Compute(OpKernelContext * context) override { + const Tensor& points_tensor=context->input(0); + OP_REQUIRES(context, points_tensor.dims()==3, errors::InvalidArgument("GroupPoint expects (batch_size, num_points, channel) points shape")); + int b = points_tensor.shape().dim_size(0); + int n = points_tensor.shape().dim_size(1); + int c = points_tensor.shape().dim_size(2); + + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==3 && idx_tensor.shape().dim_size(0)==b, errors::InvalidArgument("GroupPoint expects (batch_size, npoints, nsample) idx shape")); + int m = idx_tensor.shape().dim_size(1); + int nsample = idx_tensor.shape().dim_size(2); + + Tensor * out_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0,TensorShape{b,m,nsample,c}, &out_tensor)); + + auto points_flat = points_tensor.flat(); + const float *points = &(points_flat(0)); + auto idx_flat = idx_tensor.flat(); + const int *idx = &(idx_flat(0)); + auto out_flat = out_tensor->flat(); + float *out = &(out_flat(0)); + groupPointLauncher(b,n,c,m,nsample,points,idx,out); + } +}; +REGISTER_KERNEL_BUILDER(Name("GroupPoint").Device(DEVICE_GPU),GroupPointGpuOp); + +void groupPointGradLauncher(int b, int n, int c, int m, int nsample, const float *grad_out, const int *idx, float *grad_points); +class GroupPointGradGpuOp: public OpKernel{ + public: + explicit GroupPointGradGpuOp(OpKernelConstruction * context):OpKernel(context){} + + void Compute(OpKernelContext * context) override { + const Tensor& points_tensor=context->input(0); + OP_REQUIRES(context, points_tensor.dims()==3, errors::InvalidArgument("GroupPointGrad expects (batch_size, num_points, channel) points shape")); + int b = points_tensor.shape().dim_size(0); + int n = points_tensor.shape().dim_size(1); + int c = points_tensor.shape().dim_size(2); + + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==3 && idx_tensor.shape().dim_size(0)==b, errors::InvalidArgument("GroupPointGrad expects (batch_size, npoints, nsample) idx shape")); + int m = idx_tensor.shape().dim_size(1); + int nsample = idx_tensor.shape().dim_size(2); + + const Tensor& grad_out_tensor=context->input(2); + OP_REQUIRES(context,grad_out_tensor.dims()==4 && grad_out_tensor.shape().dim_size(0)==b && grad_out_tensor.shape().dim_size(1)==m && grad_out_tensor.shape().dim_size(2)==nsample && grad_out_tensor.shape().dim_size(3)==c, errors::InvalidArgument("GroupPointGrad expects (batch_size, npoints, nsample, channel) grad_out shape")); + + Tensor * grad_points_tensor = nullptr; + OP_REQUIRES_OK(context, context->allocate_output(0,TensorShape{b,n,c}, &grad_points_tensor)); + + auto points_flat = points_tensor.flat(); + const float *points = &(points_flat(0)); + auto idx_flat = idx_tensor.flat(); + const int *idx = &(idx_flat(0)); + auto grad_out_flat = grad_out_tensor.flat(); + const float *grad_out = &(grad_out_flat(0)); + auto grad_points_flat = grad_points_tensor->flat(); + float *grad_points = &(grad_points_flat(0)); + cudaMemset(grad_points, 0, sizeof(float)*b*n*c); + groupPointGradLauncher(b,n,c,m,nsample,grad_out,idx,grad_points); + } +}; +REGISTER_KERNEL_BUILDER(Name("GroupPointGrad").Device(DEVICE_GPU),GroupPointGradGpuOp); + + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.py" new file mode 100644 index 0000000000000000000000000000000000000000..45c96f586fdb4170114c2365d5726a6dfeac005d --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping.py" @@ -0,0 +1,105 @@ +import tensorflow as tf +from tensorflow.python.framework import ops +import sys +import os +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) +grouping_module=tf.load_op_library(os.path.join(BASE_DIR, 'tf_grouping_so.so')) +def query_ball_point(radius, nsample, xyz1, xyz2): + ''' + Input: + radius: float32, ball search radius + nsample: int32, number of points selected in each ball region + xyz1: (batch_size, ndataset, 3) float32 array, input points + xyz2: (batch_size, npoint, 3) float32 array, query points + Output: + idx: (batch_size, npoint, nsample) int32 array, indices to input points + pts_cnt: (batch_size, npoint) int32 array, number of unique points in each local region + ''' + #return grouping_module.query_ball_point(radius, nsample, xyz1, xyz2) + return grouping_module.query_ball_point(xyz1, xyz2, radius, nsample) +ops.NoGradient('QueryBallPoint') +def select_top_k(k, dist): + ''' + Input: + k: int32, number of k SMALLEST elements selected + dist: (b,m,n) float32 array, distance matrix, m query points, n dataset points + Output: + idx: (b,m,n) int32 array, first k in n are indices to the top k + dist_out: (b,m,n) float32 array, first k in n are the top k + ''' + return grouping_module.selection_sort(dist, k) +ops.NoGradient('SelectionSort') +def group_point(points, idx): + ''' + Input: + points: (batch_size, ndataset, channel) float32 array, points to sample from + idx: (batch_size, npoint, nsample) int32 array, indices to points + Output: + out: (batch_size, npoint, nsample, channel) float32 array, values sampled from points + ''' + return grouping_module.group_point(points, idx) +@tf.RegisterGradient('GroupPoint') +def _group_point_grad(op, grad_out): + points = op.inputs[0] + idx = op.inputs[1] + return [grouping_module.group_point_grad(points, idx, grad_out), None] + +def knn_point(k, xyz1, xyz2): + ''' + Input: + k: int32, number of k in k-nn search + xyz1: (batch_size, ndataset, c) float32 array, input points + xyz2: (batch_size, npoint, c) float32 array, query points + Output: + val: (batch_size, npoint, k) float32 array, L2 distances + idx: (batch_size, npoint, k) int32 array, indices to input points + ''' + b = xyz1.get_shape()[0].value + n = xyz1.get_shape()[1].value + c = xyz1.get_shape()[2].value + m = xyz2.get_shape()[1].value + print (b, n, c, m) + print (xyz1, (b,1,n,c)) + xyz1 = tf.tile(tf.reshape(xyz1, (b,1,n,c)), [1,m,1,1]) + xyz2 = tf.tile(tf.reshape(xyz2, (b,m,1,c)), [1,1,n,1]) + dist = tf.reduce_sum((xyz1-xyz2)**2, -1) + print (dist, k) + outi, out = select_top_k(k, dist) + idx = tf.slice(outi, [0,0,0], [-1,-1,k]) + val = tf.slice(out, [0,0,0], [-1,-1,k]) + print (idx, val) + #val, idx = tf.nn.top_k(-dist, k=k) # ONLY SUPPORT CPU + return val, idx + +if __name__=='__main__': + knn=True + import numpy as np + import time + np.random.seed(100) + pts = np.random.random((32,512,64)).astype('float32') + tmp1 = np.random.random((32,512,3)).astype('float32') + tmp2 = np.random.random((32,128,3)).astype('float32') + with tf.device('/gpu:1'): + points = tf.constant(pts) + xyz1 = tf.constant(tmp1) + xyz2 = tf.constant(tmp2) + radius = 0.1 + nsample = 64 + if knn: + _, idx = knn_point(nsample, xyz1, xyz2) + grouped_points = group_point(points, idx) + else: + idx, _ = query_ball_point(radius, nsample, xyz1, xyz2) + grouped_points = group_point(points, idx) + #grouped_points_grad = tf.ones_like(grouped_points) + #points_grad = tf.gradients(grouped_points, points, grouped_points_grad) + with tf.Session('') as sess: + now = time.time() + for _ in range(100): + ret = sess.run(grouped_points) + print (time.time() - now) + print (ret.shape, ret.dtype) + print (ret) + + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_compile.sh" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_compile.sh" new file mode 100644 index 0000000000000000000000000000000000000000..7bd30ad29653c47c04b314887e6ecc2b2d976d00 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_compile.sh" @@ -0,0 +1,11 @@ +#/bin/bash10 +/usr/local/cuda-9.0/bin/nvcc tf_grouping_g.cu -o tf_grouping_g.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPIC + +# TF1.2 +#g++ -std=c++11 tf_grouping.cpp tf_grouping_g.cu.o -o tf_grouping_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -lcudart -L /usr/local/cuda-8.0/lib64/ -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +# TF1.4 +#g++ -std=c++11 tf_grouping.cpp tf_grouping_g.cu.o -o tf_grouping_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-11.0/include -I /usr/local/lib/python2.7/dist-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-8.0/lib64/ -L/usr/local/lib/python2.7/dist-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + + +g++ -std=c++11 tf_grouping.cpp tf_grouping_g.cu.o -o tf_grouping_so.so -shared -fPIC -I /home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include -I /usr/local/cuda-9.0/include -I /home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-9.0/lib64/ -L/home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow -l:libtensorflow_framework.so -O2 -D_GLIBCXX_USE_CXX11_ABI=0 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu" new file mode 100644 index 0000000000000000000000000000000000000000..578330d25d90fecbda814c118ae8d17ea5520caa --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu" @@ -0,0 +1,141 @@ +// input: radius (1), nsample (1), xyz1 (b,n,3), xyz2 (b,m,3) +// output: idx (b,m,nsample), pts_cnt (b,m) +__global__ void query_ball_point_gpu(int b, int n, int m, float radius, int nsample, const float *xyz1, const float *xyz2, int *idx, int *pts_cnt) { + int batch_index = blockIdx.x; + xyz1 += n*3*batch_index; + xyz2 += m*3*batch_index; + idx += m*nsample*batch_index; + pts_cnt += m*batch_index; // counting how many unique points selected in local region + + int index = threadIdx.x; + int stride = blockDim.x; + + for (int j=index;j>>(b,n,m,radius,nsample,xyz1,xyz2,idx,pts_cnt); + //cudaDeviceSynchronize(); +} +void selectionSortLauncher(int b, int n, int m, int k, const float *dist, int *outi, float *out) { + selection_sort_gpu<<>>(b,n,m,k,dist,outi,out); + //cudaDeviceSynchronize(); +} +void groupPointLauncher(int b, int n, int c, int m, int nsample, const float *points, const int *idx, float *out){ + group_point_gpu<<>>(b,n,c,m,nsample,points,idx,out); + //cudaDeviceSynchronize(); +} +void groupPointGradLauncher(int b, int n, int c, int m, int nsample, const float *grad_out, const int *idx, float *grad_points){ + group_point_grad_gpu<<>>(b,n,c,m,nsample,grad_out,idx,grad_points); + //group_point_grad_gpu<<<1,1>>>(b,n,c,m,nsample,grad_out,idx,grad_points); + //cudaDeviceSynchronize(); +} diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu.o" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu.o" new file mode 100644 index 0000000000000000000000000000000000000000..56b51c99510d206b9274b7ca1a2a4280b149649b Binary files /dev/null and "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_g.cu.o" differ diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_op_test.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_op_test.py" new file mode 100644 index 0000000000000000000000000000000000000000..939eb1f4c5658a0b3e93cb2adcb2f298a4c287bf --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_op_test.py" @@ -0,0 +1,28 @@ +import tensorflow as tf +import numpy as np +from tf_grouping import query_ball_point, group_point + +class GroupPointTest(tf.test.TestCase): + def test(self): + pass + + def test_grad(self): + with tf.device('/gpu:0'): + points = tf.constant(np.random.random((1,128,16)).astype('float32')) + print points + xyz1 = tf.constant(np.random.random((1,128,3)).astype('float32')) + xyz2 = tf.constant(np.random.random((1,8,3)).astype('float32')) + radius = 0.3 + nsample = 32 + idx, pts_cnt = query_ball_point(radius, nsample, xyz1, xyz2) + grouped_points = group_point(points, idx) + print (grouped_points) + + with self.test_session(): + print ("---- Going to compute gradient error") + err = tf.test.compute_gradient_error(points, (1,128,16), grouped_points, (1,8,32,16)) + print (err) + self.assertLess(err, 1e-4) + +if __name__=='__main__': + tf.test.main() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_so.so" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_so.so" new file mode 100644 index 0000000000000000000000000000000000000000..c10efbe728472286c4855488f0c42ffc81f88ff3 Binary files /dev/null and "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/grouping/tf_grouping_so.so" differ diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/.gitignore" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/.gitignore" new file mode 100644 index 0000000000000000000000000000000000000000..9d22eb46a9c8ce7e6f0d17b751d0e192ed89fefe --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/.gitignore" @@ -0,0 +1,2 @@ +*.o +*.so diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.cpp" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..d92d3a76fb04f05e9e931828010817c2fd40913c --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.cpp" @@ -0,0 +1,182 @@ +/* Furthest point sampling + * Original author: Haoqiang Fan + * Modified by Charles R. Qi + * All Rights Reserved. 2017. + */ +#pragma GCC diagnostic ignored "-Wunused-result" + +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/framework/common_shape_fns.h" +#include + +using namespace tensorflow; + +REGISTER_OP("ProbSample") + .Input("inp: float32") + .Input("inpr: float32") + .Output("out: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims1; // batch_size * ncategory + c->WithRank(c->input(0), 2, &dims1); + ::tensorflow::shape_inference::ShapeHandle dims2; // batch_size * npoints + c->WithRank(c->input(1), 2, &dims2); + // batch_size * npoints + ::tensorflow::shape_inference::ShapeHandle output = c->MakeShape({c->Dim(dims2, 0), c->Dim(dims2, 1)}); + c->set_output(0, output); + return Status::OK(); + }); +REGISTER_OP("FarthestPointSample") + .Attr("npoint: int") + .Input("inp: float32") + .Output("out: int32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims1; // batch_size * npoint * 3 + c->WithRank(c->input(0), 3, &dims1); + int npoint; + TF_RETURN_IF_ERROR(c->GetAttr("npoint", &npoint)); + ::tensorflow::shape_inference::ShapeHandle output = c->MakeShape({c->Dim(dims1, 0), npoint}); + c->set_output(0, output); + return Status::OK(); + }); +REGISTER_OP("GatherPoint") + .Input("inp: float32") + .Input("idx: int32") + .Output("out: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + ::tensorflow::shape_inference::ShapeHandle dims1; // batch_size * ndataset * 3 + c->WithRank(c->input(0), 3, &dims1); + ::tensorflow::shape_inference::ShapeHandle dims2; // batch_size * npoints + + c->WithRank(c->input(1), 2, &dims2); + // batch_size * npoints * 3 + ::tensorflow::shape_inference::ShapeHandle output = c->MakeShape({c->Dim(dims1, 0), c->Dim(dims2, 1), c->Dim(dims1, 2)}); + c->set_output(0, output); + return Status::OK(); + }); +REGISTER_OP("GatherPointGrad") + .Input("inp: float32") + .Input("idx: int32") + .Input("out_g: float32") + .Output("inp_g: float32") + .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) { + c->set_output(0, c->input(0)); + return Status::OK(); + }); + +void probsampleLauncher(int b,int n,int m,const float * inp_p,const float * inp_r,float * temp,int * out); +class ProbSampleGpuOp: public OpKernel{ + public: + explicit ProbSampleGpuOp(OpKernelConstruction* context):OpKernel(context){} + void Compute(OpKernelContext * context)override{ + const Tensor& inp_tensor=context->input(0); + const Tensor& inpr_tensor=context->input(1); + auto inp_flat=inp_tensor.flat(); + auto inpr_flat=inpr_tensor.flat(); + const float * inp=&(inp_flat(0)); + const float * inpr=&(inpr_flat(0)); + OP_REQUIRES(context,inp_tensor.dims()==2,errors::InvalidArgument("ProbSample expects (batch_size,num_choices) inp shape")); + int b=inp_tensor.shape().dim_size(0); + int n=inp_tensor.shape().dim_size(1); + OP_REQUIRES(context,inpr_tensor.dims()==2 && inpr_tensor.shape().dim_size(0)==b,errors::InvalidArgument("ProbSample expects (batch_size,num_points) inpr shape")); + int m=inpr_tensor.shape().dim_size(1); + Tensor * out_tensor=NULL; + OP_REQUIRES_OK(context,context->allocate_output(0,TensorShape{b,m},&out_tensor)); + auto out_flat=out_tensor->flat(); + int * out=&(out_flat(0)); + Tensor temp_tensor; + OP_REQUIRES_OK(context,context->allocate_temp(DataTypeToEnum::value,TensorShape{b,n},&temp_tensor)); + auto temp_flat=temp_tensor.flat(); + float * temp=&(temp_flat(0)); + probsampleLauncher(b,n,m,inp,inpr,temp,out); + } +}; +REGISTER_KERNEL_BUILDER(Name("ProbSample").Device(DEVICE_GPU), ProbSampleGpuOp); + +void farthestpointsamplingLauncher(int b,int n,int m,const float * inp,float * temp,int * out); +class FarthestPointSampleGpuOp: public OpKernel{ + public: + explicit FarthestPointSampleGpuOp(OpKernelConstruction* context):OpKernel(context) { + OP_REQUIRES_OK(context, context->GetAttr("npoint", &npoint_)); + OP_REQUIRES(context, npoint_ > 0, errors::InvalidArgument("FarthestPointSample expects positive npoint")); + } + void Compute(OpKernelContext * context)override{ + int m = npoint_; + + const Tensor& inp_tensor=context->input(0); + OP_REQUIRES(context,inp_tensor.dims()==3 && inp_tensor.shape().dim_size(2)==3,errors::InvalidArgument("FarthestPointSample expects (batch_size,num_points,3) inp shape")); + int b=inp_tensor.shape().dim_size(0); + int n=inp_tensor.shape().dim_size(1); + auto inp_flat=inp_tensor.flat(); + const float * inp=&(inp_flat(0)); + Tensor * out_tensor; + OP_REQUIRES_OK(context,context->allocate_output(0,TensorShape{b,m},&out_tensor)); + auto out_flat=out_tensor->flat(); + int * out=&(out_flat(0)); + Tensor temp_tensor; + OP_REQUIRES_OK(context,context->allocate_temp(DataTypeToEnum::value,TensorShape{32,n},&temp_tensor)); + auto temp_flat=temp_tensor.flat(); + float * temp=&(temp_flat(0)); + farthestpointsamplingLauncher(b,n,m,inp,temp,out); + } + private: + int npoint_; +}; +REGISTER_KERNEL_BUILDER(Name("FarthestPointSample").Device(DEVICE_GPU),FarthestPointSampleGpuOp); + +void gatherpointLauncher(int b,int n,int m,const float * inp,const int * idx,float * out); +class GatherPointGpuOp: public OpKernel{ + public: + explicit GatherPointGpuOp(OpKernelConstruction * context):OpKernel(context){} + void Compute(OpKernelContext * context)override{ + const Tensor& inp_tensor=context->input(0); + OP_REQUIRES(context,inp_tensor.dims()==3 && inp_tensor.shape().dim_size(2)==3,errors::InvalidArgument("GatherPoint expects (batch_size,num_points,3) inp shape")); + int b=inp_tensor.shape().dim_size(0); + int n=inp_tensor.shape().dim_size(1); + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==2 && idx_tensor.shape().dim_size(0)==b,errors::InvalidArgument("GatherPoint expects (batch_size,num_result) idx shape")); + int m=idx_tensor.shape().dim_size(1); + auto inp_flat=inp_tensor.flat(); + const float * inp=&(inp_flat(0)); + auto idx_flat=idx_tensor.flat(); + const int * idx=&(idx_flat(0)); + Tensor * out_tensor=NULL; + OP_REQUIRES_OK(context,context->allocate_output(0,TensorShape{b,m,3},&out_tensor)); + auto out_flat=out_tensor->flat(); + float * out=&(out_flat(0)); + gatherpointLauncher(b,n,m,inp,idx,out); + } +}; +REGISTER_KERNEL_BUILDER(Name("GatherPoint").Device(DEVICE_GPU),GatherPointGpuOp); + +void scatteraddpointLauncher(int b,int n,int m,const float * out_g,const int * idx,float * inp_g); +class GatherPointGradGpuOp: public OpKernel{ + public: + explicit GatherPointGradGpuOp(OpKernelConstruction * context):OpKernel(context){} + void Compute(OpKernelContext * context)override{ + const Tensor& inp_tensor=context->input(0); + OP_REQUIRES(context,inp_tensor.dims()==3 && inp_tensor.shape().dim_size(2)==3,errors::InvalidArgument("GatherPointGradGpuOp expects (batch_size,num_points,3) inp")); + int b=inp_tensor.shape().dim_size(0); + int n=inp_tensor.shape().dim_size(1); + const Tensor& idx_tensor=context->input(1); + OP_REQUIRES(context,idx_tensor.dims()==2 && idx_tensor.shape().dim_size(0)==b,errors::InvalidArgument("GatherPointGradGpuOp expects (batch_size,num_result) idx shape")); + int m=idx_tensor.shape().dim_size(1); + auto inp_flat=inp_tensor.flat(); + const float * inp=&(inp_flat(0)); + auto idx_flat=idx_tensor.flat(); + const int * idx=&(idx_flat(0)); + const Tensor& out_g_tensor=context->input(2); + OP_REQUIRES(context,out_g_tensor.dims()==3 && out_g_tensor.shape().dim_size(0)==b && out_g_tensor.shape().dim_size(1)==m && out_g_tensor.shape().dim_size(2)==3,errors::InvalidArgument("GatherPointGradGpuOp expects (batch_size,num_result,3) out_g shape")); + auto out_g_flat=out_g_tensor.flat(); + const float * out_g=&(out_g_flat(0)); + Tensor * inp_g_tensor=NULL; + OP_REQUIRES_OK(context,context->allocate_output(0,TensorShape{b,n,3},&inp_g_tensor)); + auto inp_g_flat=inp_g_tensor->flat(); + float * inp_g=&(inp_g_flat(0)); + cudaMemset(inp_g,0,b*n*3*4); + scatteraddpointLauncher(b,n,m,out_g,idx,inp_g); + } +}; +REGISTER_KERNEL_BUILDER(Name("GatherPointGrad").Device(DEVICE_GPU),GatherPointGradGpuOp); + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.py" new file mode 100644 index 0000000000000000000000000000000000000000..efa842888d32f7030e39615d97d36a2fd0e6cade --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling.py" @@ -0,0 +1,91 @@ +''' Furthest point sampling +Original author: Haoqiang Fan +Modified by Charles R. Qi +All Rights Reserved. 2017. +''' +import tensorflow as tf +from tensorflow.python.framework import ops +import sys +import os +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) +sampling_module=tf.load_op_library(os.path.join(BASE_DIR, 'tf_sampling_so.so')) +def prob_sample(inp,inpr): + ''' +input: + batch_size * ncategory float32 + batch_size * npoints float32 +returns: + batch_size * npoints int32 + ''' + return sampling_module.prob_sample(inp,inpr) + +ops.NoGradient('ProbSample') +# TF1.0 API requires set shape in C++ +# @tf.RegisterShape('ProbSample') +# def _prob_sample_shape(op): +# shape1=op.inputs[0].get_shape().with_rank(2) +# shape2=op.inputs[1].get_shape().with_rank(2) +# return [tf.TensorShape([shape2.dims[0],shape2.dims[1]])] +def gather_point(inp,idx): + ''' +input: + batch_size * ndataset * 3 float32 + batch_size * npoints int32 +returns: + batch_size * npoints * 3 float32 + ''' + return sampling_module.gather_point(inp,idx) +#@tf.RegisterShape('GatherPoint') +# def _gather_point_shape(op): +# shape1=op.inputs[0].get_shape().with_rank(3) +# shape2=op.inputs[1].get_shape().with_rank(2) +# return [tf.TensorShape([shape1.dims[0],shape2.dims[1],shape1.dims[2]])] +@tf.RegisterGradient('GatherPoint') + +def _gather_point_grad(op,out_g): + inp=op.inputs[0] + idx=op.inputs[1] + return [sampling_module.gather_point_grad(inp,idx,out_g),None] +def farthest_point_sample(npoint,inp): + ''' +input: + int32 + batch_size * ndataset * 3 float32 +returns: + batch_size * npoint int32 + ''' + return sampling_module.farthest_point_sample(inp, npoint) +ops.NoGradient('FarthestPointSample') + + +if __name__=='__main__': + import numpy as np + np.random.seed(100) + triangles=np.random.rand(1,5,3,3).astype('float32') + with tf.device('/gpu:1'): + inp=tf.constant(triangles) + tria=inp[:,:,0,:] + trib=inp[:,:,1,:] + tric=inp[:,:,2,:] + areas=tf.sqrt(tf.reduce_sum(tf.cross(trib-tria,tric-tria)**2,2)+1e-9) + randomnumbers=tf.random_uniform((1,8192)) + triids=prob_sample(areas,randomnumbers) + tria_sample=gather_point(tria,triids) + trib_sample=gather_point(trib,triids) + tric_sample=gather_point(tric,triids) + us=tf.random_uniform((1,8192)) + vs=tf.random_uniform((1,8192)) + uplusv=1-tf.abs(us+vs-1) + uminusv=us-vs + us=(uplusv+uminusv)*0.5 + vs=(uplusv-uminusv)*0.5 + pt_sample=tria_sample+(trib_sample-tria_sample)*tf.expand_dims(us,-1)+(tric_sample-tria_sample)*tf.expand_dims(vs,-1) + print('pt_sample: ', pt_sample) + reduced_sample=gather_point(pt_sample,farthest_point_sample(1024,pt_sample)) + print(reduced_sample) + with tf.Session('') as sess: + ret=sess.run(reduced_sample) + print(ret.shape,ret.dtype) + import cPickle as pickle + pickle.dump(ret,open('1.pkl','wb'),-1) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_compile.sh" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_compile.sh" new file mode 100644 index 0000000000000000000000000000000000000000..ad14627a6718f24d92e63271de3f2ef3caab7b0f --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_compile.sh" @@ -0,0 +1,17 @@ +#/bin/bash +/usr/local/cuda-9.0/bin/nvcc tf_sampling_g.cu -o tf_sampling_g.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPIC + +# TF1.2 +#g++ -std=c++11 tf_sampling.cpp tf_sampling_g.cu.o -o tf_sampling_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -lcudart -L /usr/local/cuda-8.0/lib64/ -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +# TF1.2 +#g++ -std=c++11 tf_sampling.cpp tf_sampling_g.cu.o -o tf_sampling_so.so -shared -fPIC -I /home/chendiane/anaconda3/envs/tf/lib/python3.7/site-packages/tensorflow/include -I /usr/local/cuda-10.1/include -lcudart -L /usr/local/cuda-10.1/lib64/ -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +# TF1.4 +#g++ -std=c++11 tf_sampling.cpp tf_sampling_g.cu.o -o tf_sampling_so.so -shared -fPIC -I /home/chendiane/anaconda3/envs/tf/lib/python3.7/site-packages/tensorflow/include -I /usr/local/cuda-8.0/include -I /usr/local/lib/python2.7/dist-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-8.0/lib64/ -L/usr/local/lib/python2.7/dist-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 +# TF1.4 + +g++ -std=c++11 tf_sampling.cpp tf_sampling_g.cu.o -o tf_sampling_so.so -shared -fPIC -I/home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include -I /usr/local/cuda-9.0/include -I/home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-9.0/lib64/ -L/home/cc/anaconda3/envs/tf1.8.0/lib/python3.6/site-packages/tensorflow -l:libtensorflow_framework.so -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + +#.so结尾的要用l:lib......framework.so +#g++ -std=c++11 tf_sampling.cpp tf_sampling_g.cu.o -o tf_sampling_so.so -shared -fPIC -I /home/chendiane/anaconda3/envs/tf/lib/python3.7/site-packages/tensorflow/include -I /usr/local/cuda-10.1/include -I /home/chendiane/anaconda3/envs/tf/lib/python3.7/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-10.1/lib64/ -L/home/chendiane/anaconda3/envs/tf/lib/python3.7/site-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu" new file mode 100644 index 0000000000000000000000000000000000000000..6e28bc7f4d29686679f7d08fd8606e30a92c3fc2 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu" @@ -0,0 +1,212 @@ +/* Furthest point sampling GPU implementation + * Original author: Haoqiang Fan + * Modified by Charles R. Qi + * All Rights Reserved. 2017. + */ + +__global__ void cumsumKernel(int b,int n,const float * __restrict__ inp,float * __restrict__ out){ + const int BlockSize=2048; + const int paddingLevel=5; + __shared__ float buffer4[BlockSize*4]; + __shared__ float buffer[BlockSize+(BlockSize>>paddingLevel)]; + for (int i=blockIdx.x;i>2; + for (int k=threadIdx.x*4;k>2)+(k>>(2+paddingLevel))]=v4; + }else{ + float v=0; + for (int k2=k;k2>2)+(k>>(2+paddingLevel))]=v; + } + } + int u=0; + for (;(2<>(u+1));k+=blockDim.x){ + int i1=(((k<<1)+2)<>paddingLevel; + i2+=i2>>paddingLevel; + buffer[i1]+=buffer[i2]; + } + } + u--; + for (;u>=0;u--){ + __syncthreads(); + for (int k=threadIdx.x;k>(u+1));k+=blockDim.x){ + int i1=(((k<<1)+3)<>paddingLevel; + i2+=i2>>paddingLevel; + buffer[i1]+=buffer[i2]; + } + } + __syncthreads(); + for (int k=threadIdx.x*4;k>2)-1)+(((k>>2)-1)>>paddingLevel); + buffer4[k]+=buffer[k2]; + buffer4[k+1]+=buffer[k2]; + buffer4[k+2]+=buffer[k2]; + buffer4[k+3]+=buffer[k2]; + } + } + __syncthreads(); + for (int k=threadIdx.x;k>paddingLevel)]+runningsum2; + float r2=runningsum+t; + runningsum2=t-(r2-runningsum); + runningsum=r2; + __syncthreads(); + } + } +} + +__global__ void binarysearchKernel(int b,int n,int m,const float * __restrict__ dataset,const float * __restrict__ query, int * __restrict__ result){ + int base=1; + while (base=1;k>>=1) + if (r>=k && dataset[i*n+r-k]>=q) + r-=k; + result[i*m+j]=r; + } + } +} +__global__ void farthestpointsamplingKernel(int b,int n,int m,const float * __restrict__ dataset,float * __restrict__ temp,int * __restrict__ idxs){ + if (m<=0) + return; + const int BlockSize=512; + __shared__ float dists[BlockSize]; + __shared__ int dists_i[BlockSize]; + const int BufferSize=3072; + __shared__ float buf[BufferSize*3]; + for (int i=blockIdx.x;ibest){ + best=d2; + besti=k; + } + } + dists[threadIdx.x]=best; + dists_i[threadIdx.x]=besti; + for (int u=0;(1<>(u+1))){ + int i1=(threadIdx.x*2)<>>(b,n,inp,out); +} +//require b*n working space +void probsampleLauncher(int b,int n,int m,const float * inp_p,const float * inp_r,float * temp,int * out){ + cumsumKernel<<<32,512>>>(b,n,inp_p,temp); + binarysearchKernel<<>>(b,n,m,temp,inp_r,out); +} +//require 32*n working space +void farthestpointsamplingLauncher(int b,int n,int m,const float * inp,float * temp,int * out){ + farthestpointsamplingKernel<<<32,512>>>(b,n,m,inp,temp,out); +} +void gatherpointLauncher(int b,int n,int m,const float * inp,const int * idx,float * out){ + gatherpointKernel<<>>(b,n,m,inp,idx,out); +} +void scatteraddpointLauncher(int b,int n,int m,const float * out_g,const int * idx,float * inp_g){ + scatteraddpointKernel<<>>(b,n,m,out_g,idx,inp_g); +} + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu.o" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu.o" new file mode 100644 index 0000000000000000000000000000000000000000..e98acb0e5c087e1ab73f116e64c9998e24844f43 Binary files /dev/null and "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_g.cu.o" differ diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_so.so" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_so.so" new file mode 100644 index 0000000000000000000000000000000000000000..ccfa2d53220cb160338d1f327ff49d3978624eca Binary files /dev/null and "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf_ops/sampling/tf_sampling_so.so" differ diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf\346\243\200\346\237\245\345\217\257\347\224\250\347\232\204gpu\350\256\276\345\244\207.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf\346\243\200\346\237\245\345\217\257\347\224\250\347\232\204gpu\350\256\276\345\244\207.py" new file mode 100644 index 0000000000000000000000000000000000000000..8bd0d1510a0df63bb383378e62d592dfd7c95a42 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/tf\346\243\200\346\237\245\345\217\257\347\224\250\347\232\204gpu\350\256\276\345\244\207.py" @@ -0,0 +1,12 @@ +#--coding=utf8 +from tensorflow.python.client import device_lib + +print(device_lib.list_local_devices()) + +from tensorflow.python.client import device_lib + +# 列出全部的本地机器设备 +local_device_protos = device_lib.list_local_devices() +# 打印 +print(local_device_protos) + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train.py" new file mode 100644 index 0000000000000000000000000000000000000000..574f25be62c7f253361b8c053ed58d69c8873d7e --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train.py" @@ -0,0 +1,307 @@ +''' + Single-GPU training. + Will use H5 dataset in default. If using normal, will shift to the normal dataset. +''' +import argparse +import math +from datetime import datetime +import h5py +import numpy as np +import tensorflow as tf +import socket +import importlib +import os +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = BASE_DIR +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(ROOT_DIR, 'models')) +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import provider +import tf_util +import modelnet_dataset +import modelnet_h5_dataset + +# os.environ["CUDA_VISIBLE_DEVICES"] = "0" +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + +# from tensorflow.compat.v1 import ConfigProto +# from tensorflow.compat.v1 import InteractiveSession +# config = tf.compat.v1.ConfigProto() +# config.gpu_options.allow_growth = True +# session =tf.compat.v1.InteractiveSession(config=config) + + + +parser = argparse.ArgumentParser() +parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') +parser.add_argument('--model', default='pointnet2_cls_ssg', help='Model name [default: pointnet2_cls_ssg]') + +parser.add_argument('--log_dir', default='log', help='Log dir [default: log]') +parser.add_argument('--num_point', type=int, default=1024, help='Point Number [default: 1024]') +parser.add_argument('--max_epoch', type=int, default=250, help='Epoch to run [default: 251]') + + +parser.add_argument('--batch_size', type=int, default=8, help='Batch Size during training [default: 16]') + +parser.add_argument('--learning_rate', type=float, default=0.001, help='Initial learning rate [default: 0.001]') +parser.add_argument('--momentum', type=float, default=0.9, help='Initial learning rate [default: 0.9]') +parser.add_argument('--optimizer', default='adam', help='adam or momentum [default: adam]') +parser.add_argument('--decay_step', type=int, default=200000, help='Decay step for lr decay [default: 200000]') + + +parser.add_argument('--decay_rate', type=float, default=0.7, help='Decay rate for lr decay [default: 0.7]') +parser.add_argument('--normal', action='store_true', help='Whether to use normal information') +FLAGS = parser.parse_args() + +EPOCH_CNT = 0 + +BATCH_SIZE = FLAGS.batch_size +NUM_POINT = FLAGS.num_point +MAX_EPOCH = FLAGS.max_epoch +BASE_LEARNING_RATE = FLAGS.learning_rate +GPU_INDEX = FLAGS.gpu +MOMENTUM = FLAGS.momentum +OPTIMIZER = FLAGS.optimizer +DECAY_STEP = FLAGS.decay_step +DECAY_RATE = FLAGS.decay_rate + +MODEL = importlib.import_module(FLAGS.model) # import network module + + + +MODEL_FILE = os.path.join(ROOT_DIR, 'models', FLAGS.model+'.py') + +LOG_DIR = FLAGS.log_dir +if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR) +os.system('cp %s %s' % (MODEL_FILE, LOG_DIR)) # bkp of model def +os.system('cp train.py %s' % (LOG_DIR)) # bkp of train procedure +LOG_FOUT = open(os.path.join(LOG_DIR, 'log_train.txt'), 'w') +LOG_FOUT.write(str(FLAGS)+'\n') + +BN_INIT_DECAY = 0.5 +BN_DECAY_DECAY_RATE = 0.5 +BN_DECAY_DECAY_STEP = float(DECAY_STEP) +BN_DECAY_CLIP = 0.99 + +HOSTNAME = socket.gethostname() + +NUM_CLASSES = 3 + +# Shapenet official train/test split + +TRAIN_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt'), + batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=True) +TEST_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt'), + batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=False) + +def log_string(out_str): + LOG_FOUT.write(out_str+'\n') + LOG_FOUT.flush() + print(out_str) + +def get_learning_rate(batch): + learning_rate = tf.train.exponential_decay( + BASE_LEARNING_RATE, # Base learning rate. + batch * BATCH_SIZE, # Current index into the dataset. + DECAY_STEP, # Decay step. + DECAY_RATE, # Decay rate. + staircase=True) + learning_rate = tf.maximum(learning_rate, 0.00001) # CLIP THE LEARNING RATE! + return learning_rate + +def get_bn_decay(batch): + bn_momentum = tf.train.exponential_decay( + BN_INIT_DECAY, + batch*BATCH_SIZE, + BN_DECAY_DECAY_STEP, + BN_DECAY_DECAY_RATE, + staircase=True) + bn_decay = tf.minimum(BN_DECAY_CLIP, 1 - bn_momentum) + return bn_decay + +def train(): + with tf.Graph().as_default(): + with tf.device('/gpu:'+str(GPU_INDEX)): + pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) + is_training_pl = tf.placeholder(tf.bool, shape=()) + + # Note the global_step=batch parameter to minimize. + # That tells the optimizer to helpfully increment the 'batch' parameter + # for you every time it trains. + batch = tf.get_variable('batch', [], + initializer=tf.constant_initializer(0), trainable=False) + bn_decay = get_bn_decay(batch) + tf.summary.scalar('bn_decay', bn_decay) + + # Get model and loss + pred, end_points = MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay) + MODEL.get_loss(pred, labels_pl, end_points) + losses = tf.get_collection('losses') + total_loss = tf.add_n(losses, name='total_loss') + tf.summary.scalar('total_loss', total_loss) + for l in losses + [total_loss]: + tf.summary.scalar(l.op.name, l) + + correct = tf.equal(tf.argmax(pred, 1), tf.to_int64(labels_pl)) + # correct = tf.equal(tf.argmax(pred, 1), tf.cast(labels_pl)) + accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE) + tf.summary.scalar('accuracy', accuracy) + + print("--- Get training operator") + # Get training operator + learning_rate = get_learning_rate(batch) + tf.summary.scalar('learning_rate', learning_rate) + if OPTIMIZER == 'momentum': + optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=MOMENTUM) + elif OPTIMIZER == 'adam': + optimizer = tf.train.AdamOptimizer(learning_rate) + train_op = optimizer.minimize(total_loss, global_step=batch) + + # Add ops to save and restore all the variables. + saver = tf.train.Saver() + + # Create a session + config = tf.ConfigProto() + config.gpu_options.allow_growth = True + config.allow_soft_placement = True + config.log_device_placement = False + # config.graph_options.optimizer_options.global_jit_level =tf.OptimizerOptions.ON_1 + sess = tf.Session(config=config) + + + + + # Add summary writers + merged = tf.summary.merge_all() + train_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'train'), sess.graph) + test_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'test'), sess.graph) + + # Init variables + init = tf.global_variables_initializer() + sess.run(init) + + ops = {'pointclouds_pl': pointclouds_pl, + 'labels_pl': labels_pl, + 'is_training_pl': is_training_pl, + 'pred': pred, + 'loss': total_loss, + 'train_op': train_op, + 'merged': merged, + 'step': batch, + 'end_points': end_points} + + best_acc = -1 + for epoch in range(MAX_EPOCH): + log_string('**** EPOCH %03d ****' % (epoch)) + sys.stdout.flush() + + train_one_epoch(sess, ops, train_writer) + eval_one_epoch(sess, ops, test_writer) + + # Save the variables to disk. + if epoch % 10 == 0: + save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt")) + log_string("Model saved in file: %s" % save_path) + + +def train_one_epoch(sess, ops, train_writer): + """ ops: dict mapping from string to tf ops """ + is_training = True + + log_string(str(datetime.now())) + + # Make sure batch data is of same size + cur_batch_data = np.zeros((BATCH_SIZE,NUM_POINT,TRAIN_DATASET.num_channel())) + cur_batch_label = np.zeros((BATCH_SIZE), dtype=np.int32) + + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx = 0 + while TRAIN_DATASET.has_next_batch(): + batch_data, batch_label = TRAIN_DATASET.next_batch(augment=True) + #batch_data = provider.random_point_dropout(batch_data) + bsize = batch_data.shape[0] + cur_batch_data[0:bsize,...] = batch_data + cur_batch_label[0:bsize] = batch_label + + feed_dict = {ops['pointclouds_pl']: cur_batch_data, + ops['labels_pl']: cur_batch_label, + ops['is_training_pl']: is_training,} + summary, step, _, loss_val, pred_val = sess.run([ops['merged'], ops['step'], + ops['train_op'], ops['loss'], ops['pred']], feed_dict=feed_dict) + train_writer.add_summary(summary, step) + pred_val = np.argmax(pred_val, 1) + correct = np.sum(pred_val[0:bsize] == batch_label[0:bsize]) + total_correct += correct + total_seen += bsize + loss_sum += loss_val + if (batch_idx+1)%50 == 0: + log_string(' ---- batch: %03d ----' % (batch_idx+1)) + log_string('mean loss: %f' % (loss_sum / 50)) + log_string('accuracy: %f' % (total_correct / float(total_seen))) + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx += 1 + + TRAIN_DATASET.reset() + +def eval_one_epoch(sess, ops, test_writer): + """ ops: dict mapping from string to tf ops """ + global EPOCH_CNT + is_training = False + + # Make sure batch data is of same size + cur_batch_data = np.zeros((BATCH_SIZE,NUM_POINT,TEST_DATASET.num_channel())) + cur_batch_label = np.zeros((BATCH_SIZE), dtype=np.int32) + + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx = 0 + shape_ious = [] + total_seen_class = [0 for _ in range(NUM_CLASSES)] + total_correct_class = [0 for _ in range(NUM_CLASSES)] + + log_string(str(datetime.now())) + log_string('---- EPOCH %03d EVALUATION ----'%(EPOCH_CNT)) + + while TEST_DATASET.has_next_batch(): + batch_data, batch_label = TEST_DATASET.next_batch(augment=False) + bsize = batch_data.shape[0] + # for the last batch in the epoch, the bsize:end are from last batch + cur_batch_data[0:bsize,...] = batch_data + cur_batch_label[0:bsize] = batch_label + + feed_dict = {ops['pointclouds_pl']: cur_batch_data, + ops['labels_pl']: cur_batch_label, + ops['is_training_pl']: is_training} + summary, step, loss_val, pred_val = sess.run([ops['merged'], ops['step'], + ops['loss'], ops['pred']], feed_dict=feed_dict) + test_writer.add_summary(summary, step) + pred_val = np.argmax(pred_val, 1) + correct = np.sum(pred_val[0:bsize] == batch_label[0:bsize]) + total_correct += correct + total_seen += bsize + loss_sum += loss_val + batch_idx += 1 + for i in range(0, bsize): + l = batch_label[i] + total_seen_class[l] += 1 + total_correct_class[l] += (pred_val[i] == l) + + log_string('eval mean loss: %f' % (loss_sum / float(batch_idx))) + log_string('eval accuracy: %f'% (total_correct / float(total_seen))) + log_string('eval avg class acc: %f' % (np.mean(np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float)))) + EPOCH_CNT += 1 + + TEST_DATASET.reset() + return total_correct/float(total_seen) + + +if __name__ == "__main__": + log_string('pid: %s'%(str(os.getpid()))) + train() + LOG_FOUT.close() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train_multi_gpu.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train_multi_gpu.py" new file mode 100644 index 0000000000000000000000000000000000000000..51d0f8218a35b3de8fd8b6a301498138dae21f15 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/train_multi_gpu.py" @@ -0,0 +1,364 @@ +''' + Multi-GPU training. + Near linear scale acceleration for multi-gpus on a single machine. + Will use H5 dataset in default. If using normal, will shift to the normal dataset. +''' + +import argparse +import math +from datetime import datetime +import h5py +import numpy as np +import tensorflow as tf +import socket +import importlib +import os +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = BASE_DIR +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(ROOT_DIR, 'models')) +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import provider +import tf_util +import modelnet_dataset +import modelnet_h5_dataset + +parser = argparse.ArgumentParser() +parser.add_argument('--num_gpus', type=int, default=1, help='How many gpus to use [default: 1]') +parser.add_argument('--model', default='pointnet2_cls_ssg', help='Model name [default: pointnet2_cls_ssg]') +parser.add_argument('--log_dir', default='log', help='Log dir [default: log]') +parser.add_argument('--num_point', type=int, default=1024, help='Point Number [default: 1024]') +parser.add_argument('--max_epoch', type=int, default=5, help='Epoch to run [default: 251]') + + + +parser.add_argument('--batch_size', type=int, default=8, help='Batch Size during training [default: 32]') +parser.add_argument('--learning_rate', type=float, default=0.001, help='Initial learning rate [default: 0.001]') +parser.add_argument('--momentum', type=float, default=0.9, help='Initial learning rate [default: 0.9]') +parser.add_argument('--optimizer', default='adam', help='adam or momentum [default: adam]') +parser.add_argument('--decay_step', type=int, default=200000, help='Decay step for lr decay [default: 200000]') +parser.add_argument('--decay_rate', type=float, default=0.7, help='Decay rate for lr decay [default: 0.7]') +parser.add_argument('--normal', action='store_true', help='Whether to use normal information') +FLAGS = parser.parse_args() + +EPOCH_CNT = 0 + +NUM_GPUS = FLAGS.num_gpus +BATCH_SIZE = FLAGS.batch_size +assert(BATCH_SIZE % NUM_GPUS == 0) +DEVICE_BATCH_SIZE = BATCH_SIZE / NUM_GPUS + +NUM_POINT = FLAGS.num_point +MAX_EPOCH = FLAGS.max_epoch +BASE_LEARNING_RATE = FLAGS.learning_rate +MOMENTUM = FLAGS.momentum +OPTIMIZER = FLAGS.optimizer +DECAY_STEP = FLAGS.decay_step +DECAY_RATE = FLAGS.decay_rate + +MODEL = importlib.import_module(FLAGS.model) # import network module +MODEL_FILE = os.path.join(ROOT_DIR, 'models', FLAGS.model+'.py') +LOG_DIR = FLAGS.log_dir +if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR) +os.system('cp %s %s' % (MODEL_FILE, LOG_DIR)) # bkp of model def +os.system('cp train.py %s' % (LOG_DIR)) # bkp of train procedure +LOG_FOUT = open(os.path.join(LOG_DIR, 'log_train.txt'), 'w') +LOG_FOUT.write(str(FLAGS)+'\n') + +BN_INIT_DECAY = 0.5 +BN_DECAY_DECAY_RATE = 0.5 +BN_DECAY_DECAY_STEP = float(DECAY_STEP) +BN_DECAY_CLIP = 0.99 + +HOSTNAME = socket.gethostname() + +NUM_CLASSES = 40 + +# # Shapenet official train/test split +# if FLAGS.normal: +# assert(NUM_POINT<=10000) +# DATA_PATH = os.path.join(ROOT_DIR, 'data/modelnet40_normal_resampled') +# TRAIN_DATASET = modelnet_dataset.ModelNetDataset(root=DATA_PATH, npoints=NUM_POINT, split='train', normal_channel=FLAGS.normal, batch_size=BATCH_SIZE) +# TEST_DATASET = modelnet_dataset.ModelNetDataset(root=DATA_PATH, npoints=NUM_POINT, split='test', normal_channel=FLAGS.normal, batch_size=BATCH_SIZE) +# else: +# assert(NUM_POINT<=2048) +TRAIN_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt'), + batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=True) +TEST_DATASET = modelnet_h5_dataset.ModelNetH5Dataset(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt'), + batch_size=BATCH_SIZE, npoints=NUM_POINT, shuffle=False) + +def log_string(out_str): + LOG_FOUT.write(out_str+'\n') + LOG_FOUT.flush() + print(out_str) + +def average_gradients(tower_grads): + """Calculate the average gradient for each shared variable across all towers. + Note that this function provides a synchronization point across all towers. + From tensorflow tutorial: cifar10/cifar10_multi_gpu_train.py + Args: + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over individual gradients. The inner list is over the gradient + calculation for each tower. + Returns: + List of pairs of (gradient, variable) where the gradient has been averaged + across all towers. + """ + average_grads = [] + for grad_and_vars in zip(*tower_grads): + # Note that each grad_and_vars looks like the following: + # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) + grads = [] + #for g, _ in grad_and_vars: + for g, v in grad_and_vars: + # Add 0 dimension to the gradients to represent the tower. + expanded_g = tf.expand_dims(g, 0) + + # Append on a 'tower' dimension which we will average over below. + grads.append(expanded_g) + + # Average over the 'tower' dimension. + grad = tf.concat(axis=0, values=grads) + grad = tf.reduce_mean(grad, 0) + + # Keep in mind that the Variables are redundant because they are shared + # across towers. So .. we will just return the first tower's pointer to + # the Variable. + v = grad_and_vars[0][1] + grad_and_var = (grad, v) + average_grads.append(grad_and_var) + return average_grads + + +def get_learning_rate(batch): + learning_rate = tf.train.exponential_decay( + BASE_LEARNING_RATE, # Base learning rate. + batch * BATCH_SIZE, # Current index into the dataset. + DECAY_STEP, # Decay step. + DECAY_RATE, # Decay rate. + staircase=True) + learning_rate = tf.maximum(learning_rate, 0.00001) # CLIP THE LEARNING RATE! + return learning_rate + +def get_bn_decay(batch): + bn_momentum = tf.train.exponential_decay( + BN_INIT_DECAY, + batch*BATCH_SIZE, + BN_DECAY_DECAY_STEP, + BN_DECAY_DECAY_RATE, + staircase=True) + bn_decay = tf.minimum(BN_DECAY_CLIP, 1 - bn_momentum) + return bn_decay + +def train(): + with tf.Graph().as_default(): + with tf.device('/cpu:0'): + pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) + is_training_pl = tf.placeholder(tf.bool, shape=()) + + # Note the global_step=batch parameter to minimize. + # That tells the optimizer to helpfully increment the 'batch' parameter + # for you every time it trains. + batch = tf.get_variable('batch', [], + initializer=tf.constant_initializer(0), trainable=False) + bn_decay = get_bn_decay(batch) + tf.summary.scalar('bn_decay', bn_decay) + + # Set learning rate and optimizer + learning_rate = get_learning_rate(batch) + tf.summary.scalar('learning_rate', learning_rate) + if OPTIMIZER == 'momentum': + optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=MOMENTUM) + elif OPTIMIZER == 'adam': + optimizer = tf.train.AdamOptimizer(learning_rate) + + # ------------------------------------------- + # Get model and loss on multiple GPU devices + # ------------------------------------------- + # Allocating variables on CPU first will greatly accelerate multi-gpu training. + # Ref: https://github.com/kuza55/keras-extras/issues/21 + MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay) + + tower_grads = [] + pred_gpu = [] + total_loss_gpu = [] + for i in range(NUM_GPUS): + with tf.variable_scope(tf.get_variable_scope(), reuse=True): + with tf.device('/gpu:%d'%(i)), tf.name_scope('gpu_%d'%(i)) as scope: + # Evenly split input data to each GPU + pc_batch = tf.slice(pointclouds_pl, + [i*DEVICE_BATCH_SIZE,0,0], [DEVICE_BATCH_SIZE,-1,-1]) + label_batch = tf.slice(labels_pl, + [i*DEVICE_BATCH_SIZE], [DEVICE_BATCH_SIZE]) + + pred, end_points = MODEL.get_model(pc_batch, + is_training=is_training_pl, bn_decay=bn_decay) + + MODEL.get_loss(pred, label_batch, end_points) + losses = tf.get_collection('losses', scope) + total_loss = tf.add_n(losses, name='total_loss') + for l in losses + [total_loss]: + tf.summary.scalar(l.op.name, l) + + grads = optimizer.compute_gradients(total_loss) + tower_grads.append(grads) + + pred_gpu.append(pred) + total_loss_gpu.append(total_loss) + + # Merge pred and losses from multiple GPUs + pred = tf.concat(pred_gpu, 0) + total_loss = tf.reduce_mean(total_loss_gpu) + + # Get training operator + grads = average_gradients(tower_grads) + train_op = optimizer.apply_gradients(grads, global_step=batch) + + correct = tf.equal(tf.argmax(pred, 1), tf.to_int64(labels_pl)) + accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE) + tf.summary.scalar('accuracy', accuracy) + + # Add ops to save and restore all the variables. + saver = tf.train.Saver() + + # Create a session + config = tf.ConfigProto() + config.gpu_options.allow_growth = True + config.allow_soft_placement = True + config.log_device_placement = False + sess = tf.Session(config=config) + + # Add summary writers + merged = tf.summary.merge_all() + train_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'train'), sess.graph) + test_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'test'), sess.graph) + + # Init variables + init = tf.global_variables_initializer() + sess.run(init) + + ops = {'pointclouds_pl': pointclouds_pl, + 'labels_pl': labels_pl, + 'is_training_pl': is_training_pl, + 'pred': pred, + 'loss': total_loss, + 'train_op': train_op, + 'merged': merged, + 'step': batch, + 'end_points': end_points} + + best_acc = -1 + for epoch in range(MAX_EPOCH): + log_string('**** EPOCH %03d ****' % (epoch)) + sys.stdout.flush() + + train_one_epoch(sess, ops, train_writer) + eval_one_epoch(sess, ops, test_writer) + + # Save the variables to disk. + if epoch % 10 == 0: + save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt")) + log_string("Model saved in file: %s" % save_path) + + +def train_one_epoch(sess, ops, train_writer): + """ ops: dict mapping from string to tf ops """ + is_training = True + + log_string(str(datetime.now())) + + # Make sure batch data is of same size + cur_batch_data = np.zeros((BATCH_SIZE,NUM_POINT,TRAIN_DATASET.num_channel())) + cur_batch_label = np.zeros((BATCH_SIZE), dtype=np.int32) + + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx = 0 + while TRAIN_DATASET.has_next_batch(): + batch_data, batch_label = TRAIN_DATASET.next_batch(augment=True) + #batch_data = provider.random_point_dropout(batch_data) + bsize = batch_data.shape[0] + cur_batch_data[0:bsize,...] = batch_data + cur_batch_label[0:bsize] = batch_label + + feed_dict = {ops['pointclouds_pl']: cur_batch_data, + ops['labels_pl']: cur_batch_label, + ops['is_training_pl']: is_training,} + summary, step, _, loss_val, pred_val = sess.run([ops['merged'], ops['step'], + ops['train_op'], ops['loss'], ops['pred']], feed_dict=feed_dict) + train_writer.add_summary(summary, step) + pred_val = np.argmax(pred_val, 1) + correct = np.sum(pred_val[0:bsize] == batch_label[0:bsize]) + total_correct += correct + total_seen += bsize + loss_sum += loss_val + if (batch_idx+1)%50 == 0: + log_string(' ---- batch: %03d ----' % (batch_idx+1)) + log_string('mean loss: %f' % (loss_sum / 50)) + log_string('accuracy: %f' % (total_correct / float(total_seen))) + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx += 1 + + TRAIN_DATASET.reset() + +def eval_one_epoch(sess, ops, test_writer): + """ ops: dict mapping from string to tf ops """ + global EPOCH_CNT + is_training = False + + # Make sure batch data is of same size + cur_batch_data = np.zeros((BATCH_SIZE,NUM_POINT,TEST_DATASET.num_channel())) + cur_batch_label = np.zeros((BATCH_SIZE), dtype=np.int32) + + total_correct = 0 + total_seen = 0 + loss_sum = 0 + batch_idx = 0 + shape_ious = [] + total_seen_class = [0 for _ in range(NUM_CLASSES)] + total_correct_class = [0 for _ in range(NUM_CLASSES)] + + log_string(str(datetime.now())) + log_string('---- EPOCH %03d EVALUATION ----'%(EPOCH_CNT)) + + while TEST_DATASET.has_next_batch(): + batch_data, batch_label = TEST_DATASET.next_batch(augment=False) + bsize = batch_data.shape[0] + # for the last batch in the epoch, the bsize:end are from last batch + cur_batch_data[0:bsize,...] = batch_data + cur_batch_label[0:bsize] = batch_label + + feed_dict = {ops['pointclouds_pl']: cur_batch_data, + ops['labels_pl']: cur_batch_label, + ops['is_training_pl']: is_training} + summary, step, loss_val, pred_val = sess.run([ops['merged'], ops['step'], + ops['loss'], ops['pred']], feed_dict=feed_dict) + test_writer.add_summary(summary, step) + pred_val = np.argmax(pred_val, 1) + correct = np.sum(pred_val[0:bsize] == batch_label[0:bsize]) + total_correct += correct + total_seen += bsize + loss_sum += loss_val + batch_idx += 1 + for i in range(0, bsize): + l = batch_label[i] + total_seen_class[l] += 1 + total_correct_class[l] += (pred_val[i] == l) + + log_string('eval mean loss: %f' % (loss_sum / float(batch_idx))) + log_string('eval accuracy: %f'% (total_correct / float(total_seen))) + log_string('eval avg class acc: %f' % (np.mean(np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float)))) + EPOCH_CNT += 1 + + TEST_DATASET.reset() + return total_correct/float(total_seen) + + +if __name__ == "__main__": + log_string('pid: %s'%(str(os.getpid()))) + train() + LOG_FOUT.close() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/.keep" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/README.md" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/README.md" new file mode 100644 index 0000000000000000000000000000000000000000..6d2bfad8d2be6ef81645ee7941cb74381d73a479 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/README.md" @@ -0,0 +1,6 @@ +## Utilility Functions for 3D Point Cloud Deep Learning + +### visualization tool + + sh compile_render_balls_so.sh + python show3d_balls.py diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/compile_render_balls_so.sh" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/compile_render_balls_so.sh" new file mode 100644 index 0000000000000000000000000000000000000000..dc493f668da5d54034014a4af18928ee2f844e31 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/compile_render_balls_so.sh" @@ -0,0 +1,2 @@ +g++ -std=c++11 render_balls_so.cpp -o render_balls_so.so -shared -fPIC -O2 -D_GLIBCXX_USE_CXX11_ABI=0 + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/eulerangles.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/eulerangles.py" new file mode 100644 index 0000000000000000000000000000000000000000..87bd605a42cd98e1b23faea97b0639ca58efaeb4 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/eulerangles.py" @@ -0,0 +1,418 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +''' Module implementing Euler angle rotations and their conversions + +See: + +* http://en.wikipedia.org/wiki/Rotation_matrix +* http://en.wikipedia.org/wiki/Euler_angles +* http://mathworld.wolfram.com/EulerAngles.html + +See also: *Representing Attitude with Euler Angles and Quaternions: A +Reference* (2006) by James Diebel. A cached PDF link last found here: + +http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110.5134 + +Euler's rotation theorem tells us that any rotation in 3D can be +described by 3 angles. Let's call the 3 angles the *Euler angle vector* +and call the angles in the vector :math:`alpha`, :math:`beta` and +:math:`gamma`. The vector is [ :math:`alpha`, +:math:`beta`. :math:`gamma` ] and, in this description, the order of the +parameters specifies the order in which the rotations occur (so the +rotation corresponding to :math:`alpha` is applied first). + +In order to specify the meaning of an *Euler angle vector* we need to +specify the axes around which each of the rotations corresponding to +:math:`alpha`, :math:`beta` and :math:`gamma` will occur. + +There are therefore three axes for the rotations :math:`alpha`, +:math:`beta` and :math:`gamma`; let's call them :math:`i` :math:`j`, +:math:`k`. + +Let us express the rotation :math:`alpha` around axis `i` as a 3 by 3 +rotation matrix `A`. Similarly :math:`beta` around `j` becomes 3 x 3 +matrix `B` and :math:`gamma` around `k` becomes matrix `G`. Then the +whole rotation expressed by the Euler angle vector [ :math:`alpha`, +:math:`beta`. :math:`gamma` ], `R` is given by:: + + R = np.dot(G, np.dot(B, A)) + +See http://mathworld.wolfram.com/EulerAngles.html + +The order :math:`G B A` expresses the fact that the rotations are +performed in the order of the vector (:math:`alpha` around axis `i` = +`A` first). + +To convert a given Euler angle vector to a meaningful rotation, and a +rotation matrix, we need to define: + +* the axes `i`, `j`, `k` +* whether a rotation matrix should be applied on the left of a vector to + be transformed (vectors are column vectors) or on the right (vectors + are row vectors). +* whether the rotations move the axes as they are applied (intrinsic + rotations) - compared the situation where the axes stay fixed and the + vectors move within the axis frame (extrinsic) +* the handedness of the coordinate system + +See: http://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities + +We are using the following conventions: + +* axes `i`, `j`, `k` are the `z`, `y`, and `x` axes respectively. Thus + an Euler angle vector [ :math:`alpha`, :math:`beta`. :math:`gamma` ] + in our convention implies a :math:`alpha` radian rotation around the + `z` axis, followed by a :math:`beta` rotation around the `y` axis, + followed by a :math:`gamma` rotation around the `x` axis. +* the rotation matrix applies on the left, to column vectors on the + right, so if `R` is the rotation matrix, and `v` is a 3 x N matrix + with N column vectors, the transformed vector set `vdash` is given by + ``vdash = np.dot(R, v)``. +* extrinsic rotations - the axes are fixed, and do not move with the + rotations. +* a right-handed coordinate system + +The convention of rotation around ``z``, followed by rotation around +``y``, followed by rotation around ``x``, is known (confusingly) as +"xyz", pitch-roll-yaw, Cardan angles, or Tait-Bryan angles. +''' + +import math + +import sys +if sys.version_info >= (3,0): + from functools import reduce + +import numpy as np + + +_FLOAT_EPS_4 = np.finfo(float).eps * 4.0 + + +def euler2mat(z=0, y=0, x=0): + ''' Return matrix for rotations around z, y and x axes + + Uses the z, then y, then x convention above + + Parameters + ---------- + z : scalar + Rotation angle in radians around z-axis (performed first) + y : scalar + Rotation angle in radians around y-axis + x : scalar + Rotation angle in radians around x-axis (performed last) + + Returns + ------- + M : array shape (3,3) + Rotation matrix giving same rotation as for given angles + + Examples + -------- + >>> zrot = 1.3 # radians + >>> yrot = -0.1 + >>> xrot = 0.2 + >>> M = euler2mat(zrot, yrot, xrot) + >>> M.shape == (3, 3) + True + + The output rotation matrix is equal to the composition of the + individual rotations + + >>> M1 = euler2mat(zrot) + >>> M2 = euler2mat(0, yrot) + >>> M3 = euler2mat(0, 0, xrot) + >>> composed_M = np.dot(M3, np.dot(M2, M1)) + >>> np.allclose(M, composed_M) + True + + You can specify rotations by named arguments + + >>> np.all(M3 == euler2mat(x=xrot)) + True + + When applying M to a vector, the vector should column vector to the + right of M. If the right hand side is a 2D array rather than a + vector, then each column of the 2D array represents a vector. + + >>> vec = np.array([1, 0, 0]).reshape((3,1)) + >>> v2 = np.dot(M, vec) + >>> vecs = np.array([[1, 0, 0],[0, 1, 0]]).T # giving 3x2 array + >>> vecs2 = np.dot(M, vecs) + + Rotations are counter-clockwise. + + >>> zred = np.dot(euler2mat(z=np.pi/2), np.eye(3)) + >>> np.allclose(zred, [[0, -1, 0],[1, 0, 0], [0, 0, 1]]) + True + >>> yred = np.dot(euler2mat(y=np.pi/2), np.eye(3)) + >>> np.allclose(yred, [[0, 0, 1],[0, 1, 0], [-1, 0, 0]]) + True + >>> xred = np.dot(euler2mat(x=np.pi/2), np.eye(3)) + >>> np.allclose(xred, [[1, 0, 0],[0, 0, -1], [0, 1, 0]]) + True + + Notes + ----- + The direction of rotation is given by the right-hand rule (orient + the thumb of the right hand along the axis around which the rotation + occurs, with the end of the thumb at the positive end of the axis; + curl your fingers; the direction your fingers curl is the direction + of rotation). Therefore, the rotations are counterclockwise if + looking along the axis of rotation from positive to negative. + ''' + Ms = [] + if z: + cosz = math.cos(z) + sinz = math.sin(z) + Ms.append(np.array( + [[cosz, -sinz, 0], + [sinz, cosz, 0], + [0, 0, 1]])) + if y: + cosy = math.cos(y) + siny = math.sin(y) + Ms.append(np.array( + [[cosy, 0, siny], + [0, 1, 0], + [-siny, 0, cosy]])) + if x: + cosx = math.cos(x) + sinx = math.sin(x) + Ms.append(np.array( + [[1, 0, 0], + [0, cosx, -sinx], + [0, sinx, cosx]])) + if Ms: + return reduce(np.dot, Ms[::-1]) + return np.eye(3) + + +def mat2euler(M, cy_thresh=None): + ''' Discover Euler angle vector from 3x3 matrix + + Uses the conventions above. + + Parameters + ---------- + M : array-like, shape (3,3) + cy_thresh : None or scalar, optional + threshold below which to give up on straightforward arctan for + estimating x rotation. If None (default), estimate from + precision of input. + + Returns + ------- + z : scalar + y : scalar + x : scalar + Rotations in radians around z, y, x axes, respectively + + Notes + ----- + If there was no numerical error, the routine could be derived using + Sympy expression for z then y then x rotation matrix, which is:: + + [ cos(y)*cos(z), -cos(y)*sin(z), sin(y)], + [cos(x)*sin(z) + cos(z)*sin(x)*sin(y), cos(x)*cos(z) - sin(x)*sin(y)*sin(z), -cos(y)*sin(x)], + [sin(x)*sin(z) - cos(x)*cos(z)*sin(y), cos(z)*sin(x) + cos(x)*sin(y)*sin(z), cos(x)*cos(y)] + + with the obvious derivations for z, y, and x + + z = atan2(-r12, r11) + y = asin(r13) + x = atan2(-r23, r33) + + Problems arise when cos(y) is close to zero, because both of:: + + z = atan2(cos(y)*sin(z), cos(y)*cos(z)) + x = atan2(cos(y)*sin(x), cos(x)*cos(y)) + + will be close to atan2(0, 0), and highly unstable. + + The ``cy`` fix for numerical instability below is from: *Graphics + Gems IV*, Paul Heckbert (editor), Academic Press, 1994, ISBN: + 0123361559. Specifically it comes from EulerAngles.c by Ken + Shoemake, and deals with the case where cos(y) is close to zero: + + See: http://www.graphicsgems.org/ + + The code appears to be licensed (from the website) as "can be used + without restrictions". + ''' + M = np.asarray(M) + if cy_thresh is None: + try: + cy_thresh = np.finfo(M.dtype).eps * 4 + except ValueError: + cy_thresh = _FLOAT_EPS_4 + r11, r12, r13, r21, r22, r23, r31, r32, r33 = M.flat + # cy: sqrt((cos(y)*cos(z))**2 + (cos(x)*cos(y))**2) + cy = math.sqrt(r33*r33 + r23*r23) + if cy > cy_thresh: # cos(y) not close to zero, standard form + z = math.atan2(-r12, r11) # atan2(cos(y)*sin(z), cos(y)*cos(z)) + y = math.atan2(r13, cy) # atan2(sin(y), cy) + x = math.atan2(-r23, r33) # atan2(cos(y)*sin(x), cos(x)*cos(y)) + else: # cos(y) (close to) zero, so x -> 0.0 (see above) + # so r21 -> sin(z), r22 -> cos(z) and + z = math.atan2(r21, r22) + y = math.atan2(r13, cy) # atan2(sin(y), cy) + x = 0.0 + return z, y, x + + +def euler2quat(z=0, y=0, x=0): + ''' Return quaternion corresponding to these Euler angles + + Uses the z, then y, then x convention above + + Parameters + ---------- + z : scalar + Rotation angle in radians around z-axis (performed first) + y : scalar + Rotation angle in radians around y-axis + x : scalar + Rotation angle in radians around x-axis (performed last) + + Returns + ------- + quat : array shape (4,) + Quaternion in w, x, y z (real, then vector) format + + Notes + ----- + We can derive this formula in Sympy using: + + 1. Formula giving quaternion corresponding to rotation of theta radians + about arbitrary axis: + http://mathworld.wolfram.com/EulerParameters.html + 2. Generated formulae from 1.) for quaternions corresponding to + theta radians rotations about ``x, y, z`` axes + 3. Apply quaternion multiplication formula - + http://en.wikipedia.org/wiki/Quaternions#Hamilton_product - to + formulae from 2.) to give formula for combined rotations. + ''' + z = z/2.0 + y = y/2.0 + x = x/2.0 + cz = math.cos(z) + sz = math.sin(z) + cy = math.cos(y) + sy = math.sin(y) + cx = math.cos(x) + sx = math.sin(x) + return np.array([ + cx*cy*cz - sx*sy*sz, + cx*sy*sz + cy*cz*sx, + cx*cz*sy - sx*cy*sz, + cx*cy*sz + sx*cz*sy]) + + +def quat2euler(q): + ''' Return Euler angles corresponding to quaternion `q` + + Parameters + ---------- + q : 4 element sequence + w, x, y, z of quaternion + + Returns + ------- + z : scalar + Rotation angle in radians around z-axis (performed first) + y : scalar + Rotation angle in radians around y-axis + x : scalar + Rotation angle in radians around x-axis (performed last) + + Notes + ----- + It's possible to reduce the amount of calculation a little, by + combining parts of the ``quat2mat`` and ``mat2euler`` functions, but + the reduction in computation is small, and the code repetition is + large. + ''' + # delayed import to avoid cyclic dependencies + import nibabel.quaternions as nq + return mat2euler(nq.quat2mat(q)) + + +def euler2angle_axis(z=0, y=0, x=0): + ''' Return angle, axis corresponding to these Euler angles + + Uses the z, then y, then x convention above + + Parameters + ---------- + z : scalar + Rotation angle in radians around z-axis (performed first) + y : scalar + Rotation angle in radians around y-axis + x : scalar + Rotation angle in radians around x-axis (performed last) + + Returns + ------- + theta : scalar + angle of rotation + vector : array shape (3,) + axis around which rotation occurs + + Examples + -------- + >>> theta, vec = euler2angle_axis(0, 1.5, 0) + >>> print(theta) + 1.5 + >>> np.allclose(vec, [0, 1, 0]) + True + ''' + # delayed import to avoid cyclic dependencies + import nibabel.quaternions as nq + return nq.quat2angle_axis(euler2quat(z, y, x)) + + +def angle_axis2euler(theta, vector, is_normalized=False): + ''' Convert angle, axis pair to Euler angles + + Parameters + ---------- + theta : scalar + angle of rotation + vector : 3 element sequence + vector specifying axis for rotation. + is_normalized : bool, optional + True if vector is already normalized (has norm of 1). Default + False + + Returns + ------- + z : scalar + y : scalar + x : scalar + Rotations in radians around z, y, x axes, respectively + + Examples + -------- + >>> z, y, x = angle_axis2euler(0, [1, 0, 0]) + >>> np.allclose((z, y, x), 0) + True + + Notes + ----- + It's possible to reduce the amount of calculation a little, by + combining parts of the ``angle_axis2mat`` and ``mat2euler`` + functions, but the reduction in computation is small, and the code + repetition is large. + ''' + # delayed import to avoid cyclic dependencies + import nibabel.quaternions as nq + M = nq.angle_axis2mat(theta, vector, is_normalized) + return mat2euler(M) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pc_util.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pc_util.py" new file mode 100644 index 0000000000000000000000000000000000000000..81f63d867ab9ced18ae38fa7b83e69591e573bfa --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pc_util.py" @@ -0,0 +1,315 @@ +""" Utility functions for processing point clouds. + +Author: Charles R. Qi, Hao Su +Date: November 2016 +""" + +import os +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) + +# Draw point cloud +from eulerangles import euler2mat + +# Point cloud IO +import numpy as np +from plyfile import PlyData, PlyElement + + +# ---------------------------------------- +# Point Cloud/Volume Conversions +# ---------------------------------------- + +def point_cloud_to_volume_batch(point_clouds, vsize=12, radius=1.0, flatten=True): + """ Input is BxNx3 batch of point cloud + Output is Bx(vsize^3) + """ + vol_list = [] + for b in range(point_clouds.shape[0]): + vol = point_cloud_to_volume(np.squeeze(point_clouds[b,:,:]), vsize, radius) + if flatten: + vol_list.append(vol.flatten()) + else: + vol_list.append(np.expand_dims(np.expand_dims(vol, -1), 0)) + if flatten: + return np.vstack(vol_list) + else: + return np.concatenate(vol_list, 0) + + +def point_cloud_to_volume(points, vsize, radius=1.0): + """ input is Nx3 points. + output is vsize*vsize*vsize + assumes points are in range [-radius, radius] + """ + vol = np.zeros((vsize,vsize,vsize)) + voxel = 2*radius/float(vsize) + locations = (points + radius)/voxel + locations = locations.astype(int) + vol[locations[:,0],locations[:,1],locations[:,2]] = 1.0 + return vol + +#a = np.zeros((16,1024,3)) +#print point_cloud_to_volume_batch(a, 12, 1.0, False).shape + +def volume_to_point_cloud(vol): + """ vol is occupancy grid (value = 0 or 1) of size vsize*vsize*vsize + return Nx3 numpy array. + """ + vsize = vol.shape[0] + assert(vol.shape[1] == vsize and vol.shape[1] == vsize) + points = [] + for a in range(vsize): + for b in range(vsize): + for c in range(vsize): + if vol[a,b,c] == 1: + points.append(np.array([a,b,c])) + if len(points) == 0: + return np.zeros((0,3)) + points = np.vstack(points) + return points + +def point_cloud_to_volume_v2_batch(point_clouds, vsize=12, radius=1.0, num_sample=128): + """ Input is BxNx3 a batch of point cloud + Output is BxVxVxVxnum_samplex3 + Added on Feb 19 + """ + vol_list = [] + for b in range(point_clouds.shape[0]): + vol = point_cloud_to_volume_v2(point_clouds[b,:,:], vsize, radius, num_sample) + vol_list.append(np.expand_dims(vol, 0)) + return np.concatenate(vol_list, 0) + +def point_cloud_to_volume_v2(points, vsize, radius=1.0, num_sample=128): + """ input is Nx3 points + output is vsize*vsize*vsize*num_sample*3 + assumes points are in range [-radius, radius] + samples num_sample points in each voxel, if there are less than + num_sample points, replicate the points + Added on Feb 19 + """ + vol = np.zeros((vsize,vsize,vsize,num_sample,3)) + voxel = 2*radius/float(vsize) + locations = (points + radius)/voxel + locations = locations.astype(int) + loc2pc = {} + for n in range(points.shape[0]): + loc = tuple(locations[n,:]) + if loc not in loc2pc: + loc2pc[loc] = [] + loc2pc[loc].append(points[n,:]) + #print loc2pc + + for i in range(vsize): + for j in range(vsize): + for k in range(vsize): + if (i,j,k) not in loc2pc: + vol[i,j,k,:,:] = np.zeros((num_sample,3)) + else: + pc = loc2pc[(i,j,k)] # a list of (3,) arrays + pc = np.vstack(pc) # kx3 + # Sample/pad to num_sample points + if pc.shape[0]>num_sample: + choices = np.random.choice(pc.shape[0], num_sample, replace=False) + pc = pc[choices,:] + elif pc.shape[0]num_sample: + choices = np.random.choice(pc.shape[0], num_sample, replace=False) + pc = pc[choices,:] + elif pc.shape[0] 0) + dx = mask[:, 0] + dy = mask[:, 1] + dv = disk[disk > 0] + + # Order points by z-buffer + zorder = np.argsort(points[:, 2]) + points = points[zorder, :] + points[:, 2] = (points[:, 2] - np.min(points[:, 2])) / (np.max(points[:, 2] - np.min(points[:, 2]))) + max_depth = np.max(points[:, 2]) + + for i in range(points.shape[0]): + j = points.shape[0] - i - 1 + x = points[j, 0] + y = points[j, 1] + xc = canvasSize/2 + (x*space) + yc = canvasSize/2 + (y*space) + xc = int(np.round(xc)) + yc = int(np.round(yc)) + + px = dx + xc + py = dy + yc + + image[px, py] = image[px, py] * 0.7 + dv * (max_depth - points[j, 2]) * 0.3 + + image = image / np.max(image) + return image + +def point_cloud_three_views(points): + """ input points Nx3 numpy array (+y is up direction). + return an numpy array gray image of size 500x1500. """ + # +y is up direction + # xrot is azimuth + # yrot is in-plane + # zrot is elevation + img1 = draw_point_cloud(points, zrot=110/180.0*np.pi, xrot=45/180.0*np.pi, yrot=0/180.0*np.pi) + img2 = draw_point_cloud(points, zrot=70/180.0*np.pi, xrot=135/180.0*np.pi, yrot=0/180.0*np.pi) + img3 = draw_point_cloud(points, zrot=180.0/180.0*np.pi, xrot=90/180.0*np.pi, yrot=0/180.0*np.pi) + image_large = np.concatenate([img1, img2, img3], 1) + return image_large + + +def point_cloud_three_views_demo(): + """ Demo for draw_point_cloud function """ + from PIL import Image + points = read_ply('../third_party/mesh_sampling/piano.ply') + im_array = point_cloud_three_views(points) + img = Image.fromarray(np.uint8(im_array*255.0)) + img.save('piano.jpg') + +if __name__=="__main__": + point_cloud_three_views_demo() + + +def pyplot_draw_point_cloud(points, output_filename): + """ points is a Nx3 numpy array """ + import matplotlib.pyplot as plt + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + ax.scatter(points[:,0], points[:,1], points[:,2]) + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.set_zlabel('z') + #savefig(output_filename) + +def pyplot_draw_volume(vol, output_filename): + """ vol is of size vsize*vsize*vsize + output an image to output_filename + """ + points = volume_to_point_cloud(vol) + pyplot_draw_point_cloud(points, output_filename) + +def write_ply_color(points, labels, out_filename, num_classes=None): + """ Color (N,3) points with labels (N) within range 0 ~ num_classes-1 as OBJ file """ + import matplotlib.pyplot as pyplot + labels = labels.astype(int) + N = points.shape[0] + if num_classes is None: + num_classes = np.max(labels)+1 + else: + assert(num_classes>np.max(labels)) + fout = open(out_filename, 'w') + #colors = [pyplot.cm.hsv(i/float(num_classes)) for i in range(num_classes)] + colors = [pyplot.cm.jet(i/float(num_classes)) for i in range(num_classes)] + for i in range(N): + c = colors[labels[i]] + c = [int(x*255) for x in c] + fout.write('v %f %f %f %d %d %d\n' % (points[i,0],points[i,1],points[i,2],c[0],c[1],c[2])) + fout.close() diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/plyfile.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/plyfile.py" new file mode 100644 index 0000000000000000000000000000000000000000..69c2aa9e898a999406ee4ecfa856c715f14b9251 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/plyfile.py" @@ -0,0 +1,916 @@ +# Copyright 2014 Darsh Ranjan +# +# This file is part of python-plyfile. +# +# python-plyfile is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# python-plyfile is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with python-plyfile. If not, see +# . + +from itertools import islice as _islice + +import numpy as _np +from sys import byteorder as _byteorder + + +try: + _range = xrange +except NameError: + _range = range + + +# Many-many relation +_data_type_relation = [ + ('int8', 'i1'), + ('char', 'i1'), + ('uint8', 'u1'), + ('uchar', 'b1'), + ('uchar', 'u1'), + ('int16', 'i2'), + ('short', 'i2'), + ('uint16', 'u2'), + ('ushort', 'u2'), + ('int32', 'i4'), + ('int', 'i4'), + ('uint32', 'u4'), + ('uint', 'u4'), + ('float32', 'f4'), + ('float', 'f4'), + ('float64', 'f8'), + ('double', 'f8') +] + +_data_types = dict(_data_type_relation) +_data_type_reverse = dict((b, a) for (a, b) in _data_type_relation) + +_types_list = [] +_types_set = set() +for (_a, _b) in _data_type_relation: + if _a not in _types_set: + _types_list.append(_a) + _types_set.add(_a) + if _b not in _types_set: + _types_list.append(_b) + _types_set.add(_b) + + +_byte_order_map = { + 'ascii': '=', + 'binary_little_endian': '<', + 'binary_big_endian': '>' +} + +_byte_order_reverse = { + '<': 'binary_little_endian', + '>': 'binary_big_endian' +} + +_native_byte_order = {'little': '<', 'big': '>'}[_byteorder] + + +def _lookup_type(type_str): + if type_str not in _data_type_reverse: + try: + type_str = _data_types[type_str] + except KeyError: + raise ValueError("field type %r not in %r" % + (type_str, _types_list)) + + return _data_type_reverse[type_str] + + +def _split_line(line, n): + fields = line.split(None, n) + if len(fields) == n: + fields.append('') + + assert len(fields) == n + 1 + + return fields + + +def make2d(array, cols=None, dtype=None): + ''' + Make a 2D array from an array of arrays. The `cols' and `dtype' + arguments can be omitted if the array is not empty. + + ''' + if (cols is None or dtype is None) and not len(array): + raise RuntimeError("cols and dtype must be specified for empty " + "array") + + if cols is None: + cols = len(array[0]) + + if dtype is None: + dtype = array[0].dtype + + return _np.fromiter(array, [('_', dtype, (cols,))], + count=len(array))['_'] + + +class PlyParseError(Exception): + + ''' + Raised when a PLY file cannot be parsed. + + The attributes `element', `row', `property', and `message' give + additional information. + + ''' + + def __init__(self, message, element=None, row=None, prop=None): + self.message = message + self.element = element + self.row = row + self.prop = prop + + s = '' + if self.element: + s += 'element %r: ' % self.element.name + if self.row is not None: + s += 'row %d: ' % self.row + if self.prop: + s += 'property %r: ' % self.prop.name + s += self.message + + Exception.__init__(self, s) + + def __repr__(self): + return ('PlyParseError(%r, element=%r, row=%r, prop=%r)' % + self.message, self.element, self.row, self.prop) + + +class PlyData(object): + + ''' + PLY file header and data. + + A PlyData instance is created in one of two ways: by the static + method PlyData.read (to read a PLY file), or directly from __init__ + given a sequence of elements (which can then be written to a PLY + file). + + ''' + + def __init__(self, elements=[], text=False, byte_order='=', + comments=[], obj_info=[]): + ''' + elements: sequence of PlyElement instances. + + text: whether the resulting PLY file will be text (True) or + binary (False). + + byte_order: '<' for little-endian, '>' for big-endian, or '=' + for native. This is only relevant if `text' is False. + + comments: sequence of strings that will be placed in the header + between the 'ply' and 'format ...' lines. + + obj_info: like comments, but will be placed in the header with + "obj_info ..." instead of "comment ...". + + ''' + if byte_order == '=' and not text: + byte_order = _native_byte_order + + self.byte_order = byte_order + self.text = text + + self.comments = list(comments) + self.obj_info = list(obj_info) + self.elements = elements + + def _get_elements(self): + return self._elements + + def _set_elements(self, elements): + self._elements = tuple(elements) + self._index() + + elements = property(_get_elements, _set_elements) + + def _get_byte_order(self): + return self._byte_order + + def _set_byte_order(self, byte_order): + if byte_order not in ['<', '>', '=']: + raise ValueError("byte order must be '<', '>', or '='") + + self._byte_order = byte_order + + byte_order = property(_get_byte_order, _set_byte_order) + + def _index(self): + self._element_lookup = dict((elt.name, elt) for elt in + self._elements) + if len(self._element_lookup) != len(self._elements): + raise ValueError("two elements with same name") + + @staticmethod + def _parse_header(stream): + ''' + Parse a PLY header from a readable file-like stream. + + ''' + lines = [] + comments = {'comment': [], 'obj_info': []} + while True: + line = stream.readline().decode('ascii').strip() + fields = _split_line(line, 1) + + if fields[0] == 'end_header': + break + + elif fields[0] in comments.keys(): + lines.append(fields) + else: + lines.append(line.split()) + + a = 0 + if lines[a] != ['ply']: + raise PlyParseError("expected 'ply'") + + a += 1 + while lines[a][0] in comments.keys(): + comments[lines[a][0]].append(lines[a][1]) + a += 1 + + if lines[a][0] != 'format': + raise PlyParseError("expected 'format'") + + if lines[a][2] != '1.0': + raise PlyParseError("expected version '1.0'") + + if len(lines[a]) != 3: + raise PlyParseError("too many fields after 'format'") + + fmt = lines[a][1] + + if fmt not in _byte_order_map: + raise PlyParseError("don't understand format %r" % fmt) + + byte_order = _byte_order_map[fmt] + text = fmt == 'ascii' + + a += 1 + while a < len(lines) and lines[a][0] in comments.keys(): + comments[lines[a][0]].append(lines[a][1]) + a += 1 + + return PlyData(PlyElement._parse_multi(lines[a:]), + text, byte_order, + comments['comment'], comments['obj_info']) + + @staticmethod + def read(stream): + ''' + Read PLY data from a readable file-like object or filename. + + ''' + (must_close, stream) = _open_stream(stream, 'read') + try: + data = PlyData._parse_header(stream) + for elt in data: + elt._read(stream, data.text, data.byte_order) + finally: + if must_close: + stream.close() + + return data + + def write(self, stream): + ''' + Write PLY data to a writeable file-like object or filename. + + ''' + (must_close, stream) = _open_stream(stream, 'write') + try: + stream.write(self.header.encode('ascii')) + stream.write(b'\r\n') + for elt in self: + elt._write(stream, self.text, self.byte_order) + finally: + if must_close: + stream.close() + + @property + def header(self): + ''' + Provide PLY-formatted metadata for the instance. + + ''' + lines = ['ply'] + + if self.text: + lines.append('format ascii 1.0') + else: + lines.append('format ' + + _byte_order_reverse[self.byte_order] + + ' 1.0') + + # Some information is lost here, since all comments are placed + # between the 'format' line and the first element. + for c in self.comments: + lines.append('comment ' + c) + + for c in self.obj_info: + lines.append('obj_info ' + c) + + lines.extend(elt.header for elt in self.elements) + lines.append('end_header') + return '\r\n'.join(lines) + + def __iter__(self): + return iter(self.elements) + + def __len__(self): + return len(self.elements) + + def __contains__(self, name): + return name in self._element_lookup + + def __getitem__(self, name): + return self._element_lookup[name] + + def __str__(self): + return self.header + + def __repr__(self): + return ('PlyData(%r, text=%r, byte_order=%r, ' + 'comments=%r, obj_info=%r)' % + (self.elements, self.text, self.byte_order, + self.comments, self.obj_info)) + + +def _open_stream(stream, read_or_write): + if hasattr(stream, read_or_write): + return (False, stream) + try: + return (True, open(stream, read_or_write[0] + 'b')) + except TypeError: + raise RuntimeError("expected open file or filename") + + +class PlyElement(object): + + ''' + PLY file element. + + A client of this library doesn't normally need to instantiate this + directly, so the following is only for the sake of documenting the + internals. + + Creating a PlyElement instance is generally done in one of two ways: + as a byproduct of PlyData.read (when reading a PLY file) and by + PlyElement.describe (before writing a PLY file). + + ''' + + def __init__(self, name, properties, count, comments=[]): + ''' + This is not part of the public interface. The preferred methods + of obtaining PlyElement instances are PlyData.read (to read from + a file) and PlyElement.describe (to construct from a numpy + array). + + ''' + self._name = str(name) + self._check_name() + self._count = count + + self._properties = tuple(properties) + self._index() + + self.comments = list(comments) + + self._have_list = any(isinstance(p, PlyListProperty) + for p in self.properties) + + @property + def count(self): + return self._count + + def _get_data(self): + return self._data + + def _set_data(self, data): + self._data = data + self._count = len(data) + self._check_sanity() + + data = property(_get_data, _set_data) + + def _check_sanity(self): + for prop in self.properties: + if prop.name not in self._data.dtype.fields: + raise ValueError("dangling property %r" % prop.name) + + def _get_properties(self): + return self._properties + + def _set_properties(self, properties): + self._properties = tuple(properties) + self._check_sanity() + self._index() + + properties = property(_get_properties, _set_properties) + + def _index(self): + self._property_lookup = dict((prop.name, prop) + for prop in self._properties) + if len(self._property_lookup) != len(self._properties): + raise ValueError("two properties with same name") + + def ply_property(self, name): + return self._property_lookup[name] + + @property + def name(self): + return self._name + + def _check_name(self): + if any(c.isspace() for c in self._name): + msg = "element name %r contains spaces" % self._name + raise ValueError(msg) + + def dtype(self, byte_order='='): + ''' + Return the numpy dtype of the in-memory representation of the + data. (If there are no list properties, and the PLY format is + binary, then this also accurately describes the on-disk + representation of the element.) + + ''' + return [(prop.name, prop.dtype(byte_order)) + for prop in self.properties] + + @staticmethod + def _parse_multi(header_lines): + ''' + Parse a list of PLY element definitions. + + ''' + elements = [] + while header_lines: + (elt, header_lines) = PlyElement._parse_one(header_lines) + elements.append(elt) + + return elements + + @staticmethod + def _parse_one(lines): + ''' + Consume one element definition. The unconsumed input is + returned along with a PlyElement instance. + + ''' + a = 0 + line = lines[a] + + if line[0] != 'element': + raise PlyParseError("expected 'element'") + if len(line) > 3: + raise PlyParseError("too many fields after 'element'") + if len(line) < 3: + raise PlyParseError("too few fields after 'element'") + + (name, count) = (line[1], int(line[2])) + + comments = [] + properties = [] + while True: + a += 1 + if a >= len(lines): + break + + if lines[a][0] == 'comment': + comments.append(lines[a][1]) + elif lines[a][0] == 'property': + properties.append(PlyProperty._parse_one(lines[a])) + else: + break + + return (PlyElement(name, properties, count, comments), + lines[a:]) + + @staticmethod + def describe(data, name, len_types={}, val_types={}, + comments=[]): + ''' + Construct a PlyElement from an array's metadata. + + len_types and val_types can be given as mappings from list + property names to type strings (like 'u1', 'f4', etc., or + 'int8', 'float32', etc.). These can be used to define the length + and value types of list properties. List property lengths + always default to type 'u1' (8-bit unsigned integer), and value + types default to 'i4' (32-bit integer). + + ''' + if not isinstance(data, _np.ndarray): + raise TypeError("only numpy arrays are supported") + + if len(data.shape) != 1: + raise ValueError("only one-dimensional arrays are " + "supported") + + count = len(data) + + properties = [] + descr = data.dtype.descr + + for t in descr: + if not isinstance(t[1], str): + raise ValueError("nested records not supported") + + if not t[0]: + raise ValueError("field with empty name") + + if len(t) != 2 or t[1][1] == 'O': + # non-scalar field, which corresponds to a list + # property in PLY. + + if t[1][1] == 'O': + if len(t) != 2: + raise ValueError("non-scalar object fields not " + "supported") + + len_str = _data_type_reverse[len_types.get(t[0], 'u1')] + if t[1][1] == 'O': + val_type = val_types.get(t[0], 'i4') + val_str = _lookup_type(val_type) + else: + val_str = _lookup_type(t[1][1:]) + + prop = PlyListProperty(t[0], len_str, val_str) + else: + val_str = _lookup_type(t[1][1:]) + prop = PlyProperty(t[0], val_str) + + properties.append(prop) + + elt = PlyElement(name, properties, count, comments) + elt.data = data + + return elt + + def _read(self, stream, text, byte_order): + ''' + Read the actual data from a PLY file. + + ''' + if text: + self._read_txt(stream) + else: + if self._have_list: + # There are list properties, so a simple load is + # impossible. + self._read_bin(stream, byte_order) + else: + # There are no list properties, so loading the data is + # much more straightforward. + self._data = _np.fromfile(stream, + self.dtype(byte_order), + self.count) + + if len(self._data) < self.count: + k = len(self._data) + del self._data + raise PlyParseError("early end-of-file", self, k) + + self._check_sanity() + + def _write(self, stream, text, byte_order): + ''' + Write the data to a PLY file. + + ''' + if text: + self._write_txt(stream) + else: + if self._have_list: + # There are list properties, so serialization is + # slightly complicated. + self._write_bin(stream, byte_order) + else: + # no list properties, so serialization is + # straightforward. + self.data.astype(self.dtype(byte_order), + copy=False).tofile(stream) + + def _read_txt(self, stream): + ''' + Load a PLY element from an ASCII-format PLY file. The element + may contain list properties. + + ''' + self._data = _np.empty(self.count, dtype=self.dtype()) + + k = 0 + for line in _islice(iter(stream.readline, b''), self.count): + fields = iter(line.strip().split()) + for prop in self.properties: + try: + self._data[prop.name][k] = prop._from_fields(fields) + except StopIteration: + raise PlyParseError("early end-of-line", + self, k, prop) + except ValueError: + raise PlyParseError("malformed input", + self, k, prop) + try: + next(fields) + except StopIteration: + pass + else: + raise PlyParseError("expected end-of-line", self, k) + k += 1 + + if k < self.count: + del self._data + raise PlyParseError("early end-of-file", self, k) + + def _write_txt(self, stream): + ''' + Save a PLY element to an ASCII-format PLY file. The element may + contain list properties. + + ''' + for rec in self.data: + fields = [] + for prop in self.properties: + fields.extend(prop._to_fields(rec[prop.name])) + + _np.savetxt(stream, [fields], '%.18g', newline='\r\n') + + def _read_bin(self, stream, byte_order): + ''' + Load a PLY element from a binary PLY file. The element may + contain list properties. + + ''' + self._data = _np.empty(self.count, dtype=self.dtype(byte_order)) + + for k in _range(self.count): + for prop in self.properties: + try: + self._data[prop.name][k] = \ + prop._read_bin(stream, byte_order) + except StopIteration: + raise PlyParseError("early end-of-file", + self, k, prop) + + def _write_bin(self, stream, byte_order): + ''' + Save a PLY element to a binary PLY file. The element may + contain list properties. + + ''' + for rec in self.data: + for prop in self.properties: + prop._write_bin(rec[prop.name], stream, byte_order) + + @property + def header(self): + ''' + Format this element's metadata as it would appear in a PLY + header. + + ''' + lines = ['element %s %d' % (self.name, self.count)] + + # Some information is lost here, since all comments are placed + # between the 'element' line and the first property definition. + for c in self.comments: + lines.append('comment ' + c) + + lines.extend(list(map(str, self.properties))) + + return '\r\n'.join(lines) + + def __getitem__(self, key): + return self.data[key] + + def __setitem__(self, key, value): + self.data[key] = value + + def __str__(self): + return self.header + + def __repr__(self): + return ('PlyElement(%r, %r, count=%d, comments=%r)' % + (self.name, self.properties, self.count, + self.comments)) + + +class PlyProperty(object): + + ''' + PLY property description. This class is pure metadata; the data + itself is contained in PlyElement instances. + + ''' + + def __init__(self, name, val_dtype): + self._name = str(name) + self._check_name() + self.val_dtype = val_dtype + + def _get_val_dtype(self): + return self._val_dtype + + def _set_val_dtype(self, val_dtype): + self._val_dtype = _data_types[_lookup_type(val_dtype)] + + val_dtype = property(_get_val_dtype, _set_val_dtype) + + @property + def name(self): + return self._name + + def _check_name(self): + if any(c.isspace() for c in self._name): + msg = "Error: property name %r contains spaces" % self._name + raise RuntimeError(msg) + + @staticmethod + def _parse_one(line): + assert line[0] == 'property' + + if line[1] == 'list': + if len(line) > 5: + raise PlyParseError("too many fields after " + "'property list'") + if len(line) < 5: + raise PlyParseError("too few fields after " + "'property list'") + + return PlyListProperty(line[4], line[2], line[3]) + + else: + if len(line) > 3: + raise PlyParseError("too many fields after " + "'property'") + if len(line) < 3: + raise PlyParseError("too few fields after " + "'property'") + + return PlyProperty(line[2], line[1]) + + def dtype(self, byte_order='='): + ''' + Return the numpy dtype description for this property (as a tuple + of strings). + + ''' + return byte_order + self.val_dtype + + def _from_fields(self, fields): + ''' + Parse from generator. Raise StopIteration if the property could + not be read. + + ''' + return _np.dtype(self.dtype()).type(next(fields)) + + def _to_fields(self, data): + ''' + Return generator over one item. + + ''' + yield _np.dtype(self.dtype()).type(data) + + def _read_bin(self, stream, byte_order): + ''' + Read data from a binary stream. Raise StopIteration if the + property could not be read. + + ''' + try: + return _np.fromfile(stream, self.dtype(byte_order), 1)[0] + except IndexError: + raise StopIteration + + def _write_bin(self, data, stream, byte_order): + ''' + Write data to a binary stream. + + ''' + _np.dtype(self.dtype(byte_order)).type(data).tofile(stream) + + def __str__(self): + val_str = _data_type_reverse[self.val_dtype] + return 'property %s %s' % (val_str, self.name) + + def __repr__(self): + return 'PlyProperty(%r, %r)' % (self.name, + _lookup_type(self.val_dtype)) + + +class PlyListProperty(PlyProperty): + + ''' + PLY list property description. + + ''' + + def __init__(self, name, len_dtype, val_dtype): + PlyProperty.__init__(self, name, val_dtype) + + self.len_dtype = len_dtype + + def _get_len_dtype(self): + return self._len_dtype + + def _set_len_dtype(self, len_dtype): + self._len_dtype = _data_types[_lookup_type(len_dtype)] + + len_dtype = property(_get_len_dtype, _set_len_dtype) + + def dtype(self, byte_order='='): + ''' + List properties always have a numpy dtype of "object". + + ''' + return '|O' + + def list_dtype(self, byte_order='='): + ''' + Return the pair (len_dtype, val_dtype) (both numpy-friendly + strings). + + ''' + return (byte_order + self.len_dtype, + byte_order + self.val_dtype) + + def _from_fields(self, fields): + (len_t, val_t) = self.list_dtype() + + n = int(_np.dtype(len_t).type(next(fields))) + + data = _np.loadtxt(list(_islice(fields, n)), val_t, ndmin=1) + if len(data) < n: + raise StopIteration + + return data + + def _to_fields(self, data): + ''' + Return generator over the (numerical) PLY representation of the + list data (length followed by actual data). + + ''' + (len_t, val_t) = self.list_dtype() + + data = _np.asarray(data, dtype=val_t).ravel() + + yield _np.dtype(len_t).type(data.size) + for x in data: + yield x + + def _read_bin(self, stream, byte_order): + (len_t, val_t) = self.list_dtype(byte_order) + + try: + n = _np.fromfile(stream, len_t, 1)[0] + except IndexError: + raise StopIteration + + data = _np.fromfile(stream, val_t, n) + if len(data) < n: + raise StopIteration + + return data + + def _write_bin(self, data, stream, byte_order): + ''' + Write data to a binary stream. + + ''' + (len_t, val_t) = self.list_dtype(byte_order) + + data = _np.asarray(data, dtype=val_t).ravel() + + _np.array(data.size, dtype=len_t).tofile(stream) + data.tofile(stream) + + def __str__(self): + len_str = _data_type_reverse[self.len_dtype] + val_str = _data_type_reverse[self.val_dtype] + return 'property list %s %s %s' % (len_str, val_str, self.name) + + def __repr__(self): + return ('PlyListProperty(%r, %r, %r)' % + (self.name, + _lookup_type(self.len_dtype), + _lookup_type(self.val_dtype))) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pointnet_util.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pointnet_util.py" new file mode 100644 index 0000000000000000000000000000000000000000..88ff7014e0b0dff465196a694378e62cde5296f8 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/pointnet_util.py" @@ -0,0 +1,230 @@ +""" PointNet++ Layers +Author: Charles R. Qi +Date: November 2017 +""" +import os +import sys +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = os.path.dirname(BASE_DIR) +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/sampling')) +sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/grouping')) +sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/3d_interpolation')) +from tf_sampling import farthest_point_sample, gather_point +from tf_grouping import query_ball_point, group_point, knn_point +from tf_interpolate import three_nn, three_interpolate +import tensorflow as tf + +import numpy as np +import tf_util + +def sample_and_group(npoint, radius, nsample, xyz, points, knn=False, use_xyz=True): + ''' + Input: + npoint: int32 + radius: float32 + nsample: int32 + xyz: (batch_size, ndataset, 3) TF tensor + points: (batch_size, ndataset, channel) TF tensor, if None will just use xyz as points + knn: bool, if True use kNN instead of radius search + use_xyz: bool, if True concat XYZ with local point features, otherwise just use point features + Output: + new_xyz: (batch_size, npoint, 3) TF tensor + new_points: (batch_size, npoint, nsample, 3+channel) TF tensor + idx: (batch_size, npoint, nsample) TF tensor, indices of local points as in ndataset points + grouped_xyz: (batch_size, npoint, nsample, 3) TF tensor, normalized point XYZs + (subtracted by seed point XYZ) in local regions + ''' + + new_xyz = gather_point(xyz, farthest_point_sample(npoint, xyz)) # (batch_size, npoint, 3) + if knn: + _,idx = knn_point(nsample, xyz, new_xyz) + else: + idx, pts_cnt = query_ball_point(radius, nsample, xyz, new_xyz) + grouped_xyz = group_point(xyz, idx) # (batch_size, npoint, nsample, 3) + grouped_xyz -= tf.tile(tf.expand_dims(new_xyz, 2), [1,1,nsample,1]) # translation normalization + if points is not None: + grouped_points = group_point(points, idx) # (batch_size, npoint, nsample, channel) + if use_xyz: + new_points = tf.concat([grouped_xyz, grouped_points], axis=-1) # (batch_size, npoint, nample, 3+channel) + else: + new_points = grouped_points + else: + new_points = grouped_xyz + + return new_xyz, new_points, idx, grouped_xyz + + +def sample_and_group_all(xyz, points, use_xyz=True): + ''' + Inputs: + xyz: (batch_size, ndataset, 3) TF tensor + points: (batch_size, ndataset, channel) TF tensor, if None will just use xyz as points + use_xyz: bool, if True concat XYZ with local point features, otherwise just use point features + Outputs: + new_xyz: (batch_size, 1, 3) as (0,0,0) + new_points: (batch_size, 1, ndataset, 3+channel) TF tensor + Note: + Equivalent to sample_and_group with npoint=1, radius=inf, use (0,0,0) as the centroid + ''' + batch_size = xyz.get_shape()[0].value + nsample = xyz.get_shape()[1].value + new_xyz = tf.constant(np.tile(np.array([0,0,0]).reshape((1,1,3)), (batch_size,1,1)),dtype=tf.float32) # (batch_size, 1, 3) + idx = tf.constant(np.tile(np.array(range(nsample)).reshape((1,1,nsample)), (batch_size,1,1))) + grouped_xyz = tf.reshape(xyz, (batch_size, 1, nsample, 3)) # (batch_size, npoint=1, nsample, 3) + if points is not None: + if use_xyz: + new_points = tf.concat([xyz, points], axis=2) # (batch_size, 16, 259) + else: + new_points = points + new_points = tf.expand_dims(new_points, 1) # (batch_size, 1, 16, 259) + else: + new_points = grouped_xyz + return new_xyz, new_points, idx, grouped_xyz + + +def pointnet_sa_module(xyz, points, npoint, radius, nsample, mlp, mlp2, group_all, is_training, bn_decay, scope, bn=True, pooling='max', knn=False, use_xyz=True, use_nchw=False): + ''' PointNet Set Abstraction (SA) Module + Input: + xyz: (batch_size, ndataset, 3) TF tensor + points: (batch_size, ndataset, channel) TF tensor + npoint: int32 -- #points sampled in farthest point sampling + radius: float32 -- search radius in local region + nsample: int32 -- how many points in each local region + mlp: list of int32 -- output size for MLP on each point + mlp2: list of int32 -- output size for MLP on each region + group_all: bool -- group all points into one PC if set true, OVERRIDE + npoint, radius and nsample settings + use_xyz: bool, if True concat XYZ with local point features, otherwise just use point features + use_nchw: bool, if True, use NCHW data format for conv2d, which is usually faster than NHWC format + Return: + new_xyz: (batch_size, npoint, 3) TF tensor + new_points: (batch_size, npoint, mlp[-1] or mlp2[-1]) TF tensor + idx: (batch_size, npoint, nsample) int32 -- indices for local regions + ''' + data_format = 'NCHW' if use_nchw else 'NHWC' + with tf.variable_scope(scope) as sc: + # Sample and Grouping + if group_all: + nsample = xyz.get_shape()[1].value + new_xyz, new_points, idx, grouped_xyz = sample_and_group_all(xyz, points, use_xyz) + else: + new_xyz, new_points, idx, grouped_xyz = sample_and_group(npoint, radius, nsample, xyz, points, knn, use_xyz) + + # Point Feature Embedding + if use_nchw: new_points = tf.transpose(new_points, [0,3,1,2]) + for i, num_out_channel in enumerate(mlp): + new_points = tf_util.conv2d(new_points, num_out_channel, [1,1], + padding='VALID', stride=[1,1], + bn=bn, is_training=is_training, + scope='conv%d'%(i), bn_decay=bn_decay, + data_format=data_format) + if use_nchw: new_points = tf.transpose(new_points, [0,2,3,1]) + + # Pooling in Local Regions + if pooling=='max': + new_points = tf.reduce_max(new_points, axis=[2], keepdims=True, name='maxpool') + elif pooling=='avg': + new_points = tf.reduce_mean(new_points, axis=[2], keepdims=True, name='avgpool') + elif pooling=='weighted_avg': + with tf.variable_scope('weighted_avg'): + dists = tf.norm(grouped_xyz,axis=-1,ord=2,keepdims=True) + exp_dists = tf.exp(-dists * 5) + weights = exp_dists/tf.reduce_sum(exp_dists,axis=2,keepdims=True) # (batch_size, npoint, nsample, 1) + new_points *= weights # (batch_size, npoint, nsample, mlp[-1]) + new_points = tf.reduce_sum(new_points, axis=2, keepdims=True) + elif pooling=='max_and_avg': + max_points = tf.reduce_max(new_points, axis=[2], keepdims=True, name='maxpool') + avg_points = tf.reduce_mean(new_points, axis=[2], keepdims=True, name='avgpool') + new_points = tf.concat([avg_points, max_points], axis=-1) + + # [Optional] Further Processing + if mlp2 is not None: + if use_nchw: new_points = tf.transpose(new_points, [0,3,1,2]) + for i, num_out_channel in enumerate(mlp2): + new_points = tf_util.conv2d(new_points, num_out_channel, [1,1], + padding='VALID', stride=[1,1], + bn=bn, is_training=is_training, + scope='conv_post_%d'%(i), bn_decay=bn_decay, + data_format=data_format) + if use_nchw: new_points = tf.transpose(new_points, [0,2,3,1]) + + new_points = tf.squeeze(new_points, [2]) # (batch_size, npoints, mlp2[-1]) + return new_xyz, new_points, idx + +def pointnet_sa_module_msg(xyz, points, npoint, radius_list, nsample_list, mlp_list, is_training, bn_decay, scope, bn=True, use_xyz=True, use_nchw=False): + ''' PointNet Set Abstraction (SA) module with Multi-Scale Grouping (MSG) + Input: + xyz: (batch_size, ndataset, 3) TF tensor + points: (batch_size, ndataset, channel) TF tensor + npoint: int32 -- #points sampled in farthest point sampling + radius: list of float32 -- search radius in local region + nsample: list of int32 -- how many points in each local region + mlp: list of list of int32 -- output size for MLP on each point + use_xyz: bool, if True concat XYZ with local point features, otherwise just use point features + use_nchw: bool, if True, use NCHW data format for conv2d, which is usually faster than NHWC format + Return: + new_xyz: (batch_size, npoint, 3) TF tensor + new_points: (batch_size, npoint, \sum_k{mlp[k][-1]}) TF tensor + ''' + data_format = 'NCHW' if use_nchw else 'NHWC' + with tf.variable_scope(scope) as sc: + new_xyz = gather_point(xyz, farthest_point_sample(npoint, xyz)) + new_points_list = [] + for i in range(len(radius_list)): + radius = radius_list[i] + nsample = nsample_list[i] + idx, pts_cnt = query_ball_point(radius, nsample, xyz, new_xyz) + grouped_xyz = group_point(xyz, idx) + grouped_xyz -= tf.tile(tf.expand_dims(new_xyz, 2), [1,1,nsample,1]) + if points is not None: + grouped_points = group_point(points, idx) + if use_xyz: + grouped_points = tf.concat([grouped_points, grouped_xyz], axis=-1) + else: + grouped_points = grouped_xyz + if use_nchw: grouped_points = tf.transpose(grouped_points, [0,3,1,2]) + for j,num_out_channel in enumerate(mlp_list[i]): + grouped_points = tf_util.conv2d(grouped_points, num_out_channel, [1,1], + padding='VALID', stride=[1,1], bn=bn, is_training=is_training, + scope='conv%d_%d'%(i,j), bn_decay=bn_decay) + if use_nchw: grouped_points = tf.transpose(grouped_points, [0,2,3,1]) + new_points = tf.reduce_max(grouped_points, axis=[2]) + new_points_list.append(new_points) + new_points_concat = tf.concat(new_points_list, axis=-1) + return new_xyz, new_points_concat + + +def pointnet_fp_module(xyz1, xyz2, points1, points2, mlp, is_training, bn_decay, scope, bn=True): + ''' PointNet Feature Propogation (FP) Module + Input: + xyz1: (batch_size, ndataset1, 3) TF tensor + xyz2: (batch_size, ndataset2, 3) TF tensor, sparser than xyz1 + points1: (batch_size, ndataset1, nchannel1) TF tensor + points2: (batch_size, ndataset2, nchannel2) TF tensor + mlp: list of int32 -- output size for MLP on each point + Return: + new_points: (batch_size, ndataset1, mlp[-1]) TF tensor + ''' + with tf.variable_scope(scope) as sc: + dist, idx = three_nn(xyz1, xyz2) + dist = tf.maximum(dist, 1e-10) + #修改, + norm = tf.reduce_sum((1.0/dist),axis=2,keepdims=True) + + norm = tf.tile(norm,[1,1,3]) + weight = (1.0/dist) / norm + interpolated_points = three_interpolate(points2, idx, weight) + + if points1 is not None: + new_points1 = tf.concat(axis=2, values=[interpolated_points, points1]) # B,ndataset1,nchannel1+nchannel2 + else: + new_points1 = interpolated_points + new_points1 = tf.expand_dims(new_points1, 2) + for i, num_out_channel in enumerate(mlp): + new_points1 = tf_util.conv2d(new_points1, num_out_channel, [1,1], + padding='VALID', stride=[1,1], + bn=bn, is_training=is_training, + scope='conv_%d'%(i), bn_decay=bn_decay) + new_points1 = tf.squeeze(new_points1, [2]) # B,ndataset1,mlp[-1] + return new_points1 diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/provider.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/provider.py" new file mode 100644 index 0000000000000000000000000000000000000000..95118f504d67d87076531b001a0cd114daf02cb6 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/provider.py" @@ -0,0 +1,247 @@ +import os +import sys +import numpy as np +import h5py +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(BASE_DIR) + +def shuffle_data(data, labels): + """ Shuffle data and labels. + Input: + data: B,N,... numpy array + label: B,... numpy array + Return: + shuffled data, label and shuffle indices + """ + idx = np.arange(len(labels)) + np.random.shuffle(idx) + return data[idx, ...], labels[idx], idx + +def shuffle_points(batch_data): + """ Shuffle orders of points in each point cloud -- changes FPS behavior. + Use the same shuffling idx for the entire batch. + Input: + BxNxC array + Output: + BxNxC array + """ + idx = np.arange(batch_data.shape[1]) + np.random.shuffle(idx) + return batch_data[:,idx,:] + +def rotate_point_cloud(batch_data): + """ Randomly rotate the point clouds to augument the dataset + rotation is per shape based along up direction + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + rotation_angle = np.random.uniform() * 2 * np.pi + cosval = np.cos(rotation_angle) + sinval = np.sin(rotation_angle) + rotation_matrix = np.array([[cosval, 0, sinval], + [0, 1, 0], + [-sinval, 0, cosval]]) + shape_pc = batch_data[k, ...] + rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + return rotated_data + +def rotate_point_cloud_z(batch_data): + """ Randomly rotate the point clouds to augument the dataset + rotation is per shape based along up direction + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + rotation_angle = np.random.uniform() * 2 * np.pi + cosval = np.cos(rotation_angle) + sinval = np.sin(rotation_angle) + rotation_matrix = np.array([[cosval, sinval, 0], + [-sinval, cosval, 0], + [0, 0, 1]]) + shape_pc = batch_data[k, ...] + rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + return rotated_data + +def rotate_point_cloud_with_normal(batch_xyz_normal): + ''' Randomly rotate XYZ, normal point cloud. + Input: + batch_xyz_normal: B,N,6, first three channels are XYZ, last 3 all normal + Output: + B,N,6, rotated XYZ, normal point cloud + ''' + for k in range(batch_xyz_normal.shape[0]): + rotation_angle = np.random.uniform() * 2 * np.pi + cosval = np.cos(rotation_angle) + sinval = np.sin(rotation_angle) + rotation_matrix = np.array([[cosval, 0, sinval], + [0, 1, 0], + [-sinval, 0, cosval]]) + shape_pc = batch_xyz_normal[k,:,0:3] + shape_normal = batch_xyz_normal[k,:,3:6] + batch_xyz_normal[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + batch_xyz_normal[k,:,3:6] = np.dot(shape_normal.reshape((-1, 3)), rotation_matrix) + return batch_xyz_normal + +def rotate_perturbation_point_cloud_with_normal(batch_data, angle_sigma=0.06, angle_clip=0.18): + """ Randomly perturb the point clouds by small rotations + Input: + BxNx6 array, original batch of point clouds and point normals + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + angles = np.clip(angle_sigma*np.random.randn(3), -angle_clip, angle_clip) + Rx = np.array([[1,0,0], + [0,np.cos(angles[0]),-np.sin(angles[0])], + [0,np.sin(angles[0]),np.cos(angles[0])]]) + Ry = np.array([[np.cos(angles[1]),0,np.sin(angles[1])], + [0,1,0], + [-np.sin(angles[1]),0,np.cos(angles[1])]]) + Rz = np.array([[np.cos(angles[2]),-np.sin(angles[2]),0], + [np.sin(angles[2]),np.cos(angles[2]),0], + [0,0,1]]) + R = np.dot(Rz, np.dot(Ry,Rx)) + shape_pc = batch_data[k,:,0:3] + shape_normal = batch_data[k,:,3:6] + rotated_data[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), R) + rotated_data[k,:,3:6] = np.dot(shape_normal.reshape((-1, 3)), R) + return rotated_data + + +def rotate_point_cloud_by_angle(batch_data, rotation_angle): + """ Rotate the point cloud along up direction with certain angle. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + #rotation_angle = np.random.uniform() * 2 * np.pi + cosval = np.cos(rotation_angle) + sinval = np.sin(rotation_angle) + rotation_matrix = np.array([[cosval, 0, sinval], + [0, 1, 0], + [-sinval, 0, cosval]]) + shape_pc = batch_data[k,:,0:3] + rotated_data[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + return rotated_data + +def rotate_point_cloud_by_angle_with_normal(batch_data, rotation_angle): + """ Rotate the point cloud along up direction with certain angle. + Input: + BxNx6 array, original batch of point clouds with normal + scalar, angle of rotation + Return: + BxNx6 array, rotated batch of point clouds iwth normal + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + #rotation_angle = np.random.uniform() * 2 * np.pi + cosval = np.cos(rotation_angle) + sinval = np.sin(rotation_angle) + rotation_matrix = np.array([[cosval, 0, sinval], + [0, 1, 0], + [-sinval, 0, cosval]]) + shape_pc = batch_data[k,:,0:3] + shape_normal = batch_data[k,:,3:6] + rotated_data[k,:,0:3] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + rotated_data[k,:,3:6] = np.dot(shape_normal.reshape((-1,3)), rotation_matrix) + return rotated_data + + + +def rotate_perturbation_point_cloud(batch_data, angle_sigma=0.06, angle_clip=0.18): + """ Randomly perturb the point clouds by small rotations + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=np.float32) + for k in range(batch_data.shape[0]): + angles = np.clip(angle_sigma*np.random.randn(3), -angle_clip, angle_clip) + Rx = np.array([[1,0,0], + [0,np.cos(angles[0]),-np.sin(angles[0])], + [0,np.sin(angles[0]),np.cos(angles[0])]]) + Ry = np.array([[np.cos(angles[1]),0,np.sin(angles[1])], + [0,1,0], + [-np.sin(angles[1]),0,np.cos(angles[1])]]) + Rz = np.array([[np.cos(angles[2]),-np.sin(angles[2]),0], + [np.sin(angles[2]),np.cos(angles[2]),0], + [0,0,1]]) + R = np.dot(Rz, np.dot(Ry,Rx)) + shape_pc = batch_data[k, ...] + rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), R) + return rotated_data + + +def jitter_point_cloud(batch_data, sigma=0.01, clip=0.05): + """ Randomly jitter points. jittering is per point. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, jittered batch of point clouds + """ + B, N, C = batch_data.shape + assert(clip > 0) + jittered_data = np.clip(sigma * np.random.randn(B, N, C), -1*clip, clip) + jittered_data += batch_data + return jittered_data + +def shift_point_cloud(batch_data, shift_range=0.1): + """ Randomly shift point cloud. Shift is per point cloud. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, shifted batch of point clouds + """ + B, N, C = batch_data.shape + shifts = np.random.uniform(-shift_range, shift_range, (B,3)) + for batch_index in range(B): + batch_data[batch_index,:,:] += shifts[batch_index,:] + return batch_data + + +def random_scale_point_cloud(batch_data, scale_low=0.8, scale_high=1.25): + """ Randomly scale the point cloud. Scale is per point cloud. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, scaled batch of point clouds + """ + B, N, C = batch_data.shape + scales = np.random.uniform(scale_low, scale_high, B) + for batch_index in range(B): + batch_data[batch_index,:,:] *= scales[batch_index] + return batch_data + +def random_point_dropout(batch_pc, max_dropout_ratio=0.875): + ''' batch_pc: BxNx3 ''' + for b in range(batch_pc.shape[0]): + dropout_ratio = np.random.random()*max_dropout_ratio # 0~0.875 + drop_idx = np.where(np.random.random((batch_pc.shape[1]))<=dropout_ratio)[0] + if len(drop_idx)>0: + batch_pc[b,drop_idx,:] = batch_pc[b,0,:] # set to the first point + return batch_pc + + +def getDataFiles(list_filename): + return [line.rstrip() for line in open(list_filename)] + +def load_h5(h5_filename): + f = h5py.File(h5_filename) + data = f['data'][:] + label = f['label'][:] + return (data, label) + +def loadDataFile(filename): + return load_h5(filename) diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/render_balls_so.cpp" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/render_balls_so.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..e95aeba1bc6a31229eae9ce2a5c90173730916e6 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/render_balls_so.cpp" @@ -0,0 +1,58 @@ +#include +#include +#include +#include +using namespace std; + +struct PointInfo{ + int x,y,z; + float r,g,b; +}; + +extern "C"{ + +void render_ball(int h,int w,unsigned char * show,int n,int * xyzs,float * c0,float * c1,float * c2,int r){ + r=max(r,1); + vector depth(h*w,-2100000000); + vector pattern; + for (int dx=-r;dx<=r;dx++) + for (int dy=-r;dy<=r;dy++) + if (dx*dx+dy*dy=h || y2<0 || y2>=w) && depth[x2*w+y2]0: + show[:,:,0]=np.maximum(show[:,:,0],np.roll(show[:,:,0],1,axis=0)) + if magnifyBlue>=2: + show[:,:,0]=np.maximum(show[:,:,0],np.roll(show[:,:,0],-1,axis=0)) + show[:,:,0]=np.maximum(show[:,:,0],np.roll(show[:,:,0],1,axis=1)) + if magnifyBlue>=2: + show[:,:,0]=np.maximum(show[:,:,0],np.roll(show[:,:,0],-1,axis=1)) + if showrot: + cv2.putText(show,'xangle %d'%(int(xangle/np.pi*180)),(30,showsz-30),0,0.5,cv2.cv.CV_RGB(255,0,0)) + cv2.putText(show,'yangle %d'%(int(yangle/np.pi*180)),(30,showsz-50),0,0.5,cv2.cv.CV_RGB(255,0,0)) + cv2.putText(show,'zoom %d%%'%(int(zoom*100)),(30,showsz-70),0,0.5,cv2.cv.CV_RGB(255,0,0)) + changed=True + while True: + if changed: + render() + changed=False + cv2.imshow('show3d',show) + if waittime==0: + cmd=cv2.waitKey(10)%256 + else: + cmd=cv2.waitKey(waittime)%256 + if cmd==ord('q'): + break + elif cmd==ord('Q'): + sys.exit(0) + + if cmd==ord('t') or cmd == ord('p'): + if cmd == ord('t'): + if c_gt is None: + c0=np.zeros((len(xyz),),dtype='float32')+255 + c1=np.zeros((len(xyz),),dtype='float32')+255 + c2=np.zeros((len(xyz),),dtype='float32')+255 + else: + c0=c_gt[:,0] + c1=c_gt[:,1] + c2=c_gt[:,2] + else: + if c_pred is None: + c0=np.zeros((len(xyz),),dtype='float32')+255 + c1=np.zeros((len(xyz),),dtype='float32')+255 + c2=np.zeros((len(xyz),),dtype='float32')+255 + else: + c0=c_pred[:,0] + c1=c_pred[:,1] + c2=c_pred[:,2] + if normalizecolor: + c0/=(c0.max()+1e-14)/255.0 + c1/=(c1.max()+1e-14)/255.0 + c2/=(c2.max()+1e-14)/255.0 + c0=np.require(c0,'float32','C') + c1=np.require(c1,'float32','C') + c2=np.require(c2,'float32','C') + changed = True + + + + if cmd==ord('n'): + zoom*=1.1 + changed=True + elif cmd==ord('m'): + zoom/=1.1 + changed=True + elif cmd==ord('r'): + zoom=1.0 + changed=True + elif cmd==ord('s'): + cv2.imwrite('show3d.png',show) + if waittime!=0: + break + return cmd +if __name__=='__main__': + np.random.seed(100) + showpoints(np.random.randn(2500,3)) + diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/tf_util.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/tf_util.py" new file mode 100644 index 0000000000000000000000000000000000000000..2f58cb07c645c43f4df1b6bee523b9c6b63acf36 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/utils/tf_util.py" @@ -0,0 +1,616 @@ +""" Wrapper functions for TensorFlow layers. + +Author: Charles R. Qi +Date: November 2017 +""" + +import numpy as np +import tensorflow as tf + +def _variable_on_cpu(name, shape, initializer, use_fp16=False): + """Helper to create a Variable stored on CPU memory. + Args: + name: name of the variable + shape: list of ints + initializer: initializer for Variable + Returns: + Variable Tensor + """ + with tf.device("/cpu:0"): + dtype = tf.float16 if use_fp16 else tf.float32 + var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype) + return var + +def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True): + """Helper to create an initialized Variable with weight decay. + + Note that the Variable is initialized with a truncated normal distribution. + A weight decay is added only if one is specified. + + Args: + name: name of the variable + shape: list of ints + stddev: standard deviation of a truncated Gaussian + wd: add L2Loss weight decay multiplied by this float. If None, weight + decay is not added for this Variable. + use_xavier: bool, whether to use xavier initializer + + Returns: + Variable Tensor + """ + if use_xavier: + initializer = tf.contrib.layers.xavier_initializer() + else: + initializer = tf.truncated_normal_initializer(stddev=stddev) + var = _variable_on_cpu(name, shape, initializer) + if wd is not None: + weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss') + tf.add_to_collection('losses', weight_decay) + return var + + +def conv1d(inputs, + num_output_channels, + kernel_size, + scope, + stride=1, + padding='SAME', + data_format='NHWC', + use_xavier=True, + stddev=1e-3, + weight_decay=None, + activation_fn=tf.nn.relu, + bn=False, + bn_decay=None, + is_training=None): + """ 1D convolution with non-linear operation. + + Args: + inputs: 3-D tensor variable BxLxC + num_output_channels: int + kernel_size: int + scope: string + stride: int + padding: 'SAME' or 'VALID' + data_format: 'NHWC' or 'NCHW' + use_xavier: bool, use xavier_initializer if true + stddev: float, stddev for truncated_normal init + weight_decay: float + activation_fn: function + bn: bool, whether to use batch norm + bn_decay: float or float tensor variable in [0,1] + is_training: bool Tensor variable + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + assert(data_format=='NHWC' or data_format=='NCHW') + if data_format == 'NHWC': + num_in_channels = inputs.get_shape()[-1].value + elif data_format=='NCHW': + num_in_channels = inputs.get_shape()[1].value + kernel_shape = [kernel_size, + num_in_channels, num_output_channels] + kernel = _variable_with_weight_decay('weights', + shape=kernel_shape, + use_xavier=use_xavier, + stddev=stddev, + wd=weight_decay) + outputs = tf.nn.conv1d(inputs, kernel, + stride=stride, + padding=padding, + data_format=data_format) + biases = _variable_on_cpu('biases', [num_output_channels], + tf.constant_initializer(0.0)) + outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) + + if bn: + outputs = batch_norm_for_conv1d(outputs, is_training, + bn_decay=bn_decay, scope='bn', + data_format=data_format) + + if activation_fn is not None: + outputs = activation_fn(outputs) + return outputs + + + + +def conv2d(inputs, + num_output_channels, + kernel_size, + scope, + stride=[1, 1], + padding='SAME', + data_format='NHWC', + use_xavier=True, + stddev=1e-3, + weight_decay=None, + activation_fn=tf.nn.relu, + bn=False, + bn_decay=None, + is_training=None): + """ 2D convolution with non-linear operation. + + Args: + inputs: 4-D tensor variable BxHxWxC + num_output_channels: int + kernel_size: a list of 2 ints + scope: string + stride: a list of 2 ints + padding: 'SAME' or 'VALID' + data_format: 'NHWC' or 'NCHW' + use_xavier: bool, use xavier_initializer if true + stddev: float, stddev for truncated_normal init + weight_decay: float + activation_fn: function + bn: bool, whether to use batch norm + bn_decay: float or float tensor variable in [0,1] + is_training: bool Tensor variable + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_h, kernel_w = kernel_size + assert(data_format=='NHWC' or data_format=='NCHW') + if data_format == 'NHWC': + num_in_channels = inputs.get_shape()[-1].value + elif data_format=='NCHW': + num_in_channels = inputs.get_shape()[1].value + kernel_shape = [kernel_h, kernel_w, + num_in_channels, num_output_channels] + kernel = _variable_with_weight_decay('weights', + shape=kernel_shape, + use_xavier=use_xavier, + stddev=stddev, + wd=weight_decay) + stride_h, stride_w = stride + outputs = tf.nn.conv2d(inputs, kernel, + [1, stride_h, stride_w, 1], + padding=padding, + data_format=data_format) + biases = _variable_on_cpu('biases', [num_output_channels], + tf.constant_initializer(0.0)) + outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) + + if bn: + outputs = batch_norm_for_conv2d(outputs, is_training, + bn_decay=bn_decay, scope='bn', + data_format=data_format) + + if activation_fn is not None: + outputs = activation_fn(outputs) + return outputs + + +def conv2d_transpose(inputs, + num_output_channels, + kernel_size, + scope, + stride=[1, 1], + padding='SAME', + use_xavier=True, + stddev=1e-3, + weight_decay=None, + activation_fn=tf.nn.relu, + bn=False, + bn_decay=None, + is_training=None): + """ 2D convolution transpose with non-linear operation. + + Args: + inputs: 4-D tensor variable BxHxWxC + num_output_channels: int + kernel_size: a list of 2 ints + scope: string + stride: a list of 2 ints + padding: 'SAME' or 'VALID' + use_xavier: bool, use xavier_initializer if true + stddev: float, stddev for truncated_normal init + weight_decay: float + activation_fn: function + bn: bool, whether to use batch norm + bn_decay: float or float tensor variable in [0,1] + is_training: bool Tensor variable + + Returns: + Variable tensor + + Note: conv2d(conv2d_transpose(a, num_out, ksize, stride), a.shape[-1], ksize, stride) == a + """ + with tf.variable_scope(scope) as sc: + kernel_h, kernel_w = kernel_size + num_in_channels = inputs.get_shape()[-1].value + kernel_shape = [kernel_h, kernel_w, + num_output_channels, num_in_channels] # reversed to conv2d + kernel = _variable_with_weight_decay('weights', + shape=kernel_shape, + use_xavier=use_xavier, + stddev=stddev, + wd=weight_decay) + stride_h, stride_w = stride + + # from slim.convolution2d_transpose + def get_deconv_dim(dim_size, stride_size, kernel_size, padding): + dim_size *= stride_size + + if padding == 'VALID' and dim_size is not None: + dim_size += max(kernel_size - stride_size, 0) + return dim_size + + # caculate output shape + batch_size = inputs.get_shape()[0].value + height = inputs.get_shape()[1].value + width = inputs.get_shape()[2].value + out_height = get_deconv_dim(height, stride_h, kernel_h, padding) + out_width = get_deconv_dim(width, stride_w, kernel_w, padding) + output_shape = [batch_size, out_height, out_width, num_output_channels] + + outputs = tf.nn.conv2d_transpose(inputs, kernel, output_shape, + [1, stride_h, stride_w, 1], + padding=padding) + biases = _variable_on_cpu('biases', [num_output_channels], + tf.constant_initializer(0.0)) + outputs = tf.nn.bias_add(outputs, biases) + + if bn: + outputs = batch_norm_for_conv2d(outputs, is_training, + bn_decay=bn_decay, scope='bn') + + if activation_fn is not None: + outputs = activation_fn(outputs) + return outputs + + + +def conv3d(inputs, + num_output_channels, + kernel_size, + scope, + stride=[1, 1, 1], + padding='SAME', + use_xavier=True, + stddev=1e-3, + weight_decay=None, + activation_fn=tf.nn.relu, + bn=False, + bn_decay=None, + is_training=None): + """ 3D convolution with non-linear operation. + + Args: + inputs: 5-D tensor variable BxDxHxWxC + num_output_channels: int + kernel_size: a list of 3 ints + scope: string + stride: a list of 3 ints + padding: 'SAME' or 'VALID' + use_xavier: bool, use xavier_initializer if true + stddev: float, stddev for truncated_normal init + weight_decay: float + activation_fn: function + bn: bool, whether to use batch norm + bn_decay: float or float tensor variable in [0,1] + is_training: bool Tensor variable + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_d, kernel_h, kernel_w = kernel_size + num_in_channels = inputs.get_shape()[-1].value + kernel_shape = [kernel_d, kernel_h, kernel_w, + num_in_channels, num_output_channels] + kernel = _variable_with_weight_decay('weights', + shape=kernel_shape, + use_xavier=use_xavier, + stddev=stddev, + wd=weight_decay) + stride_d, stride_h, stride_w = stride + outputs = tf.nn.conv3d(inputs, kernel, + [1, stride_d, stride_h, stride_w, 1], + padding=padding) + biases = _variable_on_cpu('biases', [num_output_channels], + tf.constant_initializer(0.0)) + outputs = tf.nn.bias_add(outputs, biases) + + if bn: + outputs = batch_norm_for_conv3d(outputs, is_training, + bn_decay=bn_decay, scope='bn') + + if activation_fn is not None: + outputs = activation_fn(outputs) + return outputs + +def fully_connected(inputs, + num_outputs, + scope, + use_xavier=True, + stddev=1e-3, + weight_decay=None, + activation_fn=tf.nn.relu, + bn=False, + bn_decay=None, + is_training=None): + """ Fully connected layer with non-linear operation. + + Args: + inputs: 2-D tensor BxN + num_outputs: int + + Returns: + Variable tensor of size B x num_outputs. + """ + with tf.variable_scope(scope) as sc: + num_input_units = inputs.get_shape()[-1].value + weights = _variable_with_weight_decay('weights', + shape=[num_input_units, num_outputs], + use_xavier=use_xavier, + stddev=stddev, + wd=weight_decay) + outputs = tf.matmul(inputs, weights) + biases = _variable_on_cpu('biases', [num_outputs], + tf.constant_initializer(0.0)) + outputs = tf.nn.bias_add(outputs, biases) + + if bn: + outputs = batch_norm_for_fc(outputs, is_training, bn_decay, 'bn') + + if activation_fn is not None: + outputs = activation_fn(outputs) + return outputs + + +def max_pool2d(inputs, + kernel_size, + scope, + stride=[2, 2], + padding='VALID'): + """ 2D max pooling. + + Args: + inputs: 4-D tensor BxHxWxC + kernel_size: a list of 2 ints + stride: a list of 2 ints + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_h, kernel_w = kernel_size + stride_h, stride_w = stride + outputs = tf.nn.max_pool(inputs, + ksize=[1, kernel_h, kernel_w, 1], + strides=[1, stride_h, stride_w, 1], + padding=padding, + name=sc.name) + return outputs + +def avg_pool2d(inputs, + kernel_size, + scope, + stride=[2, 2], + padding='VALID'): + """ 2D avg pooling. + + Args: + inputs: 4-D tensor BxHxWxC + kernel_size: a list of 2 ints + stride: a list of 2 ints + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_h, kernel_w = kernel_size + stride_h, stride_w = stride + outputs = tf.nn.avg_pool(inputs, + ksize=[1, kernel_h, kernel_w, 1], + strides=[1, stride_h, stride_w, 1], + padding=padding, + name=sc.name) + return outputs + + +def max_pool3d(inputs, + kernel_size, + scope, + stride=[2, 2, 2], + padding='VALID'): + """ 3D max pooling. + + Args: + inputs: 5-D tensor BxDxHxWxC + kernel_size: a list of 3 ints + stride: a list of 3 ints + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_d, kernel_h, kernel_w = kernel_size + stride_d, stride_h, stride_w = stride + outputs = tf.nn.max_pool3d(inputs, + ksize=[1, kernel_d, kernel_h, kernel_w, 1], + strides=[1, stride_d, stride_h, stride_w, 1], + padding=padding, + name=sc.name) + return outputs + +def avg_pool3d(inputs, + kernel_size, + scope, + stride=[2, 2, 2], + padding='VALID'): + """ 3D avg pooling. + + Args: + inputs: 5-D tensor BxDxHxWxC + kernel_size: a list of 3 ints + stride: a list of 3 ints + + Returns: + Variable tensor + """ + with tf.variable_scope(scope) as sc: + kernel_d, kernel_h, kernel_w = kernel_size + stride_d, stride_h, stride_w = stride + outputs = tf.nn.avg_pool3d(inputs, + ksize=[1, kernel_d, kernel_h, kernel_w, 1], + strides=[1, stride_d, stride_h, stride_w, 1], + padding=padding, + name=sc.name) + return outputs + + +def batch_norm_template_unused(inputs, is_training, scope, moments_dims, bn_decay): + """ NOTE: this is older version of the util func. it is deprecated. + Batch normalization on convolutional maps and beyond... + Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow + + Args: + inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC + is_training: boolean tf.Varialbe, true indicates training phase + scope: string, variable scope + moments_dims: a list of ints, indicating dimensions for moments calculation + bn_decay: float or float tensor variable, controling moving average weight + Return: + normed: batch-normalized maps + """ + with tf.variable_scope(scope) as sc: + num_channels = inputs.get_shape()[-1].value + beta = _variable_on_cpu(name='beta',shape=[num_channels], + initializer=tf.constant_initializer(0)) + gamma = _variable_on_cpu(name='gamma',shape=[num_channels], + initializer=tf.constant_initializer(1.0)) + batch_mean, batch_var = tf.nn.moments(inputs, moments_dims, name='moments') + decay = bn_decay if bn_decay is not None else 0.9 + ema = tf.train.ExponentialMovingAverage(decay=decay) + # Operator that maintains moving averages of variables. + # Need to set reuse=False, otherwise if reuse, will see moments_1/mean/ExponentialMovingAverage/ does not exist + # https://github.com/shekkizh/WassersteinGAN.tensorflow/issues/3 + with tf.variable_scope(tf.get_variable_scope(), reuse=False): + ema_apply_op = tf.cond(is_training, + lambda: ema.apply([batch_mean, batch_var]), + lambda: tf.no_op()) + + # Update moving average and return current batch's avg and var. + def mean_var_with_update(): + with tf.control_dependencies([ema_apply_op]): + return tf.identity(batch_mean), tf.identity(batch_var) + + # ema.average returns the Variable holding the average of var. + mean, var = tf.cond(is_training, + mean_var_with_update, + lambda: (ema.average(batch_mean), ema.average(batch_var))) + normed = tf.nn.batch_normalization(inputs, mean, var, beta, gamma, 1e-3) + return normed + + +def batch_norm_template(inputs, is_training, scope, moments_dims_unused, bn_decay, data_format='NHWC'): + """ Batch normalization on convolutional maps and beyond... + Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow + + Args: + inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC + is_training: boolean tf.Varialbe, true indicates training phase + scope: string, variable scope + moments_dims: a list of ints, indicating dimensions for moments calculation + bn_decay: float or float tensor variable, controling moving average weight + data_format: 'NHWC' or 'NCHW' + Return: + normed: batch-normalized maps + """ + bn_decay = bn_decay if bn_decay is not None else 0.9 + return tf.contrib.layers.batch_norm(inputs, + center=True, scale=True, + is_training=is_training, decay=bn_decay,updates_collections=None, + scope=scope, + data_format=data_format) + + +def batch_norm_for_fc(inputs, is_training, bn_decay, scope): + """ Batch normalization on FC data. + + Args: + inputs: Tensor, 2D BxC input + is_training: boolean tf.Varialbe, true indicates training phase + bn_decay: float or float tensor variable, controling moving average weight + scope: string, variable scope + Return: + normed: batch-normalized maps + """ + return batch_norm_template(inputs, is_training, scope, [0,], bn_decay) + + +def batch_norm_for_conv1d(inputs, is_training, bn_decay, scope, data_format): + """ Batch normalization on 1D convolutional maps. + + Args: + inputs: Tensor, 3D BLC input maps + is_training: boolean tf.Varialbe, true indicates training phase + bn_decay: float or float tensor variable, controling moving average weight + scope: string, variable scope + data_format: 'NHWC' or 'NCHW' + Return: + normed: batch-normalized maps + """ + return batch_norm_template(inputs, is_training, scope, [0,1], bn_decay, data_format) + + + + +def batch_norm_for_conv2d(inputs, is_training, bn_decay, scope, data_format): + """ Batch normalization on 2D convolutional maps. + + Args: + inputs: Tensor, 4D BHWC input maps + is_training: boolean tf.Varialbe, true indicates training phase + bn_decay: float or float tensor variable, controling moving average weight + scope: string, variable scope + data_format: 'NHWC' or 'NCHW' + Return: + normed: batch-normalized maps + """ + return batch_norm_template(inputs, is_training, scope, [0,1,2], bn_decay, data_format) + + +def batch_norm_for_conv3d(inputs, is_training, bn_decay, scope): + """ Batch normalization on 3D convolutional maps. + + Args: + inputs: Tensor, 5D BDHWC input maps + is_training: boolean tf.Varialbe, true indicates training phase + bn_decay: float or float tensor variable, controling moving average weight + scope: string, variable scope + Return: + normed: batch-normalized maps + """ + return batch_norm_template(inputs, is_training, scope, [0,1,2,3], bn_decay) + + +def dropout(inputs, + is_training, + scope, + keep_prob=0.5, + noise_shape=None): + """ Dropout layer. + + Args: + inputs: tensor + is_training: boolean tf.Variable + scope: string + keep_prob: float in [0,1] + noise_shape: list of ints + + Returns: + tensor variable + """ + with tf.variable_scope(scope) as sc: + outputs = tf.cond(is_training, + # lambda: tf.nn.dropout(inputs, keep_prob, noise_shape), + lambda: tf.nn.dropout(inputs, keep_prob, noise_shape), + lambda: inputs) + return outputs diff --git "a/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/\346\265\213\350\257\225cudnn\346\230\257\345\220\246\345\217\257\347\224\250.py" "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/\346\265\213\350\257\225cudnn\346\230\257\345\220\246\345\217\257\347\224\250.py" new file mode 100644 index 0000000000000000000000000000000000000000..02b665f1f884f590ae845fe31829cb98cd991b84 --- /dev/null +++ "b/code/2022_autumn/\345\264\224\351\251\277\345\256\201-\345\237\272\344\272\2163D\347\202\271\344\272\221\346\267\261\345\272\246\345\255\246\344\271\240\347\232\204\351\234\207\345\256\263\345\273\272\347\255\221\347\211\251\350\257\206\345\210\253/\346\265\213\350\257\225cudnn\346\230\257\345\220\246\345\217\257\347\224\250.py" @@ -0,0 +1,3 @@ +#coding = utf-8 +import tensorflow as tf +tf.Session() \ No newline at end of file