├── .gitignore ├── LICENSE ├── README.md ├── batch_generators ├── ReadWriteLock.py ├── __init__.py ├── batch_generator.py ├── center_batch_generator.py ├── multi_scale_batch_generator.py └── neighboring_grid_batch_generator.py ├── datasets ├── __init__.py ├── color_constants.py └── general_dataset.py ├── doc └── exploring_header.png ├── experiments └── iccvw_paper_2017 │ ├── s3dis_gru │ ├── s3dis_gru_area_1.yaml │ ├── s3dis_gru_area_2.yaml │ ├── s3dis_gru_area_3.yaml │ ├── s3dis_gru_area_4.yaml │ ├── s3dis_gru_area_5.yaml │ └── s3dis_gru_area_6.yaml │ ├── s3dis_mscu │ ├── s3dis_mscu_area_1.yaml │ ├── s3dis_mscu_area_2.yaml │ ├── s3dis_mscu_area_3.yaml │ ├── s3dis_mscu_area_4.yaml │ ├── s3dis_mscu_area_5.yaml │ └── s3dis_mscu_area_6.yaml │ └── s3dis_pointnet │ ├── s3dis_pointnet_area_1.yaml │ ├── s3dis_pointnet_area_2.yaml │ ├── s3dis_pointnet_area_3.yaml │ ├── s3dis_pointnet_area_4.yaml │ ├── s3dis_pointnet_area_5.yaml │ └── s3dis_pointnet_area_6.yaml ├── models ├── __init__.py ├── gru_neighbor_model.py ├── multi_block_model.py ├── multi_scale_cu_model.py └── pointnet.py ├── optimizers ├── __init__.py └── exponential_decay_adam.py ├── run.py └── tools ├── __init__.py ├── downsample.py ├── evaluation.py ├── lazy_decorator.py ├── meta └── class_names.txt ├── prepare_s3dis.py ├── tf_util.py ├── tools.py └── viz.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *__pycache__* 3 | *.obj 4 | .history* 5 | *.pyc 6 | cache/ 7 | log/ 8 | log2/ 9 | *.npy 10 | logs/ 11 | *.so 12 | *.sh 13 | 3d-semantic-segmentation.wiki/ 14 | 15 | !experiments/ 16 | 17 | experiments/* 18 | !experiments/iccvw_paper_2017/ 19 | *.out 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Visual Computing Institute 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploring Spatial Context for 3D Semantic Segmentation of Point Clouds 2 | Created by Francis Engelmann, Theodora Kontogianni, Alexander Hermans, Jonas Schult and Bastian Leibe 3 | from RWTH Aachen University. 4 | 5 | ![prediction example](doc/exploring_header.png?raw=True "dfdf") 6 | 7 | ### Introduction 8 | This work is based on our paper 9 | [Exploring Spatial Context for 3D Semantic Segmentation of Point Clouds](https://www.vision.rwth-aachen.de/media/papers/PID4967025.pdf), 10 | which appeared at the IEEE International Conference on Computer Vision (ICCV) 2017, 3DRMS Workshop. 11 | 12 | You can also check our [project page](https://www.vision.rwth-aachen.de/page/3dsemseg) for further details. 13 | 14 | Deep learning approaches have made tremendous progress in the field of semantic segmentation over the past few years. However, most current approaches operate in the 2D image space. Direct semantic segmentation of unstructured 3D point clouds is still an open research problem. The recently proposed PointNet architecture presents an interesting step ahead in that it can operate on unstructured point clouds, achieving decent segmentation results. However, it subdivides the input points into a grid of blocks and processes each such block individually. In this paper, we investigate the question how such an architecture can be extended to incorporate larger-scale spatial context. We build upon PointNet and propose two extensions that enlarge the receptive field over the 3D scene. We evaluate the proposed strategies on challenging indoor and outdoor datasets and show improved results in both scenarios. 15 | 16 | In this repository, we release code for training and testing various pointcloud semantic segmentation networks on 17 | arbitrary datasets. 18 | 19 | ### Citation 20 | If you find our work useful in your research, please consider citing: 21 | 22 | @inproceedings{3dsemseg_ICCVW17, 23 | author = {Francis Engelmann and 24 | Theodora Kontogianni and 25 | Alexander Hermans and 26 | Bastian Leibe}, 27 | title = {Exploring Spatial Context for 3D Semantic Segmentation of Point Clouds}, 28 | booktitle = {{IEEE} International Conference on Computer Vision, 3DRMS Workshop, {ICCV}}, 29 | year = {2017} 30 | } 31 | 32 | 33 | ### Installation 34 | 35 | Install TensorFlow. 36 | The code has been tested with Python 3.6 and TensorFlow 1.8. 37 | 38 | ### Usage 39 | In order to get more representative blocks, it is encouraged to uniformly downsample the original point clouds. 40 | This is done via the following script: 41 | 42 | python tools/downsample.py --data_dir path/to/dataset --cell_size 0.03 43 | 44 | This statement will produce pointclouds where each point will be representative for its 3cm x 3cm x 3cm neighborhood. 45 | 46 | To train/test a model for semantic segmentation on pointclouds, you need to run: 47 | 48 | python run.py --config path/to/config/file.yaml 49 | 50 | Detailed instruction of the structure for the yaml config file can be found in the wiki. 51 | Additionally, some example configuration files are given in the folder `experiments`. 52 | 53 | Note that the final evaluation is done on the full sized point clouds using k-nn interpolation. 54 | 55 | ### Reproducing the scores of our paper for stanford indoor 3d 56 | 57 | #### Downloading the data set 58 | First of all, Stanford Large-Scale 3D Indoor Spaces Dataset has to be downloaded. 59 | Follow the instructions [here](https://docs.google.com/forms/d/e/1FAIpQLScDimvNMCGhy_rmBA2gHfDu3naktRm6A8BPwAWWDv-Uhm6Shw/viewform?c=0&w=1). 60 | The aligned version 1.2 is used for our results. 61 | 62 | #### Producing numpy files from the original dataset 63 | Our pipeline cannot handle the original file type of s3dis. So, we need to convert it to npy files. 64 | Note that Area_5/hallway_6 has to be fixed manually due to format inconsistencies. 65 | The following script has to be run from the `tools` directory: 66 | 67 | python prepare_s3dis.py --input_dir path/to/dataset --output_dir path/to/output 68 | 69 | #### Downsampling for training 70 | Before training, we downsampled the pointclouds. 71 | 72 | python tools/downsample.py --data_dir path/to/dataset --cell_size 0.03 73 | 74 | #### Training configuration scripts 75 | Configuration files for all experiments are located in `experiments/iccvw_paper_2017/*`. For example, they can be 76 | launched as follows: 77 | 78 | python run.py --config experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_1.yaml 79 | 80 | The above script will run our multi scale consolidation unit network on stanford indoor 3d with test area 1. 81 | 82 | #### Evaluating on full scale point clouds 83 | Reported scores on the dataset are based on the full scale pointclouds. 84 | In order to do so, we need to load the trained model and set the `TEST` flag. 85 | 86 | Replace `modus: TRAIN_VAL` with 87 | 88 | ```yaml 89 | modus: TEST 90 | model_path: 'path/to/trained/model/model_ckpts' 91 | ``` 92 | which is located in the log directory specified for training. 93 | 94 | 95 | ### VKitti instructions 96 | * Coming soon... 97 | 98 | ### Trained models for downloading 99 | * Coming soon... 100 | 101 | ### License 102 | Our code is released under MIT License (see LICENSE file for details). 103 | -------------------------------------------------------------------------------- /batch_generators/ReadWriteLock.py: -------------------------------------------------------------------------------- 1 | """ 2 | copyright by: 3 | https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s04.html 4 | """ 5 | 6 | import threading 7 | 8 | 9 | class ReadWriteLock: 10 | """ A lock object that allows many simultaneous "read locks", but 11 | only one "write lock." """ 12 | 13 | def __init__(self): 14 | self._read_ready = threading.Condition(threading.Lock()) 15 | self._readers = 0 16 | 17 | def acquire_read(self): 18 | """ Acquire a read lock. Blocks only if a thread has 19 | acquired the write lock. """ 20 | self._read_ready.acquire() 21 | try: 22 | self._readers += 1 23 | finally: 24 | self._read_ready.release() 25 | 26 | def release_read(self): 27 | """ Release a read lock. """ 28 | self._read_ready.acquire() 29 | try: 30 | self._readers -= 1 31 | if not self._readers: 32 | self._read_ready.notifyAll() 33 | finally: 34 | self._read_ready.release() 35 | 36 | def acquire_write(self): 37 | """ Acquire a write lock. Blocks until there are no 38 | acquired read or write locks. """ 39 | self._read_ready.acquire() 40 | while self._readers > 0: 41 | self._read_ready.wait() 42 | 43 | def release_write(self): 44 | """ Release a write lock. """ 45 | self._read_ready.release() 46 | -------------------------------------------------------------------------------- /batch_generators/__init__.py: -------------------------------------------------------------------------------- 1 | from batch_generators.batch_generator import * 2 | from batch_generators.center_batch_generator import * 3 | from batch_generators.multi_scale_batch_generator import * 4 | from batch_generators.neighboring_grid_batch_generator import * 5 | -------------------------------------------------------------------------------- /batch_generators/batch_generator.py: -------------------------------------------------------------------------------- 1 | from abc import * 2 | import numpy as np 3 | import tensorflow as tf 4 | import itertools 5 | from tools.lazy_decorator import * 6 | 7 | 8 | class BatchGenerator(ABC): 9 | """ 10 | Abstract base class for batch generators providing the code for parallel creation of batches 11 | """ 12 | 13 | def __init__(self, dataset, batch_size, num_points, augmentation): 14 | """ 15 | :param dataset: dataset object 16 | :type dataset: Dataset 17 | :param num_points: number of points in a batch 18 | :type num_points: int 19 | """ 20 | self.dataset = dataset 21 | self._num_points = num_points 22 | self._batch_size = batch_size 23 | self._augmentation = augmentation 24 | 25 | @lazy_property 26 | def handle_pl(self): 27 | # Handle for datasets 28 | return tf.placeholder(tf.string, shape=[], name='handle_training_test') 29 | 30 | @lazy_property 31 | def next_element(self): 32 | iterator = tf.data.Iterator.from_string_handle(self.handle_pl, self.dataset_train.output_types) 33 | return iterator.get_next() 34 | 35 | @lazy_property 36 | def dataset_train(self): 37 | # Create dataset for training 38 | dataset_train = tf.data.Dataset.from_generator(self._next_train_index, tf.int64, tf.TensorShape([])) 39 | dataset_train = dataset_train.map(self._wrapped_generate_train_blob, num_parallel_calls=8) 40 | dataset_train = dataset_train.batch(self._batch_size) 41 | return dataset_train.prefetch(buffer_size=self._batch_size * 1) 42 | 43 | @lazy_property 44 | def iterator_train(self): 45 | return self.dataset_train.make_one_shot_iterator() 46 | 47 | @lazy_property 48 | def iterator_test(self): 49 | # Create dataset for testing 50 | dataset_test = tf.data.Dataset.from_generator(self._next_test_index, tf.int64, tf.TensorShape([])) 51 | dataset_test = dataset_test.map(self._wrapped_generate_test_blob, num_parallel_calls=8) 52 | dataset_test = dataset_test.batch(self._batch_size) 53 | dataset_test = dataset_test.prefetch(buffer_size=self._batch_size * 1) 54 | return dataset_test.make_one_shot_iterator() 55 | 56 | @property 57 | def batch_size(self): 58 | return self._batch_size 59 | 60 | @property 61 | def num_points(self): 62 | return self._num_points 63 | 64 | @property 65 | def input_shape(self): 66 | return self.pointclouds_pl.get_shape().as_list() 67 | 68 | @lazy_property 69 | @abstractmethod 70 | def pointclouds_pl(self): 71 | raise NotImplementedError('Should be defined in subclass') 72 | 73 | @lazy_property 74 | @abstractmethod 75 | def labels_pl(self): 76 | raise NotImplementedError('Should be defined in subclass') 77 | 78 | @lazy_property 79 | @abstractmethod 80 | def mask_pl(self): 81 | raise NotImplementedError('Should be defined in subclass') 82 | 83 | @lazy_property 84 | @abstractmethod 85 | def cloud_ids_pl(self): 86 | raise NotImplementedError('Should be defined in subclass') 87 | 88 | @lazy_property 89 | @abstractmethod 90 | def point_ids_pl(self): 91 | raise NotImplementedError('Should be defined in subclass') 92 | 93 | def _next_train_index(self): 94 | """ 95 | get next index in training sample list (e.g. containing [pointcloud_id, center_x, center_y]) 96 | Take care that list is shuffled for each epoch! 97 | :return: next index for training 98 | """ 99 | for i in itertools.cycle(range(self.train_sample_idx.shape[0])): 100 | yield (i) 101 | 102 | def _next_test_index(self): 103 | """ 104 | get next index in test sample list (e.g. containing [pointcloud_id, center_x, center_y]) 105 | Take care that list is shuffled for each epoch! 106 | :return: next index for test 107 | """ 108 | for i in itertools.cycle(range(self.test_sample_idx.shape[0])): 109 | yield (i) 110 | 111 | def _wrapped_generate_train_blob(self, index): 112 | return tf.py_func(func=self._generate_blob, 113 | # pos_id, train, aug_trans 114 | inp=[index, True, self._augmentation], 115 | # data labels mask 116 | Tout=(tf.float32, tf.int8, tf.int8, tf.int32, tf.int64), 117 | 118 | name='generate_train_blob') 119 | 120 | def _wrapped_generate_test_blob(self, index): 121 | return tf.py_func(func=self._generate_blob, 122 | # pos_id, train, aug_trans 123 | inp=(index, False, False), 124 | # data labels mask cloud_id point_id 125 | Tout=(tf.float32, tf.int8, tf.int8, tf.int32, tf.int64), 126 | name='generate_test_blob') 127 | 128 | @property 129 | def num_train_batches(self): 130 | return self.train_sample_idx.shape[0] // self._batch_size 131 | 132 | @property 133 | def num_test_batches(self): 134 | return int(np.ceil(self.test_sample_idx.shape[0] // self._batch_size)) 135 | 136 | @property 137 | @abstractmethod 138 | def test_sample_idx(self): 139 | """ 140 | :rtype: ndarray 141 | :return: 142 | """ 143 | raise NotImplementedError('Should be defined in subclass') 144 | 145 | @property 146 | @abstractmethod 147 | def train_sample_idx(self): 148 | """ 149 | :rtype: ndarray 150 | :return: 151 | """ 152 | raise NotImplementedError('Should be defined in subclass') 153 | 154 | @abstractmethod 155 | def _generate_blob(self, index, train=True, aug_trans=True): 156 | raise NotImplementedError('Should be defined in subclass') 157 | -------------------------------------------------------------------------------- /batch_generators/center_batch_generator.py: -------------------------------------------------------------------------------- 1 | from tqdm import * 2 | from sklearn.neighbors import BallTree 3 | from .batch_generator import * 4 | from .ReadWriteLock import ReadWriteLock 5 | 6 | 7 | class CenterBatchGenerator(BatchGenerator): 8 | """ 9 | creates batches where the blobs are centered around a specific point in the grid cell 10 | """ 11 | 12 | def __init__(self, dataset, batch_size, num_points, grid_spacing, 13 | augmentation=False, metric='euclidean'): 14 | self._rw_lock = ReadWriteLock() 15 | 16 | super().__init__(dataset, batch_size, num_points, augmentation) 17 | 18 | self._grid_spacing = grid_spacing 19 | 20 | self._shuffle_train = True 21 | self._shuffle_test = True 22 | 23 | # TODO make min_num_points_per_block variable 24 | self.min_num_points_per_block = 5 25 | 26 | self._ball_trees = self._calc_ball_trees(metric=metric) 27 | self._pc_center_pos = self._generate_center_positions() 28 | 29 | @property 30 | def ball_trees(self): 31 | return self._ball_trees 32 | 33 | @property 34 | def pc_center_pos(self): 35 | return self._pc_center_pos 36 | 37 | @property 38 | def test_sample_idx(self): 39 | # after shuffling we need to recalculate the indices 40 | if self._shuffle_test: 41 | self._test_sample_idx_cache = self.pc_center_pos[np.isin(self.pc_center_pos[:, 0], self.dataset.test_pc_idx)] 42 | self._shuffle_test = False 43 | 44 | return self._test_sample_idx_cache 45 | 46 | @property 47 | def train_sample_idx(self): 48 | # after shuffling we need to recalculate the indices 49 | if self._shuffle_train: 50 | self._train_sample_idx_cache = self.pc_center_pos[np.isin(self.pc_center_pos[:, 0], self.dataset.train_pc_idx)] 51 | self._shuffle_train = False 52 | 53 | return self._train_sample_idx_cache 54 | 55 | @staticmethod 56 | def _sample_data(data, num_sample): 57 | """ data is in N x ... 58 | we want to keep num_samplexC of them. 59 | if N > num_sample, we will randomly keep num_sample of them. 60 | if N < num_sample, we will randomly duplicate samples. 61 | """ 62 | N = data.shape[0] 63 | if N == num_sample: 64 | return data, range(N) 65 | elif N > num_sample: 66 | sample = np.random.choice(N, num_sample) 67 | return data[sample, ...], sample 68 | else: 69 | sample = np.random.choice(N, num_sample - N) 70 | dup_data = data[sample, ...] 71 | return np.concatenate([data, dup_data], 0), list(range(N)) + (list(sample)) 72 | 73 | def _calc_ball_trees(self, metric='euclidean'): 74 | ball_trees = [] 75 | for pointcloud_data in tqdm(self.dataset.data, desc='Ball trees have to be calculated from scratch'): 76 | ball_trees.append(BallTree(pointcloud_data[:, :2], metric=metric)) 77 | return ball_trees 78 | 79 | @abstractmethod 80 | def _generate_blob(self, index, train=True, aug_trans=True): 81 | raise NotImplementedError('Should be defined in subclass') 82 | 83 | def shuffle(self): 84 | self._rw_lock.acquire_write() 85 | 86 | try: 87 | self._shuffle_train = True 88 | self._shuffle_test = True 89 | 90 | # Randomly shuffle training data from epoch 91 | np.random.shuffle(self.pc_center_pos) 92 | finally: 93 | self._rw_lock.release_write() 94 | 95 | def _generate_center_positions(self): 96 | """ 97 | Generate blob positions while making sure the grid stays inside the pointcloud bounding box 98 | :return: (point_cloud_id, pos_x, pos_y) 99 | """ 100 | room_pos_list = [] 101 | for room_id, room_data in enumerate(tqdm(self.dataset.data, desc='Calculate center positions')): 102 | room_maxs = np.amax(room_data[:, 0:2], axis=0) 103 | room_mins = np.amin(room_data[:, 0:2], axis=0) 104 | room_size = room_maxs - room_mins 105 | num_blobs = np.ceil(room_size / self._grid_spacing) 106 | num_blobs = num_blobs - np.array([1, 1]) + 1 107 | 108 | if num_blobs[0] <= 0: 109 | num_blobs[0] = 1 110 | if num_blobs[1] <= 0: 111 | num_blobs[1] = 1 112 | 113 | ctrs = [[room_mins[0] + x * self._grid_spacing + self._grid_spacing / 2.0, 114 | room_mins[1] + y * self._grid_spacing + self._grid_spacing / 2.0] 115 | for x in range(int(num_blobs[0])) 116 | for y in range(int(num_blobs[1]))] 117 | 118 | blob_point_ids_all = self.ball_trees[room_id].query_radius(np.reshape(ctrs, [-1, 2]), 119 | r=self._grid_spacing / 2.0) 120 | 121 | blob_point_ids_all = np.reshape(blob_point_ids_all, [-1, 1]) 122 | 123 | ctrs = np.reshape(ctrs, [-1, 1, 2]) 124 | for i in range(np.shape(ctrs)[0]): 125 | npoints = 0 126 | for j in range(np.shape(ctrs)[1]): 127 | npoints = npoints + np.shape(blob_point_ids_all[i, j])[0] 128 | if npoints >= self.min_num_points_per_block: # TODO CHECK IF MINPOINTS 5 IS GOOD 129 | room_pos_list.append(np.reshape(np.array([room_id, ctrs[i, 0, 0], ctrs[i, 0, 1]]), (1, 3))) 130 | 131 | return np.concatenate(room_pos_list) 132 | 133 | 134 | if __name__ == '__main__': 135 | pass 136 | -------------------------------------------------------------------------------- /batch_generators/multi_scale_batch_generator.py: -------------------------------------------------------------------------------- 1 | from .center_batch_generator import * 2 | import numpy as np 3 | 4 | 5 | class MultiScaleBatchGenerator(CenterBatchGenerator): 6 | """ 7 | Batch Generator for multi scale batches of different radii where the centers are equal 8 | """ 9 | 10 | def __init__(self, dataset, params): 11 | super().__init__(dataset=dataset, 12 | num_points=params['num_points'], 13 | batch_size=params['batch_size'], 14 | grid_spacing=params['grid_spacing'], 15 | augmentation=params['augmentation'], 16 | metric=params['metric']) 17 | 18 | self._radii = params['radii'] 19 | 20 | @lazy_property 21 | def pointclouds_pl(self): 22 | return tf.reshape(self.next_element[0], (self._batch_size, len(self._radii), 23 | self._num_points, self.dataset.num_features + 3)) 24 | 25 | @lazy_property 26 | def labels_pl(self): 27 | return tf.reshape(self.next_element[1], (self._batch_size, 1, self._num_points)) 28 | 29 | @lazy_property 30 | def mask_pl(self): 31 | return tf.reshape(self.next_element[2], (self._batch_size, 1)) 32 | 33 | @lazy_property 34 | def cloud_ids_pl(self): 35 | return tf.reshape(self.next_element[3], (self._batch_size, 1)) 36 | 37 | @lazy_property 38 | def point_ids_pl(self): 39 | return tf.reshape(self.next_element[4], (self._batch_size, 1, self._num_points)) 40 | 41 | def _generate_blob(self, index, train=True, aug_trans=True): 42 | b_data = np.zeros((len(self._radii), self._num_points, self.dataset.num_features + 3), dtype=np.float32) 43 | b_label = np.zeros((len(self._radii), self._num_points), dtype=np.int8) 44 | b_mask = np.ones(len(self._radii), dtype=np.int8) 45 | b_cloud_ids = np.zeros(len(self._radii), dtype=np.int32) 46 | b_point_ids = np.zeros((len(self._radii), self._num_points), dtype=np.int64) 47 | 48 | self._rw_lock.acquire_read() 49 | 50 | try: 51 | if train: 52 | [pointcloud_id, grid_center_x, grid_center_y] = np.copy(self.train_sample_idx[index, :]) 53 | else: 54 | [pointcloud_id, grid_center_x, grid_center_y] = np.copy(self.test_sample_idx[index, :]) 55 | finally: 56 | self._rw_lock.release_read() 57 | 58 | pointcloud_id = int(pointcloud_id) 59 | pointcloud_data = self.dataset.data[pointcloud_id] 60 | 61 | max_x, max_y, max_z = np.amax(pointcloud_data[:, 0:3], axis=0) 62 | min_x, min_y, min_z = np.amin(pointcloud_data[:, 0:3], axis=0) 63 | 64 | diff_x = max_x - min_x 65 | diff_y = max_y - min_y 66 | diff_z = max_z - min_z 67 | noise_x = 0 68 | noise_y = 0 69 | 70 | if aug_trans: 71 | noise_x = np.random.uniform(-self._grid_spacing / 2.0, self._grid_spacing / 2.0) 72 | noise_y = np.random.uniform(-self._grid_spacing / 2.0, self._grid_spacing / 2.0) 73 | 74 | ctr_x = grid_center_x + noise_x 75 | ctr_y = grid_center_y + noise_y 76 | ctr = np.array([ctr_x, ctr_y]) 77 | 78 | ctrs = [ctr for _ in range(len(self._radii))] 79 | 80 | blob_point_ids_all = self.ball_trees[pointcloud_id].query_radius(ctrs, r=self._radii) 81 | 82 | for radius_id, radius in enumerate(self._radii): 83 | blob_point_ids = np.array(blob_point_ids_all[radius_id]) 84 | blob = pointcloud_data[blob_point_ids, :] 85 | 86 | if blob.shape[0] < self.min_num_points_per_block: # check here if it is empty, set mask to zero 87 | b_mask[radius_id] = 0 88 | else: # blob is not empty 89 | blob, point_ids = self._sample_data(blob, self._num_points) 90 | 91 | # apply normalizations to blob 92 | blob[:, :self.dataset.num_features] /= self.dataset.normalization 93 | 94 | # Normalized coordinates 95 | additional_feats = np.zeros((self._num_points, 3)) 96 | blob = np.concatenate((blob, additional_feats), axis=-1) 97 | 98 | blob[:, -1] = blob[:, self.dataset.num_features] # put label to the end 99 | blob[:, self.dataset.num_features] = blob[:, 0] / diff_x 100 | blob[:, self.dataset.num_features+1] = blob[:, 1] / diff_y 101 | blob[:, self.dataset.num_features+2] = blob[:, 2] / diff_z 102 | blob[:, 0:2] -= ctr 103 | 104 | b_label[radius_id, :] = blob[:, -1] 105 | b_data[radius_id, :, :] = blob[:, :-1] 106 | b_point_ids[radius_id, :] = blob_point_ids[point_ids] 107 | b_cloud_ids[radius_id] = pointcloud_id 108 | 109 | # reference radius 110 | b_label = np.reshape(b_label[1, :], (1, b_label.shape[1])) 111 | b_mask = np.reshape(b_mask[1], (1)) 112 | b_point_ids = np.reshape(b_point_ids[1, :], 113 | (1, b_point_ids.shape[1])) 114 | b_cloud_ids = np.reshape(b_cloud_ids[1], (1)) 115 | 116 | return b_data, b_label, b_mask, b_cloud_ids, b_point_ids 117 | 118 | 119 | if __name__ == '__main__': 120 | pass 121 | -------------------------------------------------------------------------------- /batch_generators/neighboring_grid_batch_generator.py: -------------------------------------------------------------------------------- 1 | from .center_batch_generator import * 2 | 3 | 4 | class NeighboringGridBatchGenerator(CenterBatchGenerator): 5 | """ 6 | neighboring grid batch generator where different blobs are placed next to each other 7 | """ 8 | 9 | def __init__(self, dataset, params): 10 | print(params) 11 | super().__init__(dataset=dataset, 12 | num_points=params['num_points'], 13 | batch_size=params['batch_size'], 14 | grid_spacing=params['grid_spacing'], 15 | augmentation=params['augmentation'], 16 | metric=params['metric']) 17 | 18 | self._grid_x = params['grid_x'] 19 | self._grid_y = params['grid_y'] 20 | 21 | self._radius = params['radius'] 22 | 23 | # flattened version of grid 24 | self._num_of_blobs = self._grid_x * self._grid_y 25 | 26 | @property 27 | def num_of_blobs(self): 28 | return self._num_of_blobs 29 | 30 | @lazy_property 31 | def pointclouds_pl(self): 32 | return tf.reshape(self.next_element[0], (self._batch_size, self._num_of_blobs, 33 | self._num_points, self.dataset.num_features + 3)) 34 | 35 | @lazy_property 36 | def labels_pl(self): 37 | return tf.reshape(self.next_element[1], (self._batch_size, self._num_of_blobs, self._num_points)) 38 | 39 | @lazy_property 40 | def mask_pl(self): 41 | return tf.reshape(self.next_element[2], (self._batch_size, self._num_of_blobs)) 42 | 43 | @lazy_property 44 | def cloud_ids_pl(self): 45 | return tf.reshape(self.next_element[3], (self._batch_size, self._num_of_blobs)) 46 | 47 | @lazy_property 48 | def point_ids_pl(self): 49 | return tf.reshape(self.next_element[4], (self._batch_size, self._num_of_blobs, self._num_points)) 50 | 51 | def _generate_blob(self, index, train=True, aug_trans=True): 52 | b_data = np.zeros((self._grid_x, self._grid_y, self._num_points, self.dataset.num_features + 3), dtype=np.float32) 53 | b_label = np.zeros((self._grid_x, self._grid_y, self._num_points), dtype=np.int8) 54 | b_mask = np.ones((self._grid_x, self._grid_y), dtype=np.int8) 55 | b_cloud_ids = np.zeros((self._grid_x, self._grid_y), dtype=np.int32) 56 | b_point_ids = np.zeros((self._grid_x, self._grid_y, self._num_points), dtype=np.int64) 57 | 58 | self._rw_lock.acquire_read() 59 | 60 | try: 61 | if train: 62 | [pointcloud_id, grid_center_x, grid_center_y] = np.copy(self.train_sample_idx[index, :]) 63 | else: 64 | [pointcloud_id, grid_center_x, grid_center_y] = np.copy(self.test_sample_idx[index, :]) 65 | finally: 66 | self._rw_lock.release_read() 67 | 68 | pointcloud_id = int(pointcloud_id) 69 | pointcloud_data = self.dataset.data[pointcloud_id] 70 | max_x, max_y, max_z = np.amax(pointcloud_data[:, 0:3], axis=0) 71 | min_x, min_y, min_z = np.amin(pointcloud_data[:, 0:3], axis=0) 72 | 73 | diff_x = max_x - min_x 74 | diff_y = max_y - min_y 75 | diff_z = max_z - min_z 76 | 77 | noise_x = 0 78 | noise_y = 0 79 | 80 | num_points = 0 81 | 82 | while num_points < self.min_num_points_per_block: 83 | if aug_trans: 84 | noise_x = np.random.uniform(-self._grid_spacing / 2.0, self._grid_spacing / 2.0) 85 | noise_y = np.random.uniform(-self._grid_spacing / 2.0, self._grid_spacing / 2.0) 86 | 87 | # Create centers 88 | ctrs = [] 89 | for grid_x in range(self._grid_x): 90 | ctr_x = grid_center_x + grid_x * self._grid_spacing + noise_x 91 | for grid_y in range(self._grid_y): 92 | ctr_y = grid_center_y + grid_y * self._grid_spacing + noise_y 93 | ctr = np.array([ctr_x, ctr_y]) 94 | ctrs.append(ctr) 95 | 96 | blob_point_ids_all = self.ball_trees[pointcloud_id].query_radius(ctrs, r=self._radius) 97 | num_points = len(blob_point_ids_all[0]) 98 | 99 | curr_id = -1 100 | for grid_x in range(self._grid_x): 101 | ctr_x = grid_center_x + grid_x * self._grid_spacing + noise_x 102 | for grid_y in range(self._grid_y): 103 | ctr_y = grid_center_y + grid_y * self._grid_spacing + noise_y 104 | ctr = np.array([ctr_x, ctr_y]) 105 | 106 | curr_id += 1 107 | 108 | # New 109 | blob_point_ids = blob_point_ids_all[curr_id] 110 | blob = np.copy(pointcloud_data[blob_point_ids, :]) 111 | 112 | if blob.shape[0] < self.min_num_points_per_block: # check here if it is empty, set mask to zero 113 | b_mask[grid_x, grid_y] = 0 114 | else: # blob is not empty 115 | blob, point_ids = self._sample_data(blob, self._num_points) 116 | 117 | # apply normalizations to blob 118 | blob[:, :self.dataset.num_features] /= self.dataset.normalization 119 | 120 | # Normalized coordinates 121 | additional_feats = np.zeros((self._num_points, 3)) 122 | blob = np.concatenate((blob, additional_feats), axis=-1) 123 | 124 | blob[:, -1] = blob[:, self.dataset.num_features] # put label to the end 125 | blob[:, self.dataset.num_features] = blob[:, 0] / diff_x 126 | blob[:, self.dataset.num_features + 1] = blob[:, 1] / diff_y 127 | blob[:, self.dataset.num_features + 2] = blob[:, 2] / diff_z 128 | blob[:, 0:2] -= ctr 129 | 130 | b_label[grid_x, grid_y, :] = blob[:, -1] 131 | b_data[grid_x, grid_y, :, :] = blob[:, :-1] 132 | b_point_ids[grid_x, grid_y, :] = blob_point_ids[point_ids] 133 | 134 | b_cloud_ids[grid_x, grid_y] = pointcloud_id 135 | 136 | return b_data, b_label, b_mask, b_cloud_ids, b_point_ids 137 | 138 | 139 | if __name__ == '__main__': 140 | pass 141 | -------------------------------------------------------------------------------- /datasets/__init__.py: -------------------------------------------------------------------------------- 1 | from datasets.general_dataset import * 2 | -------------------------------------------------------------------------------- /datasets/color_constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide RGB color constants and a colors dictionary with 3 | elements formatted: colors[colorname] = CONSTANT 4 | 5 | adapted from: https://www.webucator.com/blog/2015/03/python-color-constants-module/ 6 | """ 7 | 8 | from collections import namedtuple, OrderedDict 9 | import numpy as np 10 | 11 | Color = namedtuple('RGB', 'red, green, blue') 12 | colors = {} # dict of colors 13 | 14 | 15 | class RGB(Color): 16 | 17 | def hex_format(self): 18 | """ 19 | Returns color in hex format 20 | """ 21 | return '#{:02X}{:02X}{:02X}'.format(self.red, self.green, self.blue) 22 | 23 | @property 24 | def npy(self): 25 | """ 26 | Returns npy formatted rgb color 27 | :return: 28 | """ 29 | return np.array([self.red, self.green, self.blue]) 30 | 31 | 32 | # Color Contants 33 | ALICEBLUE = RGB(240, 248, 255) 34 | ANTIQUEWHITE = RGB(250, 235, 215) 35 | ANTIQUEWHITE1 = RGB(255, 239, 219) 36 | ANTIQUEWHITE2 = RGB(238, 223, 204) 37 | ANTIQUEWHITE3 = RGB(205, 192, 176) 38 | ANTIQUEWHITE4 = RGB(139, 131, 120) 39 | AQUA = RGB(0, 255, 255) 40 | AQUAMARINE1 = RGB(127, 255, 212) 41 | AQUAMARINE2 = RGB(118, 238, 198) 42 | AQUAMARINE3 = RGB(102, 205, 170) 43 | AQUAMARINE4 = RGB(69, 139, 116) 44 | AZURE1 = RGB(240, 255, 255) 45 | AZURE2 = RGB(224, 238, 238) 46 | AZURE3 = RGB(193, 205, 205) 47 | AZURE4 = RGB(131, 139, 139) 48 | BANANA = RGB(227, 207, 87) 49 | BEIGE = RGB(245, 245, 220) 50 | BISQUE1 = RGB(255, 228, 196) 51 | BISQUE2 = RGB(238, 213, 183) 52 | BISQUE3 = RGB(205, 183, 158) 53 | BISQUE4 = RGB(139, 125, 107) 54 | BLACK = RGB(0, 0, 0) 55 | BLANCHEDALMOND = RGB(255, 235, 205) 56 | BLUE = RGB(0, 0, 255) 57 | BLUE2 = RGB(0, 0, 238) 58 | BLUE3 = RGB(0, 0, 205) 59 | BLUE4 = RGB(0, 0, 139) 60 | BLUEVIOLET = RGB(138, 43, 226) 61 | BRICK = RGB(156, 102, 31) 62 | BROWN = RGB(165, 42, 42) 63 | BROWN1 = RGB(255, 64, 64) 64 | BROWN2 = RGB(238, 59, 59) 65 | BROWN3 = RGB(205, 51, 51) 66 | BROWN4 = RGB(139, 35, 35) 67 | BURLYWOOD = RGB(222, 184, 135) 68 | BURLYWOOD1 = RGB(255, 211, 155) 69 | BURLYWOOD2 = RGB(238, 197, 145) 70 | BURLYWOOD3 = RGB(205, 170, 125) 71 | BURLYWOOD4 = RGB(139, 115, 85) 72 | BURNTSIENNA = RGB(138, 54, 15) 73 | BURNTUMBER = RGB(138, 51, 36) 74 | CADETBLUE = RGB(95, 158, 160) 75 | CADETBLUE1 = RGB(152, 245, 255) 76 | CADETBLUE2 = RGB(142, 229, 238) 77 | CADETBLUE3 = RGB(122, 197, 205) 78 | CADETBLUE4 = RGB(83, 134, 139) 79 | CADMIUMORANGE = RGB(255, 97, 3) 80 | CADMIUMYELLOW = RGB(255, 153, 18) 81 | CARROT = RGB(237, 145, 33) 82 | CHARTREUSE1 = RGB(127, 255, 0) 83 | CHARTREUSE2 = RGB(118, 238, 0) 84 | CHARTREUSE3 = RGB(102, 205, 0) 85 | CHARTREUSE4 = RGB(69, 139, 0) 86 | CHOCOLATE = RGB(210, 105, 30) 87 | CHOCOLATE1 = RGB(255, 127, 36) 88 | CHOCOLATE2 = RGB(238, 118, 33) 89 | CHOCOLATE3 = RGB(205, 102, 29) 90 | CHOCOLATE4 = RGB(139, 69, 19) 91 | COBALT = RGB(61, 89, 171) 92 | COBALTGREEN = RGB(61, 145, 64) 93 | COLDGREY = RGB(128, 138, 135) 94 | CORAL = RGB(255, 127, 80) 95 | CORAL1 = RGB(255, 114, 86) 96 | CORAL2 = RGB(238, 106, 80) 97 | CORAL3 = RGB(205, 91, 69) 98 | CORAL4 = RGB(139, 62, 47) 99 | CORNFLOWERBLUE = RGB(100, 149, 237) 100 | CORNSILK1 = RGB(255, 248, 220) 101 | CORNSILK2 = RGB(238, 232, 205) 102 | CORNSILK3 = RGB(205, 200, 177) 103 | CORNSILK4 = RGB(139, 136, 120) 104 | CRIMSON = RGB(220, 20, 60) 105 | CYAN2 = RGB(0, 238, 238) 106 | CYAN3 = RGB(0, 205, 205) 107 | CYAN4 = RGB(0, 139, 139) 108 | DARKGOLDENROD = RGB(184, 134, 11) 109 | DARKGOLDENROD1 = RGB(255, 185, 15) 110 | DARKGOLDENROD2 = RGB(238, 173, 14) 111 | DARKGOLDENROD3 = RGB(205, 149, 12) 112 | DARKGOLDENROD4 = RGB(139, 101, 8) 113 | DARKGRAY = RGB(169, 169, 169) 114 | DARKGREEN = RGB(0, 100, 0) 115 | DARKKHAKI = RGB(189, 183, 107) 116 | DARKOLIVEGREEN = RGB(85, 107, 47) 117 | DARKOLIVEGREEN1 = RGB(202, 255, 112) 118 | DARKOLIVEGREEN2 = RGB(188, 238, 104) 119 | DARKOLIVEGREEN3 = RGB(162, 205, 90) 120 | DARKOLIVEGREEN4 = RGB(110, 139, 61) 121 | DARKORANGE = RGB(255, 140, 0) 122 | DARKORANGE1 = RGB(255, 127, 0) 123 | DARKORANGE2 = RGB(238, 118, 0) 124 | DARKORANGE3 = RGB(205, 102, 0) 125 | DARKORANGE4 = RGB(139, 69, 0) 126 | DARKORCHID = RGB(153, 50, 204) 127 | DARKORCHID1 = RGB(191, 62, 255) 128 | DARKORCHID2 = RGB(178, 58, 238) 129 | DARKORCHID3 = RGB(154, 50, 205) 130 | DARKORCHID4 = RGB(104, 34, 139) 131 | DARKSALMON = RGB(233, 150, 122) 132 | DARKSEAGREEN = RGB(143, 188, 143) 133 | DARKSEAGREEN1 = RGB(193, 255, 193) 134 | DARKSEAGREEN2 = RGB(180, 238, 180) 135 | DARKSEAGREEN3 = RGB(155, 205, 155) 136 | DARKSEAGREEN4 = RGB(105, 139, 105) 137 | DARKSLATEBLUE = RGB(72, 61, 139) 138 | DARKSLATEGRAY = RGB(47, 79, 79) 139 | DARKSLATEGRAY1 = RGB(151, 255, 255) 140 | DARKSLATEGRAY2 = RGB(141, 238, 238) 141 | DARKSLATEGRAY3 = RGB(121, 205, 205) 142 | DARKSLATEGRAY4 = RGB(82, 139, 139) 143 | DARKTURQUOISE = RGB(0, 206, 209) 144 | DARKVIOLET = RGB(148, 0, 211) 145 | DEEPPINK1 = RGB(255, 20, 147) 146 | DEEPPINK2 = RGB(238, 18, 137) 147 | DEEPPINK3 = RGB(205, 16, 118) 148 | DEEPPINK4 = RGB(139, 10, 80) 149 | DEEPSKYBLUE1 = RGB(0, 191, 255) 150 | DEEPSKYBLUE2 = RGB(0, 178, 238) 151 | DEEPSKYBLUE3 = RGB(0, 154, 205) 152 | DEEPSKYBLUE4 = RGB(0, 104, 139) 153 | DIMGRAY = RGB(105, 105, 105) 154 | DIMGRAY = RGB(105, 105, 105) 155 | DODGERBLUE1 = RGB(30, 144, 255) 156 | DODGERBLUE2 = RGB(28, 134, 238) 157 | DODGERBLUE3 = RGB(24, 116, 205) 158 | DODGERBLUE4 = RGB(16, 78, 139) 159 | EGGSHELL = RGB(252, 230, 201) 160 | EMERALDGREEN = RGB(0, 201, 87) 161 | FIREBRICK = RGB(178, 34, 34) 162 | FIREBRICK1 = RGB(255, 48, 48) 163 | FIREBRICK2 = RGB(238, 44, 44) 164 | FIREBRICK3 = RGB(205, 38, 38) 165 | FIREBRICK4 = RGB(139, 26, 26) 166 | FLESH = RGB(255, 125, 64) 167 | FLORALWHITE = RGB(255, 250, 240) 168 | FORESTGREEN = RGB(34, 139, 34) 169 | GAINSBORO = RGB(220, 220, 220) 170 | GHOSTWHITE = RGB(248, 248, 255) 171 | GOLD1 = RGB(255, 215, 0) 172 | GOLD2 = RGB(238, 201, 0) 173 | GOLD3 = RGB(205, 173, 0) 174 | GOLD4 = RGB(139, 117, 0) 175 | GOLDENROD = RGB(218, 165, 32) 176 | GOLDENROD1 = RGB(255, 193, 37) 177 | GOLDENROD2 = RGB(238, 180, 34) 178 | GOLDENROD3 = RGB(205, 155, 29) 179 | GOLDENROD4 = RGB(139, 105, 20) 180 | GRAY = RGB(128, 128, 128) 181 | GRAY1 = RGB(3, 3, 3) 182 | GRAY10 = RGB(26, 26, 26) 183 | GRAY11 = RGB(28, 28, 28) 184 | GRAY12 = RGB(31, 31, 31) 185 | GRAY13 = RGB(33, 33, 33) 186 | GRAY14 = RGB(36, 36, 36) 187 | GRAY15 = RGB(38, 38, 38) 188 | GRAY16 = RGB(41, 41, 41) 189 | GRAY17 = RGB(43, 43, 43) 190 | GRAY18 = RGB(46, 46, 46) 191 | GRAY19 = RGB(48, 48, 48) 192 | GRAY2 = RGB(5, 5, 5) 193 | GRAY20 = RGB(51, 51, 51) 194 | GRAY21 = RGB(54, 54, 54) 195 | GRAY22 = RGB(56, 56, 56) 196 | GRAY23 = RGB(59, 59, 59) 197 | GRAY24 = RGB(61, 61, 61) 198 | GRAY25 = RGB(64, 64, 64) 199 | GRAY26 = RGB(66, 66, 66) 200 | GRAY27 = RGB(69, 69, 69) 201 | GRAY28 = RGB(71, 71, 71) 202 | GRAY29 = RGB(74, 74, 74) 203 | GRAY3 = RGB(8, 8, 8) 204 | GRAY30 = RGB(77, 77, 77) 205 | GRAY31 = RGB(79, 79, 79) 206 | GRAY32 = RGB(82, 82, 82) 207 | GRAY33 = RGB(84, 84, 84) 208 | GRAY34 = RGB(87, 87, 87) 209 | GRAY35 = RGB(89, 89, 89) 210 | GRAY36 = RGB(92, 92, 92) 211 | GRAY37 = RGB(94, 94, 94) 212 | GRAY38 = RGB(97, 97, 97) 213 | GRAY39 = RGB(99, 99, 99) 214 | GRAY4 = RGB(10, 10, 10) 215 | GRAY40 = RGB(102, 102, 102) 216 | GRAY42 = RGB(107, 107, 107) 217 | GRAY43 = RGB(110, 110, 110) 218 | GRAY44 = RGB(112, 112, 112) 219 | GRAY45 = RGB(115, 115, 115) 220 | GRAY46 = RGB(117, 117, 117) 221 | GRAY47 = RGB(120, 120, 120) 222 | GRAY48 = RGB(122, 122, 122) 223 | GRAY49 = RGB(125, 125, 125) 224 | GRAY5 = RGB(13, 13, 13) 225 | GRAY50 = RGB(127, 127, 127) 226 | GRAY51 = RGB(130, 130, 130) 227 | GRAY52 = RGB(133, 133, 133) 228 | GRAY53 = RGB(135, 135, 135) 229 | GRAY54 = RGB(138, 138, 138) 230 | GRAY55 = RGB(140, 140, 140) 231 | GRAY56 = RGB(143, 143, 143) 232 | GRAY57 = RGB(145, 145, 145) 233 | GRAY58 = RGB(148, 148, 148) 234 | GRAY59 = RGB(150, 150, 150) 235 | GRAY6 = RGB(15, 15, 15) 236 | GRAY60 = RGB(153, 153, 153) 237 | GRAY61 = RGB(156, 156, 156) 238 | GRAY62 = RGB(158, 158, 158) 239 | GRAY63 = RGB(161, 161, 161) 240 | GRAY64 = RGB(163, 163, 163) 241 | GRAY65 = RGB(166, 166, 166) 242 | GRAY66 = RGB(168, 168, 168) 243 | GRAY67 = RGB(171, 171, 171) 244 | GRAY68 = RGB(173, 173, 173) 245 | GRAY69 = RGB(176, 176, 176) 246 | GRAY7 = RGB(18, 18, 18) 247 | GRAY70 = RGB(179, 179, 179) 248 | GRAY71 = RGB(181, 181, 181) 249 | GRAY72 = RGB(184, 184, 184) 250 | GRAY73 = RGB(186, 186, 186) 251 | GRAY74 = RGB(189, 189, 189) 252 | GRAY75 = RGB(191, 191, 191) 253 | GRAY76 = RGB(194, 194, 194) 254 | GRAY77 = RGB(196, 196, 196) 255 | GRAY78 = RGB(199, 199, 199) 256 | GRAY79 = RGB(201, 201, 201) 257 | GRAY8 = RGB(20, 20, 20) 258 | GRAY80 = RGB(204, 204, 204) 259 | GRAY81 = RGB(207, 207, 207) 260 | GRAY82 = RGB(209, 209, 209) 261 | GRAY83 = RGB(212, 212, 212) 262 | GRAY84 = RGB(214, 214, 214) 263 | GRAY85 = RGB(217, 217, 217) 264 | GRAY86 = RGB(219, 219, 219) 265 | GRAY87 = RGB(222, 222, 222) 266 | GRAY88 = RGB(224, 224, 224) 267 | GRAY89 = RGB(227, 227, 227) 268 | GRAY9 = RGB(23, 23, 23) 269 | GRAY90 = RGB(229, 229, 229) 270 | GRAY91 = RGB(232, 232, 232) 271 | GRAY92 = RGB(235, 235, 235) 272 | GRAY93 = RGB(237, 237, 237) 273 | GRAY94 = RGB(240, 240, 240) 274 | GRAY95 = RGB(242, 242, 242) 275 | GRAY97 = RGB(247, 247, 247) 276 | GRAY98 = RGB(250, 250, 250) 277 | GRAY99 = RGB(252, 252, 252) 278 | GREEN = RGB(0, 128, 0) 279 | GREEN1 = RGB(0, 255, 0) 280 | GREEN2 = RGB(0, 238, 0) 281 | GREEN3 = RGB(0, 205, 0) 282 | GREEN4 = RGB(0, 139, 0) 283 | GREENYELLOW = RGB(173, 255, 47) 284 | HONEYDEW1 = RGB(240, 255, 240) 285 | HONEYDEW2 = RGB(224, 238, 224) 286 | HONEYDEW3 = RGB(193, 205, 193) 287 | HONEYDEW4 = RGB(131, 139, 131) 288 | HOTPINK = RGB(255, 105, 180) 289 | HOTPINK1 = RGB(255, 110, 180) 290 | HOTPINK2 = RGB(238, 106, 167) 291 | HOTPINK3 = RGB(205, 96, 144) 292 | HOTPINK4 = RGB(139, 58, 98) 293 | INDIANRED = RGB(176, 23, 31) 294 | INDIANRED = RGB(205, 92, 92) 295 | INDIANRED1 = RGB(255, 106, 106) 296 | INDIANRED2 = RGB(238, 99, 99) 297 | INDIANRED3 = RGB(205, 85, 85) 298 | INDIANRED4 = RGB(139, 58, 58) 299 | INDIGO = RGB(75, 0, 130) 300 | IVORY1 = RGB(255, 255, 240) 301 | IVORY2 = RGB(238, 238, 224) 302 | IVORY3 = RGB(205, 205, 193) 303 | IVORY4 = RGB(139, 139, 131) 304 | IVORYBLACK = RGB(41, 36, 33) 305 | KHAKI = RGB(240, 230, 140) 306 | KHAKI1 = RGB(255, 246, 143) 307 | KHAKI2 = RGB(238, 230, 133) 308 | KHAKI3 = RGB(205, 198, 115) 309 | KHAKI4 = RGB(139, 134, 78) 310 | LAVENDER = RGB(230, 230, 250) 311 | LAVENDERBLUSH1 = RGB(255, 240, 245) 312 | LAVENDERBLUSH2 = RGB(238, 224, 229) 313 | LAVENDERBLUSH3 = RGB(205, 193, 197) 314 | LAVENDERBLUSH4 = RGB(139, 131, 134) 315 | LAWNGREEN = RGB(124, 252, 0) 316 | LEMONCHIFFON1 = RGB(255, 250, 205) 317 | LEMONCHIFFON2 = RGB(238, 233, 191) 318 | LEMONCHIFFON3 = RGB(205, 201, 165) 319 | LEMONCHIFFON4 = RGB(139, 137, 112) 320 | LIGHTBLUE = RGB(173, 216, 230) 321 | LIGHTBLUE1 = RGB(191, 239, 255) 322 | LIGHTBLUE2 = RGB(178, 223, 238) 323 | LIGHTBLUE3 = RGB(154, 192, 205) 324 | LIGHTBLUE4 = RGB(104, 131, 139) 325 | LIGHTCORAL = RGB(240, 128, 128) 326 | LIGHTCYAN1 = RGB(224, 255, 255) 327 | LIGHTCYAN2 = RGB(209, 238, 238) 328 | LIGHTCYAN3 = RGB(180, 205, 205) 329 | LIGHTCYAN4 = RGB(122, 139, 139) 330 | LIGHTGOLDENROD1 = RGB(255, 236, 139) 331 | LIGHTGOLDENROD2 = RGB(238, 220, 130) 332 | LIGHTGOLDENROD3 = RGB(205, 190, 112) 333 | LIGHTGOLDENROD4 = RGB(139, 129, 76) 334 | LIGHTGOLDENRODYELLOW = RGB(250, 250, 210) 335 | LIGHTGREY = RGB(211, 211, 211) 336 | LIGHTPINK = RGB(255, 182, 193) 337 | LIGHTPINK1 = RGB(255, 174, 185) 338 | LIGHTPINK2 = RGB(238, 162, 173) 339 | LIGHTPINK3 = RGB(205, 140, 149) 340 | LIGHTPINK4 = RGB(139, 95, 101) 341 | LIGHTSALMON1 = RGB(255, 160, 122) 342 | LIGHTSALMON2 = RGB(238, 149, 114) 343 | LIGHTSALMON3 = RGB(205, 129, 98) 344 | LIGHTSALMON4 = RGB(139, 87, 66) 345 | LIGHTSEAGREEN = RGB(32, 178, 170) 346 | LIGHTSKYBLUE = RGB(135, 206, 250) 347 | LIGHTSKYBLUE1 = RGB(176, 226, 255) 348 | LIGHTSKYBLUE2 = RGB(164, 211, 238) 349 | LIGHTSKYBLUE3 = RGB(141, 182, 205) 350 | LIGHTSKYBLUE4 = RGB(96, 123, 139) 351 | LIGHTSLATEBLUE = RGB(132, 112, 255) 352 | LIGHTSLATEGRAY = RGB(119, 136, 153) 353 | LIGHTSTEELBLUE = RGB(176, 196, 222) 354 | LIGHTSTEELBLUE1 = RGB(202, 225, 255) 355 | LIGHTSTEELBLUE2 = RGB(188, 210, 238) 356 | LIGHTSTEELBLUE3 = RGB(162, 181, 205) 357 | LIGHTSTEELBLUE4 = RGB(110, 123, 139) 358 | LIGHTYELLOW1 = RGB(255, 255, 224) 359 | LIGHTYELLOW2 = RGB(238, 238, 209) 360 | LIGHTYELLOW3 = RGB(205, 205, 180) 361 | LIGHTYELLOW4 = RGB(139, 139, 122) 362 | LIMEGREEN = RGB(50, 205, 50) 363 | LINEN = RGB(250, 240, 230) 364 | MAGENTA = RGB(255, 0, 255) 365 | MAGENTA2 = RGB(238, 0, 238) 366 | MAGENTA3 = RGB(205, 0, 205) 367 | MAGENTA4 = RGB(139, 0, 139) 368 | MANGANESEBLUE = RGB(3, 168, 158) 369 | MAROON = RGB(128, 0, 0) 370 | MAROON1 = RGB(255, 52, 179) 371 | MAROON2 = RGB(238, 48, 167) 372 | MAROON3 = RGB(205, 41, 144) 373 | MAROON4 = RGB(139, 28, 98) 374 | MEDIUMORCHID = RGB(186, 85, 211) 375 | MEDIUMORCHID1 = RGB(224, 102, 255) 376 | MEDIUMORCHID2 = RGB(209, 95, 238) 377 | MEDIUMORCHID3 = RGB(180, 82, 205) 378 | MEDIUMORCHID4 = RGB(122, 55, 139) 379 | MEDIUMPURPLE = RGB(147, 112, 219) 380 | MEDIUMPURPLE1 = RGB(171, 130, 255) 381 | MEDIUMPURPLE2 = RGB(159, 121, 238) 382 | MEDIUMPURPLE3 = RGB(137, 104, 205) 383 | MEDIUMPURPLE4 = RGB(93, 71, 139) 384 | MEDIUMSEAGREEN = RGB(60, 179, 113) 385 | MEDIUMSLATEBLUE = RGB(123, 104, 238) 386 | MEDIUMSPRINGGREEN = RGB(0, 250, 154) 387 | MEDIUMTURQUOISE = RGB(72, 209, 204) 388 | MEDIUMVIOLETRED = RGB(199, 21, 133) 389 | MELON = RGB(227, 168, 105) 390 | MIDNIGHTBLUE = RGB(25, 25, 112) 391 | MINT = RGB(189, 252, 201) 392 | MINTCREAM = RGB(245, 255, 250) 393 | MISTYROSE1 = RGB(255, 228, 225) 394 | MISTYROSE2 = RGB(238, 213, 210) 395 | MISTYROSE3 = RGB(205, 183, 181) 396 | MISTYROSE4 = RGB(139, 125, 123) 397 | MOCCASIN = RGB(255, 228, 181) 398 | NAVAJOWHITE1 = RGB(255, 222, 173) 399 | NAVAJOWHITE2 = RGB(238, 207, 161) 400 | NAVAJOWHITE3 = RGB(205, 179, 139) 401 | NAVAJOWHITE4 = RGB(139, 121, 94) 402 | NAVY = RGB(0, 0, 128) 403 | OLDLACE = RGB(253, 245, 230) 404 | OLIVE = RGB(128, 128, 0) 405 | OLIVEDRAB = RGB(107, 142, 35) 406 | OLIVEDRAB1 = RGB(192, 255, 62) 407 | OLIVEDRAB2 = RGB(179, 238, 58) 408 | OLIVEDRAB3 = RGB(154, 205, 50) 409 | OLIVEDRAB4 = RGB(105, 139, 34) 410 | ORANGE = RGB(255, 128, 0) 411 | ORANGE1 = RGB(255, 165, 0) 412 | ORANGE2 = RGB(238, 154, 0) 413 | ORANGE3 = RGB(205, 133, 0) 414 | ORANGE4 = RGB(139, 90, 0) 415 | ORANGERED1 = RGB(255, 69, 0) 416 | ORANGERED2 = RGB(238, 64, 0) 417 | ORANGERED3 = RGB(205, 55, 0) 418 | ORANGERED4 = RGB(139, 37, 0) 419 | ORCHID = RGB(218, 112, 214) 420 | ORCHID1 = RGB(255, 131, 250) 421 | ORCHID2 = RGB(238, 122, 233) 422 | ORCHID3 = RGB(205, 105, 201) 423 | ORCHID4 = RGB(139, 71, 137) 424 | PALEGOLDENROD = RGB(238, 232, 170) 425 | PALEGREEN = RGB(152, 251, 152) 426 | PALEGREEN1 = RGB(154, 255, 154) 427 | PALEGREEN2 = RGB(144, 238, 144) 428 | PALEGREEN3 = RGB(124, 205, 124) 429 | PALEGREEN4 = RGB(84, 139, 84) 430 | PALETURQUOISE1 = RGB(187, 255, 255) 431 | PALETURQUOISE2 = RGB(174, 238, 238) 432 | PALETURQUOISE3 = RGB(150, 205, 205) 433 | PALETURQUOISE4 = RGB(102, 139, 139) 434 | PALEVIOLETRED = RGB(219, 112, 147) 435 | PALEVIOLETRED1 = RGB(255, 130, 171) 436 | PALEVIOLETRED2 = RGB(238, 121, 159) 437 | PALEVIOLETRED3 = RGB(205, 104, 137) 438 | PALEVIOLETRED4 = RGB(139, 71, 93) 439 | PAPAYAWHIP = RGB(255, 239, 213) 440 | PEACHPUFF1 = RGB(255, 218, 185) 441 | PEACHPUFF2 = RGB(238, 203, 173) 442 | PEACHPUFF3 = RGB(205, 175, 149) 443 | PEACHPUFF4 = RGB(139, 119, 101) 444 | PEACOCK = RGB(51, 161, 201) 445 | PINK = RGB(255, 192, 203) 446 | PINK1 = RGB(255, 181, 197) 447 | PINK2 = RGB(238, 169, 184) 448 | PINK3 = RGB(205, 145, 158) 449 | PINK4 = RGB(139, 99, 108) 450 | PLUM = RGB(221, 160, 221) 451 | PLUM1 = RGB(255, 187, 255) 452 | PLUM2 = RGB(238, 174, 238) 453 | PLUM3 = RGB(205, 150, 205) 454 | PLUM4 = RGB(139, 102, 139) 455 | POWDERBLUE = RGB(176, 224, 230) 456 | PURPLE = RGB(128, 0, 128) 457 | PURPLE1 = RGB(155, 48, 255) 458 | PURPLE2 = RGB(145, 44, 238) 459 | PURPLE3 = RGB(125, 38, 205) 460 | PURPLE4 = RGB(85, 26, 139) 461 | RASPBERRY = RGB(135, 38, 87) 462 | RAWSIENNA = RGB(199, 97, 20) 463 | RED1 = RGB(255, 0, 0) 464 | RED2 = RGB(238, 0, 0) 465 | RED3 = RGB(205, 0, 0) 466 | RED4 = RGB(139, 0, 0) 467 | ROSYBROWN = RGB(188, 143, 143) 468 | ROSYBROWN1 = RGB(255, 193, 193) 469 | ROSYBROWN2 = RGB(238, 180, 180) 470 | ROSYBROWN3 = RGB(205, 155, 155) 471 | ROSYBROWN4 = RGB(139, 105, 105) 472 | ROYALBLUE = RGB(65, 105, 225) 473 | ROYALBLUE1 = RGB(72, 118, 255) 474 | ROYALBLUE2 = RGB(67, 110, 238) 475 | ROYALBLUE3 = RGB(58, 95, 205) 476 | ROYALBLUE4 = RGB(39, 64, 139) 477 | SALMON = RGB(250, 128, 114) 478 | SALMON1 = RGB(255, 140, 105) 479 | SALMON2 = RGB(238, 130, 98) 480 | SALMON3 = RGB(205, 112, 84) 481 | SALMON4 = RGB(139, 76, 57) 482 | SANDYBROWN = RGB(244, 164, 96) 483 | SAPGREEN = RGB(48, 128, 20) 484 | SEAGREEN1 = RGB(84, 255, 159) 485 | SEAGREEN2 = RGB(78, 238, 148) 486 | SEAGREEN3 = RGB(67, 205, 128) 487 | SEAGREEN4 = RGB(46, 139, 87) 488 | SEASHELL1 = RGB(255, 245, 238) 489 | SEASHELL2 = RGB(238, 229, 222) 490 | SEASHELL3 = RGB(205, 197, 191) 491 | SEASHELL4 = RGB(139, 134, 130) 492 | SEPIA = RGB(94, 38, 18) 493 | SGIBEET = RGB(142, 56, 142) 494 | SGIBRIGHTGRAY = RGB(197, 193, 170) 495 | SGICHARTREUSE = RGB(113, 198, 113) 496 | SGIDARKGRAY = RGB(85, 85, 85) 497 | SGIGRAY12 = RGB(30, 30, 30) 498 | SGIGRAY16 = RGB(40, 40, 40) 499 | SGIGRAY32 = RGB(81, 81, 81) 500 | SGIGRAY36 = RGB(91, 91, 91) 501 | SGIGRAY52 = RGB(132, 132, 132) 502 | SGIGRAY56 = RGB(142, 142, 142) 503 | SGIGRAY72 = RGB(183, 183, 183) 504 | SGIGRAY76 = RGB(193, 193, 193) 505 | SGIGRAY92 = RGB(234, 234, 234) 506 | SGIGRAY96 = RGB(244, 244, 244) 507 | SGILIGHTBLUE = RGB(125, 158, 192) 508 | SGILIGHTGRAY = RGB(170, 170, 170) 509 | SGIOLIVEDRAB = RGB(142, 142, 56) 510 | SGISALMON = RGB(198, 113, 113) 511 | SGISLATEBLUE = RGB(113, 113, 198) 512 | SGITEAL = RGB(56, 142, 142) 513 | SIENNA = RGB(160, 82, 45) 514 | SIENNA1 = RGB(255, 130, 71) 515 | SIENNA2 = RGB(238, 121, 66) 516 | SIENNA3 = RGB(205, 104, 57) 517 | SIENNA4 = RGB(139, 71, 38) 518 | SILVER = RGB(192, 192, 192) 519 | SKYBLUE = RGB(135, 206, 235) 520 | SKYBLUE1 = RGB(135, 206, 255) 521 | SKYBLUE2 = RGB(126, 192, 238) 522 | SKYBLUE3 = RGB(108, 166, 205) 523 | SKYBLUE4 = RGB(74, 112, 139) 524 | SLATEBLUE = RGB(106, 90, 205) 525 | SLATEBLUE1 = RGB(131, 111, 255) 526 | SLATEBLUE2 = RGB(122, 103, 238) 527 | SLATEBLUE3 = RGB(105, 89, 205) 528 | SLATEBLUE4 = RGB(71, 60, 139) 529 | SLATEGRAY = RGB(112, 128, 144) 530 | SLATEGRAY1 = RGB(198, 226, 255) 531 | SLATEGRAY2 = RGB(185, 211, 238) 532 | SLATEGRAY3 = RGB(159, 182, 205) 533 | SLATEGRAY4 = RGB(108, 123, 139) 534 | SNOW1 = RGB(255, 250, 250) 535 | SNOW2 = RGB(238, 233, 233) 536 | SNOW3 = RGB(205, 201, 201) 537 | SNOW4 = RGB(139, 137, 137) 538 | SPRINGGREEN = RGB(0, 255, 127) 539 | SPRINGGREEN1 = RGB(0, 238, 118) 540 | SPRINGGREEN2 = RGB(0, 205, 102) 541 | SPRINGGREEN3 = RGB(0, 139, 69) 542 | STEELBLUE = RGB(70, 130, 180) 543 | STEELBLUE1 = RGB(99, 184, 255) 544 | STEELBLUE2 = RGB(92, 172, 238) 545 | STEELBLUE3 = RGB(79, 148, 205) 546 | STEELBLUE4 = RGB(54, 100, 139) 547 | TAN = RGB(210, 180, 140) 548 | TAN1 = RGB(255, 165, 79) 549 | TAN2 = RGB(238, 154, 73) 550 | TAN3 = RGB(205, 133, 63) 551 | TAN4 = RGB(139, 90, 43) 552 | TEAL = RGB(0, 128, 128) 553 | THISTLE = RGB(216, 191, 216) 554 | THISTLE1 = RGB(255, 225, 255) 555 | THISTLE2 = RGB(238, 210, 238) 556 | THISTLE3 = RGB(205, 181, 205) 557 | THISTLE4 = RGB(139, 123, 139) 558 | TOMATO1 = RGB(255, 99, 71) 559 | TOMATO2 = RGB(238, 92, 66) 560 | TOMATO3 = RGB(205, 79, 57) 561 | TOMATO4 = RGB(139, 54, 38) 562 | TURQUOISE = RGB(64, 224, 208) 563 | TURQUOISE1 = RGB(0, 245, 255) 564 | TURQUOISE2 = RGB(0, 229, 238) 565 | TURQUOISE3 = RGB(0, 197, 205) 566 | TURQUOISE4 = RGB(0, 134, 139) 567 | TURQUOISEBLUE = RGB(0, 199, 140) 568 | VIOLET = RGB(238, 130, 238) 569 | VIOLETRED = RGB(208, 32, 144) 570 | VIOLETRED1 = RGB(255, 62, 150) 571 | VIOLETRED2 = RGB(238, 58, 140) 572 | VIOLETRED3 = RGB(205, 50, 120) 573 | VIOLETRED4 = RGB(139, 34, 82) 574 | WARMGREY = RGB(128, 128, 105) 575 | WHEAT = RGB(245, 222, 179) 576 | WHEAT1 = RGB(255, 231, 186) 577 | WHEAT2 = RGB(238, 216, 174) 578 | WHEAT3 = RGB(205, 186, 150) 579 | WHEAT4 = RGB(139, 126, 102) 580 | WHITE = RGB(255, 255, 255) 581 | WHITESMOKE = RGB(245, 245, 245) 582 | WHITESMOKE = RGB(245, 245, 245) 583 | YELLOW1 = RGB(255, 255, 0) 584 | YELLOW2 = RGB(238, 238, 0) 585 | YELLOW3 = RGB(205, 205, 0) 586 | YELLOW4 = RGB(139, 139, 0) 587 | 588 | #Add colors to colors dictionary 589 | colors['aliceblue'] = ALICEBLUE 590 | colors['antiquewhite'] = ANTIQUEWHITE 591 | colors['antiquewhite1'] = ANTIQUEWHITE1 592 | colors['antiquewhite2'] = ANTIQUEWHITE2 593 | colors['antiquewhite3'] = ANTIQUEWHITE3 594 | colors['antiquewhite4'] = ANTIQUEWHITE4 595 | colors['aqua'] = AQUA 596 | colors['aquamarine1'] = AQUAMARINE1 597 | colors['aquamarine2'] = AQUAMARINE2 598 | colors['aquamarine3'] = AQUAMARINE3 599 | colors['aquamarine4'] = AQUAMARINE4 600 | colors['azure1'] = AZURE1 601 | colors['azure2'] = AZURE2 602 | colors['azure3'] = AZURE3 603 | colors['azure4'] = AZURE4 604 | colors['banana'] = BANANA 605 | colors['beige'] = BEIGE 606 | colors['bisque1'] = BISQUE1 607 | colors['bisque2'] = BISQUE2 608 | colors['bisque3'] = BISQUE3 609 | colors['bisque4'] = BISQUE4 610 | colors['black'] = BLACK 611 | colors['blanchedalmond'] = BLANCHEDALMOND 612 | colors['blue'] = BLUE 613 | colors['blue2'] = BLUE2 614 | colors['blue3'] = BLUE3 615 | colors['blue4'] = BLUE4 616 | colors['blueviolet'] = BLUEVIOLET 617 | colors['brick'] = BRICK 618 | colors['brown'] = BROWN 619 | colors['brown1'] = BROWN1 620 | colors['brown2'] = BROWN2 621 | colors['brown3'] = BROWN3 622 | colors['brown4'] = BROWN4 623 | colors['burlywood'] = BURLYWOOD 624 | colors['burlywood1'] = BURLYWOOD1 625 | colors['burlywood2'] = BURLYWOOD2 626 | colors['burlywood3'] = BURLYWOOD3 627 | colors['burlywood4'] = BURLYWOOD4 628 | colors['burntsienna'] = BURNTSIENNA 629 | colors['burntumber'] = BURNTUMBER 630 | colors['cadetblue'] = CADETBLUE 631 | colors['cadetblue1'] = CADETBLUE1 632 | colors['cadetblue2'] = CADETBLUE2 633 | colors['cadetblue3'] = CADETBLUE3 634 | colors['cadetblue4'] = CADETBLUE4 635 | colors['cadmiumorange'] = CADMIUMORANGE 636 | colors['cadmiumyellow'] = CADMIUMYELLOW 637 | colors['carrot'] = CARROT 638 | colors['chartreuse1'] = CHARTREUSE1 639 | colors['chartreuse2'] = CHARTREUSE2 640 | colors['chartreuse3'] = CHARTREUSE3 641 | colors['chartreuse4'] = CHARTREUSE4 642 | colors['chocolate'] = CHOCOLATE 643 | colors['chocolate1'] = CHOCOLATE1 644 | colors['chocolate2'] = CHOCOLATE2 645 | colors['chocolate3'] = CHOCOLATE3 646 | colors['chocolate4'] = CHOCOLATE4 647 | colors['cobalt'] = COBALT 648 | colors['cobaltgreen'] = COBALTGREEN 649 | colors['coldgrey'] = COLDGREY 650 | colors['coral'] = CORAL 651 | colors['coral1'] = CORAL1 652 | colors['coral2'] = CORAL2 653 | colors['coral3'] = CORAL3 654 | colors['coral4'] = CORAL4 655 | colors['cornflowerblue'] = CORNFLOWERBLUE 656 | colors['cornsilk1'] = CORNSILK1 657 | colors['cornsilk2'] = CORNSILK2 658 | colors['cornsilk3'] = CORNSILK3 659 | colors['cornsilk4'] = CORNSILK4 660 | colors['crimson'] = CRIMSON 661 | colors['cyan2'] = CYAN2 662 | colors['cyan3'] = CYAN3 663 | colors['cyan4'] = CYAN4 664 | colors['darkgoldenrod'] = DARKGOLDENROD 665 | colors['darkgoldenrod1'] = DARKGOLDENROD1 666 | colors['darkgoldenrod2'] = DARKGOLDENROD2 667 | colors['darkgoldenrod3'] = DARKGOLDENROD3 668 | colors['darkgoldenrod4'] = DARKGOLDENROD4 669 | colors['darkgray'] = DARKGRAY 670 | colors['darkgreen'] = DARKGREEN 671 | colors['darkkhaki'] = DARKKHAKI 672 | colors['darkolivegreen'] = DARKOLIVEGREEN 673 | colors['darkolivegreen1'] = DARKOLIVEGREEN1 674 | colors['darkolivegreen2'] = DARKOLIVEGREEN2 675 | colors['darkolivegreen3'] = DARKOLIVEGREEN3 676 | colors['darkolivegreen4'] = DARKOLIVEGREEN4 677 | colors['darkorange'] = DARKORANGE 678 | colors['darkorange1'] = DARKORANGE1 679 | colors['darkorange2'] = DARKORANGE2 680 | colors['darkorange3'] = DARKORANGE3 681 | colors['darkorange4'] = DARKORANGE4 682 | colors['darkorchid'] = DARKORCHID 683 | colors['darkorchid1'] = DARKORCHID1 684 | colors['darkorchid2'] = DARKORCHID2 685 | colors['darkorchid3'] = DARKORCHID3 686 | colors['darkorchid4'] = DARKORCHID4 687 | colors['darksalmon'] = DARKSALMON 688 | colors['darkseagreen'] = DARKSEAGREEN 689 | colors['darkseagreen1'] = DARKSEAGREEN1 690 | colors['darkseagreen2'] = DARKSEAGREEN2 691 | colors['darkseagreen3'] = DARKSEAGREEN3 692 | colors['darkseagreen4'] = DARKSEAGREEN4 693 | colors['darkslateblue'] = DARKSLATEBLUE 694 | colors['darkslategray'] = DARKSLATEGRAY 695 | colors['darkslategray1'] = DARKSLATEGRAY1 696 | colors['darkslategray2'] = DARKSLATEGRAY2 697 | colors['darkslategray3'] = DARKSLATEGRAY3 698 | colors['darkslategray4'] = DARKSLATEGRAY4 699 | colors['darkturquoise'] = DARKTURQUOISE 700 | colors['darkviolet'] = DARKVIOLET 701 | colors['deeppink1'] = DEEPPINK1 702 | colors['deeppink2'] = DEEPPINK2 703 | colors['deeppink3'] = DEEPPINK3 704 | colors['deeppink4'] = DEEPPINK4 705 | colors['deepskyblue1'] = DEEPSKYBLUE1 706 | colors['deepskyblue2'] = DEEPSKYBLUE2 707 | colors['deepskyblue3'] = DEEPSKYBLUE3 708 | colors['deepskyblue4'] = DEEPSKYBLUE4 709 | colors['dimgray'] = DIMGRAY 710 | colors['dimgray'] = DIMGRAY 711 | colors['dodgerblue1'] = DODGERBLUE1 712 | colors['dodgerblue2'] = DODGERBLUE2 713 | colors['dodgerblue3'] = DODGERBLUE3 714 | colors['dodgerblue4'] = DODGERBLUE4 715 | colors['eggshell'] = EGGSHELL 716 | colors['emeraldgreen'] = EMERALDGREEN 717 | colors['firebrick'] = FIREBRICK 718 | colors['firebrick1'] = FIREBRICK1 719 | colors['firebrick2'] = FIREBRICK2 720 | colors['firebrick3'] = FIREBRICK3 721 | colors['firebrick4'] = FIREBRICK4 722 | colors['flesh'] = FLESH 723 | colors['floralwhite'] = FLORALWHITE 724 | colors['forestgreen'] = FORESTGREEN 725 | colors['gainsboro'] = GAINSBORO 726 | colors['ghostwhite'] = GHOSTWHITE 727 | colors['gold1'] = GOLD1 728 | colors['gold2'] = GOLD2 729 | colors['gold3'] = GOLD3 730 | colors['gold4'] = GOLD4 731 | colors['goldenrod'] = GOLDENROD 732 | colors['goldenrod1'] = GOLDENROD1 733 | colors['goldenrod2'] = GOLDENROD2 734 | colors['goldenrod3'] = GOLDENROD3 735 | colors['goldenrod4'] = GOLDENROD4 736 | colors['gray'] = GRAY 737 | colors['gray1'] = GRAY1 738 | colors['gray10'] = GRAY10 739 | colors['gray11'] = GRAY11 740 | colors['gray12'] = GRAY12 741 | colors['gray13'] = GRAY13 742 | colors['gray14'] = GRAY14 743 | colors['gray15'] = GRAY15 744 | colors['gray16'] = GRAY16 745 | colors['gray17'] = GRAY17 746 | colors['gray18'] = GRAY18 747 | colors['gray19'] = GRAY19 748 | colors['gray2'] = GRAY2 749 | colors['gray20'] = GRAY20 750 | colors['gray21'] = GRAY21 751 | colors['gray22'] = GRAY22 752 | colors['gray23'] = GRAY23 753 | colors['gray24'] = GRAY24 754 | colors['gray25'] = GRAY25 755 | colors['gray26'] = GRAY26 756 | colors['gray27'] = GRAY27 757 | colors['gray28'] = GRAY28 758 | colors['gray29'] = GRAY29 759 | colors['gray3'] = GRAY3 760 | colors['gray30'] = GRAY30 761 | colors['gray31'] = GRAY31 762 | colors['gray32'] = GRAY32 763 | colors['gray33'] = GRAY33 764 | colors['gray34'] = GRAY34 765 | colors['gray35'] = GRAY35 766 | colors['gray36'] = GRAY36 767 | colors['gray37'] = GRAY37 768 | colors['gray38'] = GRAY38 769 | colors['gray39'] = GRAY39 770 | colors['gray4'] = GRAY4 771 | colors['gray40'] = GRAY40 772 | colors['gray42'] = GRAY42 773 | colors['gray43'] = GRAY43 774 | colors['gray44'] = GRAY44 775 | colors['gray45'] = GRAY45 776 | colors['gray46'] = GRAY46 777 | colors['gray47'] = GRAY47 778 | colors['gray48'] = GRAY48 779 | colors['gray49'] = GRAY49 780 | colors['gray5'] = GRAY5 781 | colors['gray50'] = GRAY50 782 | colors['gray51'] = GRAY51 783 | colors['gray52'] = GRAY52 784 | colors['gray53'] = GRAY53 785 | colors['gray54'] = GRAY54 786 | colors['gray55'] = GRAY55 787 | colors['gray56'] = GRAY56 788 | colors['gray57'] = GRAY57 789 | colors['gray58'] = GRAY58 790 | colors['gray59'] = GRAY59 791 | colors['gray6'] = GRAY6 792 | colors['gray60'] = GRAY60 793 | colors['gray61'] = GRAY61 794 | colors['gray62'] = GRAY62 795 | colors['gray63'] = GRAY63 796 | colors['gray64'] = GRAY64 797 | colors['gray65'] = GRAY65 798 | colors['gray66'] = GRAY66 799 | colors['gray67'] = GRAY67 800 | colors['gray68'] = GRAY68 801 | colors['gray69'] = GRAY69 802 | colors['gray7'] = GRAY7 803 | colors['gray70'] = GRAY70 804 | colors['gray71'] = GRAY71 805 | colors['gray72'] = GRAY72 806 | colors['gray73'] = GRAY73 807 | colors['gray74'] = GRAY74 808 | colors['gray75'] = GRAY75 809 | colors['gray76'] = GRAY76 810 | colors['gray77'] = GRAY77 811 | colors['gray78'] = GRAY78 812 | colors['gray79'] = GRAY79 813 | colors['gray8'] = GRAY8 814 | colors['gray80'] = GRAY80 815 | colors['gray81'] = GRAY81 816 | colors['gray82'] = GRAY82 817 | colors['gray83'] = GRAY83 818 | colors['gray84'] = GRAY84 819 | colors['gray85'] = GRAY85 820 | colors['gray86'] = GRAY86 821 | colors['gray87'] = GRAY87 822 | colors['gray88'] = GRAY88 823 | colors['gray89'] = GRAY89 824 | colors['gray9'] = GRAY9 825 | colors['gray90'] = GRAY90 826 | colors['gray91'] = GRAY91 827 | colors['gray92'] = GRAY92 828 | colors['gray93'] = GRAY93 829 | colors['gray94'] = GRAY94 830 | colors['gray95'] = GRAY95 831 | colors['gray97'] = GRAY97 832 | colors['gray98'] = GRAY98 833 | colors['gray99'] = GRAY99 834 | colors['green'] = GREEN 835 | colors['green1'] = GREEN1 836 | colors['green2'] = GREEN2 837 | colors['green3'] = GREEN3 838 | colors['green4'] = GREEN4 839 | colors['greenyellow'] = GREENYELLOW 840 | colors['honeydew1'] = HONEYDEW1 841 | colors['honeydew2'] = HONEYDEW2 842 | colors['honeydew3'] = HONEYDEW3 843 | colors['honeydew4'] = HONEYDEW4 844 | colors['hotpink'] = HOTPINK 845 | colors['hotpink1'] = HOTPINK1 846 | colors['hotpink2'] = HOTPINK2 847 | colors['hotpink3'] = HOTPINK3 848 | colors['hotpink4'] = HOTPINK4 849 | colors['indianred'] = INDIANRED 850 | colors['indianred'] = INDIANRED 851 | colors['indianred1'] = INDIANRED1 852 | colors['indianred2'] = INDIANRED2 853 | colors['indianred3'] = INDIANRED3 854 | colors['indianred4'] = INDIANRED4 855 | colors['indigo'] = INDIGO 856 | colors['ivory1'] = IVORY1 857 | colors['ivory2'] = IVORY2 858 | colors['ivory3'] = IVORY3 859 | colors['ivory4'] = IVORY4 860 | colors['ivoryblack'] = IVORYBLACK 861 | colors['khaki'] = KHAKI 862 | colors['khaki1'] = KHAKI1 863 | colors['khaki2'] = KHAKI2 864 | colors['khaki3'] = KHAKI3 865 | colors['khaki4'] = KHAKI4 866 | colors['lavender'] = LAVENDER 867 | colors['lavenderblush1'] = LAVENDERBLUSH1 868 | colors['lavenderblush2'] = LAVENDERBLUSH2 869 | colors['lavenderblush3'] = LAVENDERBLUSH3 870 | colors['lavenderblush4'] = LAVENDERBLUSH4 871 | colors['lawngreen'] = LAWNGREEN 872 | colors['lemonchiffon1'] = LEMONCHIFFON1 873 | colors['lemonchiffon2'] = LEMONCHIFFON2 874 | colors['lemonchiffon3'] = LEMONCHIFFON3 875 | colors['lemonchiffon4'] = LEMONCHIFFON4 876 | colors['lightblue'] = LIGHTBLUE 877 | colors['lightblue1'] = LIGHTBLUE1 878 | colors['lightblue2'] = LIGHTBLUE2 879 | colors['lightblue3'] = LIGHTBLUE3 880 | colors['lightblue4'] = LIGHTBLUE4 881 | colors['lightcoral'] = LIGHTCORAL 882 | colors['lightcyan1'] = LIGHTCYAN1 883 | colors['lightcyan2'] = LIGHTCYAN2 884 | colors['lightcyan3'] = LIGHTCYAN3 885 | colors['lightcyan4'] = LIGHTCYAN4 886 | colors['lightgoldenrod1'] = LIGHTGOLDENROD1 887 | colors['lightgoldenrod2'] = LIGHTGOLDENROD2 888 | colors['lightgoldenrod3'] = LIGHTGOLDENROD3 889 | colors['lightgoldenrod4'] = LIGHTGOLDENROD4 890 | colors['lightgoldenrodyellow'] = LIGHTGOLDENRODYELLOW 891 | colors['lightgrey'] = LIGHTGREY 892 | colors['lightpink'] = LIGHTPINK 893 | colors['lightpink1'] = LIGHTPINK1 894 | colors['lightpink2'] = LIGHTPINK2 895 | colors['lightpink3'] = LIGHTPINK3 896 | colors['lightpink4'] = LIGHTPINK4 897 | colors['lightsalmon1'] = LIGHTSALMON1 898 | colors['lightsalmon2'] = LIGHTSALMON2 899 | colors['lightsalmon3'] = LIGHTSALMON3 900 | colors['lightsalmon4'] = LIGHTSALMON4 901 | colors['lightseagreen'] = LIGHTSEAGREEN 902 | colors['lightskyblue'] = LIGHTSKYBLUE 903 | colors['lightskyblue1'] = LIGHTSKYBLUE1 904 | colors['lightskyblue2'] = LIGHTSKYBLUE2 905 | colors['lightskyblue3'] = LIGHTSKYBLUE3 906 | colors['lightskyblue4'] = LIGHTSKYBLUE4 907 | colors['lightslateblue'] = LIGHTSLATEBLUE 908 | colors['lightslategray'] = LIGHTSLATEGRAY 909 | colors['lightsteelblue'] = LIGHTSTEELBLUE 910 | colors['lightsteelblue1'] = LIGHTSTEELBLUE1 911 | colors['lightsteelblue2'] = LIGHTSTEELBLUE2 912 | colors['lightsteelblue3'] = LIGHTSTEELBLUE3 913 | colors['lightsteelblue4'] = LIGHTSTEELBLUE4 914 | colors['lightyellow1'] = LIGHTYELLOW1 915 | colors['lightyellow2'] = LIGHTYELLOW2 916 | colors['lightyellow3'] = LIGHTYELLOW3 917 | colors['lightyellow4'] = LIGHTYELLOW4 918 | colors['limegreen'] = LIMEGREEN 919 | colors['linen'] = LINEN 920 | colors['magenta'] = MAGENTA 921 | colors['magenta2'] = MAGENTA2 922 | colors['magenta3'] = MAGENTA3 923 | colors['magenta4'] = MAGENTA4 924 | colors['manganeseblue'] = MANGANESEBLUE 925 | colors['maroon'] = MAROON 926 | colors['maroon1'] = MAROON1 927 | colors['maroon2'] = MAROON2 928 | colors['maroon3'] = MAROON3 929 | colors['maroon4'] = MAROON4 930 | colors['mediumorchid'] = MEDIUMORCHID 931 | colors['mediumorchid1'] = MEDIUMORCHID1 932 | colors['mediumorchid2'] = MEDIUMORCHID2 933 | colors['mediumorchid3'] = MEDIUMORCHID3 934 | colors['mediumorchid4'] = MEDIUMORCHID4 935 | colors['mediumpurple'] = MEDIUMPURPLE 936 | colors['mediumpurple1'] = MEDIUMPURPLE1 937 | colors['mediumpurple2'] = MEDIUMPURPLE2 938 | colors['mediumpurple3'] = MEDIUMPURPLE3 939 | colors['mediumpurple4'] = MEDIUMPURPLE4 940 | colors['mediumseagreen'] = MEDIUMSEAGREEN 941 | colors['mediumslateblue'] = MEDIUMSLATEBLUE 942 | colors['mediumspringgreen'] = MEDIUMSPRINGGREEN 943 | colors['mediumturquoise'] = MEDIUMTURQUOISE 944 | colors['mediumvioletred'] = MEDIUMVIOLETRED 945 | colors['melon'] = MELON 946 | colors['midnightblue'] = MIDNIGHTBLUE 947 | colors['mint'] = MINT 948 | colors['mintcream'] = MINTCREAM 949 | colors['mistyrose1'] = MISTYROSE1 950 | colors['mistyrose2'] = MISTYROSE2 951 | colors['mistyrose3'] = MISTYROSE3 952 | colors['mistyrose4'] = MISTYROSE4 953 | colors['moccasin'] = MOCCASIN 954 | colors['navajowhite1'] = NAVAJOWHITE1 955 | colors['navajowhite2'] = NAVAJOWHITE2 956 | colors['navajowhite3'] = NAVAJOWHITE3 957 | colors['navajowhite4'] = NAVAJOWHITE4 958 | colors['navy'] = NAVY 959 | colors['oldlace'] = OLDLACE 960 | colors['olive'] = OLIVE 961 | colors['olivedrab'] = OLIVEDRAB 962 | colors['olivedrab1'] = OLIVEDRAB1 963 | colors['olivedrab2'] = OLIVEDRAB2 964 | colors['olivedrab3'] = OLIVEDRAB3 965 | colors['olivedrab4'] = OLIVEDRAB4 966 | colors['orange'] = ORANGE 967 | colors['orange1'] = ORANGE1 968 | colors['orange2'] = ORANGE2 969 | colors['orange3'] = ORANGE3 970 | colors['orange4'] = ORANGE4 971 | colors['orangered1'] = ORANGERED1 972 | colors['orangered2'] = ORANGERED2 973 | colors['orangered3'] = ORANGERED3 974 | colors['orangered4'] = ORANGERED4 975 | colors['orchid'] = ORCHID 976 | colors['orchid1'] = ORCHID1 977 | colors['orchid2'] = ORCHID2 978 | colors['orchid3'] = ORCHID3 979 | colors['orchid4'] = ORCHID4 980 | colors['palegoldenrod'] = PALEGOLDENROD 981 | colors['palegreen'] = PALEGREEN 982 | colors['palegreen1'] = PALEGREEN1 983 | colors['palegreen2'] = PALEGREEN2 984 | colors['palegreen3'] = PALEGREEN3 985 | colors['palegreen4'] = PALEGREEN4 986 | colors['paleturquoise1'] = PALETURQUOISE1 987 | colors['paleturquoise2'] = PALETURQUOISE2 988 | colors['paleturquoise3'] = PALETURQUOISE3 989 | colors['paleturquoise4'] = PALETURQUOISE4 990 | colors['palevioletred'] = PALEVIOLETRED 991 | colors['palevioletred1'] = PALEVIOLETRED1 992 | colors['palevioletred2'] = PALEVIOLETRED2 993 | colors['palevioletred3'] = PALEVIOLETRED3 994 | colors['palevioletred4'] = PALEVIOLETRED4 995 | colors['papayawhip'] = PAPAYAWHIP 996 | colors['peachpuff1'] = PEACHPUFF1 997 | colors['peachpuff2'] = PEACHPUFF2 998 | colors['peachpuff3'] = PEACHPUFF3 999 | colors['peachpuff4'] = PEACHPUFF4 1000 | colors['peacock'] = PEACOCK 1001 | colors['pink'] = PINK 1002 | colors['pink1'] = PINK1 1003 | colors['pink2'] = PINK2 1004 | colors['pink3'] = PINK3 1005 | colors['pink4'] = PINK4 1006 | colors['plum'] = PLUM 1007 | colors['plum1'] = PLUM1 1008 | colors['plum2'] = PLUM2 1009 | colors['plum3'] = PLUM3 1010 | colors['plum4'] = PLUM4 1011 | colors['powderblue'] = POWDERBLUE 1012 | colors['purple'] = PURPLE 1013 | colors['purple1'] = PURPLE1 1014 | colors['purple2'] = PURPLE2 1015 | colors['purple3'] = PURPLE3 1016 | colors['purple4'] = PURPLE4 1017 | colors['raspberry'] = RASPBERRY 1018 | colors['rawsienna'] = RAWSIENNA 1019 | colors['red1'] = RED1 1020 | colors['red2'] = RED2 1021 | colors['red3'] = RED3 1022 | colors['red4'] = RED4 1023 | colors['rosybrown'] = ROSYBROWN 1024 | colors['rosybrown1'] = ROSYBROWN1 1025 | colors['rosybrown2'] = ROSYBROWN2 1026 | colors['rosybrown3'] = ROSYBROWN3 1027 | colors['rosybrown4'] = ROSYBROWN4 1028 | colors['royalblue'] = ROYALBLUE 1029 | colors['royalblue1'] = ROYALBLUE1 1030 | colors['royalblue2'] = ROYALBLUE2 1031 | colors['royalblue3'] = ROYALBLUE3 1032 | colors['royalblue4'] = ROYALBLUE4 1033 | colors['salmon'] = SALMON 1034 | colors['salmon1'] = SALMON1 1035 | colors['salmon2'] = SALMON2 1036 | colors['salmon3'] = SALMON3 1037 | colors['salmon4'] = SALMON4 1038 | colors['sandybrown'] = SANDYBROWN 1039 | colors['sapgreen'] = SAPGREEN 1040 | colors['seagreen1'] = SEAGREEN1 1041 | colors['seagreen2'] = SEAGREEN2 1042 | colors['seagreen3'] = SEAGREEN3 1043 | colors['seagreen4'] = SEAGREEN4 1044 | colors['seashell1'] = SEASHELL1 1045 | colors['seashell2'] = SEASHELL2 1046 | colors['seashell3'] = SEASHELL3 1047 | colors['seashell4'] = SEASHELL4 1048 | colors['sepia'] = SEPIA 1049 | colors['sgibeet'] = SGIBEET 1050 | colors['sgibrightgray'] = SGIBRIGHTGRAY 1051 | colors['sgichartreuse'] = SGICHARTREUSE 1052 | colors['sgidarkgray'] = SGIDARKGRAY 1053 | colors['sgigray12'] = SGIGRAY12 1054 | colors['sgigray16'] = SGIGRAY16 1055 | colors['sgigray32'] = SGIGRAY32 1056 | colors['sgigray36'] = SGIGRAY36 1057 | colors['sgigray52'] = SGIGRAY52 1058 | colors['sgigray56'] = SGIGRAY56 1059 | colors['sgigray72'] = SGIGRAY72 1060 | colors['sgigray76'] = SGIGRAY76 1061 | colors['sgigray92'] = SGIGRAY92 1062 | colors['sgigray96'] = SGIGRAY96 1063 | colors['sgilightblue'] = SGILIGHTBLUE 1064 | colors['sgilightgray'] = SGILIGHTGRAY 1065 | colors['sgiolivedrab'] = SGIOLIVEDRAB 1066 | colors['sgisalmon'] = SGISALMON 1067 | colors['sgislateblue'] = SGISLATEBLUE 1068 | colors['sgiteal'] = SGITEAL 1069 | colors['sienna'] = SIENNA 1070 | colors['sienna1'] = SIENNA1 1071 | colors['sienna2'] = SIENNA2 1072 | colors['sienna3'] = SIENNA3 1073 | colors['sienna4'] = SIENNA4 1074 | colors['silver'] = SILVER 1075 | colors['skyblue'] = SKYBLUE 1076 | colors['skyblue1'] = SKYBLUE1 1077 | colors['skyblue2'] = SKYBLUE2 1078 | colors['skyblue3'] = SKYBLUE3 1079 | colors['skyblue4'] = SKYBLUE4 1080 | colors['slateblue'] = SLATEBLUE 1081 | colors['slateblue1'] = SLATEBLUE1 1082 | colors['slateblue2'] = SLATEBLUE2 1083 | colors['slateblue3'] = SLATEBLUE3 1084 | colors['slateblue4'] = SLATEBLUE4 1085 | colors['slategray'] = SLATEGRAY 1086 | colors['slategray1'] = SLATEGRAY1 1087 | colors['slategray2'] = SLATEGRAY2 1088 | colors['slategray3'] = SLATEGRAY3 1089 | colors['slategray4'] = SLATEGRAY4 1090 | colors['snow1'] = SNOW1 1091 | colors['snow2'] = SNOW2 1092 | colors['snow3'] = SNOW3 1093 | colors['snow4'] = SNOW4 1094 | colors['springgreen'] = SPRINGGREEN 1095 | colors['springgreen1'] = SPRINGGREEN1 1096 | colors['springgreen2'] = SPRINGGREEN2 1097 | colors['springgreen3'] = SPRINGGREEN3 1098 | colors['steelblue'] = STEELBLUE 1099 | colors['steelblue1'] = STEELBLUE1 1100 | colors['steelblue2'] = STEELBLUE2 1101 | colors['steelblue3'] = STEELBLUE3 1102 | colors['steelblue4'] = STEELBLUE4 1103 | colors['tan'] = TAN 1104 | colors['tan1'] = TAN1 1105 | colors['tan2'] = TAN2 1106 | colors['tan3'] = TAN3 1107 | colors['tan4'] = TAN4 1108 | colors['teal'] = TEAL 1109 | colors['thistle'] = THISTLE 1110 | colors['thistle1'] = THISTLE1 1111 | colors['thistle2'] = THISTLE2 1112 | colors['thistle3'] = THISTLE3 1113 | colors['thistle4'] = THISTLE4 1114 | colors['tomato1'] = TOMATO1 1115 | colors['tomato2'] = TOMATO2 1116 | colors['tomato3'] = TOMATO3 1117 | colors['tomato4'] = TOMATO4 1118 | colors['turquoise'] = TURQUOISE 1119 | colors['turquoise1'] = TURQUOISE1 1120 | colors['turquoise2'] = TURQUOISE2 1121 | colors['turquoise3'] = TURQUOISE3 1122 | colors['turquoise4'] = TURQUOISE4 1123 | colors['turquoiseblue'] = TURQUOISEBLUE 1124 | colors['violet'] = VIOLET 1125 | colors['violetred'] = VIOLETRED 1126 | colors['violetred1'] = VIOLETRED1 1127 | colors['violetred2'] = VIOLETRED2 1128 | colors['violetred3'] = VIOLETRED3 1129 | colors['violetred4'] = VIOLETRED4 1130 | colors['warmgrey'] = WARMGREY 1131 | colors['wheat'] = WHEAT 1132 | colors['wheat1'] = WHEAT1 1133 | colors['wheat2'] = WHEAT2 1134 | colors['wheat3'] = WHEAT3 1135 | colors['wheat4'] = WHEAT4 1136 | colors['white'] = WHITE 1137 | colors['whitesmoke'] = WHITESMOKE 1138 | colors['whitesmoke'] = WHITESMOKE 1139 | colors['yellow1'] = YELLOW1 1140 | colors['yellow2'] = YELLOW2 1141 | colors['yellow3'] = YELLOW3 1142 | colors['yellow4'] = YELLOW4 1143 | 1144 | colors = OrderedDict(sorted(colors.items(), key=lambda t: t[0])) 1145 | -------------------------------------------------------------------------------- /datasets/general_dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import datasets.color_constants as cc 4 | from tools.lazy_decorator import * 5 | from typing import Tuple, List, Dict 6 | import logging 7 | 8 | 9 | class GeneralDataset: 10 | """ 11 | Class used for reading in datasets for training/testing. 12 | Parameterized in order to handle different kinds of datasets (e.g. k-fold datasets) 13 | """ 14 | 15 | @property 16 | def data_path(self) -> str: 17 | return self._data_path 18 | 19 | @property 20 | def data(self) -> List[np.ndarray]: 21 | return self._data 22 | 23 | @property 24 | def full_sized_data(self) -> Dict[str, np.ndarray]: 25 | return self._full_sized_data 26 | 27 | @property 28 | def file_names(self) -> List[str]: 29 | return self._file_names 30 | 31 | @property 32 | def train_pc_idx(self) -> List[int]: 33 | return self._train_pc_idx 34 | 35 | @property 36 | def test_pc_idx(self) -> List[int]: 37 | return self._test_pc_idx 38 | 39 | def __init__(self, data_path: str, is_train: bool, test_sets: list, 40 | downsample_prefix: str, is_colors: bool, is_laser: bool, n_classes=None): 41 | self._test_sets = test_sets 42 | self._downsample_prefix = downsample_prefix 43 | self._is_colors = is_colors 44 | self._is_laser = is_laser 45 | 46 | # it is possible that there is no class information given for test sets 47 | if n_classes is None: 48 | self._is_class = True 49 | else: 50 | self._is_class = False 51 | self._num_classes = n_classes 52 | 53 | self._data_path = data_path 54 | self._data, self._file_names, self._full_sized_data = self._load(is_train) 55 | 56 | # log some dataset properties 57 | logging.debug(f"number of features: {self.num_features}") 58 | logging.debug(f"number of classes: {self.num_classes}") 59 | logging.debug(f"number of training samples: {len(self.train_pc_idx)}") 60 | logging.debug(f"number of test samples: {len(self.test_pc_idx)}") 61 | 62 | @lazy_property 63 | def num_classes(self) -> int: 64 | """ 65 | calculate the number of unique class labels if class information is given in npy-file. 66 | Otherwise, just return the number of classes which have been defined in the constructor 67 | :return: number of classes for this dataset 68 | """ 69 | if self._is_class: 70 | # assuming that labels are in the last column 71 | # counting unique class labels of all pointclouds 72 | _num_classes = len(np.unique(np.concatenate([np.unique(pointcloud[:, -1]) 73 | for pointcloud in self.data]))) 74 | 75 | if _num_classes > len(self.label_colors()): 76 | logging.warning(f"There are more classes than label colors for this dataset. " 77 | f"If you want to plot your results, this will not work.") 78 | 79 | return _num_classes 80 | else: 81 | return self._num_classes 82 | 83 | @lazy_property 84 | def normalization(self) -> np.ndarray: 85 | """ 86 | before blob is fed into the neural network some normalization takes place in the batch generator 87 | normalization factors specific for each dataset have to be provided 88 | note: this property can be overriden by subclasses if another normalization is needed 89 | :return: np.ndarray with normalization factors 90 | """ 91 | _normalizer = np.array([1. for _ in range(self.num_features)]) 92 | 93 | if self._is_colors: 94 | _normalizer[3:6] = 255. # normalize colors to [0,1] 95 | if self._is_laser: 96 | _normalizer[6] = 2048. # normalize laser [-1, 1] 97 | elif self._is_laser: 98 | _normalizer[3] = 2048. # normalize laser [-1, 1] 99 | 100 | return _normalizer 101 | 102 | @lazy_property 103 | def num_features(self) -> int: 104 | return 3 + self._is_colors * 3 + self._is_laser 105 | 106 | @staticmethod 107 | def label_colors() -> np.ndarray: 108 | return np.array([cc.colors['brown'].npy, 109 | cc.colors['darkgreen'].npy, 110 | cc.colors['springgreen'].npy, 111 | cc.colors['red1'].npy, 112 | cc.colors['darkgray'].npy, 113 | cc.colors['gray'].npy, 114 | cc.colors['pink'].npy, 115 | cc.colors['yellow1'].npy, 116 | cc.colors['violet'].npy, 117 | cc.colors['hotpink'].npy, 118 | cc.colors['blue'].npy, 119 | cc.colors['lightblue'].npy, 120 | cc.colors['orange'].npy, 121 | cc.colors['black'].npy]) 122 | 123 | def _load(self, is_train: bool) -> Tuple[List[np.ndarray], List[str], Dict[str, np.ndarray]]: 124 | """ 125 | Note that we assume a folder hierarchy of DATA_PATH/SET_NO/{full_size, sample_X_Y, ...}/POINTCLOUD.npy 126 | :param is_train: true iff training mode 127 | :return: list of pointclouds and list of filenames 128 | """ 129 | data_training_test = {} 130 | full_sized_test_data = {} 131 | names = set() 132 | 133 | train_pc_names = set() 134 | test_pc_names = set() 135 | 136 | # pick 137 | pick = [0, 1, 2] 138 | 139 | if self._is_colors: 140 | pick = pick + [3, 4, 5] 141 | 142 | if self._is_laser: 143 | pick = pick + [6] 144 | 145 | if self._is_laser: 146 | pick = pick + [3] 147 | 148 | pick = pick + [-1] 149 | 150 | for dirpath, dirnames, filenames in os.walk(self.data_path): 151 | for filename in [f for f in filenames if f.endswith(".npy")]: 152 | is_test_set = os.path.dirname(dirpath).split('/')[-1] in self._test_sets 153 | 154 | if not is_test_set and not is_train: 155 | # we do not have to load training examples if we only want to evaluate 156 | continue 157 | 158 | name = None 159 | if os.path.basename(dirpath) == self._downsample_prefix: 160 | # dimension of a single npy file: (number of points, number of features + label) 161 | pointcloud_data = np.load(os.path.join(dirpath, filename)) 162 | pointcloud_data = pointcloud_data[:, pick] 163 | pointcloud_data = pointcloud_data.astype(np.float32) # just to be sure! 164 | 165 | name = filename.replace('.npy', '') 166 | data_training_test[name] = pointcloud_data 167 | elif os.path.basename(dirpath) == 'full_size': 168 | if not is_train: 169 | # for testing we consider full scale point clouds 170 | if is_test_set: 171 | # dimension of a single npy file: (number of points, number of features + label) 172 | pointcloud_data = np.load(os.path.join(dirpath, filename)) 173 | pointcloud_data = pointcloud_data[:, pick] 174 | pointcloud_data = pointcloud_data.astype(np.float32) # just to be sure! 175 | 176 | name = filename.replace('.npy', '') 177 | full_sized_test_data[name] = pointcloud_data 178 | 179 | if name is not None: 180 | names.add(name) 181 | 182 | if is_test_set: 183 | test_pc_names.add(name) 184 | else: 185 | train_pc_names.add(name) 186 | 187 | names = sorted(names) 188 | 189 | data_training_test = [data_training_test[key] for key in names] 190 | 191 | self._train_pc_idx = sorted([names.index(name) for name in train_pc_names]) 192 | self._test_pc_idx = sorted([names.index(name) for name in test_pc_names]) 193 | 194 | # short sanity check to ensure that data could be read in 195 | if len(data_training_test) == 0 or len(names) == 0: 196 | # error 197 | raise ValueError(f"Dataset could not be found under {self.data_path}") 198 | else: 199 | if (not is_train) and len(full_sized_test_data) == 0: 200 | # error 201 | raise ValueError(f"Dataset could not be found in {self.data_path}") 202 | 203 | return data_training_test, names, full_sized_test_data 204 | 205 | 206 | if __name__ == '__main__': 207 | from tools.tools import setup_logger 208 | 209 | setup_logger() 210 | 211 | dataset = GeneralDataset(data_path='/fastwork/schult/stanford_indoor', 212 | is_train=False, 213 | test_sets=['area_3', 'area_2'], 214 | downsample_prefix='sample_1_1', 215 | is_colors=True, 216 | is_laser=True) 217 | -------------------------------------------------------------------------------- /doc/exploring_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualComputingInstitute/3d-semantic-segmentation/1dfc010b370a346902ad29460c9ad969c1892a97/doc/exploring_header.png -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_1.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_1'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_2.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_2'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_3.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_3'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_4.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_4'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_5.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_5'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_gru/s3dis_gru_area_6.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_6'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: gru_neighbor_model 12 | params: None 13 | batch_generator: 14 | name: neighboring_grid_batch_generator 15 | params: 16 | batch_size: 14 17 | num_points: 4096 18 | grid_spacing: 0.5 19 | metric: chebyshev 20 | augmentation: False 21 | grid_x: 2 22 | grid_y: 2 23 | radius: 0.5 24 | optimizer: 25 | name: exponential_decay_adam 26 | params: 27 | initial_lr: 0.001 28 | decay_step: 300000 29 | decay_rate: 0.5 30 | gradient_clipping: 15 31 | train: 32 | epochs: 61 33 | -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_1.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_1'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_2.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_2'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_3.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_3'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_4.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_4'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_5.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_5'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 161 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_mscu/s3dis_mscu_area_6.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_6'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: multi_scale_cu_model 12 | batch_generator: 13 | name: multi_scale_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radii: [0.25, 0.5, 1.0] 21 | optimizer: 22 | name: exponential_decay_adam 23 | params: 24 | initial_lr: 0.001 25 | decay_step: 300000 26 | decay_rate: 0.5 27 | train: 28 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_1.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_1'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_2.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_2'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_3.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_3'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_4.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_4'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_5.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_5'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /experiments/iccvw_paper_2017/s3dis_pointnet/s3dis_pointnet_area_6.yaml: -------------------------------------------------------------------------------- 1 | modus: TRAIN_VAL 2 | dataset: 3 | name: general_dataset 4 | num_classes: 13 5 | data_path: dataset/stanford_indoor/ 6 | test_sets: ['Area_6'] 7 | downsample_prefix: sample_0.03 8 | colors: True 9 | laser: False 10 | model: 11 | name: pointnet 12 | batch_generator: 13 | name: neighboring_grid_batch_generator 14 | params: 15 | batch_size: 24 16 | num_points: 4096 17 | grid_spacing: 0.5 18 | metric: chebyshev 19 | augmentation: False 20 | radius: 0.5 21 | grid_x: 1 22 | grid_y: 1 23 | optimizer: 24 | name: exponential_decay_adam 25 | params: 26 | initial_lr: 0.001 27 | decay_step: 300000 28 | decay_rate: 0.5 29 | train: 30 | epochs: 61 -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | from .multi_block_model import * 2 | from .multi_scale_cu_model import * 3 | from .pointnet import * 4 | from .gru_neighbor_model import * 5 | -------------------------------------------------------------------------------- /models/gru_neighbor_model.py: -------------------------------------------------------------------------------- 1 | from tools import tf_util 2 | from .multi_block_model import * 3 | from batch_generators import * 4 | from typing import Dict 5 | 6 | 7 | class GruNeighborModel(MultiBlockModel): 8 | """ 9 | parameterized version of a neighboring model using GRU units as described in the paper 10 | """ 11 | 12 | def __init__(self, batch_generator: BatchGenerator, params: Dict[str, list]): 13 | super().__init__(batch_generator) 14 | 15 | self._bn_decay = 0.9 16 | 17 | @lazy_property 18 | def _prediction_helper(self): 19 | num_point = self.batch_generator.num_points 20 | batch_size = self.batch_generator.batch_size 21 | 22 | dims = self.batch_generator.input_shape 23 | 24 | cumulated_batch_size = dims[0] * dims[1] 25 | time = dims[1] 26 | 27 | input_image = tf.reshape(self.batch_generator.pointclouds_pl, (cumulated_batch_size, dims[2], dims[3])) 28 | input_image = tf.expand_dims(input_image, -1) 29 | 30 | net = input_image 31 | 32 | # CONV 33 | net = tf_util.conv2d(net, 64, [1, self.batch_generator.dataset.num_features + 3], padding='VALID', stride=[1, 1], 34 | bn=True, is_training=self.is_training_pl, scope='conv1', bn_decay=self._bn_decay) 35 | net = tf_util.conv2d(net, 64, [1, 1], padding='VALID', stride=[1, 1], 36 | bn=True, is_training=self.is_training_pl, scope='conv2', bn_decay=self._bn_decay) 37 | net = tf_util.conv2d(net, 64, [1, 1], padding='VALID', stride=[1, 1], 38 | bn=True, is_training=self.is_training_pl, scope='conv3', bn_decay=self._bn_decay) 39 | net = tf_util.conv2d(net, 128, [1, 1], padding='VALID', stride=[1, 1], 40 | bn=True, is_training=self.is_training_pl, scope='conv4', bn_decay=self._bn_decay) 41 | points_feat1 = tf_util.conv2d(net, 1024, [1, 1], padding='VALID', stride=[1, 1], 42 | bn=True, is_training=self.is_training_pl, scope='conv5', bn_decay=self._bn_decay) 43 | # MAX 44 | pc_feat1 = tf_util.max_pool2d(points_feat1, [num_point, 1], padding='VALID', scope='maxpool1') 45 | 46 | # FC 47 | pc_feat2 = tf.reshape(pc_feat1, [cumulated_batch_size, -1]) 48 | pc_feat2 = tf_util.fully_connected(pc_feat2, 256, bn=True, is_training=self.is_training_pl, scope='fc1', bn_decay=self._bn_decay) 49 | pc_feat2 = tf_util.fully_connected(pc_feat2, 64, bn=True, is_training=self.is_training_pl, scope='fc2', bn_decay=self._bn_decay) 50 | tf.summary.histogram("pc_feat2_b", pc_feat2[:]) 51 | pc_feat2_ = tf.reshape(pc_feat2, (batch_size, time, 64)) 52 | 53 | pc_feat2_ = tf_util.gru_seq(pc_feat2_, 64, batch_size, time, False, scope='gru1') 54 | 55 | pc_feat2_ = tf.reshape(pc_feat2_, (cumulated_batch_size, 64)) 56 | tf.summary.histogram("pc_feat2_a", pc_feat2_[:]) 57 | 58 | pc_feat2 = tf.concat([pc_feat2, pc_feat2_], axis=1) 59 | # CONCAT 60 | pc_feat2_expand = tf.tile(tf.reshape(pc_feat2, [cumulated_batch_size, 1, 1, -1]), [1, num_point, 1, 1]) 61 | points_feat2_concat = tf.concat(axis=3, values=[points_feat1, pc_feat2_expand]) 62 | 63 | # CONV 64 | net2 = tf_util.conv2d(points_feat2_concat, 512, [1, 1], padding='VALID', stride=[1, 1], 65 | bn=True, is_training=self.is_training_pl, scope='conv6') 66 | net2 = tf_util.conv2d(net2, 256, [1, 1], padding='VALID', stride=[1, 1], 67 | bn=True, is_training=self.is_training_pl, scope='conv7') 68 | net2 = tf_util.dropout(net2, keep_prob=0.7, is_training=self.is_training_pl, scope='dp1') 69 | net2 = tf_util.conv2d(net2, self.batch_generator.dataset.num_classes, [1, 1], padding='VALID', stride=[1, 1], 70 | activation_fn=None, scope='conv8') 71 | net2 = tf.squeeze(net2, [2]) 72 | 73 | return tf.reshape(net2, (batch_size, time, num_point, -1)) 74 | -------------------------------------------------------------------------------- /models/multi_block_model.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from abc import * 3 | from tools.lazy_decorator import * 4 | 5 | 6 | class MultiBlockModel(ABC): 7 | 8 | def __init__(self, batch_generator): 9 | self.batch_generator = batch_generator 10 | 11 | self._create_placeholders() 12 | 13 | @lazy_function 14 | def _create_placeholders(self): 15 | 16 | self.eval_per_epoch_pl = tf.placeholder(tf.float32, 17 | name='evaluation_pl', 18 | shape=(3, 1)) 19 | 20 | self.block_mask_bool = tf.cast(self.batch_generator.mask_pl, tf.bool) # shape: (BS, 1) 21 | self.labels = tf.boolean_mask(self.batch_generator.labels_pl, self.block_mask_bool) # shape: (BS, N) 22 | self.labels = tf.cast(self.labels, tf.int32) 23 | 24 | num_blocks = tf.reduce_sum(self.batch_generator.mask_pl) # num of blocks per batch 25 | self.num_blocks = tf.cast(num_blocks, tf.float32) 26 | 27 | self.is_training_pl = tf.placeholder(tf.bool, 28 | name='is_training_pl', 29 | shape=()) 30 | 31 | @lazy_property 32 | def prediction(self): 33 | pred = self._prediction_helper 34 | # Apply mask to prediction and labels 35 | return tf.boolean_mask(pred, self.block_mask_bool) # shape: (BS, N, K) 36 | 37 | @lazy_property 38 | def prediction_sm(self): 39 | return tf.nn.softmax(self.prediction) 40 | 41 | @lazy_property 42 | @abstractmethod 43 | def _prediction_helper(self): 44 | raise NotImplementedError('Should be defined in subclass') 45 | 46 | @lazy_property 47 | def loss(self): 48 | loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.prediction, 49 | labels=self.labels) 50 | return tf.reduce_mean(loss) 51 | 52 | @lazy_property 53 | def correct(self): 54 | return tf.equal(tf.argmax(self.prediction, 2), tf.to_int64(self.labels)) 55 | 56 | @lazy_property 57 | def accuracy(self): 58 | return tf.reduce_sum(tf.cast(self.correct, tf.float32)) / \ 59 | tf.cast(self.batch_generator.num_points * self.num_blocks, tf.float32) 60 | 61 | @lazy_function 62 | def register_summary(self): 63 | tf.summary.scalar('loss', self.loss) 64 | tf.summary.scalar('avg_acc', self.eval_per_epoch_pl[0, 0]) 65 | tf.summary.scalar('avg_iou', self.eval_per_epoch_pl[1, 0]) 66 | tf.summary.scalar('avg_loss', self.eval_per_epoch_pl[2, 0]) 67 | tf.summary.scalar('accuracy', self.accuracy) 68 | 69 | @property 70 | def input_shape(self): 71 | return self.batch_generator.pointclouds_pl.get_shape().as_list() 72 | 73 | @property 74 | def labels_shape(self): 75 | return self.batch_generator.labels_pl.get_shape().as_list() 76 | -------------------------------------------------------------------------------- /models/multi_scale_cu_model.py: -------------------------------------------------------------------------------- 1 | from tools import tf_util 2 | from .multi_block_model import * 3 | from batch_generators import * 4 | from typing import Dict 5 | 6 | 7 | class MultiScaleCuModel(MultiBlockModel): 8 | """ 9 | parameterized version of a multiscale pointnet with consolidation units 10 | """ 11 | 12 | def __init__(self, batch_generator: BatchGenerator, params: Dict[str, list]): 13 | """ 14 | initialization of multi scale model with consolidation units 15 | :param batch_generator: 16 | :param params: contains parameter concerning 17 | - ilc_sizes (filter sizes for input level context) 18 | - olc_sizes (consolidation units' sizes) 19 | - olc_sizes (filter sizes for output level context) 20 | """ 21 | super().__init__(batch_generator) 22 | 23 | if params is None: 24 | # standard parameters from the paper 25 | self._ilc_sizes = [64, 128] 26 | self._cu_sizes = [256, 1024] 27 | self._olc_sizes = [512, 128] 28 | else: 29 | # load custom parameters 30 | self._ilc_sizes = params['ilc_sizes'] 31 | self._cu_sizes = params['cu_sizes'] 32 | self._olc_sizes = params['olc_sizes'] 33 | 34 | self._bn_decay = 0.9 35 | 36 | @lazy_property 37 | def _prediction_helper(self): 38 | # pointcloud placeholder has the following format: BxSxNxF 39 | # Batch B 40 | # Scale S 41 | # Point P 42 | # Feature F 43 | 44 | # allow an arbitrary number of scales 45 | scales = [tf.expand_dims(self.batch_generator.pointclouds_pl[:, i, ...], axis=2) 46 | for i in range(self.input_shape[1])] 47 | 48 | num_points = self.batch_generator.num_points 49 | 50 | # store reference to original scale for later concatenating 51 | scale1 = scales[1] 52 | 53 | ''' INPUT-LEVEL CONTEXT ''' 54 | for scale_index in range(len(scales)): 55 | # build global feature extractor for each scale independently 56 | for size_index, ilc_size in enumerate(self._ilc_sizes): 57 | scales[scale_index] = tf_util.conv2d(scales[scale_index], ilc_size, [1, 1], padding='VALID', 58 | stride=[1, 1], 59 | bn=True, is_training=self.is_training_pl, 60 | scope='ilc_conv' + str(size_index) + 'sc' + str(scale_index), 61 | bn_decay=self._bn_decay) 62 | # calculate global features for each scale 63 | scales[scale_index] = tf.reduce_max(scales[scale_index], axis=1, 64 | keep_dims=True, name="gf_sc" + str(scale_index)) 65 | 66 | ''' CONCATENATE GLOBAL FEATURES OF ALL SCALES ''' 67 | net = tf.concat(values=scales, axis=3) 68 | net = tf.tile(net, [1, num_points, 1, 1], name='repeat') 69 | net = tf.concat(values=[scale1, net], axis=3) 70 | 71 | ''' CONSOLIDATION UNIT SECTION ''' 72 | for index, cu_size in enumerate(self._cu_sizes): 73 | net = tf_util.consolidation_unit(net, size=cu_size, scope='cu' + str(index), bn=True, 74 | bn_decay=self._bn_decay, is_training=self.is_training_pl) 75 | 76 | ''' OUTPUT-LEVEL CONTEXT ''' 77 | for size_index, olc_size in enumerate(self._olc_sizes): 78 | net = tf_util.conv2d(net, olc_size, [1, 1], padding='VALID', stride=[1, 1], bn=True, 79 | is_training=self.is_training_pl, 80 | scope='olc_conv' + str(size_index), bn_decay=self._bn_decay) 81 | 82 | net = tf_util.conv2d(net, self.batch_generator.dataset.num_classes, [1, 1], padding='VALID', stride=[1, 1], 83 | activation_fn=None, scope='conv_output') 84 | 85 | net = tf.transpose(net, [0, 2, 1, 3]) 86 | 87 | return net 88 | -------------------------------------------------------------------------------- /models/pointnet.py: -------------------------------------------------------------------------------- 1 | from tools import tf_util 2 | from .multi_block_model import * 3 | from batch_generators import * 4 | from typing import Dict 5 | 6 | 7 | class Pointnet(MultiBlockModel): 8 | """ 9 | Pointnet network architecture from 10 | PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation 11 | Author: Qi, et al. 12 | """ 13 | 14 | def __init__(self, batch_generator: BatchGenerator, params: Dict[str, list]): 15 | super().__init__(batch_generator) 16 | self._bn_decay = 0.9 17 | 18 | @lazy_property 19 | def _prediction_helper(self): 20 | # pointcloud placeholder has the following format: BxSxNxF 21 | # Batch B 22 | # Scale S 23 | # Point P 24 | # Feature F 25 | num_points = self.batch_generator.pointclouds_pl.get_shape().as_list()[2] 26 | 27 | image_pl = tf.transpose(self.batch_generator.pointclouds_pl, [0, 2, 1, 3]) 28 | 29 | # CONV 30 | net = tf_util.conv2d(image_pl, 64, [1, 1], padding='VALID', stride=[1, 1], 31 | bn=True, is_training=self.is_training_pl, scope='conv1', bn_decay=self._bn_decay) 32 | net = tf_util.conv2d(net, 64, [1, 1], padding='VALID', stride=[1, 1], 33 | bn=True, is_training=self.is_training_pl, scope='conv2', bn_decay=self._bn_decay) 34 | net = tf_util.conv2d(net, 64, [1, 1], padding='VALID', stride=[1, 1], 35 | bn=True, is_training=self.is_training_pl, scope='conv3', bn_decay=self._bn_decay) 36 | net = tf_util.conv2d(net, 128, [1, 1], padding='VALID', stride=[1, 1], 37 | bn=True, is_training=self.is_training_pl, scope='conv4', bn_decay=self._bn_decay) 38 | points_feat1 = tf_util.conv2d(net, 1024, [1, 1], padding='VALID', stride=[1, 1], 39 | bn=True, is_training=self.is_training_pl, scope='conv5', bn_decay=self._bn_decay) 40 | # MAX 41 | pc_feat1 = tf.reduce_max(points_feat1, axis=1, keep_dims=True, name="global_features") 42 | # FC 43 | pc_feat1 = tf.reshape(pc_feat1, [-1, 1024]) 44 | pc_feat1 = tf_util.fully_connected(pc_feat1, 256, bn=True, is_training=self.is_training_pl, scope='fc1', 45 | bn_decay=self._bn_decay) 46 | pc_feat1 = tf_util.fully_connected(pc_feat1, 128, bn=True, is_training=self.is_training_pl, scope='fc2', 47 | bn_decay=self._bn_decay) 48 | 49 | # CONCAT 50 | pc_feat1_expand = tf.tile(tf.reshape(pc_feat1, [-1, 1, 1, 128]), [1, num_points, 1, 1]) 51 | points_feat1_concat = tf.concat(axis=3, values=[points_feat1, pc_feat1_expand]) 52 | 53 | # CONV 54 | net = tf_util.conv2d(points_feat1_concat, 512, [1, 1], padding='VALID', stride=[1, 1], 55 | bn=True, is_training=self.is_training_pl, scope='conv6') 56 | net = tf_util.conv2d(net, 256, [1, 1], padding='VALID', stride=[1, 1], 57 | bn=True, is_training=self.is_training_pl, scope='conv7') 58 | net = tf_util.dropout(net, keep_prob=0.7, is_training=self.is_training_pl, scope='dp1') 59 | net = tf_util.conv2d(net, self.batch_generator.dataset.num_classes, [1, 1], padding='VALID', stride=[1, 1], 60 | activation_fn=None, scope='conv8') 61 | 62 | net = tf.transpose(net, [0, 2, 1, 3]) 63 | return net 64 | -------------------------------------------------------------------------------- /optimizers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualComputingInstitute/3d-semantic-segmentation/1dfc010b370a346902ad29460c9ad969c1892a97/optimizers/__init__.py -------------------------------------------------------------------------------- /optimizers/exponential_decay_adam.py: -------------------------------------------------------------------------------- 1 | from models import * 2 | from tools.lazy_decorator import * 3 | 4 | 5 | class ExponentialDecayAdam: 6 | def __init__(self, model, params: dict): 7 | # Variable to increment by one after the variables have been updated by the optimizers 8 | # this helps to keep track of the progress of the training (e.g. adapting learning rate and decay) 9 | self.global_step = tf.Variable(0, name='global_step_counter') 10 | self._model = model 11 | self._base_learning_rate = params['initial_lr'] 12 | self._decay_step = params['decay_step'] 13 | self._decay_rate = params['decay_rate'] 14 | 15 | self._gradient_clipping = params.get('gradient_clipping') 16 | 17 | @lazy_property 18 | def learning_rate(self): 19 | learning_rate = tf.cond(self.global_step * self._model.batch_generator.batch_size < self._decay_step, 20 | lambda: tf.constant(self._base_learning_rate), 21 | lambda: tf.train.exponential_decay( 22 | self._base_learning_rate, # Base learning rate. 23 | self.global_step * self._model.batch_generator.batch_size - self._decay_step, 24 | self._decay_step // 2, # Decay step. 25 | self._decay_rate, # Decay rate. 26 | staircase=False)) 27 | return learning_rate 28 | 29 | @lazy_function 30 | def register_summary(self): 31 | tf.summary.scalar('global_step', self.global_step) 32 | tf.summary.scalar('learning_rate', self.learning_rate) 33 | 34 | @lazy_property 35 | def optimize(self): 36 | if self._gradient_clipping is not None: 37 | # Clipping gradients - check if gradients explode 38 | optimizer = tf.train.AdamOptimizer(self.learning_rate) 39 | gradients = tf.gradients(self._model.loss, tf.trainable_variables()) 40 | trainables = tf.trainable_variables() 41 | clipped_gradients, _ = tf.clip_by_global_norm(gradients, self._gradient_clipping) 42 | 43 | extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) 44 | with tf.control_dependencies(extra_update_ops): 45 | return optimizer.apply_gradients(zip(clipped_gradients, trainables), global_step=self.global_step) 46 | else: 47 | optimizer = tf.train.AdamOptimizer(self.learning_rate) 48 | return optimizer.minimize(self._model.loss, global_step=self.global_step) 49 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from datasets import * 3 | from models import * 4 | import yaml 5 | from tools.tools import * 6 | from pathlib import Path 7 | import tools.evaluation as evaluation 8 | import shutil 9 | import logging 10 | 11 | avg_iou_per_epoch = [0] 12 | avg_class_acc_per_epoch = [0] 13 | avg_loss_per_epoch = [0] 14 | 15 | 16 | def main(config: dict, log_dir: str, isTrain: bool): 17 | with tf.Graph().as_default(): 18 | Dataset = import_class('datasets', config['dataset']['name']) 19 | dataset = Dataset(config['dataset']['data_path'], 20 | is_train=isTrain, 21 | test_sets=config['dataset']['test_sets'], 22 | downsample_prefix=config['dataset']['downsample_prefix'], 23 | is_colors=config['dataset']['colors'], 24 | is_laser=config['dataset']['laser'], 25 | n_classes=config['dataset']['num_classes']) 26 | 27 | BatchGenerator = import_class('batch_generators', config['batch_generator']['name']) 28 | batch_generator = BatchGenerator(dataset, config['batch_generator']['params']) 29 | 30 | Model = import_class('models', config['model']['name']) 31 | model = Model(batch_generator, config['model'].get('params')) 32 | 33 | if isTrain: 34 | Optimizer = import_class('optimizers', config['optimizer']['name']) 35 | optimizer = Optimizer(model, config['optimizer']['params']) 36 | 37 | sess, ops, writer, saver, epoch_start = prepare_network(model, log_dir, optimizer, isTrain=isTrain, 38 | model_path=config.get('resume_path')) 39 | 40 | for epoch in range(epoch_start, config['train']['epochs']): 41 | train_one_epoch(sess, ops, writer, model, epoch, config['train']['epochs']) 42 | eval_one_epoch(sess, ops, model, dataset, epoch, config['train']['epochs']) 43 | 44 | # Save the variables to disk. 45 | if epoch % 10 == 0: 46 | path = Path(f"{log_dir}/model_ckpts") 47 | path.mkdir(parents=True, exist_ok=True) 48 | saver.save(sess, os.path.join(f"{log_dir}/model_ckpts", 49 | f"{epoch+1:03d}_model.ckpt")) 50 | else: 51 | sess, ops, writer, saver, _ = prepare_network(model, log_dir, 52 | isTrain=isTrain, model_path=config['model_path']) 53 | predict_on_test_set(sess, ops, model, dataset, log_dir) 54 | 55 | 56 | def prepare_network(model: MultiBlockModel, log_dir: str, optimizer=None, isTrain=True, model_path=None): 57 | # Create a session 58 | config = tf.ConfigProto() 59 | config.gpu_options.allow_growth = True 60 | config.allow_soft_placement = True 61 | config.log_device_placement = False 62 | sess = tf.Session(config=config) 63 | 64 | with tf.device('/gpu:0'): 65 | model.register_summary() 66 | if optimizer is not None: 67 | optimizer.register_summary() 68 | 69 | # Add summary writers 70 | merged = tf.summary.merge_all() 71 | train_writer = tf.summary.FileWriter(os.path.join(log_dir, 'tensorflow'), sess.graph) 72 | 73 | if optimizer is not None: 74 | ops = {'pointclouds_pl': model.batch_generator.pointclouds_pl, 75 | 'labels_pl': model.batch_generator.labels_pl, 76 | 'mask_pl': model.batch_generator.mask_pl, 77 | 'eval_per_epoch_pl': model.eval_per_epoch_pl, 78 | 'is_training_pl': model.is_training_pl, 79 | 'pred': model.prediction, 80 | 'pred_sm': model.prediction_sm, 81 | 'loss': model.loss, 82 | 'train_op': optimizer.optimize, 83 | 'merged': merged, 84 | 'step': optimizer.global_step, 85 | 'correct': model.correct, 86 | 'labels': model.labels, 87 | 'handle_pl': model.batch_generator.handle_pl, 88 | 'iterator_train': model.batch_generator.iterator_train, 89 | 'iterator_test': model.batch_generator.iterator_test, 90 | 'cloud_ids_pl': model.batch_generator.cloud_ids_pl, 91 | 'point_ids_pl': model.batch_generator.point_ids_pl, 92 | 'next_element': model.batch_generator.next_element 93 | } 94 | else: 95 | ops = {'pointclouds_pl': model.batch_generator.pointclouds_pl, 96 | 'labels_pl': model.batch_generator.labels_pl, 97 | 'mask_pl': model.batch_generator.mask_pl, 98 | 'eval_per_epoch_pl': model.eval_per_epoch_pl, 99 | 'is_training_pl': model.is_training_pl, 100 | 'pred': model.prediction, 101 | 'pred_sm': model.prediction_sm, 102 | 'loss': model.loss, 103 | 'correct': model.correct, 104 | 'labels': model.labels, 105 | 'handle_pl': model.batch_generator.handle_pl, 106 | 'iterator_train': model.batch_generator.iterator_train, 107 | 'iterator_test': model.batch_generator.iterator_test, 108 | 'cloud_ids_pl': model.batch_generator.cloud_ids_pl, 109 | 'point_ids_pl': model.batch_generator.point_ids_pl, 110 | 'next_element': model.batch_generator.next_element 111 | } 112 | 113 | # Init variables 114 | init = tf.global_variables_initializer() 115 | sess.run(init, {model.is_training_pl: isTrain}) 116 | 117 | # Add ops to save and restore all the variables. 118 | saver = tf.train.Saver() 119 | 120 | epoch_number = 0 121 | 122 | if model_path is not None: 123 | # resume training 124 | latest_checkpoint_path = tf.train.latest_checkpoint(model_path) 125 | # extract latest training epoch number 126 | epoch_number = int(latest_checkpoint_path.split('/')[-1].split('_')[0]) 127 | 128 | saver.restore(sess, latest_checkpoint_path) 129 | 130 | return sess, ops, train_writer, saver, epoch_number 131 | 132 | 133 | def train_one_epoch(sess, ops, train_writer, model, epoch, max_epoch): 134 | model.batch_generator.shuffle() 135 | 136 | for _ in tqdm(range(model.batch_generator.num_train_batches), 137 | desc=f"Running training epoch {epoch+1:03d} / {max_epoch:03d}"): 138 | 139 | a = np.reshape(np.array(avg_class_acc_per_epoch[-1]), [1, 1]) 140 | b = np.reshape(np.array(avg_iou_per_epoch[-1]), [1, 1]) 141 | c = np.reshape(np.array(avg_loss_per_epoch[-1]), [1, 1]) 142 | eval_per_epoch = np.concatenate((a, b, c)) 143 | 144 | handle_train = sess.run(ops['iterator_train'].string_handle()) 145 | feed_dict = {ops['is_training_pl']: True, 146 | ops['eval_per_epoch_pl']: eval_per_epoch, 147 | ops['handle_pl']: handle_train} 148 | 149 | start_time = time.time() 150 | 151 | summary, step, _, loss_val, pc_val, pred_val, labels_val, correct_val = sess.run( 152 | [ops['merged'], ops['step'], ops['train_op'], ops['loss'], 153 | ops['pointclouds_pl'], 154 | ops['pred'], ops['labels'], ops['correct']], 155 | feed_dict=feed_dict) 156 | 157 | elapsed_time = time.time() - start_time 158 | summary2 = tf.Summary() 159 | summary2.value.add(tag='secs_per_iter', simple_value=elapsed_time) 160 | train_writer.add_summary(summary2, step) 161 | train_writer.add_summary(summary, step) 162 | 163 | 164 | def eval_one_epoch(sess, ops, model, dataset, epoch, max_epoch): 165 | total_correct = 0 166 | total_seen = 0 167 | loss_sum = 0 168 | 169 | # Compute avg IoU over classes 170 | total_seen_class = [0 for _ in range(dataset.num_classes)] # true_pos + false_neg i.e. all points from this class 171 | total_correct_class = [0 for _ in range(dataset.num_classes)] # true_pos 172 | total_pred_class = [0 for _ in range(dataset.num_classes)] # true_pos + false_pos i.e. num pred classes 173 | 174 | overall_acc = [] 175 | 176 | for _ in tqdm(range(model.batch_generator.num_test_batches), 177 | desc='Running evaluation epoch %04d / %04d' % (epoch+1, max_epoch)): 178 | 179 | a = np.reshape(np.array(avg_class_acc_per_epoch[-1]), [1, 1]) 180 | b = np.reshape(np.array(avg_iou_per_epoch[-1]), [1, 1]) 181 | c = np.reshape(np.array(avg_loss_per_epoch[-1]), [1, 1]) 182 | eval_per_epoch = np.concatenate((a, b, c)) 183 | 184 | handle_test = sess.run(ops['iterator_test'].string_handle()) 185 | feed_dict = {ops['is_training_pl']: False, 186 | ops['eval_per_epoch_pl']: eval_per_epoch, 187 | ops['handle_pl']: handle_test} 188 | 189 | _, step, loss_val, pred_val, correct_val, labels_val, batch_mask, batch_cloud_ids, batch_point_ids = sess.run( 190 | [ops['merged'], ops['step'], ops['loss'], 191 | ops['pred_sm'], ops['correct'], ops['labels'], 192 | ops['mask_pl'], ops['cloud_ids_pl'], ops['point_ids_pl']], feed_dict=feed_dict) 193 | 194 | total_correct += np.sum(correct_val) # shape: scalar 195 | total_seen += pred_val.shape[0] * pred_val.shape[1] 196 | 197 | overall_acc.append(total_correct / total_seen) 198 | 199 | loss_sum += loss_val 200 | 201 | pred_val = np.argmax(pred_val, 2) # shape: (BS*B' x N) 202 | 203 | for i in range(labels_val.shape[0]): # iterate over blocks 204 | for j in range(labels_val.shape[1]): # iterate over points in block 205 | lbl_gt = int(labels_val[i, j]) 206 | lbl_pred = int(pred_val[i, j]) 207 | total_seen_class[lbl_gt] += 1 208 | total_correct_class[lbl_gt] += (lbl_pred == lbl_gt) 209 | total_pred_class[lbl_pred] += 1 210 | 211 | iou_per_class = np.zeros(dataset.num_classes) 212 | iou_per_class_mask = np.zeros(dataset.num_classes, dtype=np.int8) 213 | for i in range(dataset.num_classes): 214 | denominator = float(total_seen_class[i] + total_pred_class[i] - total_correct_class[i]) 215 | 216 | if denominator != 0: 217 | iou_per_class[i] = total_correct_class[i] / denominator 218 | else: 219 | iou_per_class_mask[i] = 1 220 | 221 | iou_per_class_masked = np.ma.array(iou_per_class, mask=iou_per_class_mask) 222 | 223 | total_seen_class_mask = [1 if seen == 0 else 0 for seen in total_seen_class] 224 | 225 | class_acc = np.array(total_correct_class) / np.array(total_seen_class, dtype=np.float) 226 | 227 | class_acc_masked = np.ma.array(class_acc, mask=total_seen_class_mask) 228 | 229 | avg_iou = iou_per_class_masked.mean() 230 | avg_loss = loss_sum / float(total_seen / model.batch_generator.num_points) 231 | avg_class_acc = class_acc_masked.mean() 232 | avg_class_acc_per_epoch.append(avg_class_acc) 233 | avg_iou_per_epoch.append(avg_iou) 234 | avg_loss_per_epoch.append(avg_loss) 235 | 236 | logging.info(f"[Epoch {epoch+1:03d}] avg class acc: {avg_class_acc}") 237 | logging.info(f"[Epoch {epoch+1:03d}] avg iou: {avg_iou}") 238 | logging.info(f"[Epoch {epoch+1:03d}] avg overall acc: {np.mean(overall_acc)}") 239 | 240 | 241 | def predict_on_test_set(sess, ops, model, dataset: GeneralDataset, log_dir: str): 242 | is_training = False 243 | 244 | cumulated_result = {} 245 | 246 | for _ in tqdm(range(model.batch_generator.num_test_batches)): 247 | handle_test = sess.run(ops['iterator_test'].string_handle()) 248 | feed_dict = {ops['is_training_pl']: is_training, 249 | ops['eval_per_epoch_pl']: np.zeros((3,1)), 250 | ops['handle_pl']: handle_test} 251 | 252 | loss_val, pred_val, correct_val, labels_val, batch_mask, batch_cloud_ids, batch_point_ids = sess.run( 253 | [ops['loss'], ops['pred_sm'], ops['correct'], ops['labels'], 254 | ops['mask_pl'], ops['cloud_ids_pl'], ops['point_ids_pl']], feed_dict=feed_dict) 255 | 256 | num_classes = pred_val.shape[2] 257 | num_batches = pred_val.shape[0] 258 | 259 | batch_mask = np.array(batch_mask, dtype=bool) # shape: (BS, B) - convert mask to bool 260 | batch_point_ids = batch_point_ids[batch_mask] # shape: (B, N) 261 | batch_cloud_ids = batch_cloud_ids[batch_mask] # shape: (B) 262 | 263 | for batch_id in range(num_batches): 264 | pc_id = batch_cloud_ids[batch_id] 265 | pc_name = dataset.file_names[pc_id] 266 | 267 | for point_in_batch, point_id in enumerate(batch_point_ids[batch_id, :]): 268 | num_fs_properties = dataset.data[pc_id].shape[1] 269 | if pc_name not in cumulated_result: 270 | # if there is not information about the point cloud so far, initialize it 271 | # label -1 means that there is not label given so far 272 | # cumulate predictions for the same point 273 | cumulated_result[pc_name] = np.zeros((dataset.data[pc_id].shape[0], 274 | num_fs_properties + num_classes + 1)) 275 | cumulated_result[pc_name][:, :num_fs_properties] = dataset.data[pc_id] 276 | cumulated_result[pc_name][:, -1] = -1 277 | 278 | cumulated_result[pc_name][point_id, num_fs_properties:-1] += pred_val[batch_id, point_in_batch] 279 | cumulated_result[pc_name][point_id, -1] = np.argmax(cumulated_result[pc_name][point_id, 280 | num_fs_properties:-1]) 281 | 282 | for key in tqdm(cumulated_result.keys(), desc='knn interpolation for full sized point cloud'): 283 | cumulated_result[key] = evaluation.knn_interpolation(cumulated_result[key], dataset.full_sized_data[key]) 284 | 285 | class_acc, class_iou, overall_acc = evaluation.calculate_scores(cumulated_result, dataset.num_classes) 286 | 287 | logging.info(f" overall accuracy: {overall_acc}") 288 | logging.info(f"mean class accuracy: {np.nanmean(class_acc)}") 289 | logging.info(f" mean iou: {np.nanmean(class_iou)}") 290 | 291 | for i in range(dataset.num_classes): 292 | logging.info(f"accuracy for class {i}: {class_acc[i]}") 293 | logging.info(f" iou for class {i}: {class_iou[i]}") 294 | 295 | evaluation.save_npy_results(cumulated_result, log_dir) 296 | evaluation.save_pc_as_obj(cumulated_result, dataset.label_colors(), log_dir) 297 | 298 | 299 | if __name__ == '__main__': 300 | log_dir = setup_logger() 301 | 302 | parser = argparse.ArgumentParser() 303 | parser.add_argument("--config", help="experiment definition file", metavar="FILE", required=True) 304 | args = parser.parse_args() 305 | 306 | params = parser.parse_args() 307 | 308 | with open(params.config, 'r') as stream: 309 | try: 310 | config = yaml.load(stream) 311 | # backup config file 312 | shutil.copy(params.config, log_dir) 313 | 314 | isTrain = False 315 | 316 | if config['modus'] == 'TRAIN_VAL': 317 | isTrain = True 318 | elif config['modus'] == 'TEST': 319 | isTrain = False 320 | 321 | main(config, log_dir, isTrain) 322 | except yaml.YAMLError as exc: 323 | logging.error('Configuration file could not be read') 324 | exit(1) 325 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisualComputingInstitute/3d-semantic-segmentation/1dfc010b370a346902ad29460c9ad969c1892a97/tools/__init__.py -------------------------------------------------------------------------------- /tools/downsample.py: -------------------------------------------------------------------------------- 1 | """ 2 | Downsample full sized point clouds in order to speed up batch generation as well as better block representations. 3 | Resulting point clouds will be saved at the appropriate positions in the file system 4 | (For further information consult the wiki) 5 | """ 6 | 7 | import numpy as np 8 | import argparse 9 | import tools 10 | from termcolor import colored 11 | import os 12 | from tqdm import tqdm 13 | 14 | 15 | def blockwise_uniform_downsample(data_labels, cell_size): 16 | data_dim = data_labels.shape[1] - 1 17 | 18 | number_classes = int(data_labels[:, -1].max()) + 1 # counting starts with label 0 19 | 20 | d = {} 21 | for i in tqdm(range(data_labels.shape[0]), desc='downsampling of points'): 22 | # create block boundaries 23 | x = int(data_labels[i, 0] / cell_size) 24 | y = int(data_labels[i, 1] / cell_size) 25 | z = int(data_labels[i, 2] / cell_size) 26 | 27 | # add space for one hot encoding (used for easier class occurence counting) 28 | # and counter value at the end of the row 29 | new_tuple = np.zeros([data_labels.shape[1] + number_classes], dtype=float) 30 | new_tuple[0:data_dim] = data_labels[i, 0:-1] 31 | new_tuple[data_dim+int(data_labels[i, -1])] = 1 # set label to one for the corresponding class 32 | new_tuple[-1] = 1 # count elements in the block 33 | 34 | # note: elementwise addition in numpy arrays!!! 35 | try: 36 | d[(x, y, z)] += new_tuple 37 | except: 38 | d[(x, y, z)] = new_tuple 39 | 40 | data = [] 41 | labels = [] 42 | 43 | for _, v in d.items(): 44 | N = v[-1] # number of points in voxel 45 | 46 | # aggregate points in block to one normalized point 47 | data.append([v[i] / N for i in range(data_dim)]) 48 | 49 | # find most prominent label (excluding counter and not in one hot encoding anymore) 50 | labels.append(np.argmax(v[data_dim:-1])) 51 | 52 | data = np.stack(data, axis=0) 53 | labels = np.stack(labels, axis=0) 54 | 55 | data_labels_new = np.hstack([data, np.expand_dims(labels, 1)]).astype(np.float32) 56 | return data_labels_new 57 | 58 | 59 | def main(params): 60 | for dirpath, dirnames, filenames in os.walk(params.data_dir): 61 | if os.path.basename(dirpath) == 'full_size': 62 | for filename in [f for f in filenames if f.endswith(".npy")]: 63 | print(f"downsampling {filename} in progress ...") 64 | data_labels = np.load(os.path.join(dirpath, filename)) 65 | sampled_data_labels = blockwise_uniform_downsample(data_labels, params.cell_size) 66 | 67 | out_folder = os.path.join(os.path.dirname(dirpath), f"sample_{params.cell_size}") 68 | 69 | if not os.path.exists(out_folder): 70 | os.makedirs(out_folder) 71 | 72 | np.save(os.path.join(out_folder, filename), sampled_data_labels) 73 | 74 | 75 | if __name__ == '__main__': 76 | parser = argparse.ArgumentParser(description='Convert original data set to uniformly downsampled numpy version ' 77 | 'in order to speed up batch generation ' 78 | 'as well as better block representations') 79 | 80 | parser.add_argument('--data_dir', required=True, help='root directory of original data') 81 | parser.add_argument('--cell_size', type=float, default=0.03, help='width/length of downsampling cell') 82 | params = parser.parse_args() 83 | 84 | tools.pretty_print_arguments(params) 85 | 86 | main(params) 87 | 88 | print(colored('Finished successfully', 'green')) 89 | -------------------------------------------------------------------------------- /tools/evaluation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains methods for evaluating and exporting the result of the network 3 | """ 4 | 5 | import os 6 | import numpy as np 7 | from typing import Dict 8 | from tqdm import tqdm 9 | from sklearn.neighbors import BallTree 10 | 11 | 12 | def knn_interpolation(cumulated_pc: np.ndarray, full_sized_data: np.ndarray, k=5): 13 | """ 14 | Using k-nn interpolation to find labels of points of the full sized pointcloud 15 | :param cumulated_pc: cumulated pointcloud results after running the network 16 | :param full_sized_data: full sized point cloud 17 | :param k: k for k nearest neighbor interpolation 18 | :return: pointcloud with predicted labels in last column and ground truth labels in last but one column 19 | """ 20 | 21 | labeled = cumulated_pc[cumulated_pc[:, -1] != -1] 22 | to_be_predicted = full_sized_data.copy() 23 | 24 | ball_tree = BallTree(labeled[:, :3], metric='euclidean') 25 | 26 | knn_classes = labeled[ball_tree.query(to_be_predicted[:, :3], k=k)[1]][:, :, -1].astype(int) 27 | 28 | interpolated = np.zeros(knn_classes.shape[0]) 29 | 30 | for i in range(knn_classes.shape[0]): 31 | interpolated[i] = np.bincount(knn_classes[i]).argmax() 32 | 33 | output = np.zeros((to_be_predicted.shape[0], to_be_predicted.shape[1]+1)) 34 | output[:, :-1] = to_be_predicted 35 | 36 | output[:, -1] = interpolated 37 | 38 | return output 39 | 40 | 41 | def calculate_scores(cumulated_result: Dict[str, np.ndarray], num_classes: int): 42 | """ 43 | calculate evaluation metrics of the prediction 44 | :param cumulated_result: cumulated_result: last column = predicted label; last but one column = ground truth 45 | :param num_classes: number of distinct classes of the dataset 46 | :return: class_acc, class_iou, overall_acc 47 | """ 48 | total_seen_from_class = [0 for _ in range(num_classes)] 49 | total_pred_from_class = [0 for _ in range(num_classes)] 50 | total_correct_from_class = [0 for _ in range(num_classes)] 51 | 52 | for key in cumulated_result.keys(): 53 | for class_id in range(num_classes): 54 | total_seen_from_class[class_id] += (cumulated_result[key][:, -2] == class_id).sum() 55 | total_pred_from_class[class_id] += (cumulated_result[key][:, -1] == class_id).sum() 56 | 57 | total_correct_from_class[class_id] += \ 58 | np.logical_and((cumulated_result[key][:, -2] == class_id), 59 | (cumulated_result[key][:, -1] == class_id)).sum() 60 | 61 | class_acc = [total_correct_from_class[i] / total_seen_from_class[i] for i in range(num_classes)] 62 | 63 | class_iou = [total_correct_from_class[i] / 64 | (total_seen_from_class[i] + total_pred_from_class[i] - total_correct_from_class[i]) 65 | for i in range(num_classes)] 66 | 67 | overall_acc = sum(total_correct_from_class) / sum(total_seen_from_class) 68 | 69 | return class_acc, class_iou, overall_acc 70 | 71 | 72 | def save_pc_as_obj(cumulated_result: Dict[str, np.ndarray], label_colors: np.ndarray, save_dir: str): 73 | """ 74 | save pointclouds as obj files for later inspection with meshlab 75 | :param cumulated_result: cumulated_result: last column = predicted label; last but one column = ground truth 76 | :param label_colors: npy array containing the color information for each label class 77 | :param save_dir: directory to save obj point clouds 78 | :return: None 79 | """ 80 | pointclouds_path = save_dir + '/pointclouds' 81 | 82 | for key in tqdm(cumulated_result.keys(), desc='Save obj point clouds to disk'): 83 | if not os.path.exists(pointclouds_path): 84 | os.makedirs(pointclouds_path) 85 | 86 | # Save predicted point clouds as obj files for later inspection using meshlab 87 | fout = open(f"{pointclouds_path}/{key}_pred.obj", 'w') 88 | pointcloud = cumulated_result[key] 89 | for j in range(pointcloud.shape[0]): 90 | color = label_colors[pointcloud[j, -1].astype(int)] 91 | fout.write(f"v {str(pointcloud[j, 0]).replace('.', ',')}" 92 | f" {str(pointcloud[j, 1]).replace('.', ',')}" 93 | f" {str(pointcloud[j, 2]).replace('.', ',')}" 94 | f" {color[0]} {color[1]} {color[2]}\n") 95 | fout.close() 96 | 97 | 98 | def save_npy_results(cumulated_result: Dict[str, np.ndarray], save_dir: str): 99 | """ 100 | save cumulated results to disk 101 | :param cumulated_result: last column = predicted label; last but one column = ground truth 102 | :param save_dir: directory to save npy arrays 103 | :return: None 104 | """ 105 | results_npy_path = save_dir + '/results_npy' 106 | 107 | for key in tqdm(cumulated_result.keys(), desc='Save npy results to disk'): 108 | if not os.path.exists(results_npy_path): 109 | os.makedirs(results_npy_path) 110 | 111 | np.save(f"{results_npy_path}/{key}", cumulated_result[key]) 112 | -------------------------------------------------------------------------------- /tools/lazy_decorator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Useful tool in order to access cached version of properties and functions which have to be executed just once 3 | (especially useful for building up the tensorflow computation graph) 4 | adapted from: https://stevenloria.com/lazy-properties/ and 5 | https://danijar.com/structuring-your-tensorflow-models/ 6 | """ 7 | import functools 8 | 9 | 10 | def lazy_property(function): 11 | """ 12 | caches the output of the property and just returns the value for next calls 13 | :param function: property to be cached 14 | :return: cached output of property 15 | """ 16 | attribute = '_cache_' + function.__name__ 17 | 18 | @property 19 | @functools.wraps(function) 20 | def decorator(self): 21 | if not hasattr(self, attribute): 22 | setattr(self, attribute, function(self)) 23 | return getattr(self, attribute) 24 | 25 | return decorator 26 | 27 | 28 | def lazy_function(function): 29 | """ 30 | caches the output of the function and just returns the value for next calls 31 | :param function: function to be cached 32 | :return: cached output of function 33 | """ 34 | attribute = '_cache_' + function.__name__ 35 | 36 | @functools.wraps(function) 37 | def decorator(self): 38 | if not hasattr(self, attribute): 39 | setattr(self, attribute, function(self)) 40 | return getattr(self, attribute) 41 | 42 | return decorator 43 | -------------------------------------------------------------------------------- /tools/meta/class_names.txt: -------------------------------------------------------------------------------- 1 | ceiling 2 | floor 3 | wall 4 | beam 5 | column 6 | window 7 | door 8 | table 9 | chair 10 | sofa 11 | bookcase 12 | board 13 | clutter 14 | -------------------------------------------------------------------------------- /tools/prepare_s3dis.py: -------------------------------------------------------------------------------- 1 | """ 2 | adapted from https://github.com/charlesq34/pointnet 3 | """ 4 | 5 | import os 6 | import numpy as np 7 | import glob 8 | import argparse 9 | import tools 10 | from pathlib import Path 11 | from tqdm import tqdm 12 | 13 | 14 | def collect_point_label(anno_path, out_filename): 15 | """ Convert original dataset files to data_label file (each line is XYZRGBL). 16 | We aggregated all the points from each instance in the room. 17 | 18 | Args: 19 | anno_path: path to annotations. e.g. Area_1/office_2/Annotations/ 20 | out_filename: path to save collected points and labels (each line is XYZRGBL) 21 | Returns: 22 | None 23 | Note: 24 | the points are shifted before save, the most negative point is now at origin. 25 | """ 26 | 27 | g_classes = [x.rstrip() for x in open('meta/class_names.txt')] 28 | g_class2label = {cls: i for i, cls in enumerate(g_classes)} 29 | 30 | points_list = [] 31 | 32 | for f in glob.glob(os.path.join(anno_path, '*.txt')): 33 | cls = os.path.basename(f).split('_')[0] 34 | if cls not in g_classes: # note: in some room there is 'staris' class.. 35 | cls = 'clutter' 36 | points = np.loadtxt(f) 37 | labels = np.ones((points.shape[0], 1)) * g_class2label[cls] 38 | points_list.append(np.concatenate([points, labels], 1)) # Nx7 39 | 40 | data_label = np.concatenate(points_list, 0) 41 | xyz_min = np.amin(data_label, axis=0)[0:3] 42 | data_label[:, 0:3] -= xyz_min 43 | data_label = data_label.astype(dtype=np.float32) 44 | 45 | output_folder = Path(os.path.dirname(out_filename)) 46 | output_folder.mkdir(parents=True, exist_ok=True) 47 | 48 | np.save(out_filename, data_label) 49 | 50 | 51 | def main(params): 52 | anno_paths = [x[0] for x in os.walk(params.input_dir) if x[0].endswith('Annotations')] 53 | 54 | # Note: there is an extra character in the v1.2 data in Area_5/hallway_6. It's fixed manually. 55 | for anno_path in tqdm(anno_paths): 56 | elements = anno_path.split('/') 57 | out_filename = elements[-3] + '_' + elements[-2] + '.npy' # Area_1_hallway_1.npy 58 | try: 59 | collect_point_label(anno_path, os.path.join(params.output_dir, elements[-3], 'full_size', out_filename)) 60 | except Exception as e: 61 | print(str(e)) 62 | print(out_filename) 63 | 64 | 65 | if __name__ == '__main__': 66 | parser = argparse.ArgumentParser(description='Convert original S3DIS dataset to npy based file format used' 67 | 'by our framework') 68 | 69 | parser.add_argument('--input_dir', required=True, help='root directory of original data') 70 | parser.add_argument('--output_dir', required=True, help='root directory of output npys') 71 | params = parser.parse_args() 72 | 73 | tools.pretty_print_arguments(params) 74 | 75 | main(params) 76 | -------------------------------------------------------------------------------- /tools/tf_util.py: -------------------------------------------------------------------------------- 1 | """ Wrapper functions for TensorFlow layers. 2 | 3 | Author: Charles R. Qi 4 | Date: November 2016 5 | """ 6 | 7 | import tensorflow as tf 8 | 9 | 10 | def gru_seq_g(inputs, n_units, dropout, scope): 11 | """ 12 | 13 | :param inputs: (BS*N, K, F) 14 | :param n_units: (F) size of GRU cell 15 | :param dropout: BOOL 16 | :param scope: 17 | :return: 18 | """ 19 | with tf.variable_scope(scope) as sc: 20 | 21 | # create gru cell 22 | gru = tf.nn.rnn_cell.GRUCell(n_units, reuse=tf.AUTO_REUSE) 23 | if dropout: 24 | gru = tf.nn.rnn_cell.DropoutWrapper(gru, output_keep_prob=0.5) 25 | 26 | input = inputs[:, 0, :] # 0th neighbours is point itself 27 | state = input 28 | output, state = gru(input, state) 29 | 30 | outputs = [] 31 | for i in range(0, inputs.shape[1]): # iterate over all neighbours 32 | input = inputs[:, i, :] 33 | output, state = gru(input, state) # shape: (BS*N, F), (BS,*N, F) 34 | outputs.append(output) 35 | 36 | outputs = tf.stack(outputs, axis=1) # shape: (BS*N, K, F) 37 | return output 38 | 39 | 40 | def _variable_on_cpu(name, shape, initializer, use_fp16=False): 41 | """Helper to create a Variable stored on CPU memory. 42 | Args: 43 | name: name of the variable 44 | shape: list of ints 45 | initializer: initializer for Variable 46 | Returns: 47 | Variable Tensor 48 | """ 49 | with tf.device('/cpu:0'): 50 | dtype = tf.float16 if use_fp16 else tf.float32 51 | var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype) 52 | return var 53 | 54 | 55 | def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True): 56 | """Helper to create an initialized Variable with weight decay. 57 | 58 | Note that the Variable is initialized with a truncated normal distribution. 59 | A weight decay is added only if one is specified. 60 | 61 | Args: 62 | name: name of the variable 63 | shape: list of ints 64 | stddev: standard deviation of a truncated Gaussian 65 | wd: add L2Loss weight decay multiplied by this float. If None, weight 66 | decay is not added for this Variable. 67 | use_xavier: bool, whether to use xavier initializer 68 | 69 | Returns: 70 | Variable Tensor 71 | """ 72 | if use_xavier: 73 | initializer = tf.contrib.layers.xavier_initializer() 74 | else: 75 | initializer = tf.truncated_normal_initializer(stddev=stddev) 76 | var = _variable_on_cpu(name, shape, initializer) 77 | if wd is not None: 78 | weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss') 79 | tf.add_to_collection('losses', weight_decay) 80 | return var 81 | 82 | 83 | def consolidation_unit(inputs, 84 | size, 85 | scope, 86 | bn=False, 87 | bn_decay=None, 88 | is_training=None): 89 | with tf.variable_scope(scope) as sc: 90 | net = conv2d(inputs, size, [1, 1], padding='VALID', stride=[1, 1], bn=bn, is_training=is_training, 91 | scope=scope + '/conv', bn_decay=bn_decay) 92 | 93 | net_pooled = tf.reduce_max(net, axis=1, keep_dims=True, name=scope + '/global_feature') 94 | net_repeated = tf.tile(net_pooled, [1, tf.shape(net)[1], 1, 1], name='repeat') 95 | net = tf.concat(values=[net, net_repeated], axis=3) # put net and global features next to each other 96 | 97 | return net 98 | 99 | 100 | def conv2d(inputs, 101 | num_output_channels, 102 | kernel_size, 103 | scope, 104 | stride=[1, 1], 105 | padding='SAME', 106 | use_xavier=True, 107 | stddev=1e-3, 108 | weight_decay=0.0, 109 | activation_fn=tf.nn.relu, 110 | bn=False, 111 | bn_decay=None, 112 | is_training=None): 113 | """ 2D convolution with non-linear operation. 114 | 115 | Args: 116 | inputs: 4-D tensor variable BxHxWxC 117 | num_output_channels: int 118 | kernel_size: a list of 2 ints 119 | scope: string 120 | stride: a list of 2 ints 121 | padding: 'SAME' or 'VALID' 122 | use_xavier: bool, use xavier_initializer if true 123 | stddev: float, stddev for truncated_normal init 124 | weight_decay: float 125 | activation_fn: function 126 | bn: bool, whether to use batch norm 127 | bn_decay: float or float tensor variable in [0,1] 128 | is_training: bool Tensor variable 129 | 130 | Returns: 131 | Variable tensor 132 | """ 133 | with tf.variable_scope(scope) as sc: 134 | kernel_h, kernel_w = kernel_size 135 | num_in_channels = inputs.get_shape()[-1].value 136 | kernel_shape = [kernel_h, kernel_w, 137 | num_in_channels, num_output_channels] 138 | kernel = _variable_with_weight_decay('weights', 139 | shape=kernel_shape, 140 | use_xavier=use_xavier, 141 | stddev=stddev, 142 | wd=weight_decay) 143 | stride_h, stride_w = stride 144 | outputs = tf.nn.conv2d(inputs, kernel, 145 | [1, stride_h, stride_w, 1], 146 | padding=padding) 147 | biases = _variable_on_cpu('biases', [num_output_channels], 148 | tf.constant_initializer(0.0)) 149 | outputs = tf.nn.bias_add(outputs, biases) 150 | 151 | if bn: 152 | outputs = batch_norm_for_conv2d(outputs, is_training, 153 | bn_decay=bn_decay, scope='bn') 154 | 155 | if activation_fn is not None: 156 | outputs = activation_fn(outputs) 157 | return outputs 158 | 159 | 160 | def conv2d_transpose(inputs, 161 | num_output_channels, 162 | kernel_size, 163 | scope, 164 | stride=[1, 1], 165 | padding='SAME', 166 | use_xavier=True, 167 | stddev=1e-3, 168 | weight_decay=0.0, 169 | activation_fn=tf.nn.relu, 170 | bn=False, 171 | bn_decay=None, 172 | is_training=None): 173 | """ 2D convolution transpose with non-linear operation. 174 | 175 | Args: 176 | inputs: 4-D tensor variable BxHxWxC 177 | num_output_channels: int 178 | kernel_size: a list of 2 ints 179 | scope: string 180 | stride: a list of 2 ints 181 | padding: 'SAME' or 'VALID' 182 | use_xavier: bool, use xavier_initializer if true 183 | stddev: float, stddev for truncated_normal init 184 | weight_decay: float 185 | activation_fn: function 186 | bn: bool, whether to use batch norm 187 | bn_decay: float or float tensor variable in [0,1] 188 | is_training: bool Tensor variable 189 | 190 | Returns: 191 | Variable tensor 192 | 193 | Note: conv2d(conv2d_transpose(a, num_out, ksize, stride), a.shape[-1], ksize, stride) == a 194 | """ 195 | with tf.variable_scope(scope) as sc: 196 | kernel_h, kernel_w = kernel_size 197 | num_in_channels = inputs.get_shape()[-1].value 198 | kernel_shape = [kernel_h, kernel_w, 199 | num_output_channels, num_in_channels] # reversed to conv2d 200 | kernel = _variable_with_weight_decay('weights', 201 | shape=kernel_shape, 202 | use_xavier=use_xavier, 203 | stddev=stddev, 204 | wd=weight_decay) 205 | stride_h, stride_w = stride 206 | 207 | # from slim.convolution2d_transpose 208 | def get_deconv_dim(dim_size, stride_size, kernel_size, padding): 209 | dim_size *= stride_size 210 | 211 | if padding == 'VALID' and dim_size is not None: 212 | dim_size += max(kernel_size - stride_size, 0) 213 | return dim_size 214 | 215 | # caculate output shape 216 | batch_size = inputs.get_shape()[0].value 217 | height = inputs.get_shape()[1].value 218 | width = inputs.get_shape()[2].value 219 | out_height = get_deconv_dim(height, stride_h, kernel_h, padding) 220 | out_width = get_deconv_dim(width, stride_w, kernel_w, padding) 221 | output_shape = [batch_size, out_height, out_width, num_output_channels] 222 | 223 | outputs = tf.nn.conv2d_transpose(inputs, kernel, output_shape, 224 | [1, stride_h, stride_w, 1], 225 | padding=padding) 226 | biases = _variable_on_cpu('biases', [num_output_channels], 227 | tf.constant_initializer(0.0)) 228 | outputs = tf.nn.bias_add(outputs, biases) 229 | 230 | if bn: 231 | outputs = batch_norm_for_conv2d(outputs, is_training, 232 | bn_decay=bn_decay, scope='bn') 233 | 234 | if activation_fn is not None: 235 | outputs = activation_fn(outputs) 236 | return outputs 237 | 238 | 239 | def gru_seq(inputs, n_units, batch_size, TIME, dropout, scope): 240 | with tf.variable_scope(scope) as _: 241 | 242 | gru = tf.contrib.rnn.GRUCell(n_units) 243 | if dropout: 244 | gru = tf.nn.rnn_cell.DropoutWrapper(gru, output_keep_prob=0.5) 245 | 246 | # print inputs.shape 247 | input = inputs[:, 0, :] 248 | state = input 249 | output, state = gru(input, state) 250 | 251 | for i in range(1,inputs.shape[1]): 252 | input = inputs[:, i, :] 253 | output, state = gru(input, state) 254 | 255 | input = inputs[:, 0, :] 256 | output, state = gru(input, state) 257 | outputs = output 258 | for i in range(1, inputs.shape[1]): 259 | input = inputs[:, i, :] 260 | input, state = gru(input, state) 261 | outputs = tf.concat([outputs, output], axis=1) 262 | 263 | output = tf.reshape(outputs, (batch_size, TIME, n_units)) 264 | 265 | return output 266 | 267 | 268 | def gru_noseq(inputs, 269 | n_units, 270 | num_layers, 271 | batch_size, TIME,dropout,scope): 272 | with tf.variable_scope(scope) as sc: 273 | 274 | gru = tf.contrib.rnn.GRUCell(n_units) 275 | if dropout: 276 | gru = tf.nn.rnn_cell.DropoutWrapper(gru, output_keep_prob=0.5) 277 | #gru = tf.nn.rnn_cell.MultiRNNCell([gru] * num_layers) 278 | 279 | 280 | eol = tf.ones(inputs[:,0,:].shape) 281 | 282 | #print inputs.shape 283 | input = inputs[:,0,:] 284 | state = input 285 | output, state = gru(input, state) 286 | #outputs = output 287 | for i in range(1,inputs.shape[1]): 288 | input = inputs[:,i,:] 289 | output,state=gru(input,state) 290 | outputs = output 291 | for i in range(1,inputs.shape[1]): 292 | outputs=tf.concat([outputs,output],axis=1) 293 | #input = inputs[:, 0, :] 294 | #output,state = gru(input,state) 295 | #outputs = output 296 | #for i in range(1,inputs.shape[1]): 297 | # input = inputs[:, i, :] 298 | # input,state=gru(input,state) 299 | # output = tf.clip_by_value(output,0,100) 300 | # outputs = tf.concat([outputs, output], axis=1) 301 | #print output.shape 302 | output = tf.reshape(outputs, (batch_size / TIME, TIME, n_units)) 303 | 304 | return output 305 | 306 | 307 | def conv3d(inputs, 308 | num_output_channels, 309 | kernel_size, 310 | scope, 311 | stride=[1, 1, 1], 312 | padding='SAME', 313 | use_xavier=True, 314 | stddev=1e-3, 315 | weight_decay=0.0, 316 | activation_fn=tf.nn.relu, 317 | bn=False, 318 | bn_decay=None, 319 | is_training=None): 320 | """ 3D convolution with non-linear operation. 321 | 322 | Args: 323 | inputs: 5-D tensor variable BxDxHxWxC 324 | num_output_channels: int 325 | kernel_size: a list of 3 ints 326 | scope: string 327 | stride: a list of 3 ints 328 | padding: 'SAME' or 'VALID' 329 | use_xavier: bool, use xavier_initializer if true 330 | stddev: float, stddev for truncated_normal init 331 | weight_decay: float 332 | activation_fn: function 333 | bn: bool, whether to use batch norm 334 | bn_decay: float or float tensor variable in [0,1] 335 | is_training: bool Tensor variable 336 | 337 | Returns: 338 | Variable tensor 339 | """ 340 | with tf.variable_scope(scope) as sc: 341 | kernel_d, kernel_h, kernel_w = kernel_size 342 | num_in_channels = inputs.get_shape()[-1].value 343 | kernel_shape = [kernel_d, kernel_h, kernel_w, 344 | num_in_channels, num_output_channels] 345 | kernel = _variable_with_weight_decay('weights', 346 | shape=kernel_shape, 347 | use_xavier=use_xavier, 348 | stddev=stddev, 349 | wd=weight_decay) 350 | stride_d, stride_h, stride_w = stride 351 | outputs = tf.nn.conv3d(inputs, kernel, 352 | [1, stride_d, stride_h, stride_w, 1], 353 | padding=padding) 354 | biases = _variable_on_cpu('biases', [num_output_channels], 355 | tf.constant_initializer(0.0)) 356 | outputs = tf.nn.bias_add(outputs, biases) 357 | 358 | if bn: 359 | outputs = batch_norm_for_conv3d(outputs, is_training, 360 | bn_decay=bn_decay, scope='bn') 361 | 362 | if activation_fn is not None: 363 | outputs = activation_fn(outputs) 364 | return outputs 365 | 366 | 367 | def fully_connected(inputs, 368 | num_outputs, 369 | scope, 370 | use_xavier=True, 371 | stddev=1e-3, 372 | weight_decay=0.0, 373 | activation_fn=tf.nn.relu, 374 | bn=False, 375 | bn_decay=None, 376 | is_training=None): 377 | """ Fully connected layer with non-linear operation. 378 | 379 | Args: 380 | inputs: 2-D tensor BxN 381 | num_outputs: int 382 | 383 | Returns: 384 | Variable tensor of size B x num_outputs. 385 | """ 386 | with tf.variable_scope(scope) as sc: 387 | num_input_units = inputs.get_shape()[-1].value 388 | weights = _variable_with_weight_decay('weights', 389 | shape=[num_input_units, num_outputs], 390 | use_xavier=use_xavier, 391 | stddev=stddev, 392 | wd=weight_decay) 393 | outputs = tf.matmul(inputs, weights) 394 | biases = _variable_on_cpu('biases', [num_outputs], 395 | tf.constant_initializer(0.0)) 396 | outputs = tf.nn.bias_add(outputs, biases) 397 | 398 | if bn: 399 | outputs = batch_norm_for_fc(outputs, is_training, bn_decay, 'bn') 400 | 401 | if activation_fn is not None: 402 | outputs = activation_fn(outputs) 403 | return outputs 404 | 405 | 406 | def max_pool2d(inputs, 407 | kernel_size, 408 | scope, 409 | stride=[2, 2], 410 | padding='VALID'): 411 | """ 2D max pooling. 412 | 413 | Args: 414 | inputs: 4-D tensor BxHxWxC 415 | kernel_size: a list of 2 ints 416 | stride: a list of 2 ints 417 | 418 | Returns: 419 | Variable tensor 420 | """ 421 | with tf.variable_scope(scope) as sc: 422 | kernel_h, kernel_w = kernel_size 423 | stride_h, stride_w = stride 424 | outputs = tf.nn.max_pool(inputs, 425 | ksize=[1, kernel_h, kernel_w, 1], 426 | strides=[1, stride_h, stride_w, 1], 427 | padding=padding, 428 | name=sc.name) 429 | return outputs 430 | 431 | def avg_pool2d(inputs, 432 | kernel_size, 433 | scope, 434 | stride=[2, 2], 435 | padding='VALID'): 436 | """ 2D avg pooling. 437 | 438 | Args: 439 | inputs: 4-D tensor BxHxWxC 440 | kernel_size: a list of 2 ints 441 | stride: a list of 2 ints 442 | 443 | Returns: 444 | Variable tensor 445 | """ 446 | with tf.variable_scope(scope) as sc: 447 | kernel_h, kernel_w = kernel_size 448 | stride_h, stride_w = stride 449 | outputs = tf.nn.avg_pool(inputs, 450 | ksize=[1, kernel_h, kernel_w, 1], 451 | strides=[1, stride_h, stride_w, 1], 452 | padding=padding, 453 | name=sc.name) 454 | return outputs 455 | 456 | 457 | def max_pool3d(inputs, 458 | kernel_size, 459 | scope, 460 | stride=[2, 2, 2], 461 | padding='VALID'): 462 | """ 3D max pooling. 463 | 464 | Args: 465 | inputs: 5-D tensor BxDxHxWxC 466 | kernel_size: a list of 3 ints 467 | stride: a list of 3 ints 468 | 469 | Returns: 470 | Variable tensor 471 | """ 472 | with tf.variable_scope(scope) as sc: 473 | kernel_d, kernel_h, kernel_w = kernel_size 474 | stride_d, stride_h, stride_w = stride 475 | outputs = tf.nn.max_pool3d(inputs, 476 | ksize=[1, kernel_d, kernel_h, kernel_w, 1], 477 | strides=[1, stride_d, stride_h, stride_w, 1], 478 | padding=padding, 479 | name=sc.name) 480 | return outputs 481 | 482 | def avg_pool3d(inputs, 483 | kernel_size, 484 | scope, 485 | stride=[2, 2, 2], 486 | padding='VALID'): 487 | """ 3D avg pooling. 488 | 489 | Args: 490 | inputs: 5-D tensor BxDxHxWxC 491 | kernel_size: a list of 3 ints 492 | stride: a list of 3 ints 493 | 494 | Returns: 495 | Variable tensor 496 | """ 497 | with tf.variable_scope(scope) as sc: 498 | kernel_d, kernel_h, kernel_w = kernel_size 499 | stride_d, stride_h, stride_w = stride 500 | outputs = tf.nn.avg_pool3d(inputs, 501 | ksize=[1, kernel_d, kernel_h, kernel_w, 1], 502 | strides=[1, stride_d, stride_h, stride_w, 1], 503 | padding=padding, 504 | name=sc.name) 505 | return outputs 506 | 507 | 508 | def batch_norm_template(inputs, is_training, scope, moments_dims, bn_decay): 509 | """ Batch normalization on convolutional maps and beyond... 510 | Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow 511 | 512 | Args: 513 | inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC 514 | is_training: boolean tf.Varialbe, true indicates training phase 515 | scope: string, variable scope 516 | moments_dims: a list of ints, indicating dimensions for moments calculation 517 | bn_decay: float or float tensor variable, controling moving average weight 518 | Return: 519 | normed: batch-normalized maps 520 | """ 521 | with tf.variable_scope(scope) as sc: 522 | num_channels = inputs.get_shape()[-1].value 523 | beta = tf.Variable(tf.constant(0.0, shape=[num_channels]), 524 | name='beta', trainable=True) 525 | gamma = tf.Variable(tf.constant(1.0, shape=[num_channels]), 526 | name='gamma', trainable=True) 527 | batch_mean, batch_var = tf.nn.moments(inputs, moments_dims, name='moments') 528 | decay = bn_decay if bn_decay is not None else 0.9 529 | ema = tf.train.ExponentialMovingAverage(decay=decay) 530 | # Operator that maintains moving averages of variables. 531 | ema_apply_op = tf.cond(is_training, 532 | lambda: ema.apply([batch_mean, batch_var]), 533 | lambda: tf.no_op()) 534 | 535 | # Update moving average and return current batch's avg and var. 536 | def mean_var_with_update(): 537 | with tf.control_dependencies([ema_apply_op]): 538 | return tf.identity(batch_mean), tf.identity(batch_var) 539 | 540 | # ema.average returns the Variable holding the average of var. 541 | mean, var = tf.cond(is_training, 542 | mean_var_with_update, 543 | lambda: (ema.average(batch_mean), ema.average(batch_var))) 544 | normed = tf.nn.batch_normalization(inputs, mean, var, beta, gamma, 1e-3) 545 | return normed 546 | 547 | 548 | def batch_norm_for_fc(inputs, is_training, bn_decay, scope): 549 | """ Batch normalization on FC data. 550 | 551 | Args: 552 | inputs: Tensor, 2D BxC input 553 | is_training: boolean tf.Varialbe, true indicates training phase 554 | bn_decay: float or float tensor variable, controling moving average weight 555 | scope: string, variable scope 556 | Return: 557 | normed: batch-normalized maps 558 | """ 559 | return batch_norm_template(inputs, is_training, scope, [0,], bn_decay) 560 | 561 | 562 | def batch_norm_for_conv1d(inputs, is_training, bn_decay, scope): 563 | """ Batch normalization on 1D convolutional maps. 564 | 565 | Args: 566 | inputs: Tensor, 3D BLC input maps 567 | is_training: boolean tf.Varialbe, true indicates training phase 568 | bn_decay: float or float tensor variable, controling moving average weight 569 | scope: string, variable scope 570 | Return: 571 | normed: batch-normalized maps 572 | """ 573 | return batch_norm_template(inputs, is_training, scope, [0,1], bn_decay) 574 | 575 | 576 | def batch_norm_for_conv2d(inputs, is_training, bn_decay, scope): 577 | """ Batch normalization on 2D convolutional maps. 578 | 579 | Args: 580 | inputs: Tensor, 4D BHWC input maps 581 | is_training: boolean tf.Varialbe, true indicates training phase 582 | bn_decay: float or float tensor variable, controling moving average weight 583 | scope: string, variable scope 584 | Return: 585 | normed: batch-normalized maps 586 | """ 587 | return batch_norm_template(inputs, is_training, scope, [0,1,2], bn_decay) 588 | 589 | 590 | def batch_norm_for_conv3d(inputs, is_training, bn_decay, scope): 591 | """ Batch normalization on 3D convolutional maps. 592 | 593 | Args: 594 | inputs: Tensor, 5D BDHWC input maps 595 | is_training: boolean tf.Varialbe, true indicates training phase 596 | bn_decay: float or float tensor variable, controling moving average weight 597 | scope: string, variable scope 598 | Return: 599 | normed: batch-normalized maps 600 | """ 601 | return batch_norm_template(inputs, is_training, scope, [0,1,2,3], bn_decay) 602 | 603 | 604 | def dropout(inputs, 605 | is_training, 606 | scope, 607 | keep_prob=0.5, 608 | noise_shape=None): 609 | """ Dropout layer. 610 | 611 | Args: 612 | inputs: tensor 613 | is_training: boolean tf.Variable 614 | scope: string 615 | keep_prob: float in [0,1] 616 | noise_shape: list of ints 617 | 618 | Returns: 619 | tensor variable 620 | """ 621 | with tf.variable_scope(scope) as sc: 622 | outputs = tf.cond(is_training, 623 | lambda: tf.nn.dropout(inputs, keep_prob, noise_shape), 624 | lambda: inputs) 625 | return outputs 626 | -------------------------------------------------------------------------------- /tools/tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | various helper functions to make life easier 3 | """ 4 | 5 | import time 6 | import subprocess 7 | import os 8 | from pathlib import Path 9 | import sys 10 | from datetime import datetime 11 | import logging 12 | import re 13 | import string 14 | import random 15 | 16 | 17 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): 18 | return ''.join(random.choice(chars) for _ in range(size)) 19 | 20 | 21 | def us2mc(x): 22 | """ 23 | underscore to mixed-case notation 24 | from https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch03s16.html 25 | """ 26 | return re.sub(r'_([a-z])', lambda m: (m.group(1).upper()), x) 27 | 28 | 29 | def us2cw(x): 30 | """ 31 | underscore to capwords notation 32 | from https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch03s16.html 33 | """ 34 | s = us2mc(x) 35 | return s[0].upper()+s[1:] 36 | 37 | 38 | def import_class(package_path, class_name): 39 | """ 40 | dynamic import of a class from a given package 41 | :param package_path: path to the package 42 | :param class_name: class to be dynamically loaded 43 | :return: dynamically loaded class 44 | """ 45 | try: 46 | logging.info(f"Loading {package_path}.{class_name} ...") 47 | module = __import__(f"{package_path}.{class_name}", fromlist=[class_name]) 48 | return getattr(module, us2cw(class_name)) 49 | except ModuleNotFoundError as exc: 50 | logging.error(f"{package_path}.{class_name} could not be found") 51 | exit(1) 52 | 53 | 54 | def setup_logger(): 55 | """ 56 | setup the logging mechanism where log messages are saved in time-encoded txt files as well as to the terminal 57 | :return: directory path in which logs are saved 58 | """ 59 | directory_path = f"logs/{datetime.now():%Y-%m-%d@%H:%M:%S}_{id_generator()}" 60 | 61 | path = Path(directory_path) 62 | path.mkdir(parents=True, exist_ok=True) 63 | 64 | log_format = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] [%(pathname)s:%(lineno)04d] %(message)s") 65 | logger = logging.getLogger() 66 | logger.setLevel(logging.DEBUG) 67 | 68 | file_handler = logging.FileHandler(f"{directory_path}/{datetime.now():%Y-%m-%d@%H:%M:%S}_{id_generator()}.log") 69 | file_handler.setFormatter(log_format) 70 | logger.addHandler(file_handler) 71 | 72 | file_handler = logging.StreamHandler(sys.stdout) 73 | file_handler.setFormatter(log_format) 74 | logger.addHandler(file_handler) 75 | 76 | logging.root = logger 77 | 78 | logging.info('START LOGGING') 79 | logging.info(f"Current Git Version: {git_version()}") 80 | 81 | return directory_path 82 | 83 | 84 | def pretty_print_arguments(args): 85 | """ 86 | return a nicely formatted list of passed arguments 87 | :param args: arguments passed to the program via terminal 88 | :return: None 89 | """ 90 | longest_key = max([len(key) for key in vars(args)]) 91 | 92 | print('Program was launched with the following arguments:') 93 | 94 | for key, item in vars(args).items(): 95 | print("~ {0:{s}} \t {1}".format(key, item, s=longest_key)) 96 | 97 | print('') 98 | # Wait a bit until program execution continues 99 | time.sleep(0.1) 100 | 101 | 102 | def git_version(): 103 | """ 104 | return git revision such that it can be also logged to keep track of results 105 | :return: git revision hash 106 | """ 107 | def _minimal_ext_cmd(cmd): 108 | # construct minimal environment 109 | env = {} 110 | for k in ['SYSTEMROOT', 'PATH']: 111 | v = os.environ.get(k) 112 | if v is not None: 113 | env[k] = v 114 | # LANGUAGE is used on win32 115 | env['LANGUAGE'] = 'C' 116 | env['LANG'] = 'C' 117 | env['LC_ALL'] = 'C' 118 | out = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env).communicate()[0] 119 | return out 120 | 121 | try: 122 | out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD']) 123 | git_revision = out.strip().decode('ascii') 124 | except OSError: 125 | git_revision = "Unknown" 126 | 127 | return git_revision 128 | -------------------------------------------------------------------------------- /tools/viz.py: -------------------------------------------------------------------------------- 1 | import vtk 2 | import numpy as np 3 | import random 4 | 5 | print('Using', vtk.vtkVersion.GetVTKSourceVersion()) 6 | 7 | 8 | class MyInteractorStyle(vtk.vtkInteractorStyleTrackballCamera): 9 | def __init__(self, parent, pointcloud): 10 | self.parent = parent 11 | self.pointcloud = pointcloud 12 | self.AddObserver("KeyPressEvent", self.keyPressEvent) 13 | 14 | def keyPressEvent(self, obj, event): 15 | key = self.parent.GetKeySym() 16 | if key == '+': 17 | point_size = self.pointcloud.vtkActor.GetProperty().GetPointSize() 18 | self.pointcloud.vtkActor.GetProperty().SetPointSize(point_size + 1) 19 | print(str(point_size) + " " + key) 20 | return 21 | 22 | 23 | class VtkPointCloud: 24 | 25 | def __init__(self, point_size=18, maxNumPoints=1e8): 26 | self.maxNumPoints = maxNumPoints 27 | self.vtkPolyData = vtk.vtkPolyData() 28 | self.clear_points() 29 | 30 | self.colors = vtk.vtkUnsignedCharArray() 31 | self.colors.SetNumberOfComponents(3) 32 | self.colors.SetName("Colors") 33 | 34 | mapper = vtk.vtkPolyDataMapper() 35 | mapper.SetInputData(self.vtkPolyData) 36 | 37 | self.vtkActor = vtk.vtkActor() 38 | self.vtkActor.SetMapper(mapper) 39 | self.vtkActor.GetProperty().SetPointSize(point_size) 40 | 41 | def add_point(self, point, color): 42 | if self.vtkPoints.GetNumberOfPoints() < self.maxNumPoints: 43 | pointId = self.vtkPoints.InsertNextPoint(point[:]) 44 | self.colors.InsertNextTuple(color) 45 | self.vtkDepth.InsertNextValue(point[2]) 46 | self.vtkCells.InsertNextCell(1) 47 | self.vtkCells.InsertCellPoint(pointId) 48 | else: 49 | print("VIZ: Reached max number of points!") 50 | r = random.randint(0, self.maxNumPoints) 51 | self.vtkPoints.SetPoint(r, point[:]) 52 | self.vtkPolyData.GetPointData().SetScalars(self.colors) 53 | self.vtkCells.Modified() 54 | self.vtkPoints.Modified() 55 | self.vtkDepth.Modified() 56 | 57 | def clear_points(self): 58 | self.vtkPoints = vtk.vtkPoints() 59 | self.vtkCells = vtk.vtkCellArray() 60 | self.vtkDepth = vtk.vtkDoubleArray() 61 | self.vtkDepth.SetName('DepthArray') 62 | self.vtkPolyData.SetPoints(self.vtkPoints) 63 | self.vtkPolyData.SetVerts(self.vtkCells) 64 | self.vtkPolyData.GetPointData().SetScalars(self.vtkDepth) 65 | self.vtkPolyData.GetPointData().SetActiveScalars('DepthArray') 66 | 67 | 68 | def getActorCircle(radius_inner=100, radius_outer=99, color=(1,0,0)): 69 | """""" 70 | # create source 71 | source = vtk.vtkDiskSource() 72 | source.SetInnerRadius(radius_inner) 73 | source.SetOuterRadius(radius_outer) 74 | source.SetRadialResolution(100) 75 | source.SetCircumferentialResolution(100) 76 | 77 | # Transformer 78 | transform = vtk.vtkTransform() 79 | transform.RotateWXYZ(90, 1, 0, 0) 80 | transformFilter = vtk.vtkTransformPolyDataFilter() 81 | transformFilter.SetTransform(transform) 82 | transformFilter.SetInputConnection(source.GetOutputPort()) 83 | transformFilter.Update() 84 | 85 | # mapper 86 | mapper = vtk.vtkPolyDataMapper() 87 | mapper.SetInputConnection(transformFilter.GetOutputPort()) 88 | 89 | # actor 90 | actor = vtk.vtkActor() 91 | actor.GetProperty().SetColor(color) 92 | actor.SetMapper(mapper) 93 | 94 | return actor 95 | 96 | 97 | def show_pointclouds(points, colors, text=[], title="Default", png_path="", interactive=True): 98 | """ 99 | Show multiple point clouds specified as lists. First clouds at the bottom. 100 | :param points: list of pointclouds, item: numpy (N x 3) XYZ 101 | :param colors: list of corresponding colors, item: numpy (N x 3) RGB [0..255] 102 | :param title: window title 103 | :param text: text per point cloud 104 | :param png_path: where to save png image 105 | :param interactive: wether to display window or not, useful if you only want to take screenshot 106 | :return: nothing 107 | """ 108 | 109 | # make sure pointclouds is a list 110 | assert isinstance(points, type([])), \ 111 | "Pointclouds argument must be a list" 112 | 113 | # make sure colors is a list 114 | assert isinstance(colors, type([])), \ 115 | "Colors argument must be a list" 116 | 117 | # make sure number of pointclouds and colors are the same 118 | assert len(points) == len(colors), \ 119 | "Number of pointclouds (%d) is different then number of colors (%d)" % (len(points), len(colors)) 120 | 121 | while len(text)