├── monitor.png ├── Desktop └── DeepLearning │ └── pointnet_vis │ ├── README.pdf │ ├── pointnet.pdf │ ├── models │ ├── .DS_Store │ ├── pointnet_cls.pyc │ ├── transform_nets.pyc │ ├── pointnet_cls.py │ └── transform_nets.py │ ├── utils │ ├── pc_util.pyc │ ├── plyfile.pyc │ ├── tf_util.pyc │ ├── eulerangles.pyc │ ├── data_prep_util.py │ ├── pc_util.py │ ├── eulerangles.py │ ├── tf_util.py │ └── plyfile.py │ ├── LICENSE │ ├── vis.py │ ├── provider.py │ ├── get_file.py │ └── train.py └── README.md /monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/monitor.png -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/README.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/README.pdf -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/pointnet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/pointnet.pdf -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/models/.DS_Store -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/pc_util.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/utils/pc_util.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/plyfile.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/utils/plyfile.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/tf_util.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/utils/tf_util.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/eulerangles.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/utils/eulerangles.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/models/pointnet_cls.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/models/pointnet_cls.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/models/transform_nets.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitBoSun/PointNet_vis/HEAD/Desktop/DeepLearning/pointnet_vis/models/transform_nets.pyc -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/LICENSE: -------------------------------------------------------------------------------- 1 | PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation. 2 | 3 | Copyright (c) 2017, Geometric Computation Group of Stanford University 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2017 Charles R. Qi 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PointNet_vis 2 | Visualize critical points and upper bound shape of pointnet. 3 | This work is based on "PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation"(https://arxiv.org/abs/1612.00593). Official code is on https://github.com/charlesq34/pointnet. 4 | 5 | Here are modifications I do to implement the visualization: 6 | 1. ./models/pointnet_cls.py: I use the classification network to implement the visualization. To get the max-pooling information, I modified “get_model” to return hx and maxpool. Hx is symmetric function to map the input data to 10224 dimensional features and maxpool is the maximum of each feature among all the input points. 7 | 8 | 2. get_file.py: I use this file to get the original data, hx and maxpool of some training samples and all points in inscribed cube in unit sphere. The former is to visualize critical points and the latter is to visualize upper bound shape and function activation. Because we cannot calculate both of them simultaneously because we should define the shape of input placeholder and their shape don’t match, I define two vis_mode(‘critical’ and ‘all’) to get them separately. 9 | 10 | 3. vis.py: Because remote server cannot show images and our images is in 3D shape, I don’t want to save them but show them directly. This file loads ‘npz’ file gotten by get_file.py and we can visualize the points images. 11 | 12 | Usage: 13 | train model: 14 | 15 | python train.py 16 | 17 | get vis file: 18 | 19 | python get_file.py –vis_mode ‘critical’ 20 | 21 | python get_file.py –vis_mode ‘all’ 22 | 23 | visualization: 24 | 25 | python vis.py 26 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/vis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from mpl_toolkits.mplot3d import Axes3D 4 | 5 | 6 | def vis_critical(): 7 | sample_num = 5 8 | out = np.load('critical.npz') 9 | points = out['points'] # 5,1024,3) 10 | hx = out['hx'].reshape(sample_num, 1024, 1024) # (5,1024,1024) 11 | 12 | cs_index = np.argmax(hx, axis = 2)#find which point contributed to max-pooling features 13 | cs = [] 14 | 15 | for i in range(sample_num): 16 | temp = [] 17 | for index in cs_index[i]: 18 | temp.append(points[i][index]) 19 | cs.append(temp) 20 | cs = np.array(cs) 21 | 22 | for i in range(sample_num): 23 | fig = plt.figure('original') 24 | ax = fig.add_subplot(111, projection = '3d') 25 | xs = points[i][:, 0] 26 | ys = points[i][:, 1] 27 | zs = points[i][:, 2] 28 | 29 | ax.scatter(xs, ys, zs) 30 | plt.axis('off') 31 | plt.show() 32 | fig = plt.figure('critical') 33 | ax = fig.add_subplot(111, projection = '3d') 34 | xs = cs[i][:, 0] 35 | ys = cs[i][:, 1] 36 | zs = cs[i][:, 2] 37 | 38 | ax.scatter(xs, ys, zs) 39 | plt.axis('off') 40 | plt.show() 41 | 42 | 43 | def vis_upper_shape(): 44 | sample_num = 5 45 | out = np.load('critical.npz') 46 | points = out['points'] # (5,1024,3) 47 | maxpool = out['maxpool'].reshape((sample_num, 1, 1024)) # (5,1,1024) 48 | 49 | out2 = np.load('all.npz') 50 | all_points = out2['points'].reshape(-1, 3) # (500*1024,3) 51 | all_hx = out2['hx'].reshape(-1, 1024) # (500*1024,1024) 52 | 53 | for i in range(sample_num): 54 | temp = [] 55 | x = maxpool[i] - all_hx 56 | x = np.min(x, axis = 1) 57 | for j in range(x.shape[0]): 58 | if (x[j] >= 0):#if its feature do do not change the maximum of hx, add it to temp 59 | temp.append(all_points[j]) 60 | temp = np.array(temp) 61 | 62 | fig = plt.figure('original') 63 | ax = fig.add_subplot(111, projection = '3d') 64 | xs = points[i][:, 0] 65 | ys = points[i][:, 1] 66 | zs = points[i][:, 2] 67 | 68 | ax.scatter(xs, ys, zs) 69 | 70 | plt.axis('off') 71 | plt.show() 72 | 73 | ax = plt.figure().add_subplot(111, projection = '3d') 74 | xs = temp[:, 0] 75 | ys = temp[:, 1] 76 | zs = temp[:, 2] 77 | 78 | ax.scatter(xs, ys, zs) 79 | plt.axis('off') 80 | plt.show() 81 | 82 | 83 | if __name__ == '__main__': 84 | vis_critical() 85 | vis_upper_shape() 86 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/provider.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | import h5py 5 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 6 | sys.path.append(BASE_DIR) 7 | 8 | # Download dataset for point cloud classification 9 | DATA_DIR = os.path.join(BASE_DIR, 'data') 10 | if not os.path.exists(DATA_DIR): 11 | os.mkdir(DATA_DIR) 12 | if not os.path.exists(os.path.join(DATA_DIR, 'modelnet40_ply_hdf5_2048')): 13 | www = 'https://shapenet.cs.stanford.edu/media/modelnet40_ply_hdf5_2048.zip' 14 | zipfile = os.path.basename(www) 15 | os.system('wget %s; unzip %s' % (www, zipfile)) 16 | os.system('mv %s %s' % (zipfile[:-4], DATA_DIR)) 17 | os.system('rm %s' % (zipfile)) 18 | 19 | 20 | def shuffle_data(data, labels): 21 | """ Shuffle data and labels. 22 | Input: 23 | data: B,N,... numpy array 24 | label: B,... numpy array 25 | Return: 26 | shuffled data, label and shuffle indices 27 | """ 28 | idx = np.arange(len(labels)) 29 | np.random.shuffle(idx) 30 | return data[idx, ...], labels[idx], idx 31 | 32 | 33 | def rotate_point_cloud(batch_data): 34 | """ Randomly rotate the point clouds to augument the dataset 35 | rotation is per shape based along up direction 36 | Input: 37 | BxNx3 array, original batch of point clouds 38 | Return: 39 | BxNx3 array, rotated batch of point clouds 40 | """ 41 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 42 | for k in range(batch_data.shape[0]): 43 | rotation_angle = np.random.uniform() * 2 * np.pi 44 | cosval = np.cos(rotation_angle) 45 | sinval = np.sin(rotation_angle) 46 | rotation_matrix = np.array([[cosval, 0, sinval], 47 | [0, 1, 0], 48 | [-sinval, 0, cosval]]) 49 | shape_pc = batch_data[k, ...] 50 | rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 51 | return rotated_data 52 | 53 | 54 | def rotate_point_cloud_by_angle(batch_data, rotation_angle): 55 | """ Rotate the point cloud along up direction with certain angle. 56 | Input: 57 | BxNx3 array, original batch of point clouds 58 | Return: 59 | BxNx3 array, rotated batch of point clouds 60 | """ 61 | rotated_data = np.zeros(batch_data.shape, dtype=np.float32) 62 | for k in range(batch_data.shape[0]): 63 | #rotation_angle = np.random.uniform() * 2 * np.pi 64 | cosval = np.cos(rotation_angle) 65 | sinval = np.sin(rotation_angle) 66 | rotation_matrix = np.array([[cosval, 0, sinval], 67 | [0, 1, 0], 68 | [-sinval, 0, cosval]]) 69 | shape_pc = batch_data[k, ...] 70 | rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) 71 | return rotated_data 72 | 73 | 74 | def jitter_point_cloud(batch_data, sigma=0.01, clip=0.05): 75 | """ Randomly jitter points. jittering is per point. 76 | Input: 77 | BxNx3 array, original batch of point clouds 78 | Return: 79 | BxNx3 array, jittered batch of point clouds 80 | """ 81 | B, N, C = batch_data.shape 82 | assert(clip > 0) 83 | jittered_data = np.clip(sigma * np.random.randn(B, N, C), -1*clip, clip) 84 | jittered_data += batch_data 85 | return jittered_data 86 | 87 | def getDataFiles(list_filename): 88 | return [line.rstrip() for line in open(list_filename)] 89 | 90 | def load_h5(h5_filename): 91 | f = h5py.File(h5_filename) 92 | data = f['data'][:] 93 | label = f['label'][:] 94 | return (data, label) 95 | 96 | def loadDataFile(filename): 97 | return load_h5(filename) 98 | 99 | def load_h5_data_label_seg(h5_filename): 100 | f = h5py.File(h5_filename) 101 | data = f['data'][:] 102 | label = f['label'][:] 103 | seg = f['pid'][:] 104 | return (data, label, seg) 105 | 106 | 107 | def loadDataFile_with_seg(filename): 108 | return load_h5_data_label_seg(filename) 109 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/models/pointnet_cls.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | import math 4 | import sys 5 | import os 6 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 7 | sys.path.append(BASE_DIR) 8 | sys.path.append(os.path.join(BASE_DIR, '../utils')) 9 | import tf_util 10 | from transform_nets import input_transform_net, feature_transform_net 11 | 12 | def placeholder_inputs(batch_size, num_point): 13 | pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) 14 | labels_pl = tf.placeholder(tf.int32, shape=(batch_size)) 15 | return pointclouds_pl, labels_pl 16 | 17 | 18 | def get_model(point_cloud, is_training, bn_decay=None): 19 | """ Classification PointNet, input is BxNx3, output Bx40 """ 20 | batch_size = point_cloud.get_shape()[0].value 21 | num_point = point_cloud.get_shape()[1].value 22 | end_points = {} 23 | 24 | with tf.variable_scope('transform_net1') as sc: 25 | transform = input_transform_net(point_cloud, is_training, bn_decay, K=3) 26 | point_cloud_transformed = tf.matmul(point_cloud, transform) 27 | input_image = tf.expand_dims(point_cloud_transformed, -1) 28 | 29 | net = tf_util.conv2d(input_image, 64, [1,3], 30 | padding='VALID', stride=[1,1], 31 | bn=True, is_training=is_training, 32 | scope='conv1', bn_decay=bn_decay) 33 | net = tf_util.conv2d(net, 64, [1,1], 34 | padding='VALID', stride=[1,1], 35 | bn=True, is_training=is_training, 36 | scope='conv2', bn_decay=bn_decay) 37 | 38 | with tf.variable_scope('transform_net2') as sc: 39 | transform = feature_transform_net(net, is_training, bn_decay, K=64) 40 | end_points['transform'] = transform 41 | net_transformed = tf.matmul(tf.squeeze(net, axis=[2]), transform) 42 | net_transformed = tf.expand_dims(net_transformed, [2]) 43 | 44 | net = tf_util.conv2d(net_transformed, 64, [1,1], 45 | padding='VALID', stride=[1,1], 46 | bn=True, is_training=is_training, 47 | scope='conv3', bn_decay=bn_decay) 48 | net = tf_util.conv2d(net, 128, [1,1], 49 | padding='VALID', stride=[1,1], 50 | bn=True, is_training=is_training, 51 | scope='conv4', bn_decay=bn_decay) 52 | hx = tf_util.conv2d(net, 1024, [1,1], 53 | padding='VALID', stride=[1,1], 54 | bn=True, is_training=is_training, 55 | scope='conv5', bn_decay=bn_decay) 56 | 57 | # Symmetric function: max pooling 58 | maxpool = tf_util.max_pool2d(hx, [num_point,1], 59 | padding='VALID', scope='maxpool') 60 | 61 | net = tf.reshape(maxpool, [batch_size, -1]) 62 | net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, 63 | scope='fc1', bn_decay=bn_decay) 64 | net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training, 65 | scope='dp1') 66 | net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training, 67 | scope='fc2', bn_decay=bn_decay) 68 | net = tf_util.dropout(net, keep_prob=0.7, is_training=is_training, 69 | scope='dp2') 70 | net = tf_util.fully_connected(net, 40, activation_fn=None, scope='fc3') 71 | 72 | return net, end_points, hx, maxpool 73 | 74 | 75 | def get_loss(pred, label, end_points, reg_weight=0.001): 76 | """ pred: B*NUM_CLASSES, 77 | label: B, """ 78 | loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=pred, labels=label) 79 | classify_loss = tf.reduce_mean(loss) 80 | tf.summary.scalar('classify loss', classify_loss) 81 | 82 | # Enforce the transformation as orthogonal matrix 83 | transform = end_points['transform'] # BxKxK 84 | K = transform.get_shape()[1].value 85 | mat_diff = tf.matmul(transform, tf.transpose(transform, perm=[0,2,1])) 86 | mat_diff -= tf.constant(np.eye(K), dtype=tf.float32) 87 | mat_diff_loss = tf.nn.l2_loss(mat_diff) 88 | tf.summary.scalar('mat loss', mat_diff_loss) 89 | 90 | return classify_loss + mat_diff_loss * reg_weight 91 | 92 | 93 | if __name__=='__main__': 94 | with tf.Graph().as_default(): 95 | inputs = tf.zeros((32,1024,3)) 96 | outputs = get_model(inputs, tf.constant(True)) 97 | print(outputs) 98 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/models/transform_nets.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | import sys 4 | import os 5 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 6 | sys.path.append(BASE_DIR) 7 | sys.path.append(os.path.join(BASE_DIR, '../utils')) 8 | import tf_util 9 | 10 | def input_transform_net(point_cloud, is_training, bn_decay=None, K=3): 11 | """ Input (XYZ) Transform Net, input is BxNx3 gray image 12 | Return: 13 | Transformation matrix of size 3xK """ 14 | batch_size = point_cloud.get_shape()[0].value 15 | num_point = point_cloud.get_shape()[1].value 16 | 17 | input_image = tf.expand_dims(point_cloud, -1) 18 | net = tf_util.conv2d(input_image, 64, [1,3], 19 | padding='VALID', stride=[1,1], 20 | bn=True, is_training=is_training, 21 | scope='tconv1', bn_decay=bn_decay) 22 | net = tf_util.conv2d(net, 128, [1,1], 23 | padding='VALID', stride=[1,1], 24 | bn=True, is_training=is_training, 25 | scope='tconv2', bn_decay=bn_decay) 26 | net = tf_util.conv2d(net, 1024, [1,1], 27 | padding='VALID', stride=[1,1], 28 | bn=True, is_training=is_training, 29 | scope='tconv3', bn_decay=bn_decay) 30 | net = tf_util.max_pool2d(net, [num_point,1], 31 | padding='VALID', scope='tmaxpool') 32 | 33 | net = tf.reshape(net, [batch_size, -1]) 34 | net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, 35 | scope='tfc1', bn_decay=bn_decay) 36 | net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training, 37 | scope='tfc2', bn_decay=bn_decay) 38 | 39 | with tf.variable_scope('transform_XYZ') as sc: 40 | assert(K==3) 41 | weights = tf.get_variable('weights', [256, 3*K], 42 | initializer=tf.constant_initializer(0.0), 43 | dtype=tf.float32) 44 | biases = tf.get_variable('biases', [3*K], 45 | initializer=tf.constant_initializer(0.0), 46 | dtype=tf.float32) 47 | biases += tf.constant([1,0,0,0,1,0,0,0,1], dtype=tf.float32) 48 | transform = tf.matmul(net, weights) 49 | transform = tf.nn.bias_add(transform, biases) 50 | 51 | transform = tf.reshape(transform, [batch_size, 3, K]) 52 | return transform 53 | 54 | 55 | def feature_transform_net(inputs, is_training, bn_decay=None, K=64): 56 | """ Feature Transform Net, input is BxNx1xK 57 | Return: 58 | Transformation matrix of size KxK """ 59 | batch_size = inputs.get_shape()[0].value 60 | num_point = inputs.get_shape()[1].value 61 | 62 | net = tf_util.conv2d(inputs, 64, [1,1], 63 | padding='VALID', stride=[1,1], 64 | bn=True, is_training=is_training, 65 | scope='tconv1', bn_decay=bn_decay) 66 | net = tf_util.conv2d(net, 128, [1,1], 67 | padding='VALID', stride=[1,1], 68 | bn=True, is_training=is_training, 69 | scope='tconv2', bn_decay=bn_decay) 70 | net = tf_util.conv2d(net, 1024, [1,1], 71 | padding='VALID', stride=[1,1], 72 | bn=True, is_training=is_training, 73 | scope='tconv3', bn_decay=bn_decay) 74 | net = tf_util.max_pool2d(net, [num_point,1], 75 | padding='VALID', scope='tmaxpool') 76 | 77 | net = tf.reshape(net, [batch_size, -1]) 78 | net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, 79 | scope='tfc1', bn_decay=bn_decay) 80 | net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training, 81 | scope='tfc2', bn_decay=bn_decay) 82 | 83 | with tf.variable_scope('transform_feat') as sc: 84 | weights = tf.get_variable('weights', [256, K*K], 85 | initializer=tf.constant_initializer(0.0), 86 | dtype=tf.float32) 87 | biases = tf.get_variable('biases', [K*K], 88 | initializer=tf.constant_initializer(0.0), 89 | dtype=tf.float32) 90 | biases += tf.constant(np.eye(K).flatten(), dtype=tf.float32) 91 | transform = tf.matmul(net, weights) 92 | transform = tf.nn.bias_add(transform, biases) 93 | 94 | transform = tf.reshape(transform, [batch_size, K, K]) 95 | return transform 96 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/get_file.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | import argparse 4 | import socket 5 | import importlib 6 | import time 7 | import os 8 | import scipy.misc 9 | import sys 10 | 11 | 12 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 13 | sys.path.append(BASE_DIR) 14 | sys.path.append(os.path.join(BASE_DIR, 'models')) 15 | sys.path.append(os.path.join(BASE_DIR, 'utils')) 16 | import provider 17 | import pc_util 18 | 19 | 20 | parser = argparse.ArgumentParser() 21 | parser.add_argument('--gpu', type = int, default = 0, help = 'GPU to use [default: GPU 0]') 22 | parser.add_argument('--model', default = 'pointnet_cls', 23 | help = 'Model name: pointnet_cls or pointnet_cls_basic [default: pointnet_cls]') 24 | parser.add_argument('--batch_size', type = int, default = 5, help = 'example size to vis [default: 5]') 25 | parser.add_argument('--num_point', type = int, default = 1024, 26 | help = 'Point Number [256/512/1024/2048] [default: 1024]') 27 | parser.add_argument('--model_path', default = 'log1/model.ckpt', 28 | help = 'model checkpoint file path [default: log1/model.ckpt]') 29 | parser.add_argument('--vis_mode', default = 'critical', help = 'Vis mode: critical[default]/all') 30 | FLAGS = parser.parse_args() 31 | 32 | BATCH_SIZE = FLAGS.batch_size 33 | NUM_POINT = FLAGS.num_point 34 | MODEL_PATH = FLAGS.model_path 35 | GPU_INDEX = FLAGS.gpu 36 | MODE = FLAGS.vis_mode 37 | MODEL = importlib.import_module(FLAGS.model) # import network module 38 | 39 | NUM_CLASSES = 40 40 | SHAPE_NAMES = [line.rstrip() for line in \ 41 | open(os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/shape_names.txt'))] 42 | 43 | HOSTNAME = socket.gethostname() 44 | 45 | # ModelNet40 official train/test split 46 | TRAIN_FILES = provider.getDataFiles( \ 47 | os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt')) 48 | 49 | 50 | def get_sample(BATCH_SIZE): 51 | current_data, current_label = provider.loadDataFile(TRAIN_FILES[0]) 52 | current_data = current_data[:, 0:NUM_POINT, :] 53 | current_label = np.squeeze(current_label) 54 | print(current_data.shape) 55 | 56 | points = current_data[0:BATCH_SIZE] 57 | labels = current_label[0:BATCH_SIZE] 58 | print labels 59 | return points, labels 60 | 61 | 62 | def get_all_points(): 63 | ''' 64 | We choose points in the inscribed cube of unit sphere as our input space. We compute 80 65 | points each axis. 66 | ''' 67 | lim = 0.577 68 | all_points = [] 69 | 70 | for x in np.linspace(-lim, lim, 80): 71 | for y in np.linspace(-lim, lim, 80): 72 | for z in np.linspace(-lim, lim, 80): 73 | all_points.append([x, y, z]) 74 | 75 | all_points = np.array(all_points) 76 | all_points = np.reshape(all_points, (500, 1024, 3)) 77 | num = all_points.shape[0] 78 | all_label = np.zeros(num) 79 | return all_points, all_label 80 | 81 | 82 | def get_vis_file(BATCH_SIZE, points, label, MODE): 83 | is_training = False 84 | with tf.device('/gpu:' + str(GPU_INDEX)): 85 | pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) 86 | is_training_pl = tf.placeholder(tf.bool, shape = ()) 87 | 88 | # simple model 89 | _, _, hx, maxpool = MODEL.get_model(pointclouds_pl, is_training_pl) 90 | 91 | # Add ops to save and restore all the variables. 92 | saver = tf.train.Saver() 93 | # Create a session 94 | config = tf.ConfigProto() 95 | config.gpu_options.allow_growth = True 96 | config.allow_soft_placement = True 97 | config.log_device_placement = True 98 | sess = tf.Session(config = config) 99 | saver.restore(sess, MODEL_PATH) 100 | print("Model restored.") 101 | 102 | ops = {'pointclouds_pl': pointclouds_pl, 103 | 'labels_pl': labels_pl, 104 | 'is_training_pl': is_training_pl, 105 | 'hx': hx, 106 | 'maxpool': maxpool} 107 | is_training = False 108 | feed_dict = {ops['pointclouds_pl']: points, 109 | ops['labels_pl']: label, 110 | ops['is_training_pl']: is_training} 111 | hx, maxpool = sess.run([ops['hx'], ops['maxpool']], feed_dict = feed_dict) 112 | print hx.shape, maxpool.shape 113 | np.savez('{}.npz'.format(MODE), points = points, hx = hx, maxpool = maxpool) 114 | 115 | 116 | if __name__ == '__main__': 117 | with tf.Graph().as_default(): 118 | sample_points, sample_label = get_sample(BATCH_SIZE) 119 | all_points, all_label = get_all_points() 120 | if (MODE == 'critical'): 121 | get_vis_file(BATCH_SIZE, sample_points, sample_label, 'critical') 122 | if (MODE == 'all'): 123 | get_vis_file(500, all_points, all_label, 'all') 124 | else: 125 | print 'please input the right vis mode, type -h for more information' 126 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/data_prep_util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 4 | sys.path.append(BASE_DIR) 5 | from plyfile import (PlyData, PlyElement, make2d, PlyParseError, PlyProperty) 6 | import numpy as np 7 | import h5py 8 | 9 | SAMPLING_BIN = os.path.join(BASE_DIR, 'third_party/mesh_sampling/build/pcsample') 10 | 11 | SAMPLING_POINT_NUM = 2048 12 | SAMPLING_LEAF_SIZE = 0.005 13 | 14 | MODELNET40_PATH = '../datasets/modelnet40' 15 | def export_ply(pc, filename): 16 | vertex = np.zeros(pc.shape[0], dtype=[('x', 'f4'), ('y', 'f4'), ('z', 'f4')]) 17 | for i in range(pc.shape[0]): 18 | vertex[i] = (pc[i][0], pc[i][1], pc[i][2]) 19 | ply_out = PlyData([PlyElement.describe(vertex, 'vertex', comments=['vertices'])]) 20 | ply_out.write(filename) 21 | 22 | # Sample points on the obj shape 23 | def get_sampling_command(obj_filename, ply_filename): 24 | cmd = SAMPLING_BIN + ' ' + obj_filename 25 | cmd += ' ' + ply_filename 26 | cmd += ' -n_samples %d ' % SAMPLING_POINT_NUM 27 | cmd += ' -leaf_size %f ' % SAMPLING_LEAF_SIZE 28 | return cmd 29 | 30 | # -------------------------------------------------------------- 31 | # Following are the helper functions to load MODELNET40 shapes 32 | # -------------------------------------------------------------- 33 | 34 | # Read in the list of categories in MODELNET40 35 | def get_category_names(): 36 | shape_names_file = os.path.join(MODELNET40_PATH, 'shape_names.txt') 37 | shape_names = [line.rstrip() for line in open(shape_names_file)] 38 | return shape_names 39 | 40 | # Return all the filepaths for the shapes in MODELNET40 41 | def get_obj_filenames(): 42 | obj_filelist_file = os.path.join(MODELNET40_PATH, 'filelist.txt') 43 | obj_filenames = [os.path.join(MODELNET40_PATH, line.rstrip()) for line in open(obj_filelist_file)] 44 | print('Got %d obj files in modelnet40.' % len(obj_filenames)) 45 | return obj_filenames 46 | 47 | # Helper function to create the father folder and all subdir folders if not exist 48 | def batch_mkdir(output_folder, subdir_list): 49 | if not os.path.exists(output_folder): 50 | os.mkdir(output_folder) 51 | for subdir in subdir_list: 52 | if not os.path.exists(os.path.join(output_folder, subdir)): 53 | os.mkdir(os.path.join(output_folder, subdir)) 54 | 55 | # ---------------------------------------------------------------- 56 | # Following are the helper functions to load save/load HDF5 files 57 | # ---------------------------------------------------------------- 58 | 59 | # Write numpy array data and label to h5_filename 60 | def save_h5_data_label_normal(h5_filename, data, label, normal, 61 | data_dtype='float32', label_dtype='uint8', noral_dtype='float32'): 62 | h5_fout = h5py.File(h5_filename) 63 | h5_fout.create_dataset( 64 | 'data', data=data, 65 | compression='gzip', compression_opts=4, 66 | dtype=data_dtype) 67 | h5_fout.create_dataset( 68 | 'normal', data=normal, 69 | compression='gzip', compression_opts=4, 70 | dtype=normal_dtype) 71 | h5_fout.create_dataset( 72 | 'label', data=label, 73 | compression='gzip', compression_opts=1, 74 | dtype=label_dtype) 75 | h5_fout.close() 76 | 77 | 78 | # Write numpy array data and label to h5_filename 79 | def save_h5(h5_filename, data, label, data_dtype='uint8', label_dtype='uint8'): 80 | h5_fout = h5py.File(h5_filename) 81 | h5_fout.create_dataset( 82 | 'data', data=data, 83 | compression='gzip', compression_opts=4, 84 | dtype=data_dtype) 85 | h5_fout.create_dataset( 86 | 'label', data=label, 87 | compression='gzip', compression_opts=1, 88 | dtype=label_dtype) 89 | h5_fout.close() 90 | 91 | # Read numpy array data and label from h5_filename 92 | def load_h5_data_label_normal(h5_filename): 93 | f = h5py.File(h5_filename) 94 | data = f['data'][:] 95 | label = f['label'][:] 96 | normal = f['normal'][:] 97 | return (data, label, normal) 98 | 99 | # Read numpy array data and label from h5_filename 100 | def load_h5_data_label_seg(h5_filename): 101 | f = h5py.File(h5_filename) 102 | data = f['data'][:] 103 | label = f['label'][:] 104 | seg = f['pid'][:] 105 | return (data, label, seg) 106 | 107 | # Read numpy array data and label from h5_filename 108 | def load_h5(h5_filename): 109 | f = h5py.File(h5_filename) 110 | data = f['data'][:] 111 | label = f['label'][:] 112 | return (data, label) 113 | 114 | # ---------------------------------------------------------------- 115 | # Following are the helper functions to load save/load PLY files 116 | # ---------------------------------------------------------------- 117 | 118 | # Load PLY file 119 | def load_ply_data(filename, point_num): 120 | plydata = PlyData.read(filename) 121 | pc = plydata['vertex'].data[:point_num] 122 | pc_array = np.array([[x, y, z] for x,y,z in pc]) 123 | return pc_array 124 | 125 | # Load PLY file 126 | def load_ply_normal(filename, point_num): 127 | plydata = PlyData.read(filename) 128 | pc = plydata['normal'].data[:point_num] 129 | pc_array = np.array([[x, y, z] for x,y,z in pc]) 130 | return pc_array 131 | 132 | # Make up rows for Nxk array 133 | # Input Pad is 'edge' or 'constant' 134 | def pad_arr_rows(arr, row, pad='edge'): 135 | assert(len(arr.shape) == 2) 136 | assert(arr.shape[0] <= row) 137 | assert(pad == 'edge' or pad == 'constant') 138 | if arr.shape[0] == row: 139 | return arr 140 | if pad == 'edge': 141 | return np.lib.pad(arr, ((0, row-arr.shape[0]), (0, 0)), 'edge') 142 | if pad == 'constant': 143 | return np.lib.pad(arr, ((0, row-arr.shape[0]), (0, 0)), 'constant', (0, 0)) 144 | 145 | 146 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/pc_util.py: -------------------------------------------------------------------------------- 1 | """ Utility functions for processing point clouds. 2 | 3 | Author: Charles R. Qi, Hao Su 4 | Date: November 2016 5 | """ 6 | 7 | import os 8 | import sys 9 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 10 | sys.path.append(BASE_DIR) 11 | 12 | # Draw point cloud 13 | from eulerangles import euler2mat 14 | 15 | # Point cloud IO 16 | import numpy as np 17 | from plyfile import PlyData, PlyElement 18 | 19 | 20 | # ---------------------------------------- 21 | # Point Cloud/Volume Conversions 22 | # ---------------------------------------- 23 | 24 | def point_cloud_to_volume_batch(point_clouds, vsize=12, radius=1.0, flatten=True): 25 | """ Input is BxNx3 batch of point cloud 26 | Output is Bx(vsize^3) 27 | """ 28 | vol_list = [] 29 | for b in range(point_clouds.shape[0]): 30 | vol = point_cloud_to_volume(np.squeeze(point_clouds[b,:,:]), vsize, radius) 31 | if flatten: 32 | vol_list.append(vol.flatten()) 33 | else: 34 | vol_list.append(np.expand_dims(np.expand_dims(vol, -1), 0)) 35 | if flatten: 36 | return np.vstack(vol_list) 37 | else: 38 | return np.concatenate(vol_list, 0) 39 | 40 | 41 | def point_cloud_to_volume(points, vsize, radius=1.0): 42 | """ input is Nx3 points. 43 | output is vsize*vsize*vsize 44 | assumes points are in range [-radius, radius] 45 | """ 46 | vol = np.zeros((vsize,vsize,vsize)) 47 | voxel = 2*radius/float(vsize) 48 | locations = (points + radius)/voxel 49 | locations = locations.astype(int) 50 | vol[locations[:,0],locations[:,1],locations[:,2]] = 1.0 51 | return vol 52 | 53 | #a = np.zeros((16,1024,3)) 54 | #print point_cloud_to_volume_batch(a, 12, 1.0, False).shape 55 | 56 | def volume_to_point_cloud(vol): 57 | """ vol is occupancy grid (value = 0 or 1) of size vsize*vsize*vsize 58 | return Nx3 numpy array. 59 | """ 60 | vsize = vol.shape[0] 61 | assert(vol.shape[1] == vsize and vol.shape[1] == vsize) 62 | points = [] 63 | for a in range(vsize): 64 | for b in range(vsize): 65 | for c in range(vsize): 66 | if vol[a,b,c] == 1: 67 | points.append(np.array([a,b,c])) 68 | if len(points) == 0: 69 | return np.zeros((0,3)) 70 | points = np.vstack(points) 71 | return points 72 | 73 | # ---------------------------------------- 74 | # Point cloud IO 75 | # ---------------------------------------- 76 | 77 | def read_ply(filename): 78 | """ read XYZ point cloud from filename PLY file """ 79 | plydata = PlyData.read(filename) 80 | pc = plydata['vertex'].data 81 | pc_array = np.array([[x, y, z] for x,y,z in pc]) 82 | return pc_array 83 | 84 | 85 | def write_ply(points, filename, text=True): 86 | """ input: Nx3, write points to filename as PLY format. """ 87 | points = [(points[i,0], points[i,1], points[i,2]) for i in range(points.shape[0])] 88 | vertex = np.array(points, dtype=[('x', 'f4'), ('y', 'f4'),('z', 'f4')]) 89 | el = PlyElement.describe(vertex, 'vertex', comments=['vertices']) 90 | PlyData([el], text=text).write(filename) 91 | 92 | 93 | # ---------------------------------------- 94 | # Simple Point cloud and Volume Renderers 95 | # ---------------------------------------- 96 | 97 | def draw_point_cloud(input_points, canvasSize=500, space=200, diameter=25, 98 | xrot=0, yrot=0, zrot=0, switch_xyz=[0,1,2], normalize=True): 99 | """ Render point cloud to image with alpha channel. 100 | Input: 101 | points: Nx3 numpy array (+y is up direction) 102 | Output: 103 | gray image as numpy array of size canvasSizexcanvasSize 104 | """ 105 | image = np.zeros((canvasSize, canvasSize)) 106 | if input_points is None or input_points.shape[0] == 0: 107 | return image 108 | 109 | points = input_points[:, switch_xyz] 110 | M = euler2mat(zrot, yrot, xrot) 111 | points = (np.dot(M, points.transpose())).transpose() 112 | 113 | # Normalize the point cloud 114 | # We normalize scale to fit points in a unit sphere 115 | if normalize: 116 | centroid = np.mean(points, axis=0) 117 | points -= centroid 118 | furthest_distance = np.max(np.sqrt(np.sum(abs(points)**2,axis=-1))) 119 | points /= furthest_distance 120 | 121 | # Pre-compute the Gaussian disk 122 | radius = (diameter-1)/2.0 123 | disk = np.zeros((diameter, diameter)) 124 | for i in range(diameter): 125 | for j in range(diameter): 126 | if (i - radius) * (i-radius) + (j-radius) * (j-radius) <= radius * radius: 127 | disk[i, j] = np.exp((-(i-radius)**2 - (j-radius)**2)/(radius**2)) 128 | mask = np.argwhere(disk > 0) 129 | dx = mask[:, 0] 130 | dy = mask[:, 1] 131 | dv = disk[disk > 0] 132 | 133 | # Order points by z-buffer 134 | zorder = np.argsort(points[:, 2]) 135 | points = points[zorder, :] 136 | points[:, 2] = (points[:, 2] - np.min(points[:, 2])) / (np.max(points[:, 2] - np.min(points[:, 2]))) 137 | max_depth = np.max(points[:, 2]) 138 | 139 | for i in range(points.shape[0]): 140 | j = points.shape[0] - i - 1 141 | x = points[j, 0] 142 | y = points[j, 1] 143 | xc = canvasSize/2 + (x*space) 144 | yc = canvasSize/2 + (y*space) 145 | xc = int(np.round(xc)) 146 | yc = int(np.round(yc)) 147 | 148 | px = dx + xc 149 | py = dy + yc 150 | 151 | image[px, py] = image[px, py] * 0.7 + dv * (max_depth - points[j, 2]) * 0.3 152 | 153 | image = image / np.max(image) 154 | return image 155 | 156 | def point_cloud_three_views(points): 157 | """ input points Nx3 numpy array (+y is up direction). 158 | return an numpy array gray image of size 500x1500. """ 159 | # +y is up direction 160 | # xrot is azimuth 161 | # yrot is in-plane 162 | # zrot is elevation 163 | img1 = draw_point_cloud(points, zrot=110/180.0*np.pi, xrot=45/180.0*np.pi, yrot=0/180.0*np.pi) 164 | img2 = draw_point_cloud(points, zrot=70/180.0*np.pi, xrot=135/180.0*np.pi, yrot=0/180.0*np.pi) 165 | img3 = draw_point_cloud(points, zrot=180.0/180.0*np.pi, xrot=90/180.0*np.pi, yrot=0/180.0*np.pi) 166 | image_large = np.concatenate([img1, img2, img3], 1) 167 | return image_large 168 | 169 | 170 | from PIL import Image 171 | def point_cloud_three_views_demo(): 172 | """ Demo for draw_point_cloud function """ 173 | points = read_ply('../third_party/mesh_sampling/piano.ply') 174 | im_array = point_cloud_three_views(points) 175 | img = Image.fromarray(np.uint8(im_array*255.0)) 176 | img.save('piano.jpg') 177 | 178 | if __name__=="__main__": 179 | point_cloud_three_views_demo() 180 | 181 | 182 | import matplotlib.pyplot as plt 183 | def pyplot_draw_point_cloud(points, output_filename): 184 | """ points is a Nx3 numpy array """ 185 | fig = plt.figure() 186 | ax = fig.add_subplot(111, projection='3d') 187 | ax.scatter(points[:,0], points[:,1], points[:,2]) 188 | ax.set_xlabel('x') 189 | ax.set_ylabel('y') 190 | ax.set_zlabel('z') 191 | #savefig(output_filename) 192 | 193 | def pyplot_draw_volume(vol, output_filename): 194 | """ vol is of size vsize*vsize*vsize 195 | output an image to output_filename 196 | """ 197 | points = volume_to_point_cloud(vol) 198 | pyplot_draw_point_cloud(points, output_filename) 199 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/train.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import math 3 | import h5py 4 | import numpy as np 5 | import tensorflow as tf 6 | import socket 7 | import importlib 8 | import os 9 | import sys 10 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 11 | sys.path.append(BASE_DIR) 12 | sys.path.append(os.path.join(BASE_DIR, 'models')) 13 | sys.path.append(os.path.join(BASE_DIR, 'utils')) 14 | import provider 15 | import tf_util 16 | 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') 19 | parser.add_argument('--model', default='pointnet_cls', help='Model name: pointnet_cls or pointnet_cls_basic [default: pointnet_cls]') 20 | parser.add_argument('--log_dir', default='log', help='Log dir [default: log]') 21 | parser.add_argument('--num_point', type=int, default=1024, help='Point Number [256/512/1024/2048] [default: 1024]') 22 | parser.add_argument('--max_epoch', type=int, default=250, help='Epoch to run [default: 250]') 23 | parser.add_argument('--batch_size', type=int, default=32, help='Batch Size during training [default: 32]') 24 | parser.add_argument('--learning_rate', type=float, default=0.001, help='Initial learning rate [default: 0.001]') 25 | parser.add_argument('--momentum', type=float, default=0.9, help='Initial learning rate [default: 0.9]') 26 | parser.add_argument('--optimizer', default='adam', help='adam or momentum [default: adam]') 27 | parser.add_argument('--decay_step', type=int, default=200000, help='Decay step for lr decay [default: 200000]') 28 | parser.add_argument('--decay_rate', type=float, default=0.7, help='Decay rate for lr decay [default: 0.8]') 29 | FLAGS = parser.parse_args() 30 | 31 | 32 | BATCH_SIZE = FLAGS.batch_size 33 | NUM_POINT = FLAGS.num_point 34 | MAX_EPOCH = FLAGS.max_epoch 35 | BASE_LEARNING_RATE = FLAGS.learning_rate 36 | GPU_INDEX = FLAGS.gpu 37 | MOMENTUM = FLAGS.momentum 38 | OPTIMIZER = FLAGS.optimizer 39 | DECAY_STEP = FLAGS.decay_step 40 | DECAY_RATE = FLAGS.decay_rate 41 | 42 | MODEL = importlib.import_module(FLAGS.model) # import network module 43 | MODEL_FILE = os.path.join(BASE_DIR, 'models', FLAGS.model+'.py') 44 | LOG_DIR = FLAGS.log_dir 45 | if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR) 46 | os.system('cp %s %s' % (MODEL_FILE, LOG_DIR)) # bkp of model def 47 | os.system('cp train.py %s' % (LOG_DIR)) # bkp of train procedure 48 | LOG_FOUT = open(os.path.join(LOG_DIR, 'log_train.txt'), 'w') 49 | LOG_FOUT.write(str(FLAGS)+'\n') 50 | 51 | MAX_NUM_POINT = 2048 52 | NUM_CLASSES = 40 53 | 54 | BN_INIT_DECAY = 0.5 55 | BN_DECAY_DECAY_RATE = 0.5 56 | BN_DECAY_DECAY_STEP = float(DECAY_STEP) 57 | BN_DECAY_CLIP = 0.99 58 | 59 | HOSTNAME = socket.gethostname() 60 | 61 | # ModelNet40 official train/test split 62 | TRAIN_FILES = provider.getDataFiles( \ 63 | os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt')) 64 | TEST_FILES = provider.getDataFiles(\ 65 | os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt')) 66 | 67 | def log_string(out_str): 68 | LOG_FOUT.write(out_str+'\n') 69 | LOG_FOUT.flush() 70 | print(out_str) 71 | 72 | 73 | def get_learning_rate(batch): 74 | learning_rate = tf.train.exponential_decay( 75 | BASE_LEARNING_RATE, # Base learning rate. 76 | batch * BATCH_SIZE, # Current index into the dataset. 77 | DECAY_STEP, # Decay step. 78 | DECAY_RATE, # Decay rate. 79 | staircase=True) 80 | learning_rate = tf.maximum(learning_rate, 0.00001) # CLIP THE LEARNING RATE! 81 | return learning_rate 82 | 83 | def get_bn_decay(batch): 84 | bn_momentum = tf.train.exponential_decay( 85 | BN_INIT_DECAY, 86 | batch*BATCH_SIZE, 87 | BN_DECAY_DECAY_STEP, 88 | BN_DECAY_DECAY_RATE, 89 | staircase=True) 90 | bn_decay = tf.minimum(BN_DECAY_CLIP, 1 - bn_momentum) 91 | return bn_decay 92 | 93 | def train(): 94 | with tf.Graph().as_default(): 95 | with tf.device('/gpu:'+str(GPU_INDEX)): 96 | pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT) 97 | is_training_pl = tf.placeholder(tf.bool, shape=()) 98 | print(is_training_pl) 99 | 100 | # Note the global_step=batch parameter to minimize. 101 | # That tells the optimizer to helpfully increment the 'batch' parameter for you every time it trains. 102 | batch = tf.Variable(0) 103 | bn_decay = get_bn_decay(batch) 104 | tf.summary.scalar('bn_decay', bn_decay) 105 | 106 | # Get model and loss 107 | pred, end_points,_,_ = MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay) 108 | loss = MODEL.get_loss(pred, labels_pl, end_points) 109 | tf.summary.scalar('loss', loss) 110 | 111 | correct = tf.equal(tf.argmax(pred, 1), tf.to_int64(labels_pl)) 112 | accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE) 113 | tf.summary.scalar('accuracy', accuracy) 114 | 115 | # Get training operator 116 | learning_rate = get_learning_rate(batch) 117 | tf.summary.scalar('learning_rate', learning_rate) 118 | if OPTIMIZER == 'momentum': 119 | optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=MOMENTUM) 120 | elif OPTIMIZER == 'adam': 121 | optimizer = tf.train.AdamOptimizer(learning_rate) 122 | train_op = optimizer.minimize(loss, global_step=batch) 123 | 124 | # Add ops to save and restore all the variables. 125 | saver = tf.train.Saver() 126 | 127 | # Create a session 128 | config = tf.ConfigProto() 129 | config.gpu_options.allow_growth = True 130 | config.allow_soft_placement = True 131 | config.log_device_placement = False 132 | sess = tf.Session(config=config) 133 | 134 | # Add summary writers 135 | #merged = tf.merge_all_summaries() 136 | merged = tf.summary.merge_all() 137 | train_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'train'), 138 | sess.graph) 139 | test_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'test')) 140 | 141 | # Init variables 142 | init = tf.global_variables_initializer() 143 | # To fix the bug introduced in TF 0.12.1 as in 144 | # http://stackoverflow.com/questions/41543774/invalidargumenterror-for-tensor-bool-tensorflow-0-12-1 145 | #sess.run(init) 146 | sess.run(init, {is_training_pl: True}) 147 | 148 | ops = {'pointclouds_pl': pointclouds_pl, 149 | 'labels_pl': labels_pl, 150 | 'is_training_pl': is_training_pl, 151 | 'pred': pred, 152 | 'loss': loss, 153 | 'train_op': train_op, 154 | 'merged': merged, 155 | 'step': batch} 156 | 157 | for epoch in range(MAX_EPOCH): 158 | log_string('**** EPOCH %03d ****' % (epoch)) 159 | sys.stdout.flush() 160 | 161 | train_one_epoch(sess, ops, train_writer) 162 | eval_one_epoch(sess, ops, test_writer) 163 | 164 | # Save the variables to disk. 165 | if epoch % 10 == 0: 166 | save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt")) 167 | log_string("Model saved in file: %s" % save_path) 168 | 169 | 170 | 171 | def train_one_epoch(sess, ops, train_writer): 172 | """ ops: dict mapping from string to tf ops """ 173 | is_training = True 174 | 175 | # Shuffle train files 176 | train_file_idxs = np.arange(0, len(TRAIN_FILES)) 177 | np.random.shuffle(train_file_idxs) 178 | 179 | for fn in range(len(TRAIN_FILES)): 180 | log_string('----' + str(fn) + '-----') 181 | current_data, current_label = provider.loadDataFile(TRAIN_FILES[train_file_idxs[fn]]) 182 | current_data = current_data[:,0:NUM_POINT,:] 183 | current_data, current_label, _ = provider.shuffle_data(current_data, np.squeeze(current_label)) 184 | current_label = np.squeeze(current_label) 185 | 186 | file_size = current_data.shape[0] 187 | num_batches = file_size // BATCH_SIZE 188 | 189 | total_correct = 0 190 | total_seen = 0 191 | loss_sum = 0 192 | 193 | for batch_idx in range(num_batches): 194 | start_idx = batch_idx * BATCH_SIZE 195 | end_idx = (batch_idx+1) * BATCH_SIZE 196 | 197 | # Augment batched point clouds by rotation and jittering 198 | rotated_data = provider.rotate_point_cloud(current_data[start_idx:end_idx, :, :]) 199 | jittered_data = provider.jitter_point_cloud(rotated_data) 200 | feed_dict = {ops['pointclouds_pl']: jittered_data, 201 | ops['labels_pl']: current_label[start_idx:end_idx], 202 | ops['is_training_pl']: is_training,} 203 | summary, step, _, loss_val, pred_val = sess.run([ops['merged'], ops['step'], 204 | ops['train_op'], ops['loss'], ops['pred']], feed_dict=feed_dict) 205 | train_writer.add_summary(summary, step) 206 | pred_val = np.argmax(pred_val, 1) 207 | correct = np.sum(pred_val == current_label[start_idx:end_idx]) 208 | total_correct += correct 209 | total_seen += BATCH_SIZE 210 | loss_sum += loss_val 211 | 212 | log_string('mean loss: %f' % (loss_sum / float(num_batches))) 213 | log_string('accuracy: %f' % (total_correct / float(total_seen))) 214 | 215 | 216 | def eval_one_epoch(sess, ops, test_writer): 217 | """ ops: dict mapping from string to tf ops """ 218 | is_training = False 219 | total_correct = 0 220 | total_seen = 0 221 | loss_sum = 0 222 | total_seen_class = [0 for _ in range(NUM_CLASSES)] 223 | total_correct_class = [0 for _ in range(NUM_CLASSES)] 224 | 225 | for fn in range(len(TEST_FILES)): 226 | log_string('----' + str(fn) + '-----') 227 | current_data, current_label = provider.loadDataFile(TEST_FILES[fn]) 228 | current_data = current_data[:,0:NUM_POINT,:] 229 | current_label = np.squeeze(current_label) 230 | 231 | file_size = current_data.shape[0] 232 | num_batches = file_size // BATCH_SIZE 233 | 234 | for batch_idx in range(num_batches): 235 | start_idx = batch_idx * BATCH_SIZE 236 | end_idx = (batch_idx+1) * BATCH_SIZE 237 | 238 | feed_dict = {ops['pointclouds_pl']: current_data[start_idx:end_idx, :, :], 239 | ops['labels_pl']: current_label[start_idx:end_idx], 240 | ops['is_training_pl']: is_training} 241 | summary, step, loss_val, pred_val = sess.run([ops['merged'], ops['step'], 242 | ops['loss'], ops['pred']], feed_dict=feed_dict) 243 | pred_val = np.argmax(pred_val, 1) 244 | correct = np.sum(pred_val == current_label[start_idx:end_idx]) 245 | total_correct += correct 246 | total_seen += BATCH_SIZE 247 | loss_sum += (loss_val*BATCH_SIZE) 248 | for i in range(start_idx, end_idx): 249 | l = current_label[i] 250 | total_seen_class[l] += 1 251 | total_correct_class[l] += (pred_val[i-start_idx] == l) 252 | 253 | log_string('eval mean loss: %f' % (loss_sum / float(total_seen))) 254 | log_string('eval accuracy: %f'% (total_correct / float(total_seen))) 255 | log_string('eval avg class acc: %f' % (np.mean(np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float)))) 256 | 257 | 258 | 259 | if __name__ == "__main__": 260 | train() 261 | LOG_FOUT.close() 262 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/eulerangles.py: -------------------------------------------------------------------------------- 1 | # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- 2 | # vi: set ft=python sts=4 ts=4 sw=4 et: 3 | ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 4 | # 5 | # See COPYING file distributed along with the NiBabel package for the 6 | # copyright and license terms. 7 | # 8 | ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 9 | ''' Module implementing Euler angle rotations and their conversions 10 | 11 | See: 12 | 13 | * http://en.wikipedia.org/wiki/Rotation_matrix 14 | * http://en.wikipedia.org/wiki/Euler_angles 15 | * http://mathworld.wolfram.com/EulerAngles.html 16 | 17 | See also: *Representing Attitude with Euler Angles and Quaternions: A 18 | Reference* (2006) by James Diebel. A cached PDF link last found here: 19 | 20 | http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110.5134 21 | 22 | Euler's rotation theorem tells us that any rotation in 3D can be 23 | described by 3 angles. Let's call the 3 angles the *Euler angle vector* 24 | and call the angles in the vector :math:`alpha`, :math:`beta` and 25 | :math:`gamma`. The vector is [ :math:`alpha`, 26 | :math:`beta`. :math:`gamma` ] and, in this description, the order of the 27 | parameters specifies the order in which the rotations occur (so the 28 | rotation corresponding to :math:`alpha` is applied first). 29 | 30 | In order to specify the meaning of an *Euler angle vector* we need to 31 | specify the axes around which each of the rotations corresponding to 32 | :math:`alpha`, :math:`beta` and :math:`gamma` will occur. 33 | 34 | There are therefore three axes for the rotations :math:`alpha`, 35 | :math:`beta` and :math:`gamma`; let's call them :math:`i` :math:`j`, 36 | :math:`k`. 37 | 38 | Let us express the rotation :math:`alpha` around axis `i` as a 3 by 3 39 | rotation matrix `A`. Similarly :math:`beta` around `j` becomes 3 x 3 40 | matrix `B` and :math:`gamma` around `k` becomes matrix `G`. Then the 41 | whole rotation expressed by the Euler angle vector [ :math:`alpha`, 42 | :math:`beta`. :math:`gamma` ], `R` is given by:: 43 | 44 | R = np.dot(G, np.dot(B, A)) 45 | 46 | See http://mathworld.wolfram.com/EulerAngles.html 47 | 48 | The order :math:`G B A` expresses the fact that the rotations are 49 | performed in the order of the vector (:math:`alpha` around axis `i` = 50 | `A` first). 51 | 52 | To convert a given Euler angle vector to a meaningful rotation, and a 53 | rotation matrix, we need to define: 54 | 55 | * the axes `i`, `j`, `k` 56 | * whether a rotation matrix should be applied on the left of a vector to 57 | be transformed (vectors are column vectors) or on the right (vectors 58 | are row vectors). 59 | * whether the rotations move the axes as they are applied (intrinsic 60 | rotations) - compared the situation where the axes stay fixed and the 61 | vectors move within the axis frame (extrinsic) 62 | * the handedness of the coordinate system 63 | 64 | See: http://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities 65 | 66 | We are using the following conventions: 67 | 68 | * axes `i`, `j`, `k` are the `z`, `y`, and `x` axes respectively. Thus 69 | an Euler angle vector [ :math:`alpha`, :math:`beta`. :math:`gamma` ] 70 | in our convention implies a :math:`alpha` radian rotation around the 71 | `z` axis, followed by a :math:`beta` rotation around the `y` axis, 72 | followed by a :math:`gamma` rotation around the `x` axis. 73 | * the rotation matrix applies on the left, to column vectors on the 74 | right, so if `R` is the rotation matrix, and `v` is a 3 x N matrix 75 | with N column vectors, the transformed vector set `vdash` is given by 76 | ``vdash = np.dot(R, v)``. 77 | * extrinsic rotations - the axes are fixed, and do not move with the 78 | rotations. 79 | * a right-handed coordinate system 80 | 81 | The convention of rotation around ``z``, followed by rotation around 82 | ``y``, followed by rotation around ``x``, is known (confusingly) as 83 | "xyz", pitch-roll-yaw, Cardan angles, or Tait-Bryan angles. 84 | ''' 85 | 86 | import math 87 | 88 | import sys 89 | if sys.version_info >= (3,0): 90 | from functools import reduce 91 | 92 | import numpy as np 93 | 94 | 95 | _FLOAT_EPS_4 = np.finfo(float).eps * 4.0 96 | 97 | 98 | def euler2mat(z=0, y=0, x=0): 99 | ''' Return matrix for rotations around z, y and x axes 100 | 101 | Uses the z, then y, then x convention above 102 | 103 | Parameters 104 | ---------- 105 | z : scalar 106 | Rotation angle in radians around z-axis (performed first) 107 | y : scalar 108 | Rotation angle in radians around y-axis 109 | x : scalar 110 | Rotation angle in radians around x-axis (performed last) 111 | 112 | Returns 113 | ------- 114 | M : array shape (3,3) 115 | Rotation matrix giving same rotation as for given angles 116 | 117 | Examples 118 | -------- 119 | >>> zrot = 1.3 # radians 120 | >>> yrot = -0.1 121 | >>> xrot = 0.2 122 | >>> M = euler2mat(zrot, yrot, xrot) 123 | >>> M.shape == (3, 3) 124 | True 125 | 126 | The output rotation matrix is equal to the composition of the 127 | individual rotations 128 | 129 | >>> M1 = euler2mat(zrot) 130 | >>> M2 = euler2mat(0, yrot) 131 | >>> M3 = euler2mat(0, 0, xrot) 132 | >>> composed_M = np.dot(M3, np.dot(M2, M1)) 133 | >>> np.allclose(M, composed_M) 134 | True 135 | 136 | You can specify rotations by named arguments 137 | 138 | >>> np.all(M3 == euler2mat(x=xrot)) 139 | True 140 | 141 | When applying M to a vector, the vector should column vector to the 142 | right of M. If the right hand side is a 2D array rather than a 143 | vector, then each column of the 2D array represents a vector. 144 | 145 | >>> vec = np.array([1, 0, 0]).reshape((3,1)) 146 | >>> v2 = np.dot(M, vec) 147 | >>> vecs = np.array([[1, 0, 0],[0, 1, 0]]).T # giving 3x2 array 148 | >>> vecs2 = np.dot(M, vecs) 149 | 150 | Rotations are counter-clockwise. 151 | 152 | >>> zred = np.dot(euler2mat(z=np.pi/2), np.eye(3)) 153 | >>> np.allclose(zred, [[0, -1, 0],[1, 0, 0], [0, 0, 1]]) 154 | True 155 | >>> yred = np.dot(euler2mat(y=np.pi/2), np.eye(3)) 156 | >>> np.allclose(yred, [[0, 0, 1],[0, 1, 0], [-1, 0, 0]]) 157 | True 158 | >>> xred = np.dot(euler2mat(x=np.pi/2), np.eye(3)) 159 | >>> np.allclose(xred, [[1, 0, 0],[0, 0, -1], [0, 1, 0]]) 160 | True 161 | 162 | Notes 163 | ----- 164 | The direction of rotation is given by the right-hand rule (orient 165 | the thumb of the right hand along the axis around which the rotation 166 | occurs, with the end of the thumb at the positive end of the axis; 167 | curl your fingers; the direction your fingers curl is the direction 168 | of rotation). Therefore, the rotations are counterclockwise if 169 | looking along the axis of rotation from positive to negative. 170 | ''' 171 | Ms = [] 172 | if z: 173 | cosz = math.cos(z) 174 | sinz = math.sin(z) 175 | Ms.append(np.array( 176 | [[cosz, -sinz, 0], 177 | [sinz, cosz, 0], 178 | [0, 0, 1]])) 179 | if y: 180 | cosy = math.cos(y) 181 | siny = math.sin(y) 182 | Ms.append(np.array( 183 | [[cosy, 0, siny], 184 | [0, 1, 0], 185 | [-siny, 0, cosy]])) 186 | if x: 187 | cosx = math.cos(x) 188 | sinx = math.sin(x) 189 | Ms.append(np.array( 190 | [[1, 0, 0], 191 | [0, cosx, -sinx], 192 | [0, sinx, cosx]])) 193 | if Ms: 194 | return reduce(np.dot, Ms[::-1]) 195 | return np.eye(3) 196 | 197 | 198 | def mat2euler(M, cy_thresh=None): 199 | ''' Discover Euler angle vector from 3x3 matrix 200 | 201 | Uses the conventions above. 202 | 203 | Parameters 204 | ---------- 205 | M : array-like, shape (3,3) 206 | cy_thresh : None or scalar, optional 207 | threshold below which to give up on straightforward arctan for 208 | estimating x rotation. If None (default), estimate from 209 | precision of input. 210 | 211 | Returns 212 | ------- 213 | z : scalar 214 | y : scalar 215 | x : scalar 216 | Rotations in radians around z, y, x axes, respectively 217 | 218 | Notes 219 | ----- 220 | If there was no numerical error, the routine could be derived using 221 | Sympy expression for z then y then x rotation matrix, which is:: 222 | 223 | [ cos(y)*cos(z), -cos(y)*sin(z), sin(y)], 224 | [cos(x)*sin(z) + cos(z)*sin(x)*sin(y), cos(x)*cos(z) - sin(x)*sin(y)*sin(z), -cos(y)*sin(x)], 225 | [sin(x)*sin(z) - cos(x)*cos(z)*sin(y), cos(z)*sin(x) + cos(x)*sin(y)*sin(z), cos(x)*cos(y)] 226 | 227 | with the obvious derivations for z, y, and x 228 | 229 | z = atan2(-r12, r11) 230 | y = asin(r13) 231 | x = atan2(-r23, r33) 232 | 233 | Problems arise when cos(y) is close to zero, because both of:: 234 | 235 | z = atan2(cos(y)*sin(z), cos(y)*cos(z)) 236 | x = atan2(cos(y)*sin(x), cos(x)*cos(y)) 237 | 238 | will be close to atan2(0, 0), and highly unstable. 239 | 240 | The ``cy`` fix for numerical instability below is from: *Graphics 241 | Gems IV*, Paul Heckbert (editor), Academic Press, 1994, ISBN: 242 | 0123361559. Specifically it comes from EulerAngles.c by Ken 243 | Shoemake, and deals with the case where cos(y) is close to zero: 244 | 245 | See: http://www.graphicsgems.org/ 246 | 247 | The code appears to be licensed (from the website) as "can be used 248 | without restrictions". 249 | ''' 250 | M = np.asarray(M) 251 | if cy_thresh is None: 252 | try: 253 | cy_thresh = np.finfo(M.dtype).eps * 4 254 | except ValueError: 255 | cy_thresh = _FLOAT_EPS_4 256 | r11, r12, r13, r21, r22, r23, r31, r32, r33 = M.flat 257 | # cy: sqrt((cos(y)*cos(z))**2 + (cos(x)*cos(y))**2) 258 | cy = math.sqrt(r33*r33 + r23*r23) 259 | if cy > cy_thresh: # cos(y) not close to zero, standard form 260 | z = math.atan2(-r12, r11) # atan2(cos(y)*sin(z), cos(y)*cos(z)) 261 | y = math.atan2(r13, cy) # atan2(sin(y), cy) 262 | x = math.atan2(-r23, r33) # atan2(cos(y)*sin(x), cos(x)*cos(y)) 263 | else: # cos(y) (close to) zero, so x -> 0.0 (see above) 264 | # so r21 -> sin(z), r22 -> cos(z) and 265 | z = math.atan2(r21, r22) 266 | y = math.atan2(r13, cy) # atan2(sin(y), cy) 267 | x = 0.0 268 | return z, y, x 269 | 270 | 271 | def euler2quat(z=0, y=0, x=0): 272 | ''' Return quaternion corresponding to these Euler angles 273 | 274 | Uses the z, then y, then x convention above 275 | 276 | Parameters 277 | ---------- 278 | z : scalar 279 | Rotation angle in radians around z-axis (performed first) 280 | y : scalar 281 | Rotation angle in radians around y-axis 282 | x : scalar 283 | Rotation angle in radians around x-axis (performed last) 284 | 285 | Returns 286 | ------- 287 | quat : array shape (4,) 288 | Quaternion in w, x, y z (real, then vector) format 289 | 290 | Notes 291 | ----- 292 | We can derive this formula in Sympy using: 293 | 294 | 1. Formula giving quaternion corresponding to rotation of theta radians 295 | about arbitrary axis: 296 | http://mathworld.wolfram.com/EulerParameters.html 297 | 2. Generated formulae from 1.) for quaternions corresponding to 298 | theta radians rotations about ``x, y, z`` axes 299 | 3. Apply quaternion multiplication formula - 300 | http://en.wikipedia.org/wiki/Quaternions#Hamilton_product - to 301 | formulae from 2.) to give formula for combined rotations. 302 | ''' 303 | z = z/2.0 304 | y = y/2.0 305 | x = x/2.0 306 | cz = math.cos(z) 307 | sz = math.sin(z) 308 | cy = math.cos(y) 309 | sy = math.sin(y) 310 | cx = math.cos(x) 311 | sx = math.sin(x) 312 | return np.array([ 313 | cx*cy*cz - sx*sy*sz, 314 | cx*sy*sz + cy*cz*sx, 315 | cx*cz*sy - sx*cy*sz, 316 | cx*cy*sz + sx*cz*sy]) 317 | 318 | 319 | def quat2euler(q): 320 | ''' Return Euler angles corresponding to quaternion `q` 321 | 322 | Parameters 323 | ---------- 324 | q : 4 element sequence 325 | w, x, y, z of quaternion 326 | 327 | Returns 328 | ------- 329 | z : scalar 330 | Rotation angle in radians around z-axis (performed first) 331 | y : scalar 332 | Rotation angle in radians around y-axis 333 | x : scalar 334 | Rotation angle in radians around x-axis (performed last) 335 | 336 | Notes 337 | ----- 338 | It's possible to reduce the amount of calculation a little, by 339 | combining parts of the ``quat2mat`` and ``mat2euler`` functions, but 340 | the reduction in computation is small, and the code repetition is 341 | large. 342 | ''' 343 | # delayed import to avoid cyclic dependencies 344 | import nibabel.quaternions as nq 345 | return mat2euler(nq.quat2mat(q)) 346 | 347 | 348 | def euler2angle_axis(z=0, y=0, x=0): 349 | ''' Return angle, axis corresponding to these Euler angles 350 | 351 | Uses the z, then y, then x convention above 352 | 353 | Parameters 354 | ---------- 355 | z : scalar 356 | Rotation angle in radians around z-axis (performed first) 357 | y : scalar 358 | Rotation angle in radians around y-axis 359 | x : scalar 360 | Rotation angle in radians around x-axis (performed last) 361 | 362 | Returns 363 | ------- 364 | theta : scalar 365 | angle of rotation 366 | vector : array shape (3,) 367 | axis around which rotation occurs 368 | 369 | Examples 370 | -------- 371 | >>> theta, vec = euler2angle_axis(0, 1.5, 0) 372 | >>> print(theta) 373 | 1.5 374 | >>> np.allclose(vec, [0, 1, 0]) 375 | True 376 | ''' 377 | # delayed import to avoid cyclic dependencies 378 | import nibabel.quaternions as nq 379 | return nq.quat2angle_axis(euler2quat(z, y, x)) 380 | 381 | 382 | def angle_axis2euler(theta, vector, is_normalized=False): 383 | ''' Convert angle, axis pair to Euler angles 384 | 385 | Parameters 386 | ---------- 387 | theta : scalar 388 | angle of rotation 389 | vector : 3 element sequence 390 | vector specifying axis for rotation. 391 | is_normalized : bool, optional 392 | True if vector is already normalized (has norm of 1). Default 393 | False 394 | 395 | Returns 396 | ------- 397 | z : scalar 398 | y : scalar 399 | x : scalar 400 | Rotations in radians around z, y, x axes, respectively 401 | 402 | Examples 403 | -------- 404 | >>> z, y, x = angle_axis2euler(0, [1, 0, 0]) 405 | >>> np.allclose((z, y, x), 0) 406 | True 407 | 408 | Notes 409 | ----- 410 | It's possible to reduce the amount of calculation a little, by 411 | combining parts of the ``angle_axis2mat`` and ``mat2euler`` 412 | functions, but the reduction in computation is small, and the code 413 | repetition is large. 414 | ''' 415 | # delayed import to avoid cyclic dependencies 416 | import nibabel.quaternions as nq 417 | M = nq.angle_axis2mat(theta, vector, is_normalized) 418 | return mat2euler(M) 419 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/tf_util.py: -------------------------------------------------------------------------------- 1 | """ Wrapper functions for TensorFlow layers. 2 | 3 | Author: Charles R. Qi 4 | Date: November 2016 5 | """ 6 | 7 | import numpy as np 8 | import tensorflow as tf 9 | 10 | def _variable_on_cpu(name, shape, initializer, use_fp16=False): 11 | """Helper to create a Variable stored on CPU memory. 12 | Args: 13 | name: name of the variable 14 | shape: list of ints 15 | initializer: initializer for Variable 16 | Returns: 17 | Variable Tensor 18 | """ 19 | with tf.device('/cpu:0'): 20 | dtype = tf.float16 if use_fp16 else tf.float32 21 | var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype) 22 | return var 23 | 24 | def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True): 25 | """Helper to create an initialized Variable with weight decay. 26 | 27 | Note that the Variable is initialized with a truncated normal distribution. 28 | A weight decay is added only if one is specified. 29 | 30 | Args: 31 | name: name of the variable 32 | shape: list of ints 33 | stddev: standard deviation of a truncated Gaussian 34 | wd: add L2Loss weight decay multiplied by this float. If None, weight 35 | decay is not added for this Variable. 36 | use_xavier: bool, whether to use xavier initializer 37 | 38 | Returns: 39 | Variable Tensor 40 | """ 41 | if use_xavier: 42 | initializer = tf.contrib.layers.xavier_initializer() 43 | else: 44 | initializer = tf.truncated_normal_initializer(stddev=stddev) 45 | var = _variable_on_cpu(name, shape, initializer) 46 | if wd is not None: 47 | weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss') 48 | tf.add_to_collection('losses', weight_decay) 49 | return var 50 | 51 | 52 | def conv1d(inputs, 53 | num_output_channels, 54 | kernel_size, 55 | scope, 56 | stride=1, 57 | padding='SAME', 58 | use_xavier=True, 59 | stddev=1e-3, 60 | weight_decay=0.0, 61 | activation_fn=tf.nn.relu, 62 | bn=False, 63 | bn_decay=None, 64 | is_training=None): 65 | """ 1D convolution with non-linear operation. 66 | 67 | Args: 68 | inputs: 3-D tensor variable BxLxC 69 | num_output_channels: int 70 | kernel_size: int 71 | scope: string 72 | stride: int 73 | padding: 'SAME' or 'VALID' 74 | use_xavier: bool, use xavier_initializer if true 75 | stddev: float, stddev for truncated_normal init 76 | weight_decay: float 77 | activation_fn: function 78 | bn: bool, whether to use batch norm 79 | bn_decay: float or float tensor variable in [0,1] 80 | is_training: bool Tensor variable 81 | 82 | Returns: 83 | Variable tensor 84 | """ 85 | with tf.variable_scope(scope) as sc: 86 | num_in_channels = inputs.get_shape()[-1].value 87 | kernel_shape = [kernel_size, 88 | num_in_channels, num_output_channels] 89 | kernel = _variable_with_weight_decay('weights', 90 | shape=kernel_shape, 91 | use_xavier=use_xavier, 92 | stddev=stddev, 93 | wd=weight_decay) 94 | outputs = tf.nn.conv1d(inputs, kernel, 95 | stride=stride, 96 | padding=padding) 97 | biases = _variable_on_cpu('biases', [num_output_channels], 98 | tf.constant_initializer(0.0)) 99 | outputs = tf.nn.bias_add(outputs, biases) 100 | 101 | if bn: 102 | outputs = batch_norm_for_conv1d(outputs, is_training, 103 | bn_decay=bn_decay, scope='bn') 104 | 105 | if activation_fn is not None: 106 | outputs = activation_fn(outputs) 107 | return outputs 108 | 109 | 110 | 111 | 112 | def conv2d(inputs, 113 | num_output_channels, 114 | kernel_size, 115 | scope, 116 | stride=[1, 1], 117 | padding='SAME', 118 | use_xavier=True, 119 | stddev=1e-3, 120 | weight_decay=0.0, 121 | activation_fn=tf.nn.relu, 122 | bn=False, 123 | bn_decay=None, 124 | is_training=None): 125 | """ 2D convolution with non-linear operation. 126 | 127 | Args: 128 | inputs: 4-D tensor variable BxHxWxC 129 | num_output_channels: int 130 | kernel_size: a list of 2 ints 131 | scope: string 132 | stride: a list of 2 ints 133 | padding: 'SAME' or 'VALID' 134 | use_xavier: bool, use xavier_initializer if true 135 | stddev: float, stddev for truncated_normal init 136 | weight_decay: float 137 | activation_fn: function 138 | bn: bool, whether to use batch norm 139 | bn_decay: float or float tensor variable in [0,1] 140 | is_training: bool Tensor variable 141 | 142 | Returns: 143 | Variable tensor 144 | """ 145 | with tf.variable_scope(scope) as sc: 146 | kernel_h, kernel_w = kernel_size 147 | num_in_channels = inputs.get_shape()[-1].value 148 | kernel_shape = [kernel_h, kernel_w, 149 | num_in_channels, num_output_channels] 150 | kernel = _variable_with_weight_decay('weights', 151 | shape=kernel_shape, 152 | use_xavier=use_xavier, 153 | stddev=stddev, 154 | wd=weight_decay) 155 | stride_h, stride_w = stride 156 | outputs = tf.nn.conv2d(inputs, kernel, 157 | [1, stride_h, stride_w, 1], 158 | padding=padding) 159 | biases = _variable_on_cpu('biases', [num_output_channels], 160 | tf.constant_initializer(0.0)) 161 | outputs = tf.nn.bias_add(outputs, biases) 162 | 163 | if bn: 164 | outputs = batch_norm_for_conv2d(outputs, is_training, 165 | bn_decay=bn_decay, scope='bn') 166 | 167 | if activation_fn is not None: 168 | outputs = activation_fn(outputs) 169 | return outputs 170 | 171 | 172 | def conv2d_transpose(inputs, 173 | num_output_channels, 174 | kernel_size, 175 | scope, 176 | stride=[1, 1], 177 | padding='SAME', 178 | use_xavier=True, 179 | stddev=1e-3, 180 | weight_decay=0.0, 181 | activation_fn=tf.nn.relu, 182 | bn=False, 183 | bn_decay=None, 184 | is_training=None): 185 | """ 2D convolution transpose with non-linear operation. 186 | 187 | Args: 188 | inputs: 4-D tensor variable BxHxWxC 189 | num_output_channels: int 190 | kernel_size: a list of 2 ints 191 | scope: string 192 | stride: a list of 2 ints 193 | padding: 'SAME' or 'VALID' 194 | use_xavier: bool, use xavier_initializer if true 195 | stddev: float, stddev for truncated_normal init 196 | weight_decay: float 197 | activation_fn: function 198 | bn: bool, whether to use batch norm 199 | bn_decay: float or float tensor variable in [0,1] 200 | is_training: bool Tensor variable 201 | 202 | Returns: 203 | Variable tensor 204 | 205 | Note: conv2d(conv2d_transpose(a, num_out, ksize, stride), a.shape[-1], ksize, stride) == a 206 | """ 207 | with tf.variable_scope(scope) as sc: 208 | kernel_h, kernel_w = kernel_size 209 | num_in_channels = inputs.get_shape()[-1].value 210 | kernel_shape = [kernel_h, kernel_w, 211 | num_output_channels, num_in_channels] # reversed to conv2d 212 | kernel = _variable_with_weight_decay('weights', 213 | shape=kernel_shape, 214 | use_xavier=use_xavier, 215 | stddev=stddev, 216 | wd=weight_decay) 217 | stride_h, stride_w = stride 218 | 219 | # from slim.convolution2d_transpose 220 | def get_deconv_dim(dim_size, stride_size, kernel_size, padding): 221 | dim_size *= stride_size 222 | 223 | if padding == 'VALID' and dim_size is not None: 224 | dim_size += max(kernel_size - stride_size, 0) 225 | return dim_size 226 | 227 | # caculate output shape 228 | batch_size = inputs.get_shape()[0].value 229 | height = inputs.get_shape()[1].value 230 | width = inputs.get_shape()[2].value 231 | out_height = get_deconv_dim(height, stride_h, kernel_h, padding) 232 | out_width = get_deconv_dim(width, stride_w, kernel_w, padding) 233 | output_shape = [batch_size, out_height, out_width, num_output_channels] 234 | 235 | outputs = tf.nn.conv2d_transpose(inputs, kernel, output_shape, 236 | [1, stride_h, stride_w, 1], 237 | padding=padding) 238 | biases = _variable_on_cpu('biases', [num_output_channels], 239 | tf.constant_initializer(0.0)) 240 | outputs = tf.nn.bias_add(outputs, biases) 241 | 242 | if bn: 243 | outputs = batch_norm_for_conv2d(outputs, is_training, 244 | bn_decay=bn_decay, scope='bn') 245 | 246 | if activation_fn is not None: 247 | outputs = activation_fn(outputs) 248 | return outputs 249 | 250 | 251 | 252 | def conv3d(inputs, 253 | num_output_channels, 254 | kernel_size, 255 | scope, 256 | stride=[1, 1, 1], 257 | padding='SAME', 258 | use_xavier=True, 259 | stddev=1e-3, 260 | weight_decay=0.0, 261 | activation_fn=tf.nn.relu, 262 | bn=False, 263 | bn_decay=None, 264 | is_training=None): 265 | """ 3D convolution with non-linear operation. 266 | 267 | Args: 268 | inputs: 5-D tensor variable BxDxHxWxC 269 | num_output_channels: int 270 | kernel_size: a list of 3 ints 271 | scope: string 272 | stride: a list of 3 ints 273 | padding: 'SAME' or 'VALID' 274 | use_xavier: bool, use xavier_initializer if true 275 | stddev: float, stddev for truncated_normal init 276 | weight_decay: float 277 | activation_fn: function 278 | bn: bool, whether to use batch norm 279 | bn_decay: float or float tensor variable in [0,1] 280 | is_training: bool Tensor variable 281 | 282 | Returns: 283 | Variable tensor 284 | """ 285 | with tf.variable_scope(scope) as sc: 286 | kernel_d, kernel_h, kernel_w = kernel_size 287 | num_in_channels = inputs.get_shape()[-1].value 288 | kernel_shape = [kernel_d, kernel_h, kernel_w, 289 | num_in_channels, num_output_channels] 290 | kernel = _variable_with_weight_decay('weights', 291 | shape=kernel_shape, 292 | use_xavier=use_xavier, 293 | stddev=stddev, 294 | wd=weight_decay) 295 | stride_d, stride_h, stride_w = stride 296 | outputs = tf.nn.conv3d(inputs, kernel, 297 | [1, stride_d, stride_h, stride_w, 1], 298 | padding=padding) 299 | biases = _variable_on_cpu('biases', [num_output_channels], 300 | tf.constant_initializer(0.0)) 301 | outputs = tf.nn.bias_add(outputs, biases) 302 | 303 | if bn: 304 | outputs = batch_norm_for_conv3d(outputs, is_training, 305 | bn_decay=bn_decay, scope='bn') 306 | 307 | if activation_fn is not None: 308 | outputs = activation_fn(outputs) 309 | return outputs 310 | 311 | def fully_connected(inputs, 312 | num_outputs, 313 | scope, 314 | use_xavier=True, 315 | stddev=1e-3, 316 | weight_decay=0.0, 317 | activation_fn=tf.nn.relu, 318 | bn=False, 319 | bn_decay=None, 320 | is_training=None): 321 | """ Fully connected layer with non-linear operation. 322 | 323 | Args: 324 | inputs: 2-D tensor BxN 325 | num_outputs: int 326 | 327 | Returns: 328 | Variable tensor of size B x num_outputs. 329 | """ 330 | with tf.variable_scope(scope) as sc: 331 | num_input_units = inputs.get_shape()[-1].value 332 | weights = _variable_with_weight_decay('weights', 333 | shape=[num_input_units, num_outputs], 334 | use_xavier=use_xavier, 335 | stddev=stddev, 336 | wd=weight_decay) 337 | outputs = tf.matmul(inputs, weights) 338 | biases = _variable_on_cpu('biases', [num_outputs], 339 | tf.constant_initializer(0.0)) 340 | outputs = tf.nn.bias_add(outputs, biases) 341 | 342 | if bn: 343 | outputs = batch_norm_for_fc(outputs, is_training, bn_decay, 'bn') 344 | 345 | if activation_fn is not None: 346 | outputs = activation_fn(outputs) 347 | return outputs 348 | 349 | 350 | def max_pool2d(inputs, 351 | kernel_size, 352 | scope, 353 | stride=[2, 2], 354 | padding='VALID'): 355 | """ 2D max pooling. 356 | 357 | Args: 358 | inputs: 4-D tensor BxHxWxC 359 | kernel_size: a list of 2 ints 360 | stride: a list of 2 ints 361 | 362 | Returns: 363 | Variable tensor 364 | """ 365 | with tf.variable_scope(scope) as sc: 366 | kernel_h, kernel_w = kernel_size 367 | stride_h, stride_w = stride 368 | outputs = tf.nn.max_pool(inputs, 369 | ksize=[1, kernel_h, kernel_w, 1], 370 | strides=[1, stride_h, stride_w, 1], 371 | padding=padding, 372 | name=sc.name) 373 | return outputs 374 | 375 | def avg_pool2d(inputs, 376 | kernel_size, 377 | scope, 378 | stride=[2, 2], 379 | padding='VALID'): 380 | """ 2D avg pooling. 381 | 382 | Args: 383 | inputs: 4-D tensor BxHxWxC 384 | kernel_size: a list of 2 ints 385 | stride: a list of 2 ints 386 | 387 | Returns: 388 | Variable tensor 389 | """ 390 | with tf.variable_scope(scope) as sc: 391 | kernel_h, kernel_w = kernel_size 392 | stride_h, stride_w = stride 393 | outputs = tf.nn.avg_pool(inputs, 394 | ksize=[1, kernel_h, kernel_w, 1], 395 | strides=[1, stride_h, stride_w, 1], 396 | padding=padding, 397 | name=sc.name) 398 | return outputs 399 | 400 | 401 | def max_pool3d(inputs, 402 | kernel_size, 403 | scope, 404 | stride=[2, 2, 2], 405 | padding='VALID'): 406 | """ 3D max pooling. 407 | 408 | Args: 409 | inputs: 5-D tensor BxDxHxWxC 410 | kernel_size: a list of 3 ints 411 | stride: a list of 3 ints 412 | 413 | Returns: 414 | Variable tensor 415 | """ 416 | with tf.variable_scope(scope) as sc: 417 | kernel_d, kernel_h, kernel_w = kernel_size 418 | stride_d, stride_h, stride_w = stride 419 | outputs = tf.nn.max_pool3d(inputs, 420 | ksize=[1, kernel_d, kernel_h, kernel_w, 1], 421 | strides=[1, stride_d, stride_h, stride_w, 1], 422 | padding=padding, 423 | name=sc.name) 424 | return outputs 425 | 426 | def avg_pool3d(inputs, 427 | kernel_size, 428 | scope, 429 | stride=[2, 2, 2], 430 | padding='VALID'): 431 | """ 3D avg pooling. 432 | 433 | Args: 434 | inputs: 5-D tensor BxDxHxWxC 435 | kernel_size: a list of 3 ints 436 | stride: a list of 3 ints 437 | 438 | Returns: 439 | Variable tensor 440 | """ 441 | with tf.variable_scope(scope) as sc: 442 | kernel_d, kernel_h, kernel_w = kernel_size 443 | stride_d, stride_h, stride_w = stride 444 | outputs = tf.nn.avg_pool3d(inputs, 445 | ksize=[1, kernel_d, kernel_h, kernel_w, 1], 446 | strides=[1, stride_d, stride_h, stride_w, 1], 447 | padding=padding, 448 | name=sc.name) 449 | return outputs 450 | 451 | 452 | 453 | 454 | 455 | def batch_norm_template(inputs, is_training, scope, moments_dims, bn_decay): 456 | """ Batch normalization on convolutional maps and beyond... 457 | Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow 458 | 459 | Args: 460 | inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC 461 | is_training: boolean tf.Varialbe, true indicates training phase 462 | scope: string, variable scope 463 | moments_dims: a list of ints, indicating dimensions for moments calculation 464 | bn_decay: float or float tensor variable, controling moving average weight 465 | Return: 466 | normed: batch-normalized maps 467 | """ 468 | with tf.variable_scope(scope) as sc: 469 | num_channels = inputs.get_shape()[-1].value 470 | beta = tf.Variable(tf.constant(0.0, shape=[num_channels]), 471 | name='beta', trainable=True) 472 | gamma = tf.Variable(tf.constant(1.0, shape=[num_channels]), 473 | name='gamma', trainable=True) 474 | batch_mean, batch_var = tf.nn.moments(inputs, moments_dims, name='moments') 475 | decay = bn_decay if bn_decay is not None else 0.9 476 | ema = tf.train.ExponentialMovingAverage(decay=decay) 477 | # Operator that maintains moving averages of variables. 478 | ema_apply_op = tf.cond(is_training, 479 | lambda: ema.apply([batch_mean, batch_var]), 480 | lambda: tf.no_op()) 481 | 482 | # Update moving average and return current batch's avg and var. 483 | def mean_var_with_update(): 484 | with tf.control_dependencies([ema_apply_op]): 485 | return tf.identity(batch_mean), tf.identity(batch_var) 486 | 487 | # ema.average returns the Variable holding the average of var. 488 | mean, var = tf.cond(is_training, 489 | mean_var_with_update, 490 | lambda: (ema.average(batch_mean), ema.average(batch_var))) 491 | normed = tf.nn.batch_normalization(inputs, mean, var, beta, gamma, 1e-3) 492 | return normed 493 | 494 | 495 | def batch_norm_for_fc(inputs, is_training, bn_decay, scope): 496 | """ Batch normalization on FC data. 497 | 498 | Args: 499 | inputs: Tensor, 2D BxC input 500 | is_training: boolean tf.Varialbe, true indicates training phase 501 | bn_decay: float or float tensor variable, controling moving average weight 502 | scope: string, variable scope 503 | Return: 504 | normed: batch-normalized maps 505 | """ 506 | return batch_norm_template(inputs, is_training, scope, [0,], bn_decay) 507 | 508 | 509 | def batch_norm_for_conv1d(inputs, is_training, bn_decay, scope): 510 | """ Batch normalization on 1D convolutional maps. 511 | 512 | Args: 513 | inputs: Tensor, 3D BLC input maps 514 | is_training: boolean tf.Varialbe, true indicates training phase 515 | bn_decay: float or float tensor variable, controling moving average weight 516 | scope: string, variable scope 517 | Return: 518 | normed: batch-normalized maps 519 | """ 520 | return batch_norm_template(inputs, is_training, scope, [0,1], bn_decay) 521 | 522 | 523 | 524 | 525 | def batch_norm_for_conv2d(inputs, is_training, bn_decay, scope): 526 | """ Batch normalization on 2D convolutional maps. 527 | 528 | Args: 529 | inputs: Tensor, 4D BHWC input maps 530 | is_training: boolean tf.Varialbe, true indicates training phase 531 | bn_decay: float or float tensor variable, controling moving average weight 532 | scope: string, variable scope 533 | Return: 534 | normed: batch-normalized maps 535 | """ 536 | return batch_norm_template(inputs, is_training, scope, [0,1,2], bn_decay) 537 | 538 | 539 | 540 | def batch_norm_for_conv3d(inputs, is_training, bn_decay, scope): 541 | """ Batch normalization on 3D convolutional maps. 542 | 543 | Args: 544 | inputs: Tensor, 5D BDHWC input maps 545 | is_training: boolean tf.Varialbe, true indicates training phase 546 | bn_decay: float or float tensor variable, controling moving average weight 547 | scope: string, variable scope 548 | Return: 549 | normed: batch-normalized maps 550 | """ 551 | return batch_norm_template(inputs, is_training, scope, [0,1,2,3], bn_decay) 552 | 553 | 554 | def dropout(inputs, 555 | is_training, 556 | scope, 557 | keep_prob=0.5, 558 | noise_shape=None): 559 | """ Dropout layer. 560 | 561 | Args: 562 | inputs: tensor 563 | is_training: boolean tf.Variable 564 | scope: string 565 | keep_prob: float in [0,1] 566 | noise_shape: list of ints 567 | 568 | Returns: 569 | tensor variable 570 | """ 571 | with tf.variable_scope(scope) as sc: 572 | outputs = tf.cond(is_training, 573 | lambda: tf.nn.dropout(inputs, keep_prob, noise_shape), 574 | lambda: inputs) 575 | return outputs 576 | -------------------------------------------------------------------------------- /Desktop/DeepLearning/pointnet_vis/utils/plyfile.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Darsh Ranjan 2 | # 3 | # This file is part of python-plyfile. 4 | # 5 | # python-plyfile is free software: you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License as 7 | # published by the Free Software Foundation, either version 3 of the 8 | # License, or (at your option) any later version. 9 | # 10 | # python-plyfile is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with python-plyfile. If not, see 17 | # . 18 | 19 | from itertools import islice as _islice 20 | 21 | import numpy as _np 22 | from sys import byteorder as _byteorder 23 | 24 | 25 | try: 26 | _range = xrange 27 | except NameError: 28 | _range = range 29 | 30 | 31 | # Many-many relation 32 | _data_type_relation = [ 33 | ('int8', 'i1'), 34 | ('char', 'i1'), 35 | ('uint8', 'u1'), 36 | ('uchar', 'b1'), 37 | ('uchar', 'u1'), 38 | ('int16', 'i2'), 39 | ('short', 'i2'), 40 | ('uint16', 'u2'), 41 | ('ushort', 'u2'), 42 | ('int32', 'i4'), 43 | ('int', 'i4'), 44 | ('uint32', 'u4'), 45 | ('uint', 'u4'), 46 | ('float32', 'f4'), 47 | ('float', 'f4'), 48 | ('float64', 'f8'), 49 | ('double', 'f8') 50 | ] 51 | 52 | _data_types = dict(_data_type_relation) 53 | _data_type_reverse = dict((b, a) for (a, b) in _data_type_relation) 54 | 55 | _types_list = [] 56 | _types_set = set() 57 | for (_a, _b) in _data_type_relation: 58 | if _a not in _types_set: 59 | _types_list.append(_a) 60 | _types_set.add(_a) 61 | if _b not in _types_set: 62 | _types_list.append(_b) 63 | _types_set.add(_b) 64 | 65 | 66 | _byte_order_map = { 67 | 'ascii': '=', 68 | 'binary_little_endian': '<', 69 | 'binary_big_endian': '>' 70 | } 71 | 72 | _byte_order_reverse = { 73 | '<': 'binary_little_endian', 74 | '>': 'binary_big_endian' 75 | } 76 | 77 | _native_byte_order = {'little': '<', 'big': '>'}[_byteorder] 78 | 79 | 80 | def _lookup_type(type_str): 81 | if type_str not in _data_type_reverse: 82 | try: 83 | type_str = _data_types[type_str] 84 | except KeyError: 85 | raise ValueError("field type %r not in %r" % 86 | (type_str, _types_list)) 87 | 88 | return _data_type_reverse[type_str] 89 | 90 | 91 | def _split_line(line, n): 92 | fields = line.split(None, n) 93 | if len(fields) == n: 94 | fields.append('') 95 | 96 | assert len(fields) == n + 1 97 | 98 | return fields 99 | 100 | 101 | def make2d(array, cols=None, dtype=None): 102 | ''' 103 | Make a 2D array from an array of arrays. The `cols' and `dtype' 104 | arguments can be omitted if the array is not empty. 105 | 106 | ''' 107 | if (cols is None or dtype is None) and not len(array): 108 | raise RuntimeError("cols and dtype must be specified for empty " 109 | "array") 110 | 111 | if cols is None: 112 | cols = len(array[0]) 113 | 114 | if dtype is None: 115 | dtype = array[0].dtype 116 | 117 | return _np.fromiter(array, [('_', dtype, (cols,))], 118 | count=len(array))['_'] 119 | 120 | 121 | class PlyParseError(Exception): 122 | 123 | ''' 124 | Raised when a PLY file cannot be parsed. 125 | 126 | The attributes `element', `row', `property', and `message' give 127 | additional information. 128 | 129 | ''' 130 | 131 | def __init__(self, message, element=None, row=None, prop=None): 132 | self.message = message 133 | self.element = element 134 | self.row = row 135 | self.prop = prop 136 | 137 | s = '' 138 | if self.element: 139 | s += 'element %r: ' % self.element.name 140 | if self.row is not None: 141 | s += 'row %d: ' % self.row 142 | if self.prop: 143 | s += 'property %r: ' % self.prop.name 144 | s += self.message 145 | 146 | Exception.__init__(self, s) 147 | 148 | def __repr__(self): 149 | return ('PlyParseError(%r, element=%r, row=%r, prop=%r)' % 150 | self.message, self.element, self.row, self.prop) 151 | 152 | 153 | class PlyData(object): 154 | 155 | ''' 156 | PLY file header and data. 157 | 158 | A PlyData instance is created in one of two ways: by the static 159 | method PlyData.read (to read a PLY file), or directly from __init__ 160 | given a sequence of elements (which can then be written to a PLY 161 | file). 162 | 163 | ''' 164 | 165 | def __init__(self, elements=[], text=False, byte_order='=', 166 | comments=[], obj_info=[]): 167 | ''' 168 | elements: sequence of PlyElement instances. 169 | 170 | text: whether the resulting PLY file will be text (True) or 171 | binary (False). 172 | 173 | byte_order: '<' for little-endian, '>' for big-endian, or '=' 174 | for native. This is only relevant if `text' is False. 175 | 176 | comments: sequence of strings that will be placed in the header 177 | between the 'ply' and 'format ...' lines. 178 | 179 | obj_info: like comments, but will be placed in the header with 180 | "obj_info ..." instead of "comment ...". 181 | 182 | ''' 183 | if byte_order == '=' and not text: 184 | byte_order = _native_byte_order 185 | 186 | self.byte_order = byte_order 187 | self.text = text 188 | 189 | self.comments = list(comments) 190 | self.obj_info = list(obj_info) 191 | self.elements = elements 192 | 193 | def _get_elements(self): 194 | return self._elements 195 | 196 | def _set_elements(self, elements): 197 | self._elements = tuple(elements) 198 | self._index() 199 | 200 | elements = property(_get_elements, _set_elements) 201 | 202 | def _get_byte_order(self): 203 | return self._byte_order 204 | 205 | def _set_byte_order(self, byte_order): 206 | if byte_order not in ['<', '>', '=']: 207 | raise ValueError("byte order must be '<', '>', or '='") 208 | 209 | self._byte_order = byte_order 210 | 211 | byte_order = property(_get_byte_order, _set_byte_order) 212 | 213 | def _index(self): 214 | self._element_lookup = dict((elt.name, elt) for elt in 215 | self._elements) 216 | if len(self._element_lookup) != len(self._elements): 217 | raise ValueError("two elements with same name") 218 | 219 | @staticmethod 220 | def _parse_header(stream): 221 | ''' 222 | Parse a PLY header from a readable file-like stream. 223 | 224 | ''' 225 | lines = [] 226 | comments = {'comment': [], 'obj_info': []} 227 | while True: 228 | line = stream.readline().decode('ascii').strip() 229 | fields = _split_line(line, 1) 230 | 231 | if fields[0] == 'end_header': 232 | break 233 | 234 | elif fields[0] in comments.keys(): 235 | lines.append(fields) 236 | else: 237 | lines.append(line.split()) 238 | 239 | a = 0 240 | if lines[a] != ['ply']: 241 | raise PlyParseError("expected 'ply'") 242 | 243 | a += 1 244 | while lines[a][0] in comments.keys(): 245 | comments[lines[a][0]].append(lines[a][1]) 246 | a += 1 247 | 248 | if lines[a][0] != 'format': 249 | raise PlyParseError("expected 'format'") 250 | 251 | if lines[a][2] != '1.0': 252 | raise PlyParseError("expected version '1.0'") 253 | 254 | if len(lines[a]) != 3: 255 | raise PlyParseError("too many fields after 'format'") 256 | 257 | fmt = lines[a][1] 258 | 259 | if fmt not in _byte_order_map: 260 | raise PlyParseError("don't understand format %r" % fmt) 261 | 262 | byte_order = _byte_order_map[fmt] 263 | text = fmt == 'ascii' 264 | 265 | a += 1 266 | while a < len(lines) and lines[a][0] in comments.keys(): 267 | comments[lines[a][0]].append(lines[a][1]) 268 | a += 1 269 | 270 | return PlyData(PlyElement._parse_multi(lines[a:]), 271 | text, byte_order, 272 | comments['comment'], comments['obj_info']) 273 | 274 | @staticmethod 275 | def read(stream): 276 | ''' 277 | Read PLY data from a readable file-like object or filename. 278 | 279 | ''' 280 | (must_close, stream) = _open_stream(stream, 'read') 281 | try: 282 | data = PlyData._parse_header(stream) 283 | for elt in data: 284 | elt._read(stream, data.text, data.byte_order) 285 | finally: 286 | if must_close: 287 | stream.close() 288 | 289 | return data 290 | 291 | def write(self, stream): 292 | ''' 293 | Write PLY data to a writeable file-like object or filename. 294 | 295 | ''' 296 | (must_close, stream) = _open_stream(stream, 'write') 297 | try: 298 | stream.write(self.header.encode('ascii')) 299 | stream.write(b'\r\n') 300 | for elt in self: 301 | elt._write(stream, self.text, self.byte_order) 302 | finally: 303 | if must_close: 304 | stream.close() 305 | 306 | @property 307 | def header(self): 308 | ''' 309 | Provide PLY-formatted metadata for the instance. 310 | 311 | ''' 312 | lines = ['ply'] 313 | 314 | if self.text: 315 | lines.append('format ascii 1.0') 316 | else: 317 | lines.append('format ' + 318 | _byte_order_reverse[self.byte_order] + 319 | ' 1.0') 320 | 321 | # Some information is lost here, since all comments are placed 322 | # between the 'format' line and the first element. 323 | for c in self.comments: 324 | lines.append('comment ' + c) 325 | 326 | for c in self.obj_info: 327 | lines.append('obj_info ' + c) 328 | 329 | lines.extend(elt.header for elt in self.elements) 330 | lines.append('end_header') 331 | return '\r\n'.join(lines) 332 | 333 | def __iter__(self): 334 | return iter(self.elements) 335 | 336 | def __len__(self): 337 | return len(self.elements) 338 | 339 | def __contains__(self, name): 340 | return name in self._element_lookup 341 | 342 | def __getitem__(self, name): 343 | return self._element_lookup[name] 344 | 345 | def __str__(self): 346 | return self.header 347 | 348 | def __repr__(self): 349 | return ('PlyData(%r, text=%r, byte_order=%r, ' 350 | 'comments=%r, obj_info=%r)' % 351 | (self.elements, self.text, self.byte_order, 352 | self.comments, self.obj_info)) 353 | 354 | 355 | def _open_stream(stream, read_or_write): 356 | if hasattr(stream, read_or_write): 357 | return (False, stream) 358 | try: 359 | return (True, open(stream, read_or_write[0] + 'b')) 360 | except TypeError: 361 | raise RuntimeError("expected open file or filename") 362 | 363 | 364 | class PlyElement(object): 365 | 366 | ''' 367 | PLY file element. 368 | 369 | A client of this library doesn't normally need to instantiate this 370 | directly, so the following is only for the sake of documenting the 371 | internals. 372 | 373 | Creating a PlyElement instance is generally done in one of two ways: 374 | as a byproduct of PlyData.read (when reading a PLY file) and by 375 | PlyElement.describe (before writing a PLY file). 376 | 377 | ''' 378 | 379 | def __init__(self, name, properties, count, comments=[]): 380 | ''' 381 | This is not part of the public interface. The preferred methods 382 | of obtaining PlyElement instances are PlyData.read (to read from 383 | a file) and PlyElement.describe (to construct from a numpy 384 | array). 385 | 386 | ''' 387 | self._name = str(name) 388 | self._check_name() 389 | self._count = count 390 | 391 | self._properties = tuple(properties) 392 | self._index() 393 | 394 | self.comments = list(comments) 395 | 396 | self._have_list = any(isinstance(p, PlyListProperty) 397 | for p in self.properties) 398 | 399 | @property 400 | def count(self): 401 | return self._count 402 | 403 | def _get_data(self): 404 | return self._data 405 | 406 | def _set_data(self, data): 407 | self._data = data 408 | self._count = len(data) 409 | self._check_sanity() 410 | 411 | data = property(_get_data, _set_data) 412 | 413 | def _check_sanity(self): 414 | for prop in self.properties: 415 | if prop.name not in self._data.dtype.fields: 416 | raise ValueError("dangling property %r" % prop.name) 417 | 418 | def _get_properties(self): 419 | return self._properties 420 | 421 | def _set_properties(self, properties): 422 | self._properties = tuple(properties) 423 | self._check_sanity() 424 | self._index() 425 | 426 | properties = property(_get_properties, _set_properties) 427 | 428 | def _index(self): 429 | self._property_lookup = dict((prop.name, prop) 430 | for prop in self._properties) 431 | if len(self._property_lookup) != len(self._properties): 432 | raise ValueError("two properties with same name") 433 | 434 | def ply_property(self, name): 435 | return self._property_lookup[name] 436 | 437 | @property 438 | def name(self): 439 | return self._name 440 | 441 | def _check_name(self): 442 | if any(c.isspace() for c in self._name): 443 | msg = "element name %r contains spaces" % self._name 444 | raise ValueError(msg) 445 | 446 | def dtype(self, byte_order='='): 447 | ''' 448 | Return the numpy dtype of the in-memory representation of the 449 | data. (If there are no list properties, and the PLY format is 450 | binary, then this also accurately describes the on-disk 451 | representation of the element.) 452 | 453 | ''' 454 | return [(prop.name, prop.dtype(byte_order)) 455 | for prop in self.properties] 456 | 457 | @staticmethod 458 | def _parse_multi(header_lines): 459 | ''' 460 | Parse a list of PLY element definitions. 461 | 462 | ''' 463 | elements = [] 464 | while header_lines: 465 | (elt, header_lines) = PlyElement._parse_one(header_lines) 466 | elements.append(elt) 467 | 468 | return elements 469 | 470 | @staticmethod 471 | def _parse_one(lines): 472 | ''' 473 | Consume one element definition. The unconsumed input is 474 | returned along with a PlyElement instance. 475 | 476 | ''' 477 | a = 0 478 | line = lines[a] 479 | 480 | if line[0] != 'element': 481 | raise PlyParseError("expected 'element'") 482 | if len(line) > 3: 483 | raise PlyParseError("too many fields after 'element'") 484 | if len(line) < 3: 485 | raise PlyParseError("too few fields after 'element'") 486 | 487 | (name, count) = (line[1], int(line[2])) 488 | 489 | comments = [] 490 | properties = [] 491 | while True: 492 | a += 1 493 | if a >= len(lines): 494 | break 495 | 496 | if lines[a][0] == 'comment': 497 | comments.append(lines[a][1]) 498 | elif lines[a][0] == 'property': 499 | properties.append(PlyProperty._parse_one(lines[a])) 500 | else: 501 | break 502 | 503 | return (PlyElement(name, properties, count, comments), 504 | lines[a:]) 505 | 506 | @staticmethod 507 | def describe(data, name, len_types={}, val_types={}, 508 | comments=[]): 509 | ''' 510 | Construct a PlyElement from an array's metadata. 511 | 512 | len_types and val_types can be given as mappings from list 513 | property names to type strings (like 'u1', 'f4', etc., or 514 | 'int8', 'float32', etc.). These can be used to define the length 515 | and value types of list properties. List property lengths 516 | always default to type 'u1' (8-bit unsigned integer), and value 517 | types default to 'i4' (32-bit integer). 518 | 519 | ''' 520 | if not isinstance(data, _np.ndarray): 521 | raise TypeError("only numpy arrays are supported") 522 | 523 | if len(data.shape) != 1: 524 | raise ValueError("only one-dimensional arrays are " 525 | "supported") 526 | 527 | count = len(data) 528 | 529 | properties = [] 530 | descr = data.dtype.descr 531 | 532 | for t in descr: 533 | if not isinstance(t[1], str): 534 | raise ValueError("nested records not supported") 535 | 536 | if not t[0]: 537 | raise ValueError("field with empty name") 538 | 539 | if len(t) != 2 or t[1][1] == 'O': 540 | # non-scalar field, which corresponds to a list 541 | # property in PLY. 542 | 543 | if t[1][1] == 'O': 544 | if len(t) != 2: 545 | raise ValueError("non-scalar object fields not " 546 | "supported") 547 | 548 | len_str = _data_type_reverse[len_types.get(t[0], 'u1')] 549 | if t[1][1] == 'O': 550 | val_type = val_types.get(t[0], 'i4') 551 | val_str = _lookup_type(val_type) 552 | else: 553 | val_str = _lookup_type(t[1][1:]) 554 | 555 | prop = PlyListProperty(t[0], len_str, val_str) 556 | else: 557 | val_str = _lookup_type(t[1][1:]) 558 | prop = PlyProperty(t[0], val_str) 559 | 560 | properties.append(prop) 561 | 562 | elt = PlyElement(name, properties, count, comments) 563 | elt.data = data 564 | 565 | return elt 566 | 567 | def _read(self, stream, text, byte_order): 568 | ''' 569 | Read the actual data from a PLY file. 570 | 571 | ''' 572 | if text: 573 | self._read_txt(stream) 574 | else: 575 | if self._have_list: 576 | # There are list properties, so a simple load is 577 | # impossible. 578 | self._read_bin(stream, byte_order) 579 | else: 580 | # There are no list properties, so loading the data is 581 | # much more straightforward. 582 | self._data = _np.fromfile(stream, 583 | self.dtype(byte_order), 584 | self.count) 585 | 586 | if len(self._data) < self.count: 587 | k = len(self._data) 588 | del self._data 589 | raise PlyParseError("early end-of-file", self, k) 590 | 591 | self._check_sanity() 592 | 593 | def _write(self, stream, text, byte_order): 594 | ''' 595 | Write the data to a PLY file. 596 | 597 | ''' 598 | if text: 599 | self._write_txt(stream) 600 | else: 601 | if self._have_list: 602 | # There are list properties, so serialization is 603 | # slightly complicated. 604 | self._write_bin(stream, byte_order) 605 | else: 606 | # no list properties, so serialization is 607 | # straightforward. 608 | self.data.astype(self.dtype(byte_order), 609 | copy=False).tofile(stream) 610 | 611 | def _read_txt(self, stream): 612 | ''' 613 | Load a PLY element from an ASCII-format PLY file. The element 614 | may contain list properties. 615 | 616 | ''' 617 | self._data = _np.empty(self.count, dtype=self.dtype()) 618 | 619 | k = 0 620 | for line in _islice(iter(stream.readline, b''), self.count): 621 | fields = iter(line.strip().split()) 622 | for prop in self.properties: 623 | try: 624 | self._data[prop.name][k] = prop._from_fields(fields) 625 | except StopIteration: 626 | raise PlyParseError("early end-of-line", 627 | self, k, prop) 628 | except ValueError: 629 | raise PlyParseError("malformed input", 630 | self, k, prop) 631 | try: 632 | next(fields) 633 | except StopIteration: 634 | pass 635 | else: 636 | raise PlyParseError("expected end-of-line", self, k) 637 | k += 1 638 | 639 | if k < self.count: 640 | del self._data 641 | raise PlyParseError("early end-of-file", self, k) 642 | 643 | def _write_txt(self, stream): 644 | ''' 645 | Save a PLY element to an ASCII-format PLY file. The element may 646 | contain list properties. 647 | 648 | ''' 649 | for rec in self.data: 650 | fields = [] 651 | for prop in self.properties: 652 | fields.extend(prop._to_fields(rec[prop.name])) 653 | 654 | _np.savetxt(stream, [fields], '%.18g', newline='\r\n') 655 | 656 | def _read_bin(self, stream, byte_order): 657 | ''' 658 | Load a PLY element from a binary PLY file. The element may 659 | contain list properties. 660 | 661 | ''' 662 | self._data = _np.empty(self.count, dtype=self.dtype(byte_order)) 663 | 664 | for k in _range(self.count): 665 | for prop in self.properties: 666 | try: 667 | self._data[prop.name][k] = \ 668 | prop._read_bin(stream, byte_order) 669 | except StopIteration: 670 | raise PlyParseError("early end-of-file", 671 | self, k, prop) 672 | 673 | def _write_bin(self, stream, byte_order): 674 | ''' 675 | Save a PLY element to a binary PLY file. The element may 676 | contain list properties. 677 | 678 | ''' 679 | for rec in self.data: 680 | for prop in self.properties: 681 | prop._write_bin(rec[prop.name], stream, byte_order) 682 | 683 | @property 684 | def header(self): 685 | ''' 686 | Format this element's metadata as it would appear in a PLY 687 | header. 688 | 689 | ''' 690 | lines = ['element %s %d' % (self.name, self.count)] 691 | 692 | # Some information is lost here, since all comments are placed 693 | # between the 'element' line and the first property definition. 694 | for c in self.comments: 695 | lines.append('comment ' + c) 696 | 697 | lines.extend(list(map(str, self.properties))) 698 | 699 | return '\r\n'.join(lines) 700 | 701 | def __getitem__(self, key): 702 | return self.data[key] 703 | 704 | def __setitem__(self, key, value): 705 | self.data[key] = value 706 | 707 | def __str__(self): 708 | return self.header 709 | 710 | def __repr__(self): 711 | return ('PlyElement(%r, %r, count=%d, comments=%r)' % 712 | (self.name, self.properties, self.count, 713 | self.comments)) 714 | 715 | 716 | class PlyProperty(object): 717 | 718 | ''' 719 | PLY property description. This class is pure metadata; the data 720 | itself is contained in PlyElement instances. 721 | 722 | ''' 723 | 724 | def __init__(self, name, val_dtype): 725 | self._name = str(name) 726 | self._check_name() 727 | self.val_dtype = val_dtype 728 | 729 | def _get_val_dtype(self): 730 | return self._val_dtype 731 | 732 | def _set_val_dtype(self, val_dtype): 733 | self._val_dtype = _data_types[_lookup_type(val_dtype)] 734 | 735 | val_dtype = property(_get_val_dtype, _set_val_dtype) 736 | 737 | @property 738 | def name(self): 739 | return self._name 740 | 741 | def _check_name(self): 742 | if any(c.isspace() for c in self._name): 743 | msg = "Error: property name %r contains spaces" % self._name 744 | raise RuntimeError(msg) 745 | 746 | @staticmethod 747 | def _parse_one(line): 748 | assert line[0] == 'property' 749 | 750 | if line[1] == 'list': 751 | if len(line) > 5: 752 | raise PlyParseError("too many fields after " 753 | "'property list'") 754 | if len(line) < 5: 755 | raise PlyParseError("too few fields after " 756 | "'property list'") 757 | 758 | return PlyListProperty(line[4], line[2], line[3]) 759 | 760 | else: 761 | if len(line) > 3: 762 | raise PlyParseError("too many fields after " 763 | "'property'") 764 | if len(line) < 3: 765 | raise PlyParseError("too few fields after " 766 | "'property'") 767 | 768 | return PlyProperty(line[2], line[1]) 769 | 770 | def dtype(self, byte_order='='): 771 | ''' 772 | Return the numpy dtype description for this property (as a tuple 773 | of strings). 774 | 775 | ''' 776 | return byte_order + self.val_dtype 777 | 778 | def _from_fields(self, fields): 779 | ''' 780 | Parse from generator. Raise StopIteration if the property could 781 | not be read. 782 | 783 | ''' 784 | return _np.dtype(self.dtype()).type(next(fields)) 785 | 786 | def _to_fields(self, data): 787 | ''' 788 | Return generator over one item. 789 | 790 | ''' 791 | yield _np.dtype(self.dtype()).type(data) 792 | 793 | def _read_bin(self, stream, byte_order): 794 | ''' 795 | Read data from a binary stream. Raise StopIteration if the 796 | property could not be read. 797 | 798 | ''' 799 | try: 800 | return _np.fromfile(stream, self.dtype(byte_order), 1)[0] 801 | except IndexError: 802 | raise StopIteration 803 | 804 | def _write_bin(self, data, stream, byte_order): 805 | ''' 806 | Write data to a binary stream. 807 | 808 | ''' 809 | _np.dtype(self.dtype(byte_order)).type(data).tofile(stream) 810 | 811 | def __str__(self): 812 | val_str = _data_type_reverse[self.val_dtype] 813 | return 'property %s %s' % (val_str, self.name) 814 | 815 | def __repr__(self): 816 | return 'PlyProperty(%r, %r)' % (self.name, 817 | _lookup_type(self.val_dtype)) 818 | 819 | 820 | class PlyListProperty(PlyProperty): 821 | 822 | ''' 823 | PLY list property description. 824 | 825 | ''' 826 | 827 | def __init__(self, name, len_dtype, val_dtype): 828 | PlyProperty.__init__(self, name, val_dtype) 829 | 830 | self.len_dtype = len_dtype 831 | 832 | def _get_len_dtype(self): 833 | return self._len_dtype 834 | 835 | def _set_len_dtype(self, len_dtype): 836 | self._len_dtype = _data_types[_lookup_type(len_dtype)] 837 | 838 | len_dtype = property(_get_len_dtype, _set_len_dtype) 839 | 840 | def dtype(self, byte_order='='): 841 | ''' 842 | List properties always have a numpy dtype of "object". 843 | 844 | ''' 845 | return '|O' 846 | 847 | def list_dtype(self, byte_order='='): 848 | ''' 849 | Return the pair (len_dtype, val_dtype) (both numpy-friendly 850 | strings). 851 | 852 | ''' 853 | return (byte_order + self.len_dtype, 854 | byte_order + self.val_dtype) 855 | 856 | def _from_fields(self, fields): 857 | (len_t, val_t) = self.list_dtype() 858 | 859 | n = int(_np.dtype(len_t).type(next(fields))) 860 | 861 | data = _np.loadtxt(list(_islice(fields, n)), val_t, ndmin=1) 862 | if len(data) < n: 863 | raise StopIteration 864 | 865 | return data 866 | 867 | def _to_fields(self, data): 868 | ''' 869 | Return generator over the (numerical) PLY representation of the 870 | list data (length followed by actual data). 871 | 872 | ''' 873 | (len_t, val_t) = self.list_dtype() 874 | 875 | data = _np.asarray(data, dtype=val_t).ravel() 876 | 877 | yield _np.dtype(len_t).type(data.size) 878 | for x in data: 879 | yield x 880 | 881 | def _read_bin(self, stream, byte_order): 882 | (len_t, val_t) = self.list_dtype(byte_order) 883 | 884 | try: 885 | n = _np.fromfile(stream, len_t, 1)[0] 886 | except IndexError: 887 | raise StopIteration 888 | 889 | data = _np.fromfile(stream, val_t, n) 890 | if len(data) < n: 891 | raise StopIteration 892 | 893 | return data 894 | 895 | def _write_bin(self, data, stream, byte_order): 896 | ''' 897 | Write data to a binary stream. 898 | 899 | ''' 900 | (len_t, val_t) = self.list_dtype(byte_order) 901 | 902 | data = _np.asarray(data, dtype=val_t).ravel() 903 | 904 | _np.array(data.size, dtype=len_t).tofile(stream) 905 | data.tofile(stream) 906 | 907 | def __str__(self): 908 | len_str = _data_type_reverse[self.len_dtype] 909 | val_str = _data_type_reverse[self.val_dtype] 910 | return 'property list %s %s %s' % (len_str, val_str, self.name) 911 | 912 | def __repr__(self): 913 | return ('PlyListProperty(%r, %r, %r)' % 914 | (self.name, 915 | _lookup_type(self.len_dtype), 916 | _lookup_type(self.val_dtype))) 917 | --------------------------------------------------------------------------------