├── LICENSE ├── README.md ├── cfgs ├── modelnet40 │ └── pointstack.yaml ├── partnormal │ └── pointstack.yaml └── scanobjectnn │ └── pointstack.yaml ├── core ├── builders.py ├── datasets │ ├── __init__.py │ ├── dataset_template.py │ ├── modelnet40.py │ ├── partnormal.py │ └── scanobjectnn.py └── networks │ ├── encoders │ ├── __init__.py │ ├── pointMLP.py │ ├── pointstack_cls_encoder.py │ └── pointstack_seg_encoder.py │ ├── heads │ ├── __init__.py │ ├── linear_classifier.py │ └── linear_segmentator.py │ └── networks │ ├── __init__.py │ ├── network_template.py │ ├── pointmlp.py │ └── pointstack.py ├── docs └── overview.png ├── pointnet2_ops_lib ├── MANIFEST.in ├── build │ ├── lib.linux-x86_64-3.7 │ │ └── pointnet2_ops │ │ │ ├── __init__.py │ │ │ ├── _ext-src │ │ │ ├── include │ │ │ │ ├── ball_query.h │ │ │ │ ├── cuda_utils.h │ │ │ │ ├── group_points.h │ │ │ │ ├── interpolate.h │ │ │ │ ├── sampling.h │ │ │ │ └── utils.h │ │ │ └── src │ │ │ │ ├── ball_query.cpp │ │ │ │ ├── ball_query_gpu.cu │ │ │ │ ├── bindings.cpp │ │ │ │ ├── group_points.cpp │ │ │ │ ├── group_points_gpu.cu │ │ │ │ ├── interpolate.cpp │ │ │ │ ├── interpolate_gpu.cu │ │ │ │ ├── sampling.cpp │ │ │ │ └── sampling_gpu.cu │ │ │ ├── _ext.cpython-37m-x86_64-linux-gnu.so │ │ │ ├── _version.py │ │ │ ├── pointnet2_modules.py │ │ │ └── pointnet2_utils.py │ └── temp.linux-x86_64-3.7 │ │ └── pointnet2_ops │ │ └── _ext-src │ │ └── src │ │ ├── ball_query.o │ │ ├── ball_query_gpu.o │ │ ├── bindings.o │ │ ├── group_points.o │ │ ├── group_points_gpu.o │ │ ├── interpolate.o │ │ ├── interpolate_gpu.o │ │ ├── sampling.o │ │ └── sampling_gpu.o ├── pointnet2_ops.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── requires.txt │ └── top_level.txt ├── pointnet2_ops │ ├── __init__.py │ ├── _ext-src │ │ ├── include │ │ │ ├── ball_query.h │ │ │ ├── cuda_utils.h │ │ │ ├── group_points.h │ │ │ ├── interpolate.h │ │ │ ├── sampling.h │ │ │ └── utils.h │ │ └── src │ │ │ ├── ball_query.cpp │ │ │ ├── ball_query_gpu.cu │ │ │ ├── bindings.cpp │ │ │ ├── group_points.cpp │ │ │ ├── group_points_gpu.cu │ │ │ ├── interpolate.cpp │ │ │ ├── interpolate_gpu.cu │ │ │ ├── sampling.cpp │ │ │ └── sampling_gpu.cu │ ├── _version.py │ ├── pointnet2_modules.py │ └── pointnet2_utils.py └── setup.py ├── requirements.txt ├── test.py ├── train.py ├── utils ├── runtime_utils.py └── vis_utils.py └── visualize_part.py /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PointStack 2 | 3 |

4 | overview 5 |

6 | 7 | This repository provides the official PyTorch implementation for the following paper: 8 | 9 | **Advanced Feature Learning on Point Clouds using Multi-resolution Features and Learnable Pooling**
10 | [Kevin Tirta Wijaya](https://www.ktirta.xyz), [Dong-Hee Paek](http://ave.kaist.ac.kr/bbs/board.php?bo_table=sub1_2&wr_id=5), and [Seung-Hyun Kong](http://ave.kaist.ac.kr/)
11 | [\[**arXiv**\]](https://arxiv.org/abs/2205.09962) 12 | > **Abstract:** *Existing point cloud feature learning networks often incorporate sequences of sampling, neighborhood grouping, neighborhood-wise feature learning, and feature aggregation to learn high-semantic point features that represent the global context of a point cloud. 13 | Unfortunately, such a process may result in a substantial loss of granular information due to the sampling operation. 14 | Moreover, the widely-used max-pooling feature aggregation may exacerbate the loss since it completely neglects information from non-maximum point features. 15 | Due to the compounded loss of information concerning granularity and non-maximum point features, the resulting high-semantic point features from existing networks could be insufficient to represent the local context of a point cloud, which in turn may hinder the network in distinguishing fine shapes. 16 | To cope with this problem, we propose a novel point cloud feature learning network, PointStack, using multi-resolution feature learning and learnable pooling (LP). 17 | The multi-resolution feature learning is realized by aggregating point features of various resolutions in the multiple layers, so that the final point features contain both high-semantic and high-resolution information. 18 | On the other hand, the LP is used as a generalized pooling function that calculates the weighted sum of multi-resolution point features through the attention mechanism with learnable queries, in order to extract all possible information from all available point features. 19 | Consequently, PointStack is capable of extracting high-semantic point features with minimal loss of information concerning granularity and non-maximum point features. 20 | Therefore, the final aggregated point features can effectively represent both global and local contexts of a point cloud. 21 | In addition, both the global structure and the local shape details of a point cloud can be well comprehended by the network head, which enables PointStack to advance the state-of-the-art of feature learning on point clouds. 22 | Specifically, PointStack outperforms various existing feature learning networks for shape classification and part segmentation on the ScanObjectNN and ShapeNetPart datasets.* 23 | 24 | ## Preparations 25 | 26 | **To install the requirements**: 27 | 28 | ```setup 29 | # 1. Create new environment 30 | conda create -n python=3.7 31 | 32 | # 2. Install PyTorch with corresponding cudatoolkit, for example, 33 | pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113 34 | 35 | # 3. Install other requirements 36 | pip install -r requirements.txt 37 | ``` 38 | 39 | **To prepare the dataset**: 40 | 41 | 1. Create empty directories for 'modelnet40', 'partnormal', and 'scanobjectnn' inside './data' directory. 42 | 2. Download the corresponding dataset for [Modelnet40](https://github.com/AnTao97/PointCloudDatasets), [ShapeNetPart](https://shapenet.cs.stanford.edu/media/shapenetcore_partanno_segmentation_benchmark_v0_normal.zip), and [ScanObjectNN](https://hkust-vgd.github.io/scanobjectnn/). 43 | 3. Create 'data' and 'experiments' directories on the workspace, and organize as follows, 44 | 45 | ``` 46 | ./ 47 | |-- cfgs 48 | |-- core 49 | |-- data 50 | |-- modelnet40 51 | |-- .json files 52 | |-- .txt files 53 | |-- partnormal 54 | |-- dirs 55 | |-- synsetoffset2category.txt 56 | |-- scanobjectnn 57 | |-- dirs 58 | |-- experiments 59 | ``` 60 | 61 | ## Training 62 | 63 | To train the network, run this command: 64 | 65 | ```train 66 | python train.py --cfg_file --exp_name --val_steps 67 | ``` 68 | 69 | ## Evaluation 70 | 71 | To evaluate the network with pre-trained weights, run: 72 | 73 | ```eval 74 | python test.py --cfg_file --ckpt 75 | ``` 76 | 77 | ## Results 78 | 79 | Our pretrained model achieves the following performances on : 80 | 81 | ### [3D Point Cloud Shape Classification on ModelNet40](https://paperswithcode.com/sota/3d-point-cloud-classification-on-modelnet40) 82 | 83 | | Model name | Overall Accuracy | Class Mean Accuracy | 84 | | ------------------ |---------------- | -------------- | 85 | | [PointStack](https://drive.google.com/file/d/1Emw8kR48htvPSNZ7e2UjO1CKy8W857s6/view?usp=sharing) | 93.3 | 89.6% | 86 | 87 | ### [3D Point Cloud Shape Classification on ScanObjectNN](https://paperswithcode.com/sota/3d-point-cloud-classification-on-scanobjectnn) 88 | 89 | | Model name | Overall Accuracy | Class Mean Accuracy | 90 | | ------------------ |---------------- | -------------- | 91 | | [PointStack](https://drive.google.com/file/d/1XTfYSkc0m4GKEhcV0wLAsb8GJ3-hBaiz/view?usp=sharing) | 87.2% | 86.2% | 92 | 93 | ### [3D Part Segmentation on ShapeNetPart](https://paperswithcode.com/sota/3d-part-segmentation-on-shapenet-part) 94 | 95 | | Model name | Instance mIoU | 96 | | ------------------ |---------------- | 97 | | [PointStack](https://drive.google.com/file/d/1Gab0_Cmdc-QFDgdMnWNMCP1NnE4_Uex1/view?usp=sharing) | 87.2% | 98 | -------------------------------------------------------------------------------- /cfgs/modelnet40/pointstack.yaml: -------------------------------------------------------------------------------- 1 | RANDOM_SEED: 0 2 | DATASET: 3 | NAME: ModelNet40 4 | NUM_CLASS: 40 5 | NUM_POINTS: 1024 6 | IS_SEGMENTATION: False 7 | USE_AUG_JIT: False 8 | USE_AUG_ROT: False 9 | USE_AUG_TRANS: True 10 | USE_RANDOM_SHUFFLE: True 11 | 12 | NETWORK: 13 | NAME: PointStack 14 | 15 | ENCODER: 16 | NAME: PointStackCls 17 | 18 | NUM_POINTS: 1024 19 | EMBED_DIM: 64 20 | GROUPS: 1 21 | RES_EXP: 1.0 22 | 23 | DIM_EXP: [2, 2, 2, 2] 24 | PRE_BLOCKS: [2, 2, 2, 2] 25 | POS_BLOCKS: [2, 2, 2, 2] 26 | K_NEIGHBORS: [24, 24, 24, 24] 27 | REDUCERS: [2, 2, 2, 2] 28 | 29 | HEAD: 30 | CLASSIFIER: 31 | NAME: LinearClassifier 32 | IN_CHANNELS: [4096] 33 | ACT: relu 34 | DIMS: [512, 256] 35 | 36 | OPTIMIZER: 37 | NAME: SGD 38 | MAX_EPOCH: 300 39 | BATCH_SIZE: 48 40 | GRAD_ACCUMULATION: 1 41 | GRAD_CLIP: 100 42 | LR: 0.01 43 | MIN_LR: 0.005 44 | MOMENTUM: 0.9 45 | NESTEROV: True 46 | WEIGHT_DECAY: 0.0002 47 | BETAS: [0.9, 0.999] # For Adam 48 | SCHEDULER: cosine_annealing 49 | WARM_RESTART_EVERY: 300 -------------------------------------------------------------------------------- /cfgs/partnormal/pointstack.yaml: -------------------------------------------------------------------------------- 1 | RANDOM_SEED: 2 2 | DATASET: 3 | NAME: PartNormal 4 | NUM_CLASS: 50 5 | NUM_POINTS: 2048 6 | IS_SEGMENTATION: True 7 | USE_AUG_JIT: False 8 | USE_AUG_ROT: False 9 | USE_AUG_TRANS: False 10 | USE_RANDOM_SHUFFLE: True 11 | 12 | NETWORK: 13 | NAME: PointStack 14 | 15 | ENCODER: 16 | NAME: PointStackSeg 17 | NUM_POINTS: 2048 18 | EMBED_DIM: 64 19 | GROUPS: 1 20 | RES_EXP: 1.0 21 | 22 | DIM_EXP: [2, 2, 2, 2] 23 | PRE_BLOCKS: [2, 2, 2, 2] 24 | POS_BLOCKS: [2, 2, 2, 2] 25 | K_NEIGHBORS: [24, 24, 24, 24] 26 | REDUCERS: [2, 2, 2, 2] 27 | 28 | HEAD: 29 | SEGMENTATOR: 30 | NAME: LinearSegmentator 31 | IN_CHANNELS: [4288] 32 | ACT: relu 33 | DIMS: [512, 256] 34 | 35 | OPTIMIZER: 36 | NAME: SGD 37 | MAX_EPOCH: 400 38 | BATCH_SIZE: 24 39 | GRAD_ACCUMULATION: 1 40 | GRAD_CLIP: 100 41 | LR: 0.01 42 | MIN_LR: 0.0001 43 | MOMENTUM: 0.9 44 | NESTEROV: True 45 | WEIGHT_DECAY: 0.0002 46 | BETAS: [0.9, 0.999] # For Adam 47 | SCHEDULER: cosine_annealing 48 | WARM_RESTART_EVERY: 400 -------------------------------------------------------------------------------- /cfgs/scanobjectnn/pointstack.yaml: -------------------------------------------------------------------------------- 1 | RANDOM_SEED: 2 2 | DATASET: 3 | NAME: ScanObjectNN 4 | DIRSPLIT: main # For ScanObjectNN, can be {main, supplementary, all} 5 | AUGSPLIT: PB_T50_RS # For ScanObjectNN, can be {OBJ_ONLY, PB_T25, PB_T25_R, PB_T50_R, PB_T50_RS} 6 | NUM_CLASS: 15 7 | NUM_POINTS: 1024 8 | IS_SEGMENTATION: False 9 | USE_AUG_JIT: False 10 | USE_AUG_ROT: True 11 | USE_AUG_TRANS: True 12 | USE_RANDOM_SHUFFLE: True 13 | LIMIT_TO_MODELNET40: False 14 | 15 | NETWORK: 16 | NAME: PointStack 17 | 18 | ENCODER: 19 | NAME: PointStackCls 20 | 21 | NUM_POINTS: 1024 22 | EMBED_DIM: 64 23 | GROUPS: 1 24 | RES_EXP: 1.0 25 | 26 | DIM_EXP: [2, 2, 2, 2] 27 | PRE_BLOCKS: [2, 2, 2, 2] 28 | POS_BLOCKS: [2, 2, 2, 2] 29 | K_NEIGHBORS: [24, 24, 24, 24] 30 | REDUCERS: [2, 2, 2, 2] 31 | 32 | HEAD: 33 | CLASSIFIER: 34 | NAME: LinearClassifier 35 | IN_CHANNELS: [4096] 36 | ACT: relu 37 | DIMS: [1024, 512, 256] 38 | 39 | OPTIMIZER: 40 | NAME: SGD 41 | MAX_EPOCH: 200 42 | BATCH_SIZE: 48 43 | GRAD_ACCUMULATION: 1 44 | GRAD_CLIP: 100 45 | LR: 0.01 46 | MIN_LR: 0.0001 47 | MOMENTUM: 0.9 48 | NESTEROV: True 49 | WEIGHT_DECAY: 0.0005 50 | BETAS: [0.9, 0.999] # For Adam 51 | SCHEDULER: cosine_annealing 52 | WARM_RESTART_EVERY: 200 -------------------------------------------------------------------------------- /core/builders.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | 4 | from . import datasets 5 | from .networks import networks 6 | 7 | def build_network(cfg, topology = None): 8 | net = networks.__all__[cfg.NETWORK.NAME](cfg, topology = topology) 9 | 10 | return net 11 | 12 | def build_dataset(cfg, split = 'train'): 13 | Dataset = datasets.__all__[cfg.DATASET.NAME](cfg, split = split) 14 | return Dataset 15 | 16 | def build_optimizer(cfg, params, data_loader_length, mode = None): 17 | opt_cfg = cfg.OPTIMIZER 18 | 19 | if mode == None: 20 | if (opt_cfg.NAME.lower() == 'adam'): 21 | opt = torch.optim.Adam(params, lr=opt_cfg.LR, betas=opt_cfg.BETAS, weight_decay=opt_cfg.WEIGHT_DECAY) 22 | elif (opt_cfg.NAME.lower() == 'sgd'): 23 | opt = torch.optim.SGD(params, lr=opt_cfg.LR, momentum=opt_cfg.MOMENTUM, weight_decay=opt_cfg.WEIGHT_DECAY, nesterov=opt_cfg.NESTEROV) 24 | 25 | if (opt_cfg.SCHEDULER is not None): 26 | if opt_cfg.SCHEDULER == 'cosine_annealing': 27 | scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(opt, T_0 = opt_cfg.WARM_RESTART_EVERY * data_loader_length, eta_min = opt_cfg.MIN_LR) 28 | else: 29 | 30 | if (getattr(opt_cfg, mode).NAME.lower() == 'adam'): 31 | opt = torch.optim.Adam(params, lr=getattr(opt_cfg, mode).LR, betas=getattr(opt_cfg, mode).BETAS, weight_decay=getattr(opt_cfg, mode).WEIGHT_DECAY) 32 | elif (getattr(opt_cfg, mode).NAME.lower() == 'sgd'): 33 | opt = torch.optim.SGD(params, lr=getattr(opt_cfg, mode).LR, momentum=getattr(opt_cfg, mode).MOMENTUM, weight_decay=getattr(opt_cfg, mode).WEIGHT_DECAY, nesterov=getattr(opt_cfg, mode).NESTEROV) 34 | 35 | if (getattr(opt_cfg, mode).SCHEDULER is not None): 36 | if getattr(opt_cfg, mode).SCHEDULER.lower()== 'cosine_annealing': 37 | scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max = opt_cfg.MAX_EPOCH//2) 38 | 39 | return opt, scheduler 40 | -------------------------------------------------------------------------------- /core/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | from .modelnet40 import ModelNet40 2 | from .scanobjectnn import ScanObjectNN 3 | from .partnormal import PartNormal 4 | 5 | __all__ = { 6 | 'ModelNet40': ModelNet40, 7 | 'ScanObjectNN': ScanObjectNN, 8 | 'PartNormal': PartNormal 9 | } -------------------------------------------------------------------------------- /core/datasets/dataset_template.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Based on codes originally written by An Tao (ta19@mails.tsinghua.edu.cn) 5 | """ 6 | 7 | import os 8 | import torch 9 | import json 10 | import h5py 11 | from glob import glob 12 | import numpy as np 13 | import torch.utils.data as data 14 | 15 | from utils.vis_utils import visualize_numpy 16 | 17 | shapenetpart_cat2id = {'airplane': 0, 'bag': 1, 'cap': 2, 'car': 3, 'chair': 4, 18 | 'earphone': 5, 'guitar': 6, 'knife': 7, 'lamp': 8, 'laptop': 9, 19 | 'motor': 10, 'mug': 11, 'pistol': 12, 'rocket': 13, 'skateboard': 14, 'table': 15} 20 | shapenetpart_seg_num = [4, 2, 2, 4, 4, 3, 3, 2, 4, 2, 6, 2, 3, 3, 3, 3] 21 | shapenetpart_seg_start_index = [0, 4, 6, 8, 12, 16, 19, 22, 24, 28, 30, 36, 38, 41, 44, 47] 22 | 23 | 24 | class DatasetTemplate(data.Dataset): 25 | def __init__(self, cfg, class_choice=None, split='train', load_name=False, load_file=False, random_rotate=False, random_jitter=False, 26 | random_translate=False): 27 | assert cfg.DATASET.NUM_POINTS <= 2048 28 | if cfg.DATASET.USE_RANDOM_SHUFFLE: 29 | print('using random shuffle per point set') 30 | 31 | self.cfg = cfg 32 | self.data_dir = os.path.join(cfg.ROOT_DIR, 'data', cfg.DATASET.NAME.lower()) 33 | print('Loading dataset: ', self.data_dir) 34 | self.class_choice = class_choice 35 | self.split = split 36 | self.load_name = load_name 37 | self.load_file = load_file 38 | 39 | self.dataset_name = cfg.DATASET.NAME 40 | self.num_points = cfg.DATASET.NUM_POINTS 41 | self.segmentation = cfg.DATASET.IS_SEGMENTATION 42 | self.random_rotate = cfg.DATASET.USE_AUG_ROT if self.split in ['train'] else False 43 | self.random_jitter = cfg.DATASET.USE_AUG_JIT if self.split in ['train'] else False 44 | self.random_translate = cfg.DATASET.USE_AUG_TRANS if self.split in ['train'] else False 45 | 46 | def get_path(self, type): 47 | path_h5py = os.path.join(self.data_dir, '*%s*.h5'%type) 48 | self.path_h5py_all += glob(path_h5py) 49 | if self.load_name: 50 | path_json = os.path.join(self.data_dir, '%s*_id2name.json'%type) 51 | self.path_name_all += glob(path_json) 52 | if self.load_file: 53 | path_json = os.path.join(self.data_dir, '%s*_id2file.json'%type) 54 | self.path_file_all += glob(path_json) 55 | return 56 | 57 | def load_h5py(self, path): 58 | all_data = [] 59 | all_label = [] 60 | all_seg = [] 61 | for h5_name in path: 62 | f = h5py.File(h5_name, 'r+') 63 | 64 | data = f['data'][:].astype('float32') 65 | label = f['label'][:].astype('int64') 66 | if self.segmentation: 67 | if 'mask' in f.keys(): 68 | seg = f['mask'][:].astype('int64') 69 | seg[np.where(seg > -1)] = 1 # Foreground 70 | seg[np.where(seg == -1)] = 0 # Background 71 | else: 72 | seg = f['seg'][:].astype('int64') 73 | f.close() 74 | all_data.append(data) 75 | all_label.append(label) 76 | if self.segmentation: 77 | all_seg.append(seg) 78 | return all_data, all_label, all_seg 79 | 80 | def load_json(self, path): 81 | all_data = [] 82 | for json_name in path: 83 | j = open(json_name, 'r+') 84 | data = json.load(j) 85 | all_data += data 86 | return all_data 87 | 88 | def __getitem__(self, item): 89 | point_set = self.data[item][:self.num_points] 90 | # visualize_numpy(point_set) 91 | label = self.label[item] 92 | if self.segmentation: 93 | seg = self.seg[item][:self.num_points] 94 | 95 | if self.random_rotate: 96 | point_set, theta = self.rotate_pointcloud(point_set) 97 | if self.random_jitter: 98 | point_set, sigma, clip = self.jitter_pointcloud(point_set) 99 | if self.random_translate: 100 | point_set, xyz1, xyz2 = self.translate_pointcloud(point_set) 101 | 102 | if self.split == 'train': 103 | if self.cfg.DATASET.USE_RANDOM_SHUFFLE: 104 | p = np.random.permutation(len(point_set)) 105 | # np.random.shuffle(point_set) 106 | point_set = point_set[p] 107 | 108 | if self.segmentation: 109 | assert len(point_set) == len(seg) 110 | seg = seg[p] 111 | 112 | # convert numpy array to pytorch Tensor 113 | point_set = torch.from_numpy(point_set) 114 | label = torch.from_numpy(np.array([label]).astype(np.int64)) 115 | label = label.squeeze(0) 116 | 117 | data_dic = { 118 | 'points': point_set.cuda(), 119 | 'cls_id': label.cuda(), 120 | } 121 | 122 | if self.segmentation: 123 | seg = torch.from_numpy(seg).cuda() 124 | data_dic['seg_id'] = seg 125 | 126 | return data_dic 127 | 128 | def __len__(self): 129 | return self.data.shape[0] 130 | 131 | def __repr__(self): 132 | return ('Name=%s, Class Choice=%s, Num Points=%s, Split=%s, Random Rotate=%s, Random Jitter=%s, Random Translate=%s, )' 133 | % (repr(self.dataset_name), repr(self.class_choice), repr(self.num_points), 134 | repr(self.split), repr(self.random_rotate), repr(self.random_jitter), repr(self.random_translate))) 135 | 136 | def translate_pointcloud(self, pointcloud, xyz1 = None, xyz2 = None): 137 | if xyz1 == None: 138 | xyz1 = np.random.uniform(low=2./3., high=3./2., size=[3]) 139 | if xyz2 == None: 140 | xyz2 = np.random.uniform(low=-0.2, high=0.2, size=[3]) 141 | 142 | translated_pointcloud = np.add(np.multiply(pointcloud, xyz1), xyz2).astype('float32') 143 | return translated_pointcloud, xyz1, xyz2 144 | 145 | 146 | def jitter_pointcloud(self, pointcloud, sigma=0.01, clip=0.02): 147 | N, C = pointcloud.shape 148 | pointcloud += np.clip(sigma * np.random.randn(N, C), -1*clip, clip) 149 | return pointcloud, sigma, clip 150 | 151 | 152 | def rotate_pointcloud(self, pointcloud, theta = None): 153 | if theta == None: 154 | theta = np.pi*2 * np.random.rand() 155 | rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]]) 156 | pointcloud[:,[0,2]] = pointcloud[:,[0,2]].dot(rotation_matrix) # random rotation (x,z) 157 | return pointcloud, theta -------------------------------------------------------------------------------- /core/datasets/modelnet40.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Based on codes originally written by An Tao (ta19@mails.tsinghua.edu.cn) 5 | """ 6 | 7 | import os 8 | import torch 9 | import json 10 | import h5py 11 | from glob import glob 12 | import numpy as np 13 | 14 | from .dataset_template import DatasetTemplate 15 | 16 | class ModelNet40(DatasetTemplate): 17 | def __init__(self, cfg, class_choice=None, split='train', load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False): 18 | super().__init__(cfg = cfg, class_choice=None, split=split, load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False) 19 | 20 | self.path_h5py_all = [] 21 | self.path_name_all = [] 22 | self.path_file_all = [] 23 | 24 | if self.split in ['train','trainval','all']: 25 | self.get_path('train') 26 | if self.split in ['val', 'test', 'all']: 27 | self.get_path('test') 28 | 29 | self.path_h5py_all.sort() 30 | data, label, seg = self.load_h5py(self.path_h5py_all) 31 | 32 | if self.load_name or self.class_choice != None: 33 | self.path_name_all.sort() 34 | self.name = np.array(self.load_json(self.path_name_all)) # load label name 35 | 36 | if self.load_file: 37 | self.path_file_all.sort() 38 | self.file = np.array(self.load_json(self.path_file_all)) # load file name 39 | 40 | self.data = np.concatenate(data, axis=0) 41 | self.label = np.concatenate(label, axis=0) 42 | 43 | 44 | if self.segmentation: 45 | self.seg = np.concatenate(seg, axis=0) 46 | 47 | if self.class_choice != None: 48 | indices = (self.name == class_choice) 49 | self.data = self.data[indices] 50 | self.label = self.label[indices] 51 | self.name = self.name[indices] 52 | if self.load_file: 53 | self.file = self.file[indices] -------------------------------------------------------------------------------- /core/datasets/partnormal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import torch 5 | import json 6 | import h5py 7 | from glob import glob 8 | import numpy as np 9 | 10 | from .dataset_template import DatasetTemplate 11 | 12 | class PartNormal(DatasetTemplate): 13 | def __init__(self, cfg, class_choice=None, split='train', load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False): 14 | super().__init__(cfg = cfg, class_choice=None, split=split, load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False) 15 | self.npoints = cfg.DATASET.NUM_POINTS 16 | self.root = self.data_dir 17 | self.catfile = os.path.join(self.root, 'synsetoffset2category.txt') 18 | self.cat = {} 19 | self.normalize = False 20 | 21 | self.split = split 22 | 23 | with open(self.catfile, 'r') as f: 24 | for line in f: 25 | ls = line.strip().split() 26 | self.cat[ls[0]] = ls[1] 27 | self.cat = {k: v for k, v in self.cat.items()} 28 | 29 | self.meta = {} 30 | with open(os.path.join(self.root, 'train_test_split', 'shuffled_train_file_list.json'), 'r') as f: 31 | train_ids = set([str(d.split('/')[2]) for d in json.load(f)]) 32 | with open(os.path.join(self.root, 'train_test_split', 'shuffled_val_file_list.json'), 'r') as f: 33 | val_ids = set([str(d.split('/')[2]) for d in json.load(f)]) 34 | with open(os.path.join(self.root, 'train_test_split', 'shuffled_test_file_list.json'), 'r') as f: 35 | test_ids = set([str(d.split('/')[2]) for d in json.load(f)]) 36 | for item in self.cat: 37 | self.meta[item] = [] 38 | dir_point = os.path.join(self.root, self.cat[item]) 39 | fns = sorted(os.listdir(dir_point)) 40 | 41 | if split == 'trainval': 42 | fns = [fn for fn in fns if ((fn[0:-4] in train_ids) or (fn[0:-4] in val_ids))] 43 | elif split == 'train': 44 | fns = [fn for fn in fns if fn[0:-4] in train_ids] 45 | elif split == 'val': 46 | fns = [fn for fn in fns if fn[0:-4] in val_ids] 47 | elif split == 'test': 48 | fns = [fn for fn in fns if fn[0:-4] in test_ids] 49 | else: 50 | print('Unknown split: %s. Exiting..' % (split)) 51 | exit(-1) 52 | 53 | for fn in fns: 54 | token = (os.path.splitext(os.path.basename(fn))[0]) 55 | self.meta[item].append(os.path.join(dir_point, token + '.txt')) 56 | 57 | self.datapath = [] 58 | for item in self.cat: 59 | for fn in self.meta[item]: 60 | self.datapath.append((item, fn)) 61 | 62 | self.classes = dict(zip(self.cat, range(len(self.cat)))) 63 | # Mapping from category ('Chair') to a list of int [10,11,12,13] as segmentation labels 64 | self.seg_classes = {'Earphone': [16, 17, 18], 'Motorbike': [30, 31, 32, 33, 34, 35], 'Rocket': [41, 42, 43], 65 | 'Car': [8, 9, 10, 11], 'Laptop': [28, 29], 'Cap': [6, 7], 'Skateboard': [44, 45, 46], 66 | 'Mug': [36, 37], 'Guitar': [19, 20, 21], 'Bag': [4, 5], 'Lamp': [24, 25, 26, 27], 67 | 'Table': [47, 48, 49], 'Airplane': [0, 1, 2, 3], 'Pistol': [38, 39, 40], 68 | 'Chair': [12, 13, 14, 15], 'Knife': [22, 23]} 69 | 70 | self.cache = {} # from index to (point_set, cls, seg) tuple 71 | self.cache_size = 20000 72 | 73 | def __len__(self): 74 | return len(self.datapath) 75 | 76 | def __getitem__(self, index): 77 | if index in self.cache: 78 | point_set, normal, seg, cls = self.cache[index] 79 | else: 80 | fn = self.datapath[index] 81 | cat = self.datapath[index][0] 82 | cls = self.classes[cat] 83 | cls = np.array([cls]).astype(np.int32) 84 | data = np.loadtxt(fn[1]).astype(np.float32) 85 | point_set = data[:, 0:3] 86 | normal = data[:, 3:6] 87 | seg = data[:, -1].astype(np.int32) 88 | if len(self.cache) < self.cache_size: 89 | self.cache[index] = (point_set, normal, seg, cls) 90 | 91 | # if self.normalize: 92 | # point_set = pc_normalize(point_set) 93 | 94 | choice = np.random.choice(min(len(seg), self.npoints), self.npoints, replace=True) 95 | #choice = np.linspace(0, self.num_points, num=self.num_points).astype(int) 96 | # resample 97 | # note that the number of points in some points clouds is less than 2048, thus use random.choice 98 | # remember to use the same seed during train and test for a getting stable result 99 | 100 | point_set = point_set[choice, :] 101 | seg = seg[choice] 102 | normal = normal[choice, :] 103 | 104 | data_dic = { 105 | 'points' : torch.from_numpy(point_set).cuda(), 106 | 'seg_id' : torch.from_numpy(seg).cuda(), 107 | 'cls_tokens': torch.from_numpy(cls).cuda(), 108 | 'norms' : torch.from_numpy(normal).cuda() 109 | } 110 | 111 | return data_dic -------------------------------------------------------------------------------- /core/datasets/scanobjectnn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Based on codes originally written by An Tao (ta19@mails.tsinghua.edu.cn) 5 | """ 6 | 7 | import os 8 | import torch 9 | import json 10 | import h5py 11 | from glob import glob 12 | import numpy as np 13 | 14 | from .dataset_template import DatasetTemplate 15 | 16 | CLS_NAME_TO_ID_DIC = {'bag': 0, 'bin': 1, 'box': 2, 'cabinet': 3, 'chair': 4, 'desk': 5, 'display': 6, 'door': 7, 'shelf': 8, 'table': 9, 'bed': 10, 'pillow': 11, 'sink': 12, 'sofa': 13, 'toilet': 14} 17 | CLS_ID_TO_NAME_DIC = {0: 'bag', 1: 'bin', 2: 'box', 3: 'cabinet', 4: 'chair', 5: 'desk', 6: 'display', 7: 'door', 8: 'shelf', 9: 'table', 10: 'bed', 11: 'pillow', 12: 'sink', 13: 'sofa', 14: 'toilet'} 18 | MODELNET40_LIMITER = [246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246] 19 | 20 | class ScanObjectNN(DatasetTemplate): 21 | def __init__(self, cfg, class_choice=None, split='train', load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False): 22 | super().__init__(cfg = cfg, class_choice=None, split=split, load_name=True, load_file=True, random_rotate=False, random_jitter=False, random_translate=False) 23 | 24 | self.path_h5py_all = [] 25 | self.path_h5py_nobg = [] 26 | 27 | self.path_name_all = [] 28 | 29 | self.path_file_all = [] 30 | self.path_file_nobg = [] 31 | 32 | if self.cfg.DATASET.DIRSPLIT == 'main': 33 | self.dirsplit_all = ['main_split'] 34 | self.dirsplit_nobg = ['main_split_nobg'] 35 | elif self.cfg.DATASET.DIRSPLIT == 'supplementary': 36 | self.dirsplit_all = ['split1', 'split2', 'split3', 'split4'] 37 | self.dirsplit_nobg = ['split1_nobg', 'split2_nobg', 'split3_nobg', 'split4_nobg'] 38 | else: 39 | self.dirsplit_all = ['main_split', 'split1', 'split2', 'split3', 'split4'] 40 | self.dirsplit_nobg = ['main_split_nobg', 'split1_nobg', 'split2_nobg', 'split3_nobg', 'split4_nobg'] 41 | 42 | if self.cfg.DATASET.AUGSPLIT == 'OBJ_ONLY': 43 | self.augsplit = 'objectdataset' 44 | elif self.cfg.DATASET.AUGSPLIT == 'PB_T25': 45 | self.augsplit = 'objectdataset_augmented25_norot' 46 | elif self.cfg.DATASET.AUGSPLIT == 'PB_T25_R': 47 | self.augsplit = 'objectdataset_augmented25rot' 48 | elif self.cfg.DATASET.AUGSPLIT == 'PB_T50_R': 49 | self.augsplit = 'objectdataset_augmentedrot' 50 | elif self.cfg.DATASET.AUGSPLIT == 'PB_T50_RS': 51 | self.augsplit = 'objectdataset_augmentedrot_scale75' 52 | 53 | if self.split in ['train','trainval','all']: 54 | self.get_path('training', self.dirsplit_all, self.augsplit) 55 | if self.split in ['val', 'test', 'all']: 56 | self.get_path('test', self.dirsplit_all, self.augsplit) 57 | 58 | self.path_h5py_all.sort() 59 | self.path_h5py_nobg.sort() 60 | 61 | 62 | data, label, seg = self.load_h5py(self.path_h5py_all) 63 | 64 | if self.load_name or self.class_choice != None: 65 | self.path_name_all.sort() 66 | self.name = np.array(self.load_json(self.path_name_all)) # load label name 67 | 68 | if self.load_file: 69 | self.path_file_all.sort() 70 | self.file = np.array(self.load_json(self.path_file_all)) # load file name 71 | 72 | self.data = np.concatenate(data, axis=0) 73 | 74 | self.label = np.concatenate(label, axis=0) 75 | 76 | if self.segmentation: 77 | self.seg = np.concatenate(seg, axis=0) 78 | 79 | if self.class_choice != None: 80 | indices = (self.name == class_choice) 81 | self.data = self.data[indices] 82 | self.label = self.label[indices] 83 | 84 | self.name = self.name[indices] 85 | if self.load_file: 86 | self.file = self.file[indices] 87 | 88 | if (cfg.DATASET.LIMIT_TO_MODELNET40) and (split == 'train'): 89 | chosen_data = [] 90 | chosen_label = [] 91 | 92 | for cls_id in range(15): 93 | indices = np.where(self.label == cls_id) 94 | label = self.label[indices] 95 | data = self.data[indices] 96 | 97 | chosen_data.extend(data[:MODELNET40_LIMITER[cls_id]]) 98 | chosen_label.extend(label[:MODELNET40_LIMITER[cls_id]]) 99 | 100 | self.data = np.array(chosen_data) 101 | self.label = np.array(chosen_label) 102 | 103 | 104 | def get_path(self, type, dirsplits, augsplit): 105 | for dirsplit in dirsplits: 106 | path_h5py = os.path.join(self.data_dir, dirsplit, type+'*'+augsplit+'.h5') 107 | path_h5py_nobg = os.path.join(self.data_dir, dirsplit + '_nobg', type+'*'+augsplit+'.h5') 108 | 109 | self.path_h5py_all += glob(path_h5py) 110 | self.path_h5py_nobg += glob(path_h5py_nobg) 111 | 112 | return 113 | 114 | def __getitem__(self, item): 115 | point_set = self.data[item][:self.num_points] 116 | label = self.label[item] 117 | 118 | if self.segmentation: 119 | seg = self.seg[item][:self.num_points] 120 | 121 | pose_target = { 122 | 'theta': 0, 123 | 'dx': 0, 124 | 'dy': 0, 125 | 'dz': 0 126 | } 127 | 128 | if self.random_rotate: 129 | point_set, theta = self.rotate_pointcloud(point_set) 130 | pose_target['theta'] = theta 131 | 132 | if self.random_jitter: 133 | point_set, sigma, clip = self.jitter_pointcloud(point_set) 134 | 135 | if self.random_translate: 136 | point_set, xyz1, xyz2 = self.translate_pointcloud(point_set) 137 | pose_target['dx'], pose_target['dy'], pose_target['dz'] = xyz2 138 | 139 | if self.split == 'train': 140 | if self.cfg.DATASET.USE_RANDOM_SHUFFLE: 141 | p = np.random.permutation(len(point_set)) 142 | point_set = point_set[p] 143 | 144 | if self.segmentation: 145 | assert len(point_set) == len(seg) 146 | seg = seg[p] 147 | point_set = torch.from_numpy(point_set) 148 | 149 | label = torch.from_numpy(np.array([label]).astype(np.int64)) 150 | label = label.squeeze(0) 151 | 152 | data_dic = { 153 | 'points': point_set.cuda(), 154 | 'cls_id': label.cuda(), 155 | } 156 | 157 | if self.segmentation: 158 | seg = torch.from_numpy(seg).cuda() 159 | data_dic['seg_id'] = seg 160 | 161 | return data_dic -------------------------------------------------------------------------------- /core/networks/encoders/__init__.py: -------------------------------------------------------------------------------- 1 | from .pointMLP import PointMLP 2 | from .pointstack_seg_encoder import PointStackSeg 3 | from .pointstack_cls_encoder import PointStackCls 4 | 5 | 6 | __all__ = { 7 | 'PointMLP': PointMLP, 8 | 'PointStackSeg': PointStackSeg, 9 | 'PointStackCls': PointStackCls, 10 | } -------------------------------------------------------------------------------- /core/networks/encoders/pointMLP.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from pointnet2_ops import pointnet2_utils 6 | 7 | 8 | def get_activation(activation): 9 | if activation.lower() == 'gelu': 10 | return nn.GELU() 11 | elif activation.lower() == 'rrelu': 12 | return nn.RReLU(inplace=True) 13 | elif activation.lower() == 'selu': 14 | return nn.SELU(inplace=True) 15 | elif activation.lower() == 'silu': 16 | return nn.SiLU(inplace=True) 17 | elif activation.lower() == 'hardswish': 18 | return nn.Hardswish(inplace=True) 19 | elif activation.lower() == 'leakyrelu': 20 | return nn.LeakyReLU(inplace=True) 21 | else: 22 | return nn.ReLU(inplace=True) 23 | 24 | 25 | def square_distance(src, dst): 26 | """ 27 | Calculate Euclid distance between each two points. 28 | src^T * dst = xn * xm + yn * ym + zn * zm; 29 | sum(src^2, dim=-1) = xn*xn + yn*yn + zn*zn; 30 | sum(dst^2, dim=-1) = xm*xm + ym*ym + zm*zm; 31 | dist = (xn - xm)^2 + (yn - ym)^2 + (zn - zm)^2 32 | = sum(src**2,dim=-1)+sum(dst**2,dim=-1)-2*src^T*dst 33 | Input: 34 | src: source points, [B, N, C] 35 | dst: target points, [B, M, C] 36 | Output: 37 | dist: per-point square distance, [B, N, M] 38 | """ 39 | B, N, _ = src.shape 40 | _, M, _ = dst.shape 41 | dist = -2 * torch.matmul(src, dst.permute(0, 2, 1)) 42 | dist += torch.sum(src ** 2, -1).view(B, N, 1) 43 | dist += torch.sum(dst ** 2, -1).view(B, 1, M) 44 | return dist 45 | 46 | 47 | def index_points(points, idx): 48 | """ 49 | Input: 50 | points: input points data, [B, N, C] 51 | idx: sample index data, [B, S] 52 | Return: 53 | new_points:, indexed points data, [B, S, C] 54 | """ 55 | device = points.device 56 | B = points.shape[0] 57 | view_shape = list(idx.shape) 58 | view_shape[1:] = [1] * (len(view_shape) - 1) 59 | repeat_shape = list(idx.shape) 60 | repeat_shape[0] = 1 61 | batch_indices = torch.arange(B, dtype=torch.long).to(device).view(view_shape).repeat(repeat_shape) 62 | new_points = points[batch_indices, idx, :] 63 | return new_points 64 | 65 | 66 | def farthest_point_sample(xyz, npoint): 67 | """ 68 | Input: 69 | xyz: pointcloud data, [B, N, 3] 70 | npoint: number of samples 71 | Return: 72 | centroids: sampled pointcloud index, [B, npoint] 73 | """ 74 | device = xyz.device 75 | B, N, C = xyz.shape 76 | centroids = torch.zeros(B, npoint, dtype=torch.long).to(device) 77 | distance = torch.ones(B, N).to(device) * 1e10 78 | farthest = torch.randint(0, N, (B,), dtype=torch.long).to(device) 79 | batch_indices = torch.arange(B, dtype=torch.long).to(device) 80 | for i in range(npoint): 81 | centroids[:, i] = farthest 82 | centroid = xyz[batch_indices, farthest, :].view(B, 1, 3) 83 | dist = torch.sum((xyz - centroid) ** 2, -1) 84 | distance = torch.min(distance, dist) 85 | farthest = torch.max(distance, -1)[1] 86 | return centroids 87 | 88 | 89 | def query_ball_point(radius, nsample, xyz, new_xyz): 90 | """ 91 | Input: 92 | radius: local region radius 93 | nsample: max sample number in local region 94 | xyz: all points, [B, N, 3] 95 | new_xyz: query points, [B, S, 3] 96 | Return: 97 | group_idx: grouped points index, [B, S, nsample] 98 | """ 99 | device = xyz.device 100 | B, N, C = xyz.shape 101 | _, S, _ = new_xyz.shape 102 | group_idx = torch.arange(N, dtype=torch.long).to(device).view(1, 1, N).repeat([B, S, 1]) 103 | sqrdists = square_distance(new_xyz, xyz) 104 | group_idx[sqrdists > radius ** 2] = N 105 | group_idx = group_idx.sort(dim=-1)[0][:, :, :nsample] 106 | group_first = group_idx[:, :, 0].view(B, S, 1).repeat([1, 1, nsample]) 107 | mask = group_idx == N 108 | group_idx[mask] = group_first[mask] 109 | return group_idx 110 | 111 | 112 | def knn_point(nsample, xyz, new_xyz): 113 | """ 114 | Input: 115 | nsample: max sample number in local region 116 | xyz: all points, [B, N, C] 117 | new_xyz: query points, [B, S, C] 118 | Return: 119 | group_idx: grouped points index, [B, S, nsample] 120 | """ 121 | sqrdists = square_distance(new_xyz, xyz) 122 | _, group_idx = torch.topk(sqrdists, nsample, dim=-1, largest=False, sorted=False) 123 | return group_idx 124 | 125 | 126 | class LocalGrouper(nn.Module): 127 | def __init__(self, channel, groups, kneighbors, use_xyz=True, normalize="center", **kwargs): 128 | """ 129 | Give xyz[b,p,3] and fea[b,p,d], return new_xyz[b,g,3] and new_fea[b,g,k,d] 130 | :param groups: groups number 131 | :param kneighbors: k-nerighbors 132 | :param kwargs: others 133 | """ 134 | super(LocalGrouper, self).__init__() 135 | self.groups = groups 136 | self.kneighbors = kneighbors 137 | self.use_xyz = use_xyz 138 | if normalize is not None: 139 | self.normalize = normalize.lower() 140 | else: 141 | self.normalize = None 142 | if self.normalize not in ["center", "anchor"]: 143 | print(f"Unrecognized normalize parameter (self.normalize), set to None. Should be one of [center, anchor].") 144 | self.normalize = None 145 | if self.normalize is not None: 146 | add_channel=3 if self.use_xyz else 0 147 | self.affine_alpha = nn.Parameter(torch.ones([1,1,1,channel + add_channel])) 148 | self.affine_beta = nn.Parameter(torch.zeros([1, 1, 1, channel + add_channel])) 149 | 150 | def forward(self, xyz, points): 151 | B, N, C = xyz.shape 152 | S = self.groups 153 | xyz = xyz.contiguous() # xyz [btach, points, xyz] 154 | 155 | # fps_idx = torch.multinomial(torch.linspace(0, N - 1, steps=N).repeat(B, 1).to(xyz.device), num_samples=self.groups, replacement=False).long() 156 | # fps_idx = farthest_point_sample(xyz, self.groups).long() 157 | fps_idx = pointnet2_utils.furthest_point_sample(xyz, self.groups).long() # [B, npoint] 158 | new_xyz = index_points(xyz, fps_idx) # [B, npoint, 3] 159 | new_points = index_points(points, fps_idx) # [B, npoint, d] 160 | 161 | idx = knn_point(self.kneighbors, xyz, new_xyz) 162 | # idx = query_ball_point(radius, nsample, xyz, new_xyz) 163 | grouped_xyz = index_points(xyz, idx) # [B, npoint, k, 3] 164 | grouped_points = index_points(points, idx) # [B, npoint, k, d] 165 | if self.use_xyz: 166 | grouped_points = torch.cat([grouped_points, grouped_xyz],dim=-1) # [B, npoint, k, d+3] 167 | if self.normalize is not None: 168 | if self.normalize =="center": 169 | mean = torch.mean(grouped_points, dim=2, keepdim=True) 170 | if self.normalize =="anchor": 171 | mean = torch.cat([new_points, new_xyz],dim=-1) if self.use_xyz else new_points 172 | mean = mean.unsqueeze(dim=-2) # [B, npoint, 1, d+3] 173 | std = torch.std((grouped_points-mean).reshape(B,-1),dim=-1,keepdim=True).unsqueeze(dim=-1).unsqueeze(dim=-1) 174 | grouped_points = (grouped_points-mean)/(std + 1e-5) 175 | grouped_points = self.affine_alpha*grouped_points + self.affine_beta 176 | 177 | new_points = torch.cat([grouped_points, new_points.view(B, S, 1, -1).repeat(1, 1, self.kneighbors, 1)], dim=-1) 178 | return new_xyz, new_points 179 | 180 | 181 | class ConvBNReLU1D(nn.Module): 182 | def __init__(self, in_channels, out_channels, kernel_size=1, bias=True, activation='relu'): 183 | super(ConvBNReLU1D, self).__init__() 184 | self.act = get_activation(activation) 185 | self.net = nn.Sequential( 186 | nn.Conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, bias=bias), 187 | nn.BatchNorm1d(out_channels), 188 | self.act 189 | ) 190 | 191 | def forward(self, x): 192 | return self.net(x) 193 | 194 | 195 | class ConvBNReLURes1D(nn.Module): 196 | def __init__(self, channel, kernel_size=1, groups=1, res_expansion=1.0, bias=True, activation='relu'): 197 | super(ConvBNReLURes1D, self).__init__() 198 | self.act = get_activation(activation) 199 | self.net1 = nn.Sequential( 200 | nn.Conv1d(in_channels=channel, out_channels=int(channel * res_expansion), 201 | kernel_size=kernel_size, groups=groups, bias=bias), 202 | nn.BatchNorm1d(int(channel * res_expansion)), 203 | self.act 204 | ) 205 | if groups > 1: 206 | self.net2 = nn.Sequential( 207 | nn.Conv1d(in_channels=int(channel * res_expansion), out_channels=channel, 208 | kernel_size=kernel_size, groups=groups, bias=bias), 209 | nn.BatchNorm1d(channel), 210 | self.act, 211 | nn.Conv1d(in_channels=channel, out_channels=channel, 212 | kernel_size=kernel_size, bias=bias), 213 | nn.BatchNorm1d(channel), 214 | ) 215 | else: 216 | self.net2 = nn.Sequential( 217 | nn.Conv1d(in_channels=int(channel * res_expansion), out_channels=channel, 218 | kernel_size=kernel_size, bias=bias), 219 | nn.BatchNorm1d(channel) 220 | ) 221 | 222 | def forward(self, x): 223 | return self.act(self.net2(self.net1(x)) + x) 224 | 225 | 226 | class PreExtraction(nn.Module): 227 | def __init__(self, channels, out_channels, blocks=1, groups=1, res_expansion=1, bias=True, 228 | activation='relu', use_xyz=True): 229 | """ 230 | input: [b,g,k,d]: output:[b,d,g] 231 | :param channels: 232 | :param blocks: 233 | """ 234 | super(PreExtraction, self).__init__() 235 | in_channels = 3+2*channels if use_xyz else 2*channels 236 | self.transfer = ConvBNReLU1D(in_channels, out_channels, bias=bias, activation=activation) 237 | operation = [] 238 | for _ in range(blocks): 239 | operation.append( 240 | ConvBNReLURes1D(out_channels, groups=groups, res_expansion=res_expansion, 241 | bias=bias, activation=activation) 242 | ) 243 | self.operation = nn.Sequential(*operation) 244 | 245 | def forward(self, x): 246 | b, n, s, d = x.size() # torch.Size([32, 512, 32, 6]) 247 | x = x.permute(0, 1, 3, 2) 248 | x = x.reshape(-1, d, s) 249 | x = self.transfer(x) 250 | batch_size, _, _ = x.size() 251 | x = self.operation(x) # [b, d, k] 252 | x = F.adaptive_max_pool1d(x, 1).view(batch_size, -1) 253 | x = x.reshape(b, n, -1).permute(0, 2, 1) 254 | return x 255 | 256 | 257 | class PosExtraction(nn.Module): 258 | def __init__(self, channels, blocks=1, groups=1, res_expansion=1, bias=True, activation='relu'): 259 | """ 260 | input[b,d,g]; output[b,d,g] 261 | :param channels: 262 | :param blocks: 263 | """ 264 | super(PosExtraction, self).__init__() 265 | operation = [] 266 | for _ in range(blocks): 267 | operation.append( 268 | ConvBNReLURes1D(channels, groups=groups, res_expansion=res_expansion, bias=bias, activation=activation) 269 | ) 270 | self.operation = nn.Sequential(*operation) 271 | 272 | def forward(self, x): # [b, d, g] 273 | return self.operation(x) 274 | 275 | 276 | class PointMLP(nn.Module): 277 | def __init__(self, cfg, activation="relu", bias=False, use_xyz=False, normalize="anchor", **kwargs): 278 | super(PointMLP, self).__init__() 279 | self.stages = len(cfg.NETWORK.ENCODER.PRE_BLOCKS) 280 | self.class_num = cfg.DATASET.NUM_CLASS 281 | self.points = cfg.DATASET.NUM_POINTS 282 | self.embedding = ConvBNReLU1D(3, cfg.NETWORK.ENCODER.EMBED_DIM, bias=bias, activation=activation) 283 | assert len(cfg.NETWORK.ENCODER.PRE_BLOCKS) == len(cfg.NETWORK.ENCODER.K_NEIGHBORS) == len(cfg.NETWORK.ENCODER.REDUCERS) == len(cfg.NETWORK.ENCODER.POS_BLOCKS) == len(cfg.NETWORK.ENCODER.DIM_EXP), \ 284 | "Please check stage number consistent for pre_blocks, pos_blocks k_neighbors, reducers." 285 | self.local_grouper_list = nn.ModuleList() 286 | self.pre_blocks_list = nn.ModuleList() 287 | self.pos_blocks_list = nn.ModuleList() 288 | last_channel = cfg.NETWORK.ENCODER.EMBED_DIM 289 | anchor_points = self.points 290 | for i in range(len(cfg.NETWORK.ENCODER.PRE_BLOCKS)): 291 | out_channel = last_channel * cfg.NETWORK.ENCODER.DIM_EXP[i] 292 | pre_block_num = cfg.NETWORK.ENCODER.PRE_BLOCKS[i] 293 | pos_block_num = cfg.NETWORK.ENCODER.POS_BLOCKS[i] 294 | kneighbor = cfg.NETWORK.ENCODER.K_NEIGHBORS[i] 295 | reduce = cfg.NETWORK.ENCODER.REDUCERS[i] 296 | anchor_points = anchor_points // reduce 297 | # append local_grouper_list 298 | local_grouper = LocalGrouper(last_channel, anchor_points, kneighbor, use_xyz, normalize) # [b,g,k,d] 299 | self.local_grouper_list.append(local_grouper) 300 | # append pre_block_list 301 | pre_block_module = PreExtraction(last_channel, out_channel, pre_block_num, groups=cfg.NETWORK.ENCODER.GROUPS, 302 | res_expansion=cfg.NETWORK.ENCODER.RES_EXP, 303 | bias=bias, activation=activation, use_xyz=use_xyz) 304 | self.pre_blocks_list.append(pre_block_module) 305 | # append pos_block_list 306 | pos_block_module = PosExtraction(out_channel, pos_block_num, groups=cfg.NETWORK.ENCODER.GROUPS, 307 | res_expansion=cfg.NETWORK.ENCODER.RES_EXP, bias=bias, activation=activation) 308 | self.pos_blocks_list.append(pos_block_module) 309 | 310 | last_channel = out_channel 311 | 312 | def forward(self, data_dic): 313 | x = data_dic['points'] 314 | x = x.permute(0, 2, 1) 315 | xyz = x.permute(0, 2, 1) 316 | batch_size, _, _ = x.size() 317 | x = self.embedding(x) # B,D,N 318 | for i in range(self.stages): 319 | # Give xyz[b, p, 3] and fea[b, p, d], return new_xyz[b, g, 3] and new_fea[b, g, k, d] 320 | xyz, x = self.local_grouper_list[i](xyz, x.permute(0, 2, 1)) # [b,g,3] [b,g,k,d] 321 | x = self.pre_blocks_list[i](x) # [b,d,g] 322 | x = self.pos_blocks_list[i](x) # [b,d,g] 323 | 324 | x = F.adaptive_max_pool1d(x, 1).squeeze(dim=-1) 325 | data_dic['point_features'] = x 326 | return data_dic 327 | 328 | 329 | # def pointMLP(num_classes=40, **kwargs) -> PointMLP: 330 | # return PointMLP(points=1024, class_num=num_classes, embed_dim=64, groups=1, res_expansion=1.0, 331 | # activation="relu", bias=False, use_xyz=False, normalize="anchor", 332 | # dim_expansion=[2, 2, 2, 2], pre_blocks=[2, 2, 2, 2], pos_blocks=[2, 2, 2, 2], 333 | # k_neighbors=[24, 24, 24, 24], reducers=[2, 2, 2, 2], **kwargs) 334 | 335 | 336 | # def pointMLPElite(num_classes=40, **kwargs) -> PointMLP: 337 | # return PointMLP(points=1024, class_num=num_classes, embed_dim=32, groups=1, res_expansion=0.25, 338 | # activation="relu", bias=False, use_xyz=False, normalize="anchor", 339 | # dim_expansion=[2, 2, 2, 1], pre_blocks=[1, 1, 2, 1], pos_blocks=[1, 1, 2, 1], 340 | # k_neighbors=[24,24,24,24], reducers=[2, 2, 2, 2], **kwargs) 341 | 342 | if __name__ == '__main__': 343 | data = torch.rand(2, 1024, 3).cuda() 344 | model = PointMLP(None).cuda() 345 | data_dic = { 346 | 'points': data 347 | } 348 | out = model(data_dic) 349 | print(out) 350 | -------------------------------------------------------------------------------- /core/networks/heads/__init__.py: -------------------------------------------------------------------------------- 1 | from .linear_classifier import LinearClassifier 2 | from .linear_segmentator import LinearSegmentator 3 | 4 | __all__ = { 5 | 'LinearClassifier': LinearClassifier, 6 | 'LinearSegmentator': LinearSegmentator, 7 | } -------------------------------------------------------------------------------- /core/networks/heads/linear_classifier.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class LinearClassifier(nn.Module): 5 | def __init__(self, cfg): 6 | super(LinearClassifier, self).__init__() 7 | self.cfg = cfg 8 | self.in_channels = self.cfg.NETWORK.HEAD.CLASSIFIER.IN_CHANNELS + self.cfg.NETWORK.HEAD.CLASSIFIER.DIMS[0:-1] 9 | self.out_channels = self.cfg.NETWORK.HEAD.CLASSIFIER.DIMS 10 | 11 | self.classifier = [] 12 | for c_in, c_out in zip(self.in_channels, self.out_channels): 13 | self.classifier.extend(nn.Sequential( 14 | nn.Linear(c_in, c_out), 15 | nn.BatchNorm1d(c_out), 16 | nn.ReLU(), 17 | nn.Dropout(0.5) 18 | )) 19 | 20 | self.classifier.extend(nn.Sequential( 21 | nn.Linear(self.cfg.NETWORK.HEAD.CLASSIFIER.DIMS[-1], self.cfg.DATASET.NUM_CLASS) 22 | )) 23 | 24 | self.classifier = nn.ModuleList(self.classifier) 25 | 26 | def forward(self, data_dic): 27 | logits = data_dic['point_features'] 28 | 29 | for cur_module in self.classifier: 30 | logits = cur_module(logits) 31 | 32 | data_dic['pred_score_logits'] = logits 33 | return data_dic -------------------------------------------------------------------------------- /core/networks/heads/linear_segmentator.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | def to_categorical(y, num_classes): 5 | """ 1-hot encodes a tensor """ 6 | new_y = torch.eye(num_classes)[y.cpu().data.numpy(),] 7 | if (y.is_cuda): 8 | return new_y.cuda(non_blocking=True) 9 | return new_y 10 | 11 | class LinearSegmentator(nn.Module): 12 | def __init__(self, cfg): 13 | super(LinearSegmentator, self).__init__() 14 | self.cfg = cfg 15 | self.in_channels = self.cfg.NETWORK.HEAD.SEGMENTATOR.IN_CHANNELS + self.cfg.NETWORK.HEAD.SEGMENTATOR.DIMS[0:-1] 16 | self.out_channels = self.cfg.NETWORK.HEAD.SEGMENTATOR.DIMS 17 | 18 | self.segmentator = [] 19 | for c_in, c_out in zip(self.in_channels, self.out_channels): 20 | self.segmentator.extend(nn.Sequential( 21 | nn.Conv1d(c_in, c_out, kernel_size=1), 22 | nn.BatchNorm1d(c_out), 23 | nn.ReLU(), 24 | nn.Dropout(0.5) 25 | )) 26 | 27 | self.segmentator.extend(nn.Sequential( 28 | nn.Conv1d(self.cfg.NETWORK.HEAD.SEGMENTATOR.DIMS[-1], self.cfg.DATASET.NUM_CLASS, kernel_size=1) 29 | )) 30 | 31 | self.segmentator = nn.ModuleList(self.segmentator) 32 | 33 | self.cls_map = nn.Sequential( 34 | nn.Conv1d(in_channels=16, out_channels=64, kernel_size=1, bias=True), 35 | nn.BatchNorm1d(64), 36 | nn.ReLU(), 37 | nn.Conv1d(in_channels=64, out_channels=64, kernel_size=1, bias=True), 38 | nn.BatchNorm1d(64), 39 | nn.ReLU(), 40 | ) 41 | 42 | def forward(self, data_dic): 43 | cls_tokens = data_dic['cls_tokens'] # (B, 1, 1) 44 | cls_tokens = to_categorical(cls_tokens, 16) # (B, 1, 16) 45 | cls_tokens = self.cls_map(cls_tokens.permute(0, 2, 1)) # B, 1, 64 46 | 47 | logits = data_dic['point_features'] 48 | logits = torch.cat([logits, cls_tokens.repeat(1, 1, logits.shape[-1])], dim = 1) 49 | 50 | for cur_module in self.segmentator: 51 | logits = cur_module(logits) 52 | data_dic['pred_score_logits'] = logits.permute(0,2,1) 53 | return data_dic 54 | -------------------------------------------------------------------------------- /core/networks/networks/__init__.py: -------------------------------------------------------------------------------- 1 | from .pointmlp import PointMLP 2 | from .pointstack import PointStack 3 | 4 | __all__ = { 5 | 'PointMLP': PointMLP, 6 | 'PointStack': PointStack 7 | } -------------------------------------------------------------------------------- /core/networks/networks/network_template.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * author: Kevin Tirta Wijaya, AVELab, KAIST 3 | * e-mail: kevin.tirta@kaist.ac.kr 4 | ''' 5 | 6 | from .. import heads, encoders 7 | 8 | import torch 9 | import torch.nn as nn 10 | import torch.nn.functional as F 11 | 12 | class NetworkTemplate(nn.Module): 13 | def __init__(self, cfg, topology = None): 14 | super().__init__() 15 | self.cfg = cfg 16 | self.num_class = cfg.DATASET.NUM_CLASS 17 | 18 | if topology == None: 19 | self.module_topology = ['encoder', 'head'] 20 | else: 21 | self.module_topology = topology 22 | 23 | self.module_list = self.build_networks() 24 | 25 | def build_networks(self): 26 | model_info_dict = { 27 | 'module_list': [], 28 | } 29 | for module_name in self.module_topology: 30 | modules, model_info_dict = getattr(self, 'build_%s' % module_name)( 31 | model_info_dict=model_info_dict 32 | ) 33 | 34 | 35 | return nn.ModuleList(model_info_dict['module_list']) 36 | 37 | def build_encoder(self, model_info_dict): 38 | if self.cfg.NETWORK.get('ENCODER', None) is None: 39 | return None, model_info_dict 40 | encoder_modules = [] 41 | encoder_module = encoders.__all__[self.cfg.NETWORK.ENCODER.NAME](cfg=self.cfg) 42 | model_info_dict['module_list'].append(encoder_module) 43 | encoder_modules.append(encoder_module) 44 | return nn.ModuleList(encoder_modules), model_info_dict 45 | 46 | 47 | def build_head(self, model_info_dict): 48 | if self.cfg.NETWORK.get('HEAD', None) is None: 49 | return None, model_info_dict 50 | 51 | head_modules = [] 52 | if self.cfg.NETWORK.HEAD.get('CLASSIFIER', None) is None: 53 | classifier_module = None 54 | else: 55 | classifier_module = heads.__all__[self.cfg.NETWORK.HEAD.CLASSIFIER.NAME](cfg=self.cfg) 56 | model_info_dict['module_list'].append(classifier_module) 57 | head_modules.append(classifier_module) 58 | 59 | if self.cfg.NETWORK.HEAD.get('SEGMENTATOR', None) is None: 60 | segmentator_module = None 61 | else: 62 | segmentator_module = heads.__all__[self.cfg.NETWORK.HEAD.SEGMENTATOR.NAME](cfg=self.cfg) 63 | model_info_dict['module_list'].append(segmentator_module) 64 | head_modules.append(segmentator_module) 65 | 66 | return nn.ModuleList(head_modules), model_info_dict 67 | 68 | def forward(self, data_dic): 69 | for curr_module in self.module_list: 70 | data_dic = curr_module(data_dic) 71 | 72 | return data_dic -------------------------------------------------------------------------------- /core/networks/networks/pointmlp.py: -------------------------------------------------------------------------------- 1 | from .. import heads, encoders 2 | from .network_template import NetworkTemplate 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | 8 | import numpy as np 9 | 10 | class PointMLP(NetworkTemplate): 11 | def __init__(self, cfg, topology = None): 12 | super().__init__(cfg, topology=topology) 13 | self.build_networks() 14 | self.cfg = cfg 15 | 16 | 17 | def get_loss(self, data_dic, smoothing=True, is_segmentation = False): 18 | ''' Calculate cross entropy loss, apply label smoothing if needed. ''' 19 | 20 | if is_segmentation: 21 | pred_logits = data_dic['pred_score_logits'].contiguous().view(-1, self.cfg.DATASET.NUM_CLASS) # (Batch size * Num Points, Num Classes) 22 | gt_cls_id = data_dic['seg_id'].contiguous().view(-1, 1).long() # (Batch size * Num Points, 1) 23 | 24 | else: 25 | pred_logits = data_dic['pred_score_logits'] # (Batch size, Num Classes) 26 | gt_cls_id = data_dic['cls_id'] # (Batch size, 1) 27 | 28 | if smoothing: 29 | eps = 0.2 30 | n_class = pred_logits.size(1) 31 | 32 | one_hot = torch.zeros_like(pred_logits).scatter(1, gt_cls_id.view(-1, 1), 1) 33 | one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) 34 | log_prb = F.log_softmax(pred_logits, dim=1) 35 | loss = -(one_hot * log_prb).sum(dim=1) 36 | loss = loss[torch.isfinite(loss)].mean() 37 | else: 38 | loss = F.cross_entropy(pred_logits, gt_cls_id, reduction='mean') 39 | 40 | loss_dict = { 41 | 'Cls': loss.item(), 42 | } 43 | 44 | return loss, loss_dict 45 | 46 | def compute_overall_iou(self, pred, target, num_classes): 47 | shape_ious = [] 48 | pred = pred.max(dim=2)[1] # (batch_size, num_points) 49 | pred_np = pred.cpu().data.numpy() 50 | 51 | target_np = target.cpu().data.numpy() 52 | for shape_idx in range(pred.size(0)): # sample_idx 53 | part_ious = [] 54 | for part in range(num_classes): 55 | I = np.sum(np.logical_and(pred_np[shape_idx] == part, target_np[shape_idx] == part)) 56 | U = np.sum(np.logical_or(pred_np[shape_idx] == part, target_np[shape_idx] == part)) 57 | 58 | F = np.sum(target_np[shape_idx] == part) 59 | 60 | if F != 0: 61 | iou = I / float(U) 62 | part_ious.append(iou) 63 | shape_ious.append(np.mean(part_ious)) 64 | return shape_ious # [batch_size] -------------------------------------------------------------------------------- /core/networks/networks/pointstack.py: -------------------------------------------------------------------------------- 1 | ''' 2 | * author: Kevin Tirta Wijaya, AVELab, KAIST 3 | * e-mail: kevin.tirta@kaist.ac.kr 4 | ''' 5 | 6 | from .. import heads, encoders 7 | from .network_template import NetworkTemplate 8 | 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | import numpy as np 14 | 15 | class PointStack(NetworkTemplate): 16 | def __init__(self, cfg, topology = None): 17 | super().__init__(cfg, topology=topology) 18 | self.build_networks() 19 | self.cfg = cfg 20 | 21 | 22 | def get_loss(self, data_dic, smoothing=True, is_segmentation = False): 23 | ''' Calculate cross entropy loss, apply label smoothing if needed. ''' 24 | 25 | if is_segmentation: 26 | pred_logits = data_dic['pred_score_logits'].contiguous().view(-1, self.cfg.DATASET.NUM_CLASS) # (Batch size * Num Points, Num Classes) 27 | gt_cls_id = data_dic['seg_id'].contiguous().view(-1, 1).long() # (Batch size * Num Points, 1) 28 | 29 | else: 30 | pred_logits = data_dic['pred_score_logits'] # (Batch size, Num Classes) 31 | gt_cls_id = data_dic['cls_id'] # (Batch size, 1) 32 | 33 | if smoothing: 34 | eps = 0.2 35 | n_class = pred_logits.size(1) 36 | 37 | one_hot = torch.zeros_like(pred_logits).scatter(1, gt_cls_id.view(-1, 1), 1) 38 | one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) 39 | log_prb = F.log_softmax(pred_logits, dim=1) 40 | loss = -(one_hot * log_prb).sum(dim=1) 41 | loss = loss[torch.isfinite(loss)].mean() 42 | else: 43 | loss = F.cross_entropy(pred_logits, gt_cls_id, reduction='mean') 44 | 45 | loss_dict = { 46 | 'Cls': loss.item(), 47 | } 48 | 49 | return loss, loss_dict 50 | 51 | def compute_overall_iou(self, pred, target, num_classes): 52 | shape_ious = [] 53 | pred = pred.max(dim=2)[1] # (batch_size, num_points) 54 | pred_np = pred.cpu().data.numpy() 55 | 56 | target_np = target.cpu().data.numpy() 57 | for shape_idx in range(pred.size(0)): # sample_idx 58 | part_ious = [] 59 | for part in range(num_classes): 60 | I = np.sum(np.logical_and(pred_np[shape_idx] == part, target_np[shape_idx] == part)) 61 | U = np.sum(np.logical_or(pred_np[shape_idx] == part, target_np[shape_idx] == part)) 62 | 63 | F = np.sum(target_np[shape_idx] == part) 64 | 65 | if F != 0: 66 | iou = I / float(U) 67 | part_ious.append(iou) 68 | shape_ious.append(np.mean(part_ious)) 69 | return shape_ious # [batch_size] -------------------------------------------------------------------------------- /docs/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/docs/overview.png -------------------------------------------------------------------------------- /pointnet2_ops_lib/MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft pointnet2_ops/_ext-src 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/__init__.py: -------------------------------------------------------------------------------- 1 | import pointnet2_ops.pointnet2_modules 2 | import pointnet2_ops.pointnet2_utils 3 | from pointnet2_ops._version import __version__ 4 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/ball_query.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius, 5 | const int nsample); 6 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/cuda_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _CUDA_UTILS_H 2 | #define _CUDA_UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #define TOTAL_THREADS 512 14 | 15 | inline int opt_n_threads(int work_size) { 16 | const int pow_2 = std::log(static_cast(work_size)) / std::log(2.0); 17 | 18 | return max(min(1 << pow_2, TOTAL_THREADS), 1); 19 | } 20 | 21 | inline dim3 opt_block_config(int x, int y) { 22 | const int x_threads = opt_n_threads(x); 23 | const int y_threads = 24 | max(min(opt_n_threads(y), TOTAL_THREADS / x_threads), 1); 25 | dim3 block_config(x_threads, y_threads, 1); 26 | 27 | return block_config; 28 | } 29 | 30 | #define CUDA_CHECK_ERRORS() \ 31 | do { \ 32 | cudaError_t err = cudaGetLastError(); \ 33 | if (cudaSuccess != err) { \ 34 | fprintf(stderr, "CUDA kernel failed : %s\n%s at L:%d in %s\n", \ 35 | cudaGetErrorString(err), __PRETTY_FUNCTION__, __LINE__, \ 36 | __FILE__); \ 37 | exit(-1); \ 38 | } \ 39 | } while (0) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/group_points.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor group_points(at::Tensor points, at::Tensor idx); 5 | at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n); 6 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/interpolate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::vector three_nn(at::Tensor unknowns, at::Tensor knows); 7 | at::Tensor three_interpolate(at::Tensor points, at::Tensor idx, 8 | at::Tensor weight); 9 | at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx, 10 | at::Tensor weight, const int m); 11 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/sampling.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor gather_points(at::Tensor points, at::Tensor idx); 5 | at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx, const int n); 6 | at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples); 7 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/include/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define CHECK_CUDA(x) \ 6 | do { \ 7 | AT_ASSERT(x.is_cuda(), #x " must be a CUDA tensor"); \ 8 | } while (0) 9 | 10 | #define CHECK_CONTIGUOUS(x) \ 11 | do { \ 12 | AT_ASSERT(x.is_contiguous(), #x " must be a contiguous tensor"); \ 13 | } while (0) 14 | 15 | #define CHECK_IS_INT(x) \ 16 | do { \ 17 | AT_ASSERT(x.scalar_type() == at::ScalarType::Int, \ 18 | #x " must be an int tensor"); \ 19 | } while (0) 20 | 21 | #define CHECK_IS_FLOAT(x) \ 22 | do { \ 23 | AT_ASSERT(x.scalar_type() == at::ScalarType::Float, \ 24 | #x " must be a float tensor"); \ 25 | } while (0) 26 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query.cpp: -------------------------------------------------------------------------------- 1 | #include "ball_query.h" 2 | #include "utils.h" 3 | 4 | void query_ball_point_kernel_wrapper(int b, int n, int m, float radius, 5 | int nsample, const float *new_xyz, 6 | const float *xyz, int *idx); 7 | 8 | at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius, 9 | const int nsample) { 10 | CHECK_CONTIGUOUS(new_xyz); 11 | CHECK_CONTIGUOUS(xyz); 12 | CHECK_IS_FLOAT(new_xyz); 13 | CHECK_IS_FLOAT(xyz); 14 | 15 | if (new_xyz.is_cuda()) { 16 | CHECK_CUDA(xyz); 17 | } 18 | 19 | at::Tensor idx = 20 | torch::zeros({new_xyz.size(0), new_xyz.size(1), nsample}, 21 | at::device(new_xyz.device()).dtype(at::ScalarType::Int)); 22 | 23 | if (new_xyz.is_cuda()) { 24 | query_ball_point_kernel_wrapper(xyz.size(0), xyz.size(1), new_xyz.size(1), 25 | radius, nsample, new_xyz.data_ptr(), 26 | xyz.data_ptr(), idx.data_ptr()); 27 | } else { 28 | AT_ASSERT(false, "CPU not supported"); 29 | } 30 | 31 | return idx; 32 | } 33 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cuda_utils.h" 6 | 7 | // input: new_xyz(b, m, 3) xyz(b, n, 3) 8 | // output: idx(b, m, nsample) 9 | __global__ void query_ball_point_kernel(int b, int n, int m, float radius, 10 | int nsample, 11 | const float *__restrict__ new_xyz, 12 | const float *__restrict__ xyz, 13 | int *__restrict__ idx) { 14 | int batch_index = blockIdx.x; 15 | xyz += batch_index * n * 3; 16 | new_xyz += batch_index * m * 3; 17 | idx += m * nsample * batch_index; 18 | 19 | int index = threadIdx.x; 20 | int stride = blockDim.x; 21 | 22 | float radius2 = radius * radius; 23 | for (int j = index; j < m; j += stride) { 24 | float new_x = new_xyz[j * 3 + 0]; 25 | float new_y = new_xyz[j * 3 + 1]; 26 | float new_z = new_xyz[j * 3 + 2]; 27 | for (int k = 0, cnt = 0; k < n && cnt < nsample; ++k) { 28 | float x = xyz[k * 3 + 0]; 29 | float y = xyz[k * 3 + 1]; 30 | float z = xyz[k * 3 + 2]; 31 | float d2 = (new_x - x) * (new_x - x) + (new_y - y) * (new_y - y) + 32 | (new_z - z) * (new_z - z); 33 | if (d2 < radius2) { 34 | if (cnt == 0) { 35 | for (int l = 0; l < nsample; ++l) { 36 | idx[j * nsample + l] = k; 37 | } 38 | } 39 | idx[j * nsample + cnt] = k; 40 | ++cnt; 41 | } 42 | } 43 | } 44 | } 45 | 46 | void query_ball_point_kernel_wrapper(int b, int n, int m, float radius, 47 | int nsample, const float *new_xyz, 48 | const float *xyz, int *idx) { 49 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 50 | query_ball_point_kernel<<>>( 51 | b, n, m, radius, nsample, new_xyz, xyz, idx); 52 | 53 | CUDA_CHECK_ERRORS(); 54 | } 55 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/bindings.cpp: -------------------------------------------------------------------------------- 1 | #include "ball_query.h" 2 | #include "group_points.h" 3 | #include "interpolate.h" 4 | #include "sampling.h" 5 | 6 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 7 | m.def("gather_points", &gather_points); 8 | m.def("gather_points_grad", &gather_points_grad); 9 | m.def("furthest_point_sampling", &furthest_point_sampling); 10 | 11 | m.def("three_nn", &three_nn); 12 | m.def("three_interpolate", &three_interpolate); 13 | m.def("three_interpolate_grad", &three_interpolate_grad); 14 | 15 | m.def("ball_query", &ball_query); 16 | 17 | m.def("group_points", &group_points); 18 | m.def("group_points_grad", &group_points_grad); 19 | } 20 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points.cpp: -------------------------------------------------------------------------------- 1 | #include "group_points.h" 2 | #include "utils.h" 3 | 4 | void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample, 5 | const float *points, const int *idx, 6 | float *out); 7 | 8 | void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 9 | int nsample, const float *grad_out, 10 | const int *idx, float *grad_points); 11 | 12 | at::Tensor group_points(at::Tensor points, at::Tensor idx) { 13 | CHECK_CONTIGUOUS(points); 14 | CHECK_CONTIGUOUS(idx); 15 | CHECK_IS_FLOAT(points); 16 | CHECK_IS_INT(idx); 17 | 18 | if (points.is_cuda()) { 19 | CHECK_CUDA(idx); 20 | } 21 | 22 | at::Tensor output = 23 | torch::zeros({points.size(0), points.size(1), idx.size(1), idx.size(2)}, 24 | at::device(points.device()).dtype(at::ScalarType::Float)); 25 | 26 | if (points.is_cuda()) { 27 | group_points_kernel_wrapper(points.size(0), points.size(1), points.size(2), 28 | idx.size(1), idx.size(2), 29 | points.data_ptr(), idx.data_ptr(), 30 | output.data_ptr()); 31 | } else { 32 | AT_ASSERT(false, "CPU not supported"); 33 | } 34 | 35 | return output; 36 | } 37 | 38 | at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n) { 39 | CHECK_CONTIGUOUS(grad_out); 40 | CHECK_CONTIGUOUS(idx); 41 | CHECK_IS_FLOAT(grad_out); 42 | CHECK_IS_INT(idx); 43 | 44 | if (grad_out.is_cuda()) { 45 | CHECK_CUDA(idx); 46 | } 47 | 48 | at::Tensor output = 49 | torch::zeros({grad_out.size(0), grad_out.size(1), n}, 50 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 51 | 52 | if (grad_out.is_cuda()) { 53 | group_points_grad_kernel_wrapper( 54 | grad_out.size(0), grad_out.size(1), n, idx.size(1), idx.size(2), 55 | grad_out.data_ptr(), idx.data_ptr(), 56 | output.data_ptr()); 57 | } else { 58 | AT_ASSERT(false, "CPU not supported"); 59 | } 60 | 61 | return output; 62 | } 63 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cuda_utils.h" 5 | 6 | // input: points(b, c, n) idx(b, npoints, nsample) 7 | // output: out(b, c, npoints, nsample) 8 | __global__ void group_points_kernel(int b, int c, int n, int npoints, 9 | int nsample, 10 | const float *__restrict__ points, 11 | const int *__restrict__ idx, 12 | float *__restrict__ out) { 13 | int batch_index = blockIdx.x; 14 | points += batch_index * n * c; 15 | idx += batch_index * npoints * nsample; 16 | out += batch_index * npoints * nsample * c; 17 | 18 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 19 | const int stride = blockDim.y * blockDim.x; 20 | for (int i = index; i < c * npoints; i += stride) { 21 | const int l = i / npoints; 22 | const int j = i % npoints; 23 | for (int k = 0; k < nsample; ++k) { 24 | int ii = idx[j * nsample + k]; 25 | out[(l * npoints + j) * nsample + k] = points[l * n + ii]; 26 | } 27 | } 28 | } 29 | 30 | void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample, 31 | const float *points, const int *idx, 32 | float *out) { 33 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 34 | 35 | group_points_kernel<<>>( 36 | b, c, n, npoints, nsample, points, idx, out); 37 | 38 | CUDA_CHECK_ERRORS(); 39 | } 40 | 41 | // input: grad_out(b, c, npoints, nsample), idx(b, npoints, nsample) 42 | // output: grad_points(b, c, n) 43 | __global__ void group_points_grad_kernel(int b, int c, int n, int npoints, 44 | int nsample, 45 | const float *__restrict__ grad_out, 46 | const int *__restrict__ idx, 47 | float *__restrict__ grad_points) { 48 | int batch_index = blockIdx.x; 49 | grad_out += batch_index * npoints * nsample * c; 50 | idx += batch_index * npoints * nsample; 51 | grad_points += batch_index * n * c; 52 | 53 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 54 | const int stride = blockDim.y * blockDim.x; 55 | for (int i = index; i < c * npoints; i += stride) { 56 | const int l = i / npoints; 57 | const int j = i % npoints; 58 | for (int k = 0; k < nsample; ++k) { 59 | int ii = idx[j * nsample + k]; 60 | atomicAdd(grad_points + l * n + ii, 61 | grad_out[(l * npoints + j) * nsample + k]); 62 | } 63 | } 64 | } 65 | 66 | void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 67 | int nsample, const float *grad_out, 68 | const int *idx, float *grad_points) { 69 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 70 | 71 | group_points_grad_kernel<<>>( 72 | b, c, n, npoints, nsample, grad_out, idx, grad_points); 73 | 74 | CUDA_CHECK_ERRORS(); 75 | } 76 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate.cpp: -------------------------------------------------------------------------------- 1 | #include "interpolate.h" 2 | #include "utils.h" 3 | 4 | void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown, 5 | const float *known, float *dist2, int *idx); 6 | void three_interpolate_kernel_wrapper(int b, int c, int m, int n, 7 | const float *points, const int *idx, 8 | const float *weight, float *out); 9 | void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m, 10 | const float *grad_out, 11 | const int *idx, const float *weight, 12 | float *grad_points); 13 | 14 | std::vector three_nn(at::Tensor unknowns, at::Tensor knows) { 15 | CHECK_CONTIGUOUS(unknowns); 16 | CHECK_CONTIGUOUS(knows); 17 | CHECK_IS_FLOAT(unknowns); 18 | CHECK_IS_FLOAT(knows); 19 | 20 | if (unknowns.is_cuda()) { 21 | CHECK_CUDA(knows); 22 | } 23 | 24 | at::Tensor idx = 25 | torch::zeros({unknowns.size(0), unknowns.size(1), 3}, 26 | at::device(unknowns.device()).dtype(at::ScalarType::Int)); 27 | at::Tensor dist2 = 28 | torch::zeros({unknowns.size(0), unknowns.size(1), 3}, 29 | at::device(unknowns.device()).dtype(at::ScalarType::Float)); 30 | 31 | if (unknowns.is_cuda()) { 32 | three_nn_kernel_wrapper(unknowns.size(0), unknowns.size(1), knows.size(1), 33 | unknowns.data_ptr(), knows.data_ptr(), 34 | dist2.data_ptr(), idx.data_ptr()); 35 | } else { 36 | AT_ASSERT(false, "CPU not supported"); 37 | } 38 | 39 | return {dist2, idx}; 40 | } 41 | 42 | at::Tensor three_interpolate(at::Tensor points, at::Tensor idx, 43 | at::Tensor weight) { 44 | CHECK_CONTIGUOUS(points); 45 | CHECK_CONTIGUOUS(idx); 46 | CHECK_CONTIGUOUS(weight); 47 | CHECK_IS_FLOAT(points); 48 | CHECK_IS_INT(idx); 49 | CHECK_IS_FLOAT(weight); 50 | 51 | if (points.is_cuda()) { 52 | CHECK_CUDA(idx); 53 | CHECK_CUDA(weight); 54 | } 55 | 56 | at::Tensor output = 57 | torch::zeros({points.size(0), points.size(1), idx.size(1)}, 58 | at::device(points.device()).dtype(at::ScalarType::Float)); 59 | 60 | if (points.is_cuda()) { 61 | three_interpolate_kernel_wrapper( 62 | points.size(0), points.size(1), points.size(2), idx.size(1), 63 | points.data_ptr(), idx.data_ptr(), weight.data_ptr(), 64 | output.data_ptr()); 65 | } else { 66 | AT_ASSERT(false, "CPU not supported"); 67 | } 68 | 69 | return output; 70 | } 71 | at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx, 72 | at::Tensor weight, const int m) { 73 | CHECK_CONTIGUOUS(grad_out); 74 | CHECK_CONTIGUOUS(idx); 75 | CHECK_CONTIGUOUS(weight); 76 | CHECK_IS_FLOAT(grad_out); 77 | CHECK_IS_INT(idx); 78 | CHECK_IS_FLOAT(weight); 79 | 80 | if (grad_out.is_cuda()) { 81 | CHECK_CUDA(idx); 82 | CHECK_CUDA(weight); 83 | } 84 | 85 | at::Tensor output = 86 | torch::zeros({grad_out.size(0), grad_out.size(1), m}, 87 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 88 | 89 | if (grad_out.is_cuda()) { 90 | three_interpolate_grad_kernel_wrapper( 91 | grad_out.size(0), grad_out.size(1), grad_out.size(2), m, 92 | grad_out.data_ptr(), idx.data_ptr(), 93 | weight.data_ptr(), output.data_ptr()); 94 | } else { 95 | AT_ASSERT(false, "CPU not supported"); 96 | } 97 | 98 | return output; 99 | } 100 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cuda_utils.h" 6 | 7 | // input: unknown(b, n, 3) known(b, m, 3) 8 | // output: dist2(b, n, 3), idx(b, n, 3) 9 | __global__ void three_nn_kernel(int b, int n, int m, 10 | const float *__restrict__ unknown, 11 | const float *__restrict__ known, 12 | float *__restrict__ dist2, 13 | int *__restrict__ idx) { 14 | int batch_index = blockIdx.x; 15 | unknown += batch_index * n * 3; 16 | known += batch_index * m * 3; 17 | dist2 += batch_index * n * 3; 18 | idx += batch_index * n * 3; 19 | 20 | int index = threadIdx.x; 21 | int stride = blockDim.x; 22 | for (int j = index; j < n; j += stride) { 23 | float ux = unknown[j * 3 + 0]; 24 | float uy = unknown[j * 3 + 1]; 25 | float uz = unknown[j * 3 + 2]; 26 | 27 | double best1 = 1e40, best2 = 1e40, best3 = 1e40; 28 | int besti1 = 0, besti2 = 0, besti3 = 0; 29 | for (int k = 0; k < m; ++k) { 30 | float x = known[k * 3 + 0]; 31 | float y = known[k * 3 + 1]; 32 | float z = known[k * 3 + 2]; 33 | float d = (ux - x) * (ux - x) + (uy - y) * (uy - y) + (uz - z) * (uz - z); 34 | if (d < best1) { 35 | best3 = best2; 36 | besti3 = besti2; 37 | best2 = best1; 38 | besti2 = besti1; 39 | best1 = d; 40 | besti1 = k; 41 | } else if (d < best2) { 42 | best3 = best2; 43 | besti3 = besti2; 44 | best2 = d; 45 | besti2 = k; 46 | } else if (d < best3) { 47 | best3 = d; 48 | besti3 = k; 49 | } 50 | } 51 | dist2[j * 3 + 0] = best1; 52 | dist2[j * 3 + 1] = best2; 53 | dist2[j * 3 + 2] = best3; 54 | 55 | idx[j * 3 + 0] = besti1; 56 | idx[j * 3 + 1] = besti2; 57 | idx[j * 3 + 2] = besti3; 58 | } 59 | } 60 | 61 | void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown, 62 | const float *known, float *dist2, int *idx) { 63 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 64 | three_nn_kernel<<>>(b, n, m, unknown, known, 65 | dist2, idx); 66 | 67 | CUDA_CHECK_ERRORS(); 68 | } 69 | 70 | // input: points(b, c, m), idx(b, n, 3), weight(b, n, 3) 71 | // output: out(b, c, n) 72 | __global__ void three_interpolate_kernel(int b, int c, int m, int n, 73 | const float *__restrict__ points, 74 | const int *__restrict__ idx, 75 | const float *__restrict__ weight, 76 | float *__restrict__ out) { 77 | int batch_index = blockIdx.x; 78 | points += batch_index * m * c; 79 | 80 | idx += batch_index * n * 3; 81 | weight += batch_index * n * 3; 82 | 83 | out += batch_index * n * c; 84 | 85 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 86 | const int stride = blockDim.y * blockDim.x; 87 | for (int i = index; i < c * n; i += stride) { 88 | const int l = i / n; 89 | const int j = i % n; 90 | float w1 = weight[j * 3 + 0]; 91 | float w2 = weight[j * 3 + 1]; 92 | float w3 = weight[j * 3 + 2]; 93 | 94 | int i1 = idx[j * 3 + 0]; 95 | int i2 = idx[j * 3 + 1]; 96 | int i3 = idx[j * 3 + 2]; 97 | 98 | out[i] = points[l * m + i1] * w1 + points[l * m + i2] * w2 + 99 | points[l * m + i3] * w3; 100 | } 101 | } 102 | 103 | void three_interpolate_kernel_wrapper(int b, int c, int m, int n, 104 | const float *points, const int *idx, 105 | const float *weight, float *out) { 106 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 107 | three_interpolate_kernel<<>>( 108 | b, c, m, n, points, idx, weight, out); 109 | 110 | CUDA_CHECK_ERRORS(); 111 | } 112 | 113 | // input: grad_out(b, c, n), idx(b, n, 3), weight(b, n, 3) 114 | // output: grad_points(b, c, m) 115 | 116 | __global__ void three_interpolate_grad_kernel( 117 | int b, int c, int n, int m, const float *__restrict__ grad_out, 118 | const int *__restrict__ idx, const float *__restrict__ weight, 119 | float *__restrict__ grad_points) { 120 | int batch_index = blockIdx.x; 121 | grad_out += batch_index * n * c; 122 | idx += batch_index * n * 3; 123 | weight += batch_index * n * 3; 124 | grad_points += batch_index * m * c; 125 | 126 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 127 | const int stride = blockDim.y * blockDim.x; 128 | for (int i = index; i < c * n; i += stride) { 129 | const int l = i / n; 130 | const int j = i % n; 131 | float w1 = weight[j * 3 + 0]; 132 | float w2 = weight[j * 3 + 1]; 133 | float w3 = weight[j * 3 + 2]; 134 | 135 | int i1 = idx[j * 3 + 0]; 136 | int i2 = idx[j * 3 + 1]; 137 | int i3 = idx[j * 3 + 2]; 138 | 139 | atomicAdd(grad_points + l * m + i1, grad_out[i] * w1); 140 | atomicAdd(grad_points + l * m + i2, grad_out[i] * w2); 141 | atomicAdd(grad_points + l * m + i3, grad_out[i] * w3); 142 | } 143 | } 144 | 145 | void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m, 146 | const float *grad_out, 147 | const int *idx, const float *weight, 148 | float *grad_points) { 149 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 150 | three_interpolate_grad_kernel<<>>( 151 | b, c, n, m, grad_out, idx, weight, grad_points); 152 | 153 | CUDA_CHECK_ERRORS(); 154 | } 155 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling.cpp: -------------------------------------------------------------------------------- 1 | #include "sampling.h" 2 | #include "utils.h" 3 | 4 | void gather_points_kernel_wrapper(int b, int c, int n, int npoints, 5 | const float *points, const int *idx, 6 | float *out); 7 | void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 8 | const float *grad_out, const int *idx, 9 | float *grad_points); 10 | 11 | void furthest_point_sampling_kernel_wrapper(int b, int n, int m, 12 | const float *dataset, float *temp, 13 | int *idxs); 14 | 15 | at::Tensor gather_points(at::Tensor points, at::Tensor idx) { 16 | CHECK_CONTIGUOUS(points); 17 | CHECK_CONTIGUOUS(idx); 18 | CHECK_IS_FLOAT(points); 19 | CHECK_IS_INT(idx); 20 | 21 | if (points.is_cuda()) { 22 | CHECK_CUDA(idx); 23 | } 24 | 25 | at::Tensor output = 26 | torch::zeros({points.size(0), points.size(1), idx.size(1)}, 27 | at::device(points.device()).dtype(at::ScalarType::Float)); 28 | 29 | if (points.is_cuda()) { 30 | gather_points_kernel_wrapper(points.size(0), points.size(1), points.size(2), 31 | idx.size(1), points.data_ptr(), 32 | idx.data_ptr(), output.data_ptr()); 33 | } else { 34 | AT_ASSERT(false, "CPU not supported"); 35 | } 36 | 37 | return output; 38 | } 39 | 40 | at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx, 41 | const int n) { 42 | CHECK_CONTIGUOUS(grad_out); 43 | CHECK_CONTIGUOUS(idx); 44 | CHECK_IS_FLOAT(grad_out); 45 | CHECK_IS_INT(idx); 46 | 47 | if (grad_out.is_cuda()) { 48 | CHECK_CUDA(idx); 49 | } 50 | 51 | at::Tensor output = 52 | torch::zeros({grad_out.size(0), grad_out.size(1), n}, 53 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 54 | 55 | if (grad_out.is_cuda()) { 56 | gather_points_grad_kernel_wrapper(grad_out.size(0), grad_out.size(1), n, 57 | idx.size(1), grad_out.data_ptr(), 58 | idx.data_ptr(), 59 | output.data_ptr()); 60 | } else { 61 | AT_ASSERT(false, "CPU not supported"); 62 | } 63 | 64 | return output; 65 | } 66 | at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples) { 67 | CHECK_CONTIGUOUS(points); 68 | CHECK_IS_FLOAT(points); 69 | 70 | at::Tensor output = 71 | torch::zeros({points.size(0), nsamples}, 72 | at::device(points.device()).dtype(at::ScalarType::Int)); 73 | 74 | at::Tensor tmp = 75 | torch::full({points.size(0), points.size(1)}, 1e10, 76 | at::device(points.device()).dtype(at::ScalarType::Float)); 77 | 78 | if (points.is_cuda()) { 79 | furthest_point_sampling_kernel_wrapper( 80 | points.size(0), points.size(1), nsamples, points.data_ptr(), 81 | tmp.data_ptr(), output.data_ptr()); 82 | } else { 83 | AT_ASSERT(false, "CPU not supported"); 84 | } 85 | 86 | return output; 87 | } 88 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cuda_utils.h" 5 | 6 | // input: points(b, c, n) idx(b, m) 7 | // output: out(b, c, m) 8 | __global__ void gather_points_kernel(int b, int c, int n, int m, 9 | const float *__restrict__ points, 10 | const int *__restrict__ idx, 11 | float *__restrict__ out) { 12 | for (int i = blockIdx.x; i < b; i += gridDim.x) { 13 | for (int l = blockIdx.y; l < c; l += gridDim.y) { 14 | for (int j = threadIdx.x; j < m; j += blockDim.x) { 15 | int a = idx[i * m + j]; 16 | out[(i * c + l) * m + j] = points[(i * c + l) * n + a]; 17 | } 18 | } 19 | } 20 | } 21 | 22 | void gather_points_kernel_wrapper(int b, int c, int n, int npoints, 23 | const float *points, const int *idx, 24 | float *out) { 25 | gather_points_kernel<<>>(b, c, n, npoints, 27 | points, idx, out); 28 | 29 | CUDA_CHECK_ERRORS(); 30 | } 31 | 32 | // input: grad_out(b, c, m) idx(b, m) 33 | // output: grad_points(b, c, n) 34 | __global__ void gather_points_grad_kernel(int b, int c, int n, int m, 35 | const float *__restrict__ grad_out, 36 | const int *__restrict__ idx, 37 | float *__restrict__ grad_points) { 38 | for (int i = blockIdx.x; i < b; i += gridDim.x) { 39 | for (int l = blockIdx.y; l < c; l += gridDim.y) { 40 | for (int j = threadIdx.x; j < m; j += blockDim.x) { 41 | int a = idx[i * m + j]; 42 | atomicAdd(grad_points + (i * c + l) * n + a, 43 | grad_out[(i * c + l) * m + j]); 44 | } 45 | } 46 | } 47 | } 48 | 49 | void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 50 | const float *grad_out, const int *idx, 51 | float *grad_points) { 52 | gather_points_grad_kernel<<>>( 54 | b, c, n, npoints, grad_out, idx, grad_points); 55 | 56 | CUDA_CHECK_ERRORS(); 57 | } 58 | 59 | __device__ void __update(float *__restrict__ dists, int *__restrict__ dists_i, 60 | int idx1, int idx2) { 61 | const float v1 = dists[idx1], v2 = dists[idx2]; 62 | const int i1 = dists_i[idx1], i2 = dists_i[idx2]; 63 | dists[idx1] = max(v1, v2); 64 | dists_i[idx1] = v2 > v1 ? i2 : i1; 65 | } 66 | 67 | // Input dataset: (b, n, 3), tmp: (b, n) 68 | // Ouput idxs (b, m) 69 | template 70 | __global__ void furthest_point_sampling_kernel( 71 | int b, int n, int m, const float *__restrict__ dataset, 72 | float *__restrict__ temp, int *__restrict__ idxs) { 73 | if (m <= 0) return; 74 | __shared__ float dists[block_size]; 75 | __shared__ int dists_i[block_size]; 76 | 77 | int batch_index = blockIdx.x; 78 | dataset += batch_index * n * 3; 79 | temp += batch_index * n; 80 | idxs += batch_index * m; 81 | 82 | int tid = threadIdx.x; 83 | const int stride = block_size; 84 | 85 | int old = 0; 86 | if (threadIdx.x == 0) idxs[0] = old; 87 | 88 | __syncthreads(); 89 | for (int j = 1; j < m; j++) { 90 | int besti = 0; 91 | float best = -1; 92 | float x1 = dataset[old * 3 + 0]; 93 | float y1 = dataset[old * 3 + 1]; 94 | float z1 = dataset[old * 3 + 2]; 95 | for (int k = tid; k < n; k += stride) { 96 | float x2, y2, z2; 97 | x2 = dataset[k * 3 + 0]; 98 | y2 = dataset[k * 3 + 1]; 99 | z2 = dataset[k * 3 + 2]; 100 | float mag = (x2 * x2) + (y2 * y2) + (z2 * z2); 101 | if (mag <= 1e-3) continue; 102 | 103 | float d = 104 | (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1); 105 | 106 | float d2 = min(d, temp[k]); 107 | temp[k] = d2; 108 | besti = d2 > best ? k : besti; 109 | best = d2 > best ? d2 : best; 110 | } 111 | dists[tid] = best; 112 | dists_i[tid] = besti; 113 | __syncthreads(); 114 | 115 | if (block_size >= 512) { 116 | if (tid < 256) { 117 | __update(dists, dists_i, tid, tid + 256); 118 | } 119 | __syncthreads(); 120 | } 121 | if (block_size >= 256) { 122 | if (tid < 128) { 123 | __update(dists, dists_i, tid, tid + 128); 124 | } 125 | __syncthreads(); 126 | } 127 | if (block_size >= 128) { 128 | if (tid < 64) { 129 | __update(dists, dists_i, tid, tid + 64); 130 | } 131 | __syncthreads(); 132 | } 133 | if (block_size >= 64) { 134 | if (tid < 32) { 135 | __update(dists, dists_i, tid, tid + 32); 136 | } 137 | __syncthreads(); 138 | } 139 | if (block_size >= 32) { 140 | if (tid < 16) { 141 | __update(dists, dists_i, tid, tid + 16); 142 | } 143 | __syncthreads(); 144 | } 145 | if (block_size >= 16) { 146 | if (tid < 8) { 147 | __update(dists, dists_i, tid, tid + 8); 148 | } 149 | __syncthreads(); 150 | } 151 | if (block_size >= 8) { 152 | if (tid < 4) { 153 | __update(dists, dists_i, tid, tid + 4); 154 | } 155 | __syncthreads(); 156 | } 157 | if (block_size >= 4) { 158 | if (tid < 2) { 159 | __update(dists, dists_i, tid, tid + 2); 160 | } 161 | __syncthreads(); 162 | } 163 | if (block_size >= 2) { 164 | if (tid < 1) { 165 | __update(dists, dists_i, tid, tid + 1); 166 | } 167 | __syncthreads(); 168 | } 169 | 170 | old = dists_i[0]; 171 | if (tid == 0) idxs[j] = old; 172 | } 173 | } 174 | 175 | void furthest_point_sampling_kernel_wrapper(int b, int n, int m, 176 | const float *dataset, float *temp, 177 | int *idxs) { 178 | unsigned int n_threads = opt_n_threads(n); 179 | 180 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 181 | 182 | switch (n_threads) { 183 | case 512: 184 | furthest_point_sampling_kernel<512> 185 | <<>>(b, n, m, dataset, temp, idxs); 186 | break; 187 | case 256: 188 | furthest_point_sampling_kernel<256> 189 | <<>>(b, n, m, dataset, temp, idxs); 190 | break; 191 | case 128: 192 | furthest_point_sampling_kernel<128> 193 | <<>>(b, n, m, dataset, temp, idxs); 194 | break; 195 | case 64: 196 | furthest_point_sampling_kernel<64> 197 | <<>>(b, n, m, dataset, temp, idxs); 198 | break; 199 | case 32: 200 | furthest_point_sampling_kernel<32> 201 | <<>>(b, n, m, dataset, temp, idxs); 202 | break; 203 | case 16: 204 | furthest_point_sampling_kernel<16> 205 | <<>>(b, n, m, dataset, temp, idxs); 206 | break; 207 | case 8: 208 | furthest_point_sampling_kernel<8> 209 | <<>>(b, n, m, dataset, temp, idxs); 210 | break; 211 | case 4: 212 | furthest_point_sampling_kernel<4> 213 | <<>>(b, n, m, dataset, temp, idxs); 214 | break; 215 | case 2: 216 | furthest_point_sampling_kernel<2> 217 | <<>>(b, n, m, dataset, temp, idxs); 218 | break; 219 | case 1: 220 | furthest_point_sampling_kernel<1> 221 | <<>>(b, n, m, dataset, temp, idxs); 222 | break; 223 | default: 224 | furthest_point_sampling_kernel<512> 225 | <<>>(b, n, m, dataset, temp, idxs); 226 | } 227 | 228 | CUDA_CHECK_ERRORS(); 229 | } 230 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext.cpython-37m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_ext.cpython-37m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.0.0" 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/pointnet2_modules.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Tuple 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from pointnet2_ops import pointnet2_utils 7 | 8 | 9 | def build_shared_mlp(mlp_spec: List[int], bn: bool = True): 10 | layers = [] 11 | for i in range(1, len(mlp_spec)): 12 | layers.append( 13 | nn.Conv2d(mlp_spec[i - 1], mlp_spec[i], kernel_size=1, bias=not bn) 14 | ) 15 | if bn: 16 | layers.append(nn.BatchNorm2d(mlp_spec[i])) 17 | layers.append(nn.ReLU(True)) 18 | 19 | return nn.Sequential(*layers) 20 | 21 | 22 | class _PointnetSAModuleBase(nn.Module): 23 | def __init__(self): 24 | super(_PointnetSAModuleBase, self).__init__() 25 | self.npoint = None 26 | self.groupers = None 27 | self.mlps = None 28 | 29 | def forward( 30 | self, xyz: torch.Tensor, features: Optional[torch.Tensor] 31 | ) -> Tuple[torch.Tensor, torch.Tensor]: 32 | r""" 33 | Parameters 34 | ---------- 35 | xyz : torch.Tensor 36 | (B, N, 3) tensor of the xyz coordinates of the features 37 | features : torch.Tensor 38 | (B, C, N) tensor of the descriptors of the the features 39 | 40 | Returns 41 | ------- 42 | new_xyz : torch.Tensor 43 | (B, npoint, 3) tensor of the new features' xyz 44 | new_features : torch.Tensor 45 | (B, \sum_k(mlps[k][-1]), npoint) tensor of the new_features descriptors 46 | """ 47 | 48 | new_features_list = [] 49 | 50 | xyz_flipped = xyz.transpose(1, 2).contiguous() 51 | new_xyz = ( 52 | pointnet2_utils.gather_operation( 53 | xyz_flipped, pointnet2_utils.furthest_point_sample(xyz, self.npoint) 54 | ) 55 | .transpose(1, 2) 56 | .contiguous() 57 | if self.npoint is not None 58 | else None 59 | ) 60 | 61 | for i in range(len(self.groupers)): 62 | new_features = self.groupers[i]( 63 | xyz, new_xyz, features 64 | ) # (B, C, npoint, nsample) 65 | 66 | new_features = self.mlps[i](new_features) # (B, mlp[-1], npoint, nsample) 67 | new_features = F.max_pool2d( 68 | new_features, kernel_size=[1, new_features.size(3)] 69 | ) # (B, mlp[-1], npoint, 1) 70 | new_features = new_features.squeeze(-1) # (B, mlp[-1], npoint) 71 | 72 | new_features_list.append(new_features) 73 | 74 | return new_xyz, torch.cat(new_features_list, dim=1) 75 | 76 | 77 | class PointnetSAModuleMSG(_PointnetSAModuleBase): 78 | r"""Pointnet set abstrction layer with multiscale grouping 79 | 80 | Parameters 81 | ---------- 82 | npoint : int 83 | Number of features 84 | radii : list of float32 85 | list of radii to group with 86 | nsamples : list of int32 87 | Number of samples in each ball query 88 | mlps : list of list of int32 89 | Spec of the pointnet before the global max_pool for each scale 90 | bn : bool 91 | Use batchnorm 92 | """ 93 | 94 | def __init__(self, npoint, radii, nsamples, mlps, bn=True, use_xyz=True): 95 | # type: (PointnetSAModuleMSG, int, List[float], List[int], List[List[int]], bool, bool) -> None 96 | super(PointnetSAModuleMSG, self).__init__() 97 | 98 | assert len(radii) == len(nsamples) == len(mlps) 99 | 100 | self.npoint = npoint 101 | self.groupers = nn.ModuleList() 102 | self.mlps = nn.ModuleList() 103 | for i in range(len(radii)): 104 | radius = radii[i] 105 | nsample = nsamples[i] 106 | self.groupers.append( 107 | pointnet2_utils.QueryAndGroup(radius, nsample, use_xyz=use_xyz) 108 | if npoint is not None 109 | else pointnet2_utils.GroupAll(use_xyz) 110 | ) 111 | mlp_spec = mlps[i] 112 | if use_xyz: 113 | mlp_spec[0] += 3 114 | 115 | self.mlps.append(build_shared_mlp(mlp_spec, bn)) 116 | 117 | 118 | class PointnetSAModule(PointnetSAModuleMSG): 119 | r"""Pointnet set abstrction layer 120 | 121 | Parameters 122 | ---------- 123 | npoint : int 124 | Number of features 125 | radius : float 126 | Radius of ball 127 | nsample : int 128 | Number of samples in the ball query 129 | mlp : list 130 | Spec of the pointnet before the global max_pool 131 | bn : bool 132 | Use batchnorm 133 | """ 134 | 135 | def __init__( 136 | self, mlp, npoint=None, radius=None, nsample=None, bn=True, use_xyz=True 137 | ): 138 | # type: (PointnetSAModule, List[int], int, float, int, bool, bool) -> None 139 | super(PointnetSAModule, self).__init__( 140 | mlps=[mlp], 141 | npoint=npoint, 142 | radii=[radius], 143 | nsamples=[nsample], 144 | bn=bn, 145 | use_xyz=use_xyz, 146 | ) 147 | 148 | 149 | class PointnetFPModule(nn.Module): 150 | r"""Propigates the features of one set to another 151 | 152 | Parameters 153 | ---------- 154 | mlp : list 155 | Pointnet module parameters 156 | bn : bool 157 | Use batchnorm 158 | """ 159 | 160 | def __init__(self, mlp, bn=True): 161 | # type: (PointnetFPModule, List[int], bool) -> None 162 | super(PointnetFPModule, self).__init__() 163 | self.mlp = build_shared_mlp(mlp, bn=bn) 164 | 165 | def forward(self, unknown, known, unknow_feats, known_feats): 166 | # type: (PointnetFPModule, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor) -> torch.Tensor 167 | r""" 168 | Parameters 169 | ---------- 170 | unknown : torch.Tensor 171 | (B, n, 3) tensor of the xyz positions of the unknown features 172 | known : torch.Tensor 173 | (B, m, 3) tensor of the xyz positions of the known features 174 | unknow_feats : torch.Tensor 175 | (B, C1, n) tensor of the features to be propigated to 176 | known_feats : torch.Tensor 177 | (B, C2, m) tensor of features to be propigated 178 | 179 | Returns 180 | ------- 181 | new_features : torch.Tensor 182 | (B, mlp[-1], n) tensor of the features of the unknown features 183 | """ 184 | 185 | if known is not None: 186 | dist, idx = pointnet2_utils.three_nn(unknown, known) 187 | dist_recip = 1.0 / (dist + 1e-8) 188 | norm = torch.sum(dist_recip, dim=2, keepdim=True) 189 | weight = dist_recip / norm 190 | 191 | interpolated_feats = pointnet2_utils.three_interpolate( 192 | known_feats, idx, weight 193 | ) 194 | else: 195 | interpolated_feats = known_feats.expand( 196 | *(known_feats.size()[0:2] + [unknown.size(1)]) 197 | ) 198 | 199 | if unknow_feats is not None: 200 | new_features = torch.cat( 201 | [interpolated_feats, unknow_feats], dim=1 202 | ) # (B, C2 + C1, n) 203 | else: 204 | new_features = interpolated_feats 205 | 206 | new_features = new_features.unsqueeze(-1) 207 | new_features = self.mlp(new_features) 208 | 209 | return new_features.squeeze(-1) 210 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/lib.linux-x86_64-3.7/pointnet2_ops/pointnet2_utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import warnings 4 | from torch.autograd import Function 5 | from typing import * 6 | 7 | try: 8 | import pointnet2_ops._ext as _ext 9 | except ImportError: 10 | from torch.utils.cpp_extension import load 11 | import glob 12 | import os.path as osp 13 | import os 14 | 15 | warnings.warn("Unable to load pointnet2_ops cpp extension. JIT Compiling.") 16 | 17 | _ext_src_root = osp.join(osp.dirname(__file__), "_ext-src") 18 | _ext_sources = glob.glob(osp.join(_ext_src_root, "src", "*.cpp")) + glob.glob( 19 | osp.join(_ext_src_root, "src", "*.cu") 20 | ) 21 | _ext_headers = glob.glob(osp.join(_ext_src_root, "include", "*")) 22 | 23 | os.environ["TORCH_CUDA_ARCH_LIST"] = "3.7+PTX;5.0;6.0;6.1;6.2;7.0;7.5" 24 | _ext = load( 25 | "_ext", 26 | sources=_ext_sources, 27 | extra_include_paths=[osp.join(_ext_src_root, "include")], 28 | extra_cflags=["-O3"], 29 | extra_cuda_cflags=["-O3", "-Xfatbin", "-compress-all"], 30 | with_cuda=True, 31 | ) 32 | 33 | 34 | class FurthestPointSampling(Function): 35 | @staticmethod 36 | def forward(ctx, xyz, npoint): 37 | # type: (Any, torch.Tensor, int) -> torch.Tensor 38 | r""" 39 | Uses iterative furthest point sampling to select a set of npoint features that have the largest 40 | minimum distance 41 | 42 | Parameters 43 | ---------- 44 | xyz : torch.Tensor 45 | (B, N, 3) tensor where N > npoint 46 | npoint : int32 47 | number of features in the sampled set 48 | 49 | Returns 50 | ------- 51 | torch.Tensor 52 | (B, npoint) tensor containing the set 53 | """ 54 | out = _ext.furthest_point_sampling(xyz, npoint) 55 | 56 | ctx.mark_non_differentiable(out) 57 | 58 | return out 59 | 60 | @staticmethod 61 | def backward(ctx, grad_out): 62 | return () 63 | 64 | 65 | furthest_point_sample = FurthestPointSampling.apply 66 | 67 | 68 | class GatherOperation(Function): 69 | @staticmethod 70 | def forward(ctx, features, idx): 71 | # type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor 72 | r""" 73 | 74 | Parameters 75 | ---------- 76 | features : torch.Tensor 77 | (B, C, N) tensor 78 | 79 | idx : torch.Tensor 80 | (B, npoint) tensor of the features to gather 81 | 82 | Returns 83 | ------- 84 | torch.Tensor 85 | (B, C, npoint) tensor 86 | """ 87 | 88 | ctx.save_for_backward(idx, features) 89 | 90 | return _ext.gather_points(features, idx) 91 | 92 | @staticmethod 93 | def backward(ctx, grad_out): 94 | idx, features = ctx.saved_tensors 95 | N = features.size(2) 96 | 97 | grad_features = _ext.gather_points_grad(grad_out.contiguous(), idx, N) 98 | return grad_features, None 99 | 100 | 101 | gather_operation = GatherOperation.apply 102 | 103 | 104 | class ThreeNN(Function): 105 | @staticmethod 106 | def forward(ctx, unknown, known): 107 | # type: (Any, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor] 108 | r""" 109 | Find the three nearest neighbors of unknown in known 110 | Parameters 111 | ---------- 112 | unknown : torch.Tensor 113 | (B, n, 3) tensor of known features 114 | known : torch.Tensor 115 | (B, m, 3) tensor of unknown features 116 | 117 | Returns 118 | ------- 119 | dist : torch.Tensor 120 | (B, n, 3) l2 distance to the three nearest neighbors 121 | idx : torch.Tensor 122 | (B, n, 3) index of 3 nearest neighbors 123 | """ 124 | dist2, idx = _ext.three_nn(unknown, known) 125 | dist = torch.sqrt(dist2) 126 | 127 | ctx.mark_non_differentiable(dist, idx) 128 | 129 | return dist, idx 130 | 131 | @staticmethod 132 | def backward(ctx, grad_dist, grad_idx): 133 | return () 134 | 135 | 136 | three_nn = ThreeNN.apply 137 | 138 | 139 | class ThreeInterpolate(Function): 140 | @staticmethod 141 | def forward(ctx, features, idx, weight): 142 | # type(Any, torch.Tensor, torch.Tensor, torch.Tensor) -> Torch.Tensor 143 | r""" 144 | Performs weight linear interpolation on 3 features 145 | Parameters 146 | ---------- 147 | features : torch.Tensor 148 | (B, c, m) Features descriptors to be interpolated from 149 | idx : torch.Tensor 150 | (B, n, 3) three nearest neighbors of the target features in features 151 | weight : torch.Tensor 152 | (B, n, 3) weights 153 | 154 | Returns 155 | ------- 156 | torch.Tensor 157 | (B, c, n) tensor of the interpolated features 158 | """ 159 | ctx.save_for_backward(idx, weight, features) 160 | 161 | return _ext.three_interpolate(features, idx, weight) 162 | 163 | @staticmethod 164 | def backward(ctx, grad_out): 165 | # type: (Any, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor] 166 | r""" 167 | Parameters 168 | ---------- 169 | grad_out : torch.Tensor 170 | (B, c, n) tensor with gradients of ouputs 171 | 172 | Returns 173 | ------- 174 | grad_features : torch.Tensor 175 | (B, c, m) tensor with gradients of features 176 | 177 | None 178 | 179 | None 180 | """ 181 | idx, weight, features = ctx.saved_tensors 182 | m = features.size(2) 183 | 184 | grad_features = _ext.three_interpolate_grad( 185 | grad_out.contiguous(), idx, weight, m 186 | ) 187 | 188 | return grad_features, torch.zeros_like(idx), torch.zeros_like(weight) 189 | 190 | 191 | three_interpolate = ThreeInterpolate.apply 192 | 193 | 194 | class GroupingOperation(Function): 195 | @staticmethod 196 | def forward(ctx, features, idx): 197 | # type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor 198 | r""" 199 | 200 | Parameters 201 | ---------- 202 | features : torch.Tensor 203 | (B, C, N) tensor of features to group 204 | idx : torch.Tensor 205 | (B, npoint, nsample) tensor containing the indicies of features to group with 206 | 207 | Returns 208 | ------- 209 | torch.Tensor 210 | (B, C, npoint, nsample) tensor 211 | """ 212 | ctx.save_for_backward(idx, features) 213 | 214 | return _ext.group_points(features, idx) 215 | 216 | @staticmethod 217 | def backward(ctx, grad_out): 218 | # type: (Any, torch.tensor) -> Tuple[torch.Tensor, torch.Tensor] 219 | r""" 220 | 221 | Parameters 222 | ---------- 223 | grad_out : torch.Tensor 224 | (B, C, npoint, nsample) tensor of the gradients of the output from forward 225 | 226 | Returns 227 | ------- 228 | torch.Tensor 229 | (B, C, N) gradient of the features 230 | None 231 | """ 232 | idx, features = ctx.saved_tensors 233 | N = features.size(2) 234 | 235 | grad_features = _ext.group_points_grad(grad_out.contiguous(), idx, N) 236 | 237 | return grad_features, torch.zeros_like(idx) 238 | 239 | 240 | grouping_operation = GroupingOperation.apply 241 | 242 | 243 | class BallQuery(Function): 244 | @staticmethod 245 | def forward(ctx, radius, nsample, xyz, new_xyz): 246 | # type: (Any, float, int, torch.Tensor, torch.Tensor) -> torch.Tensor 247 | r""" 248 | 249 | Parameters 250 | ---------- 251 | radius : float 252 | radius of the balls 253 | nsample : int 254 | maximum number of features in the balls 255 | xyz : torch.Tensor 256 | (B, N, 3) xyz coordinates of the features 257 | new_xyz : torch.Tensor 258 | (B, npoint, 3) centers of the ball query 259 | 260 | Returns 261 | ------- 262 | torch.Tensor 263 | (B, npoint, nsample) tensor with the indicies of the features that form the query balls 264 | """ 265 | output = _ext.ball_query(new_xyz, xyz, radius, nsample) 266 | 267 | ctx.mark_non_differentiable(output) 268 | 269 | return output 270 | 271 | @staticmethod 272 | def backward(ctx, grad_out): 273 | return () 274 | 275 | 276 | ball_query = BallQuery.apply 277 | 278 | 279 | class QueryAndGroup(nn.Module): 280 | r""" 281 | Groups with a ball query of radius 282 | 283 | Parameters 284 | --------- 285 | radius : float32 286 | Radius of ball 287 | nsample : int32 288 | Maximum number of features to gather in the ball 289 | """ 290 | 291 | def __init__(self, radius, nsample, use_xyz=True): 292 | # type: (QueryAndGroup, float, int, bool) -> None 293 | super(QueryAndGroup, self).__init__() 294 | self.radius, self.nsample, self.use_xyz = radius, nsample, use_xyz 295 | 296 | def forward(self, xyz, new_xyz, features=None): 297 | # type: (QueryAndGroup, torch.Tensor. torch.Tensor, torch.Tensor) -> Tuple[Torch.Tensor] 298 | r""" 299 | Parameters 300 | ---------- 301 | xyz : torch.Tensor 302 | xyz coordinates of the features (B, N, 3) 303 | new_xyz : torch.Tensor 304 | centriods (B, npoint, 3) 305 | features : torch.Tensor 306 | Descriptors of the features (B, C, N) 307 | 308 | Returns 309 | ------- 310 | new_features : torch.Tensor 311 | (B, 3 + C, npoint, nsample) tensor 312 | """ 313 | 314 | idx = ball_query(self.radius, self.nsample, xyz, new_xyz) 315 | xyz_trans = xyz.transpose(1, 2).contiguous() 316 | grouped_xyz = grouping_operation(xyz_trans, idx) # (B, 3, npoint, nsample) 317 | grouped_xyz -= new_xyz.transpose(1, 2).unsqueeze(-1) 318 | 319 | if features is not None: 320 | grouped_features = grouping_operation(features, idx) 321 | if self.use_xyz: 322 | new_features = torch.cat( 323 | [grouped_xyz, grouped_features], dim=1 324 | ) # (B, C + 3, npoint, nsample) 325 | else: 326 | new_features = grouped_features 327 | else: 328 | assert ( 329 | self.use_xyz 330 | ), "Cannot have not features and not use xyz as a feature!" 331 | new_features = grouped_xyz 332 | 333 | return new_features 334 | 335 | 336 | class GroupAll(nn.Module): 337 | r""" 338 | Groups all features 339 | 340 | Parameters 341 | --------- 342 | """ 343 | 344 | def __init__(self, use_xyz=True): 345 | # type: (GroupAll, bool) -> None 346 | super(GroupAll, self).__init__() 347 | self.use_xyz = use_xyz 348 | 349 | def forward(self, xyz, new_xyz, features=None): 350 | # type: (GroupAll, torch.Tensor, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor] 351 | r""" 352 | Parameters 353 | ---------- 354 | xyz : torch.Tensor 355 | xyz coordinates of the features (B, N, 3) 356 | new_xyz : torch.Tensor 357 | Ignored 358 | features : torch.Tensor 359 | Descriptors of the features (B, C, N) 360 | 361 | Returns 362 | ------- 363 | new_features : torch.Tensor 364 | (B, C + 3, 1, N) tensor 365 | """ 366 | 367 | grouped_xyz = xyz.transpose(1, 2).unsqueeze(2) 368 | if features is not None: 369 | grouped_features = features.unsqueeze(2) 370 | if self.use_xyz: 371 | new_features = torch.cat( 372 | [grouped_xyz, grouped_features], dim=1 373 | ) # (B, 3 + C, 1, N) 374 | else: 375 | new_features = grouped_features 376 | else: 377 | new_features = grouped_xyz 378 | 379 | return new_features 380 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query_gpu.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/ball_query_gpu.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/bindings.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/bindings.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points_gpu.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/group_points_gpu.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate_gpu.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/interpolate_gpu.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling_gpu.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LongerVision/PointStack/9fe227bc654008452283a075d50f7406a66016be/pointnet2_ops_lib/build/temp.linux-x86_64-3.7/pointnet2_ops/_ext-src/src/sampling_gpu.o -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: pointnet2-ops 3 | Version: 3.0.0 4 | Summary: UNKNOWN 5 | Author: Erik Wijmans 6 | License: UNKNOWN 7 | Platform: UNKNOWN 8 | 9 | UNKNOWN 10 | 11 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | MANIFEST.in 2 | setup.py 3 | pointnet2_ops/__init__.py 4 | pointnet2_ops/_version.py 5 | pointnet2_ops/pointnet2_modules.py 6 | pointnet2_ops/pointnet2_utils.py 7 | pointnet2_ops.egg-info/PKG-INFO 8 | pointnet2_ops.egg-info/SOURCES.txt 9 | pointnet2_ops.egg-info/dependency_links.txt 10 | pointnet2_ops.egg-info/requires.txt 11 | pointnet2_ops.egg-info/top_level.txt 12 | pointnet2_ops/_ext-src/include/ball_query.h 13 | pointnet2_ops/_ext-src/include/cuda_utils.h 14 | pointnet2_ops/_ext-src/include/group_points.h 15 | pointnet2_ops/_ext-src/include/interpolate.h 16 | pointnet2_ops/_ext-src/include/sampling.h 17 | pointnet2_ops/_ext-src/include/utils.h 18 | pointnet2_ops/_ext-src/src/ball_query.cpp 19 | pointnet2_ops/_ext-src/src/ball_query_gpu.cu 20 | pointnet2_ops/_ext-src/src/bindings.cpp 21 | pointnet2_ops/_ext-src/src/group_points.cpp 22 | pointnet2_ops/_ext-src/src/group_points_gpu.cu 23 | pointnet2_ops/_ext-src/src/interpolate.cpp 24 | pointnet2_ops/_ext-src/src/interpolate_gpu.cu 25 | pointnet2_ops/_ext-src/src/sampling.cpp 26 | pointnet2_ops/_ext-src/src/sampling_gpu.cu -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | torch>=1.4 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pointnet2_ops 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/__init__.py: -------------------------------------------------------------------------------- 1 | import pointnet2_ops.pointnet2_modules 2 | import pointnet2_ops.pointnet2_utils 3 | from pointnet2_ops._version import __version__ 4 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/ball_query.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius, 5 | const int nsample); 6 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/cuda_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _CUDA_UTILS_H 2 | #define _CUDA_UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #define TOTAL_THREADS 512 14 | 15 | inline int opt_n_threads(int work_size) { 16 | const int pow_2 = std::log(static_cast(work_size)) / std::log(2.0); 17 | 18 | return max(min(1 << pow_2, TOTAL_THREADS), 1); 19 | } 20 | 21 | inline dim3 opt_block_config(int x, int y) { 22 | const int x_threads = opt_n_threads(x); 23 | const int y_threads = 24 | max(min(opt_n_threads(y), TOTAL_THREADS / x_threads), 1); 25 | dim3 block_config(x_threads, y_threads, 1); 26 | 27 | return block_config; 28 | } 29 | 30 | #define CUDA_CHECK_ERRORS() \ 31 | do { \ 32 | cudaError_t err = cudaGetLastError(); \ 33 | if (cudaSuccess != err) { \ 34 | fprintf(stderr, "CUDA kernel failed : %s\n%s at L:%d in %s\n", \ 35 | cudaGetErrorString(err), __PRETTY_FUNCTION__, __LINE__, \ 36 | __FILE__); \ 37 | exit(-1); \ 38 | } \ 39 | } while (0) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/group_points.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor group_points(at::Tensor points, at::Tensor idx); 5 | at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n); 6 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/interpolate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | std::vector three_nn(at::Tensor unknowns, at::Tensor knows); 7 | at::Tensor three_interpolate(at::Tensor points, at::Tensor idx, 8 | at::Tensor weight); 9 | at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx, 10 | at::Tensor weight, const int m); 11 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/sampling.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | at::Tensor gather_points(at::Tensor points, at::Tensor idx); 5 | at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx, const int n); 6 | at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples); 7 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/include/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define CHECK_CUDA(x) \ 6 | do { \ 7 | AT_ASSERT(x.is_cuda(), #x " must be a CUDA tensor"); \ 8 | } while (0) 9 | 10 | #define CHECK_CONTIGUOUS(x) \ 11 | do { \ 12 | AT_ASSERT(x.is_contiguous(), #x " must be a contiguous tensor"); \ 13 | } while (0) 14 | 15 | #define CHECK_IS_INT(x) \ 16 | do { \ 17 | AT_ASSERT(x.scalar_type() == at::ScalarType::Int, \ 18 | #x " must be an int tensor"); \ 19 | } while (0) 20 | 21 | #define CHECK_IS_FLOAT(x) \ 22 | do { \ 23 | AT_ASSERT(x.scalar_type() == at::ScalarType::Float, \ 24 | #x " must be a float tensor"); \ 25 | } while (0) 26 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/ball_query.cpp: -------------------------------------------------------------------------------- 1 | #include "ball_query.h" 2 | #include "utils.h" 3 | 4 | void query_ball_point_kernel_wrapper(int b, int n, int m, float radius, 5 | int nsample, const float *new_xyz, 6 | const float *xyz, int *idx); 7 | 8 | at::Tensor ball_query(at::Tensor new_xyz, at::Tensor xyz, const float radius, 9 | const int nsample) { 10 | CHECK_CONTIGUOUS(new_xyz); 11 | CHECK_CONTIGUOUS(xyz); 12 | CHECK_IS_FLOAT(new_xyz); 13 | CHECK_IS_FLOAT(xyz); 14 | 15 | if (new_xyz.is_cuda()) { 16 | CHECK_CUDA(xyz); 17 | } 18 | 19 | at::Tensor idx = 20 | torch::zeros({new_xyz.size(0), new_xyz.size(1), nsample}, 21 | at::device(new_xyz.device()).dtype(at::ScalarType::Int)); 22 | 23 | if (new_xyz.is_cuda()) { 24 | query_ball_point_kernel_wrapper(xyz.size(0), xyz.size(1), new_xyz.size(1), 25 | radius, nsample, new_xyz.data_ptr(), 26 | xyz.data_ptr(), idx.data_ptr()); 27 | } else { 28 | AT_ASSERT(false, "CPU not supported"); 29 | } 30 | 31 | return idx; 32 | } 33 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/ball_query_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cuda_utils.h" 6 | 7 | // input: new_xyz(b, m, 3) xyz(b, n, 3) 8 | // output: idx(b, m, nsample) 9 | __global__ void query_ball_point_kernel(int b, int n, int m, float radius, 10 | int nsample, 11 | const float *__restrict__ new_xyz, 12 | const float *__restrict__ xyz, 13 | int *__restrict__ idx) { 14 | int batch_index = blockIdx.x; 15 | xyz += batch_index * n * 3; 16 | new_xyz += batch_index * m * 3; 17 | idx += m * nsample * batch_index; 18 | 19 | int index = threadIdx.x; 20 | int stride = blockDim.x; 21 | 22 | float radius2 = radius * radius; 23 | for (int j = index; j < m; j += stride) { 24 | float new_x = new_xyz[j * 3 + 0]; 25 | float new_y = new_xyz[j * 3 + 1]; 26 | float new_z = new_xyz[j * 3 + 2]; 27 | for (int k = 0, cnt = 0; k < n && cnt < nsample; ++k) { 28 | float x = xyz[k * 3 + 0]; 29 | float y = xyz[k * 3 + 1]; 30 | float z = xyz[k * 3 + 2]; 31 | float d2 = (new_x - x) * (new_x - x) + (new_y - y) * (new_y - y) + 32 | (new_z - z) * (new_z - z); 33 | if (d2 < radius2) { 34 | if (cnt == 0) { 35 | for (int l = 0; l < nsample; ++l) { 36 | idx[j * nsample + l] = k; 37 | } 38 | } 39 | idx[j * nsample + cnt] = k; 40 | ++cnt; 41 | } 42 | } 43 | } 44 | } 45 | 46 | void query_ball_point_kernel_wrapper(int b, int n, int m, float radius, 47 | int nsample, const float *new_xyz, 48 | const float *xyz, int *idx) { 49 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 50 | query_ball_point_kernel<<>>( 51 | b, n, m, radius, nsample, new_xyz, xyz, idx); 52 | 53 | CUDA_CHECK_ERRORS(); 54 | } 55 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/bindings.cpp: -------------------------------------------------------------------------------- 1 | #include "ball_query.h" 2 | #include "group_points.h" 3 | #include "interpolate.h" 4 | #include "sampling.h" 5 | 6 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 7 | m.def("gather_points", &gather_points); 8 | m.def("gather_points_grad", &gather_points_grad); 9 | m.def("furthest_point_sampling", &furthest_point_sampling); 10 | 11 | m.def("three_nn", &three_nn); 12 | m.def("three_interpolate", &three_interpolate); 13 | m.def("three_interpolate_grad", &three_interpolate_grad); 14 | 15 | m.def("ball_query", &ball_query); 16 | 17 | m.def("group_points", &group_points); 18 | m.def("group_points_grad", &group_points_grad); 19 | } 20 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/group_points.cpp: -------------------------------------------------------------------------------- 1 | #include "group_points.h" 2 | #include "utils.h" 3 | 4 | void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample, 5 | const float *points, const int *idx, 6 | float *out); 7 | 8 | void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 9 | int nsample, const float *grad_out, 10 | const int *idx, float *grad_points); 11 | 12 | at::Tensor group_points(at::Tensor points, at::Tensor idx) { 13 | CHECK_CONTIGUOUS(points); 14 | CHECK_CONTIGUOUS(idx); 15 | CHECK_IS_FLOAT(points); 16 | CHECK_IS_INT(idx); 17 | 18 | if (points.is_cuda()) { 19 | CHECK_CUDA(idx); 20 | } 21 | 22 | at::Tensor output = 23 | torch::zeros({points.size(0), points.size(1), idx.size(1), idx.size(2)}, 24 | at::device(points.device()).dtype(at::ScalarType::Float)); 25 | 26 | if (points.is_cuda()) { 27 | group_points_kernel_wrapper(points.size(0), points.size(1), points.size(2), 28 | idx.size(1), idx.size(2), 29 | points.data_ptr(), idx.data_ptr(), 30 | output.data_ptr()); 31 | } else { 32 | AT_ASSERT(false, "CPU not supported"); 33 | } 34 | 35 | return output; 36 | } 37 | 38 | at::Tensor group_points_grad(at::Tensor grad_out, at::Tensor idx, const int n) { 39 | CHECK_CONTIGUOUS(grad_out); 40 | CHECK_CONTIGUOUS(idx); 41 | CHECK_IS_FLOAT(grad_out); 42 | CHECK_IS_INT(idx); 43 | 44 | if (grad_out.is_cuda()) { 45 | CHECK_CUDA(idx); 46 | } 47 | 48 | at::Tensor output = 49 | torch::zeros({grad_out.size(0), grad_out.size(1), n}, 50 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 51 | 52 | if (grad_out.is_cuda()) { 53 | group_points_grad_kernel_wrapper( 54 | grad_out.size(0), grad_out.size(1), n, idx.size(1), idx.size(2), 55 | grad_out.data_ptr(), idx.data_ptr(), 56 | output.data_ptr()); 57 | } else { 58 | AT_ASSERT(false, "CPU not supported"); 59 | } 60 | 61 | return output; 62 | } 63 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/group_points_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cuda_utils.h" 5 | 6 | // input: points(b, c, n) idx(b, npoints, nsample) 7 | // output: out(b, c, npoints, nsample) 8 | __global__ void group_points_kernel(int b, int c, int n, int npoints, 9 | int nsample, 10 | const float *__restrict__ points, 11 | const int *__restrict__ idx, 12 | float *__restrict__ out) { 13 | int batch_index = blockIdx.x; 14 | points += batch_index * n * c; 15 | idx += batch_index * npoints * nsample; 16 | out += batch_index * npoints * nsample * c; 17 | 18 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 19 | const int stride = blockDim.y * blockDim.x; 20 | for (int i = index; i < c * npoints; i += stride) { 21 | const int l = i / npoints; 22 | const int j = i % npoints; 23 | for (int k = 0; k < nsample; ++k) { 24 | int ii = idx[j * nsample + k]; 25 | out[(l * npoints + j) * nsample + k] = points[l * n + ii]; 26 | } 27 | } 28 | } 29 | 30 | void group_points_kernel_wrapper(int b, int c, int n, int npoints, int nsample, 31 | const float *points, const int *idx, 32 | float *out) { 33 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 34 | 35 | group_points_kernel<<>>( 36 | b, c, n, npoints, nsample, points, idx, out); 37 | 38 | CUDA_CHECK_ERRORS(); 39 | } 40 | 41 | // input: grad_out(b, c, npoints, nsample), idx(b, npoints, nsample) 42 | // output: grad_points(b, c, n) 43 | __global__ void group_points_grad_kernel(int b, int c, int n, int npoints, 44 | int nsample, 45 | const float *__restrict__ grad_out, 46 | const int *__restrict__ idx, 47 | float *__restrict__ grad_points) { 48 | int batch_index = blockIdx.x; 49 | grad_out += batch_index * npoints * nsample * c; 50 | idx += batch_index * npoints * nsample; 51 | grad_points += batch_index * n * c; 52 | 53 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 54 | const int stride = blockDim.y * blockDim.x; 55 | for (int i = index; i < c * npoints; i += stride) { 56 | const int l = i / npoints; 57 | const int j = i % npoints; 58 | for (int k = 0; k < nsample; ++k) { 59 | int ii = idx[j * nsample + k]; 60 | atomicAdd(grad_points + l * n + ii, 61 | grad_out[(l * npoints + j) * nsample + k]); 62 | } 63 | } 64 | } 65 | 66 | void group_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 67 | int nsample, const float *grad_out, 68 | const int *idx, float *grad_points) { 69 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 70 | 71 | group_points_grad_kernel<<>>( 72 | b, c, n, npoints, nsample, grad_out, idx, grad_points); 73 | 74 | CUDA_CHECK_ERRORS(); 75 | } 76 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/interpolate.cpp: -------------------------------------------------------------------------------- 1 | #include "interpolate.h" 2 | #include "utils.h" 3 | 4 | void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown, 5 | const float *known, float *dist2, int *idx); 6 | void three_interpolate_kernel_wrapper(int b, int c, int m, int n, 7 | const float *points, const int *idx, 8 | const float *weight, float *out); 9 | void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m, 10 | const float *grad_out, 11 | const int *idx, const float *weight, 12 | float *grad_points); 13 | 14 | std::vector three_nn(at::Tensor unknowns, at::Tensor knows) { 15 | CHECK_CONTIGUOUS(unknowns); 16 | CHECK_CONTIGUOUS(knows); 17 | CHECK_IS_FLOAT(unknowns); 18 | CHECK_IS_FLOAT(knows); 19 | 20 | if (unknowns.is_cuda()) { 21 | CHECK_CUDA(knows); 22 | } 23 | 24 | at::Tensor idx = 25 | torch::zeros({unknowns.size(0), unknowns.size(1), 3}, 26 | at::device(unknowns.device()).dtype(at::ScalarType::Int)); 27 | at::Tensor dist2 = 28 | torch::zeros({unknowns.size(0), unknowns.size(1), 3}, 29 | at::device(unknowns.device()).dtype(at::ScalarType::Float)); 30 | 31 | if (unknowns.is_cuda()) { 32 | three_nn_kernel_wrapper(unknowns.size(0), unknowns.size(1), knows.size(1), 33 | unknowns.data_ptr(), knows.data_ptr(), 34 | dist2.data_ptr(), idx.data_ptr()); 35 | } else { 36 | AT_ASSERT(false, "CPU not supported"); 37 | } 38 | 39 | return {dist2, idx}; 40 | } 41 | 42 | at::Tensor three_interpolate(at::Tensor points, at::Tensor idx, 43 | at::Tensor weight) { 44 | CHECK_CONTIGUOUS(points); 45 | CHECK_CONTIGUOUS(idx); 46 | CHECK_CONTIGUOUS(weight); 47 | CHECK_IS_FLOAT(points); 48 | CHECK_IS_INT(idx); 49 | CHECK_IS_FLOAT(weight); 50 | 51 | if (points.is_cuda()) { 52 | CHECK_CUDA(idx); 53 | CHECK_CUDA(weight); 54 | } 55 | 56 | at::Tensor output = 57 | torch::zeros({points.size(0), points.size(1), idx.size(1)}, 58 | at::device(points.device()).dtype(at::ScalarType::Float)); 59 | 60 | if (points.is_cuda()) { 61 | three_interpolate_kernel_wrapper( 62 | points.size(0), points.size(1), points.size(2), idx.size(1), 63 | points.data_ptr(), idx.data_ptr(), weight.data_ptr(), 64 | output.data_ptr()); 65 | } else { 66 | AT_ASSERT(false, "CPU not supported"); 67 | } 68 | 69 | return output; 70 | } 71 | at::Tensor three_interpolate_grad(at::Tensor grad_out, at::Tensor idx, 72 | at::Tensor weight, const int m) { 73 | CHECK_CONTIGUOUS(grad_out); 74 | CHECK_CONTIGUOUS(idx); 75 | CHECK_CONTIGUOUS(weight); 76 | CHECK_IS_FLOAT(grad_out); 77 | CHECK_IS_INT(idx); 78 | CHECK_IS_FLOAT(weight); 79 | 80 | if (grad_out.is_cuda()) { 81 | CHECK_CUDA(idx); 82 | CHECK_CUDA(weight); 83 | } 84 | 85 | at::Tensor output = 86 | torch::zeros({grad_out.size(0), grad_out.size(1), m}, 87 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 88 | 89 | if (grad_out.is_cuda()) { 90 | three_interpolate_grad_kernel_wrapper( 91 | grad_out.size(0), grad_out.size(1), grad_out.size(2), m, 92 | grad_out.data_ptr(), idx.data_ptr(), 93 | weight.data_ptr(), output.data_ptr()); 94 | } else { 95 | AT_ASSERT(false, "CPU not supported"); 96 | } 97 | 98 | return output; 99 | } 100 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/interpolate_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "cuda_utils.h" 6 | 7 | // input: unknown(b, n, 3) known(b, m, 3) 8 | // output: dist2(b, n, 3), idx(b, n, 3) 9 | __global__ void three_nn_kernel(int b, int n, int m, 10 | const float *__restrict__ unknown, 11 | const float *__restrict__ known, 12 | float *__restrict__ dist2, 13 | int *__restrict__ idx) { 14 | int batch_index = blockIdx.x; 15 | unknown += batch_index * n * 3; 16 | known += batch_index * m * 3; 17 | dist2 += batch_index * n * 3; 18 | idx += batch_index * n * 3; 19 | 20 | int index = threadIdx.x; 21 | int stride = blockDim.x; 22 | for (int j = index; j < n; j += stride) { 23 | float ux = unknown[j * 3 + 0]; 24 | float uy = unknown[j * 3 + 1]; 25 | float uz = unknown[j * 3 + 2]; 26 | 27 | double best1 = 1e40, best2 = 1e40, best3 = 1e40; 28 | int besti1 = 0, besti2 = 0, besti3 = 0; 29 | for (int k = 0; k < m; ++k) { 30 | float x = known[k * 3 + 0]; 31 | float y = known[k * 3 + 1]; 32 | float z = known[k * 3 + 2]; 33 | float d = (ux - x) * (ux - x) + (uy - y) * (uy - y) + (uz - z) * (uz - z); 34 | if (d < best1) { 35 | best3 = best2; 36 | besti3 = besti2; 37 | best2 = best1; 38 | besti2 = besti1; 39 | best1 = d; 40 | besti1 = k; 41 | } else if (d < best2) { 42 | best3 = best2; 43 | besti3 = besti2; 44 | best2 = d; 45 | besti2 = k; 46 | } else if (d < best3) { 47 | best3 = d; 48 | besti3 = k; 49 | } 50 | } 51 | dist2[j * 3 + 0] = best1; 52 | dist2[j * 3 + 1] = best2; 53 | dist2[j * 3 + 2] = best3; 54 | 55 | idx[j * 3 + 0] = besti1; 56 | idx[j * 3 + 1] = besti2; 57 | idx[j * 3 + 2] = besti3; 58 | } 59 | } 60 | 61 | void three_nn_kernel_wrapper(int b, int n, int m, const float *unknown, 62 | const float *known, float *dist2, int *idx) { 63 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 64 | three_nn_kernel<<>>(b, n, m, unknown, known, 65 | dist2, idx); 66 | 67 | CUDA_CHECK_ERRORS(); 68 | } 69 | 70 | // input: points(b, c, m), idx(b, n, 3), weight(b, n, 3) 71 | // output: out(b, c, n) 72 | __global__ void three_interpolate_kernel(int b, int c, int m, int n, 73 | const float *__restrict__ points, 74 | const int *__restrict__ idx, 75 | const float *__restrict__ weight, 76 | float *__restrict__ out) { 77 | int batch_index = blockIdx.x; 78 | points += batch_index * m * c; 79 | 80 | idx += batch_index * n * 3; 81 | weight += batch_index * n * 3; 82 | 83 | out += batch_index * n * c; 84 | 85 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 86 | const int stride = blockDim.y * blockDim.x; 87 | for (int i = index; i < c * n; i += stride) { 88 | const int l = i / n; 89 | const int j = i % n; 90 | float w1 = weight[j * 3 + 0]; 91 | float w2 = weight[j * 3 + 1]; 92 | float w3 = weight[j * 3 + 2]; 93 | 94 | int i1 = idx[j * 3 + 0]; 95 | int i2 = idx[j * 3 + 1]; 96 | int i3 = idx[j * 3 + 2]; 97 | 98 | out[i] = points[l * m + i1] * w1 + points[l * m + i2] * w2 + 99 | points[l * m + i3] * w3; 100 | } 101 | } 102 | 103 | void three_interpolate_kernel_wrapper(int b, int c, int m, int n, 104 | const float *points, const int *idx, 105 | const float *weight, float *out) { 106 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 107 | three_interpolate_kernel<<>>( 108 | b, c, m, n, points, idx, weight, out); 109 | 110 | CUDA_CHECK_ERRORS(); 111 | } 112 | 113 | // input: grad_out(b, c, n), idx(b, n, 3), weight(b, n, 3) 114 | // output: grad_points(b, c, m) 115 | 116 | __global__ void three_interpolate_grad_kernel( 117 | int b, int c, int n, int m, const float *__restrict__ grad_out, 118 | const int *__restrict__ idx, const float *__restrict__ weight, 119 | float *__restrict__ grad_points) { 120 | int batch_index = blockIdx.x; 121 | grad_out += batch_index * n * c; 122 | idx += batch_index * n * 3; 123 | weight += batch_index * n * 3; 124 | grad_points += batch_index * m * c; 125 | 126 | const int index = threadIdx.y * blockDim.x + threadIdx.x; 127 | const int stride = blockDim.y * blockDim.x; 128 | for (int i = index; i < c * n; i += stride) { 129 | const int l = i / n; 130 | const int j = i % n; 131 | float w1 = weight[j * 3 + 0]; 132 | float w2 = weight[j * 3 + 1]; 133 | float w3 = weight[j * 3 + 2]; 134 | 135 | int i1 = idx[j * 3 + 0]; 136 | int i2 = idx[j * 3 + 1]; 137 | int i3 = idx[j * 3 + 2]; 138 | 139 | atomicAdd(grad_points + l * m + i1, grad_out[i] * w1); 140 | atomicAdd(grad_points + l * m + i2, grad_out[i] * w2); 141 | atomicAdd(grad_points + l * m + i3, grad_out[i] * w3); 142 | } 143 | } 144 | 145 | void three_interpolate_grad_kernel_wrapper(int b, int c, int n, int m, 146 | const float *grad_out, 147 | const int *idx, const float *weight, 148 | float *grad_points) { 149 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 150 | three_interpolate_grad_kernel<<>>( 151 | b, c, n, m, grad_out, idx, weight, grad_points); 152 | 153 | CUDA_CHECK_ERRORS(); 154 | } 155 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/sampling.cpp: -------------------------------------------------------------------------------- 1 | #include "sampling.h" 2 | #include "utils.h" 3 | 4 | void gather_points_kernel_wrapper(int b, int c, int n, int npoints, 5 | const float *points, const int *idx, 6 | float *out); 7 | void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 8 | const float *grad_out, const int *idx, 9 | float *grad_points); 10 | 11 | void furthest_point_sampling_kernel_wrapper(int b, int n, int m, 12 | const float *dataset, float *temp, 13 | int *idxs); 14 | 15 | at::Tensor gather_points(at::Tensor points, at::Tensor idx) { 16 | CHECK_CONTIGUOUS(points); 17 | CHECK_CONTIGUOUS(idx); 18 | CHECK_IS_FLOAT(points); 19 | CHECK_IS_INT(idx); 20 | 21 | if (points.is_cuda()) { 22 | CHECK_CUDA(idx); 23 | } 24 | 25 | at::Tensor output = 26 | torch::zeros({points.size(0), points.size(1), idx.size(1)}, 27 | at::device(points.device()).dtype(at::ScalarType::Float)); 28 | 29 | if (points.is_cuda()) { 30 | gather_points_kernel_wrapper(points.size(0), points.size(1), points.size(2), 31 | idx.size(1), points.data_ptr(), 32 | idx.data_ptr(), output.data_ptr()); 33 | } else { 34 | AT_ASSERT(false, "CPU not supported"); 35 | } 36 | 37 | return output; 38 | } 39 | 40 | at::Tensor gather_points_grad(at::Tensor grad_out, at::Tensor idx, 41 | const int n) { 42 | CHECK_CONTIGUOUS(grad_out); 43 | CHECK_CONTIGUOUS(idx); 44 | CHECK_IS_FLOAT(grad_out); 45 | CHECK_IS_INT(idx); 46 | 47 | if (grad_out.is_cuda()) { 48 | CHECK_CUDA(idx); 49 | } 50 | 51 | at::Tensor output = 52 | torch::zeros({grad_out.size(0), grad_out.size(1), n}, 53 | at::device(grad_out.device()).dtype(at::ScalarType::Float)); 54 | 55 | if (grad_out.is_cuda()) { 56 | gather_points_grad_kernel_wrapper(grad_out.size(0), grad_out.size(1), n, 57 | idx.size(1), grad_out.data_ptr(), 58 | idx.data_ptr(), 59 | output.data_ptr()); 60 | } else { 61 | AT_ASSERT(false, "CPU not supported"); 62 | } 63 | 64 | return output; 65 | } 66 | at::Tensor furthest_point_sampling(at::Tensor points, const int nsamples) { 67 | CHECK_CONTIGUOUS(points); 68 | CHECK_IS_FLOAT(points); 69 | 70 | at::Tensor output = 71 | torch::zeros({points.size(0), nsamples}, 72 | at::device(points.device()).dtype(at::ScalarType::Int)); 73 | 74 | at::Tensor tmp = 75 | torch::full({points.size(0), points.size(1)}, 1e10, 76 | at::device(points.device()).dtype(at::ScalarType::Float)); 77 | 78 | if (points.is_cuda()) { 79 | furthest_point_sampling_kernel_wrapper( 80 | points.size(0), points.size(1), nsamples, points.data_ptr(), 81 | tmp.data_ptr(), output.data_ptr()); 82 | } else { 83 | AT_ASSERT(false, "CPU not supported"); 84 | } 85 | 86 | return output; 87 | } 88 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_ext-src/src/sampling_gpu.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "cuda_utils.h" 5 | 6 | // input: points(b, c, n) idx(b, m) 7 | // output: out(b, c, m) 8 | __global__ void gather_points_kernel(int b, int c, int n, int m, 9 | const float *__restrict__ points, 10 | const int *__restrict__ idx, 11 | float *__restrict__ out) { 12 | for (int i = blockIdx.x; i < b; i += gridDim.x) { 13 | for (int l = blockIdx.y; l < c; l += gridDim.y) { 14 | for (int j = threadIdx.x; j < m; j += blockDim.x) { 15 | int a = idx[i * m + j]; 16 | out[(i * c + l) * m + j] = points[(i * c + l) * n + a]; 17 | } 18 | } 19 | } 20 | } 21 | 22 | void gather_points_kernel_wrapper(int b, int c, int n, int npoints, 23 | const float *points, const int *idx, 24 | float *out) { 25 | gather_points_kernel<<>>(b, c, n, npoints, 27 | points, idx, out); 28 | 29 | CUDA_CHECK_ERRORS(); 30 | } 31 | 32 | // input: grad_out(b, c, m) idx(b, m) 33 | // output: grad_points(b, c, n) 34 | __global__ void gather_points_grad_kernel(int b, int c, int n, int m, 35 | const float *__restrict__ grad_out, 36 | const int *__restrict__ idx, 37 | float *__restrict__ grad_points) { 38 | for (int i = blockIdx.x; i < b; i += gridDim.x) { 39 | for (int l = blockIdx.y; l < c; l += gridDim.y) { 40 | for (int j = threadIdx.x; j < m; j += blockDim.x) { 41 | int a = idx[i * m + j]; 42 | atomicAdd(grad_points + (i * c + l) * n + a, 43 | grad_out[(i * c + l) * m + j]); 44 | } 45 | } 46 | } 47 | } 48 | 49 | void gather_points_grad_kernel_wrapper(int b, int c, int n, int npoints, 50 | const float *grad_out, const int *idx, 51 | float *grad_points) { 52 | gather_points_grad_kernel<<>>( 54 | b, c, n, npoints, grad_out, idx, grad_points); 55 | 56 | CUDA_CHECK_ERRORS(); 57 | } 58 | 59 | __device__ void __update(float *__restrict__ dists, int *__restrict__ dists_i, 60 | int idx1, int idx2) { 61 | const float v1 = dists[idx1], v2 = dists[idx2]; 62 | const int i1 = dists_i[idx1], i2 = dists_i[idx2]; 63 | dists[idx1] = max(v1, v2); 64 | dists_i[idx1] = v2 > v1 ? i2 : i1; 65 | } 66 | 67 | // Input dataset: (b, n, 3), tmp: (b, n) 68 | // Ouput idxs (b, m) 69 | template 70 | __global__ void furthest_point_sampling_kernel( 71 | int b, int n, int m, const float *__restrict__ dataset, 72 | float *__restrict__ temp, int *__restrict__ idxs) { 73 | if (m <= 0) return; 74 | __shared__ float dists[block_size]; 75 | __shared__ int dists_i[block_size]; 76 | 77 | int batch_index = blockIdx.x; 78 | dataset += batch_index * n * 3; 79 | temp += batch_index * n; 80 | idxs += batch_index * m; 81 | 82 | int tid = threadIdx.x; 83 | const int stride = block_size; 84 | 85 | int old = 0; 86 | if (threadIdx.x == 0) idxs[0] = old; 87 | 88 | __syncthreads(); 89 | for (int j = 1; j < m; j++) { 90 | int besti = 0; 91 | float best = -1; 92 | float x1 = dataset[old * 3 + 0]; 93 | float y1 = dataset[old * 3 + 1]; 94 | float z1 = dataset[old * 3 + 2]; 95 | for (int k = tid; k < n; k += stride) { 96 | float x2, y2, z2; 97 | x2 = dataset[k * 3 + 0]; 98 | y2 = dataset[k * 3 + 1]; 99 | z2 = dataset[k * 3 + 2]; 100 | float mag = (x2 * x2) + (y2 * y2) + (z2 * z2); 101 | if (mag <= 1e-3) continue; 102 | 103 | float d = 104 | (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1); 105 | 106 | float d2 = min(d, temp[k]); 107 | temp[k] = d2; 108 | besti = d2 > best ? k : besti; 109 | best = d2 > best ? d2 : best; 110 | } 111 | dists[tid] = best; 112 | dists_i[tid] = besti; 113 | __syncthreads(); 114 | 115 | if (block_size >= 512) { 116 | if (tid < 256) { 117 | __update(dists, dists_i, tid, tid + 256); 118 | } 119 | __syncthreads(); 120 | } 121 | if (block_size >= 256) { 122 | if (tid < 128) { 123 | __update(dists, dists_i, tid, tid + 128); 124 | } 125 | __syncthreads(); 126 | } 127 | if (block_size >= 128) { 128 | if (tid < 64) { 129 | __update(dists, dists_i, tid, tid + 64); 130 | } 131 | __syncthreads(); 132 | } 133 | if (block_size >= 64) { 134 | if (tid < 32) { 135 | __update(dists, dists_i, tid, tid + 32); 136 | } 137 | __syncthreads(); 138 | } 139 | if (block_size >= 32) { 140 | if (tid < 16) { 141 | __update(dists, dists_i, tid, tid + 16); 142 | } 143 | __syncthreads(); 144 | } 145 | if (block_size >= 16) { 146 | if (tid < 8) { 147 | __update(dists, dists_i, tid, tid + 8); 148 | } 149 | __syncthreads(); 150 | } 151 | if (block_size >= 8) { 152 | if (tid < 4) { 153 | __update(dists, dists_i, tid, tid + 4); 154 | } 155 | __syncthreads(); 156 | } 157 | if (block_size >= 4) { 158 | if (tid < 2) { 159 | __update(dists, dists_i, tid, tid + 2); 160 | } 161 | __syncthreads(); 162 | } 163 | if (block_size >= 2) { 164 | if (tid < 1) { 165 | __update(dists, dists_i, tid, tid + 1); 166 | } 167 | __syncthreads(); 168 | } 169 | 170 | old = dists_i[0]; 171 | if (tid == 0) idxs[j] = old; 172 | } 173 | } 174 | 175 | void furthest_point_sampling_kernel_wrapper(int b, int n, int m, 176 | const float *dataset, float *temp, 177 | int *idxs) { 178 | unsigned int n_threads = opt_n_threads(n); 179 | 180 | cudaStream_t stream = at::cuda::getCurrentCUDAStream(); 181 | 182 | switch (n_threads) { 183 | case 512: 184 | furthest_point_sampling_kernel<512> 185 | <<>>(b, n, m, dataset, temp, idxs); 186 | break; 187 | case 256: 188 | furthest_point_sampling_kernel<256> 189 | <<>>(b, n, m, dataset, temp, idxs); 190 | break; 191 | case 128: 192 | furthest_point_sampling_kernel<128> 193 | <<>>(b, n, m, dataset, temp, idxs); 194 | break; 195 | case 64: 196 | furthest_point_sampling_kernel<64> 197 | <<>>(b, n, m, dataset, temp, idxs); 198 | break; 199 | case 32: 200 | furthest_point_sampling_kernel<32> 201 | <<>>(b, n, m, dataset, temp, idxs); 202 | break; 203 | case 16: 204 | furthest_point_sampling_kernel<16> 205 | <<>>(b, n, m, dataset, temp, idxs); 206 | break; 207 | case 8: 208 | furthest_point_sampling_kernel<8> 209 | <<>>(b, n, m, dataset, temp, idxs); 210 | break; 211 | case 4: 212 | furthest_point_sampling_kernel<4> 213 | <<>>(b, n, m, dataset, temp, idxs); 214 | break; 215 | case 2: 216 | furthest_point_sampling_kernel<2> 217 | <<>>(b, n, m, dataset, temp, idxs); 218 | break; 219 | case 1: 220 | furthest_point_sampling_kernel<1> 221 | <<>>(b, n, m, dataset, temp, idxs); 222 | break; 223 | default: 224 | furthest_point_sampling_kernel<512> 225 | <<>>(b, n, m, dataset, temp, idxs); 226 | } 227 | 228 | CUDA_CHECK_ERRORS(); 229 | } 230 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.0.0" 2 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/pointnet2_modules.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Tuple 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from pointnet2_ops import pointnet2_utils 7 | 8 | 9 | def build_shared_mlp(mlp_spec: List[int], bn: bool = True): 10 | layers = [] 11 | for i in range(1, len(mlp_spec)): 12 | layers.append( 13 | nn.Conv2d(mlp_spec[i - 1], mlp_spec[i], kernel_size=1, bias=not bn) 14 | ) 15 | if bn: 16 | layers.append(nn.BatchNorm2d(mlp_spec[i])) 17 | layers.append(nn.ReLU(True)) 18 | 19 | return nn.Sequential(*layers) 20 | 21 | 22 | class _PointnetSAModuleBase(nn.Module): 23 | def __init__(self): 24 | super(_PointnetSAModuleBase, self).__init__() 25 | self.npoint = None 26 | self.groupers = None 27 | self.mlps = None 28 | 29 | def forward( 30 | self, xyz: torch.Tensor, features: Optional[torch.Tensor] 31 | ) -> Tuple[torch.Tensor, torch.Tensor]: 32 | r""" 33 | Parameters 34 | ---------- 35 | xyz : torch.Tensor 36 | (B, N, 3) tensor of the xyz coordinates of the features 37 | features : torch.Tensor 38 | (B, C, N) tensor of the descriptors of the the features 39 | 40 | Returns 41 | ------- 42 | new_xyz : torch.Tensor 43 | (B, npoint, 3) tensor of the new features' xyz 44 | new_features : torch.Tensor 45 | (B, \sum_k(mlps[k][-1]), npoint) tensor of the new_features descriptors 46 | """ 47 | 48 | new_features_list = [] 49 | 50 | xyz_flipped = xyz.transpose(1, 2).contiguous() 51 | new_xyz = ( 52 | pointnet2_utils.gather_operation( 53 | xyz_flipped, pointnet2_utils.furthest_point_sample(xyz, self.npoint) 54 | ) 55 | .transpose(1, 2) 56 | .contiguous() 57 | if self.npoint is not None 58 | else None 59 | ) 60 | 61 | for i in range(len(self.groupers)): 62 | new_features = self.groupers[i]( 63 | xyz, new_xyz, features 64 | ) # (B, C, npoint, nsample) 65 | 66 | new_features = self.mlps[i](new_features) # (B, mlp[-1], npoint, nsample) 67 | new_features = F.max_pool2d( 68 | new_features, kernel_size=[1, new_features.size(3)] 69 | ) # (B, mlp[-1], npoint, 1) 70 | new_features = new_features.squeeze(-1) # (B, mlp[-1], npoint) 71 | 72 | new_features_list.append(new_features) 73 | 74 | return new_xyz, torch.cat(new_features_list, dim=1) 75 | 76 | 77 | class PointnetSAModuleMSG(_PointnetSAModuleBase): 78 | r"""Pointnet set abstrction layer with multiscale grouping 79 | 80 | Parameters 81 | ---------- 82 | npoint : int 83 | Number of features 84 | radii : list of float32 85 | list of radii to group with 86 | nsamples : list of int32 87 | Number of samples in each ball query 88 | mlps : list of list of int32 89 | Spec of the pointnet before the global max_pool for each scale 90 | bn : bool 91 | Use batchnorm 92 | """ 93 | 94 | def __init__(self, npoint, radii, nsamples, mlps, bn=True, use_xyz=True): 95 | # type: (PointnetSAModuleMSG, int, List[float], List[int], List[List[int]], bool, bool) -> None 96 | super(PointnetSAModuleMSG, self).__init__() 97 | 98 | assert len(radii) == len(nsamples) == len(mlps) 99 | 100 | self.npoint = npoint 101 | self.groupers = nn.ModuleList() 102 | self.mlps = nn.ModuleList() 103 | for i in range(len(radii)): 104 | radius = radii[i] 105 | nsample = nsamples[i] 106 | self.groupers.append( 107 | pointnet2_utils.QueryAndGroup(radius, nsample, use_xyz=use_xyz) 108 | if npoint is not None 109 | else pointnet2_utils.GroupAll(use_xyz) 110 | ) 111 | mlp_spec = mlps[i] 112 | if use_xyz: 113 | mlp_spec[0] += 3 114 | 115 | self.mlps.append(build_shared_mlp(mlp_spec, bn)) 116 | 117 | 118 | class PointnetSAModule(PointnetSAModuleMSG): 119 | r"""Pointnet set abstrction layer 120 | 121 | Parameters 122 | ---------- 123 | npoint : int 124 | Number of features 125 | radius : float 126 | Radius of ball 127 | nsample : int 128 | Number of samples in the ball query 129 | mlp : list 130 | Spec of the pointnet before the global max_pool 131 | bn : bool 132 | Use batchnorm 133 | """ 134 | 135 | def __init__( 136 | self, mlp, npoint=None, radius=None, nsample=None, bn=True, use_xyz=True 137 | ): 138 | # type: (PointnetSAModule, List[int], int, float, int, bool, bool) -> None 139 | super(PointnetSAModule, self).__init__( 140 | mlps=[mlp], 141 | npoint=npoint, 142 | radii=[radius], 143 | nsamples=[nsample], 144 | bn=bn, 145 | use_xyz=use_xyz, 146 | ) 147 | 148 | 149 | class PointnetFPModule(nn.Module): 150 | r"""Propigates the features of one set to another 151 | 152 | Parameters 153 | ---------- 154 | mlp : list 155 | Pointnet module parameters 156 | bn : bool 157 | Use batchnorm 158 | """ 159 | 160 | def __init__(self, mlp, bn=True): 161 | # type: (PointnetFPModule, List[int], bool) -> None 162 | super(PointnetFPModule, self).__init__() 163 | self.mlp = build_shared_mlp(mlp, bn=bn) 164 | 165 | def forward(self, unknown, known, unknow_feats, known_feats): 166 | # type: (PointnetFPModule, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor) -> torch.Tensor 167 | r""" 168 | Parameters 169 | ---------- 170 | unknown : torch.Tensor 171 | (B, n, 3) tensor of the xyz positions of the unknown features 172 | known : torch.Tensor 173 | (B, m, 3) tensor of the xyz positions of the known features 174 | unknow_feats : torch.Tensor 175 | (B, C1, n) tensor of the features to be propigated to 176 | known_feats : torch.Tensor 177 | (B, C2, m) tensor of features to be propigated 178 | 179 | Returns 180 | ------- 181 | new_features : torch.Tensor 182 | (B, mlp[-1], n) tensor of the features of the unknown features 183 | """ 184 | 185 | if known is not None: 186 | dist, idx = pointnet2_utils.three_nn(unknown, known) 187 | dist_recip = 1.0 / (dist + 1e-8) 188 | norm = torch.sum(dist_recip, dim=2, keepdim=True) 189 | weight = dist_recip / norm 190 | 191 | interpolated_feats = pointnet2_utils.three_interpolate( 192 | known_feats, idx, weight 193 | ) 194 | else: 195 | interpolated_feats = known_feats.expand( 196 | *(known_feats.size()[0:2] + [unknown.size(1)]) 197 | ) 198 | 199 | if unknow_feats is not None: 200 | new_features = torch.cat( 201 | [interpolated_feats, unknow_feats], dim=1 202 | ) # (B, C2 + C1, n) 203 | else: 204 | new_features = interpolated_feats 205 | 206 | new_features = new_features.unsqueeze(-1) 207 | new_features = self.mlp(new_features) 208 | 209 | return new_features.squeeze(-1) 210 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/pointnet2_ops/pointnet2_utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import warnings 4 | from torch.autograd import Function 5 | from typing import * 6 | 7 | try: 8 | import pointnet2_ops._ext as _ext 9 | except ImportError: 10 | from torch.utils.cpp_extension import load 11 | import glob 12 | import os.path as osp 13 | import os 14 | 15 | warnings.warn("Unable to load pointnet2_ops cpp extension. JIT Compiling.") 16 | 17 | _ext_src_root = osp.join(osp.dirname(__file__), "_ext-src") 18 | _ext_sources = glob.glob(osp.join(_ext_src_root, "src", "*.cpp")) + glob.glob( 19 | osp.join(_ext_src_root, "src", "*.cu") 20 | ) 21 | _ext_headers = glob.glob(osp.join(_ext_src_root, "include", "*")) 22 | 23 | os.environ["TORCH_CUDA_ARCH_LIST"] = "3.7+PTX;5.0;6.0;6.1;6.2;7.0;7.5" 24 | _ext = load( 25 | "_ext", 26 | sources=_ext_sources, 27 | extra_include_paths=[osp.join(_ext_src_root, "include")], 28 | extra_cflags=["-O3"], 29 | extra_cuda_cflags=["-O3", "-Xfatbin", "-compress-all"], 30 | with_cuda=True, 31 | ) 32 | 33 | 34 | class FurthestPointSampling(Function): 35 | @staticmethod 36 | def forward(ctx, xyz, npoint): 37 | # type: (Any, torch.Tensor, int) -> torch.Tensor 38 | r""" 39 | Uses iterative furthest point sampling to select a set of npoint features that have the largest 40 | minimum distance 41 | 42 | Parameters 43 | ---------- 44 | xyz : torch.Tensor 45 | (B, N, 3) tensor where N > npoint 46 | npoint : int32 47 | number of features in the sampled set 48 | 49 | Returns 50 | ------- 51 | torch.Tensor 52 | (B, npoint) tensor containing the set 53 | """ 54 | out = _ext.furthest_point_sampling(xyz, npoint) 55 | 56 | ctx.mark_non_differentiable(out) 57 | 58 | return out 59 | 60 | @staticmethod 61 | def backward(ctx, grad_out): 62 | return () 63 | 64 | 65 | furthest_point_sample = FurthestPointSampling.apply 66 | 67 | 68 | class GatherOperation(Function): 69 | @staticmethod 70 | def forward(ctx, features, idx): 71 | # type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor 72 | r""" 73 | 74 | Parameters 75 | ---------- 76 | features : torch.Tensor 77 | (B, C, N) tensor 78 | 79 | idx : torch.Tensor 80 | (B, npoint) tensor of the features to gather 81 | 82 | Returns 83 | ------- 84 | torch.Tensor 85 | (B, C, npoint) tensor 86 | """ 87 | 88 | ctx.save_for_backward(idx, features) 89 | 90 | return _ext.gather_points(features, idx) 91 | 92 | @staticmethod 93 | def backward(ctx, grad_out): 94 | idx, features = ctx.saved_tensors 95 | N = features.size(2) 96 | 97 | grad_features = _ext.gather_points_grad(grad_out.contiguous(), idx, N) 98 | return grad_features, None 99 | 100 | 101 | gather_operation = GatherOperation.apply 102 | 103 | 104 | class ThreeNN(Function): 105 | @staticmethod 106 | def forward(ctx, unknown, known): 107 | # type: (Any, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor] 108 | r""" 109 | Find the three nearest neighbors of unknown in known 110 | Parameters 111 | ---------- 112 | unknown : torch.Tensor 113 | (B, n, 3) tensor of known features 114 | known : torch.Tensor 115 | (B, m, 3) tensor of unknown features 116 | 117 | Returns 118 | ------- 119 | dist : torch.Tensor 120 | (B, n, 3) l2 distance to the three nearest neighbors 121 | idx : torch.Tensor 122 | (B, n, 3) index of 3 nearest neighbors 123 | """ 124 | dist2, idx = _ext.three_nn(unknown, known) 125 | dist = torch.sqrt(dist2) 126 | 127 | ctx.mark_non_differentiable(dist, idx) 128 | 129 | return dist, idx 130 | 131 | @staticmethod 132 | def backward(ctx, grad_dist, grad_idx): 133 | return () 134 | 135 | 136 | three_nn = ThreeNN.apply 137 | 138 | 139 | class ThreeInterpolate(Function): 140 | @staticmethod 141 | def forward(ctx, features, idx, weight): 142 | # type(Any, torch.Tensor, torch.Tensor, torch.Tensor) -> Torch.Tensor 143 | r""" 144 | Performs weight linear interpolation on 3 features 145 | Parameters 146 | ---------- 147 | features : torch.Tensor 148 | (B, c, m) Features descriptors to be interpolated from 149 | idx : torch.Tensor 150 | (B, n, 3) three nearest neighbors of the target features in features 151 | weight : torch.Tensor 152 | (B, n, 3) weights 153 | 154 | Returns 155 | ------- 156 | torch.Tensor 157 | (B, c, n) tensor of the interpolated features 158 | """ 159 | ctx.save_for_backward(idx, weight, features) 160 | 161 | return _ext.three_interpolate(features, idx, weight) 162 | 163 | @staticmethod 164 | def backward(ctx, grad_out): 165 | # type: (Any, torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor] 166 | r""" 167 | Parameters 168 | ---------- 169 | grad_out : torch.Tensor 170 | (B, c, n) tensor with gradients of ouputs 171 | 172 | Returns 173 | ------- 174 | grad_features : torch.Tensor 175 | (B, c, m) tensor with gradients of features 176 | 177 | None 178 | 179 | None 180 | """ 181 | idx, weight, features = ctx.saved_tensors 182 | m = features.size(2) 183 | 184 | grad_features = _ext.three_interpolate_grad( 185 | grad_out.contiguous(), idx, weight, m 186 | ) 187 | 188 | return grad_features, torch.zeros_like(idx), torch.zeros_like(weight) 189 | 190 | 191 | three_interpolate = ThreeInterpolate.apply 192 | 193 | 194 | class GroupingOperation(Function): 195 | @staticmethod 196 | def forward(ctx, features, idx): 197 | # type: (Any, torch.Tensor, torch.Tensor) -> torch.Tensor 198 | r""" 199 | 200 | Parameters 201 | ---------- 202 | features : torch.Tensor 203 | (B, C, N) tensor of features to group 204 | idx : torch.Tensor 205 | (B, npoint, nsample) tensor containing the indicies of features to group with 206 | 207 | Returns 208 | ------- 209 | torch.Tensor 210 | (B, C, npoint, nsample) tensor 211 | """ 212 | ctx.save_for_backward(idx, features) 213 | 214 | return _ext.group_points(features, idx) 215 | 216 | @staticmethod 217 | def backward(ctx, grad_out): 218 | # type: (Any, torch.tensor) -> Tuple[torch.Tensor, torch.Tensor] 219 | r""" 220 | 221 | Parameters 222 | ---------- 223 | grad_out : torch.Tensor 224 | (B, C, npoint, nsample) tensor of the gradients of the output from forward 225 | 226 | Returns 227 | ------- 228 | torch.Tensor 229 | (B, C, N) gradient of the features 230 | None 231 | """ 232 | idx, features = ctx.saved_tensors 233 | N = features.size(2) 234 | 235 | grad_features = _ext.group_points_grad(grad_out.contiguous(), idx, N) 236 | 237 | return grad_features, torch.zeros_like(idx) 238 | 239 | 240 | grouping_operation = GroupingOperation.apply 241 | 242 | 243 | class BallQuery(Function): 244 | @staticmethod 245 | def forward(ctx, radius, nsample, xyz, new_xyz): 246 | # type: (Any, float, int, torch.Tensor, torch.Tensor) -> torch.Tensor 247 | r""" 248 | 249 | Parameters 250 | ---------- 251 | radius : float 252 | radius of the balls 253 | nsample : int 254 | maximum number of features in the balls 255 | xyz : torch.Tensor 256 | (B, N, 3) xyz coordinates of the features 257 | new_xyz : torch.Tensor 258 | (B, npoint, 3) centers of the ball query 259 | 260 | Returns 261 | ------- 262 | torch.Tensor 263 | (B, npoint, nsample) tensor with the indicies of the features that form the query balls 264 | """ 265 | output = _ext.ball_query(new_xyz, xyz, radius, nsample) 266 | 267 | ctx.mark_non_differentiable(output) 268 | 269 | return output 270 | 271 | @staticmethod 272 | def backward(ctx, grad_out): 273 | return () 274 | 275 | 276 | ball_query = BallQuery.apply 277 | 278 | 279 | class QueryAndGroup(nn.Module): 280 | r""" 281 | Groups with a ball query of radius 282 | 283 | Parameters 284 | --------- 285 | radius : float32 286 | Radius of ball 287 | nsample : int32 288 | Maximum number of features to gather in the ball 289 | """ 290 | 291 | def __init__(self, radius, nsample, use_xyz=True): 292 | # type: (QueryAndGroup, float, int, bool) -> None 293 | super(QueryAndGroup, self).__init__() 294 | self.radius, self.nsample, self.use_xyz = radius, nsample, use_xyz 295 | 296 | def forward(self, xyz, new_xyz, features=None): 297 | # type: (QueryAndGroup, torch.Tensor. torch.Tensor, torch.Tensor) -> Tuple[Torch.Tensor] 298 | r""" 299 | Parameters 300 | ---------- 301 | xyz : torch.Tensor 302 | xyz coordinates of the features (B, N, 3) 303 | new_xyz : torch.Tensor 304 | centriods (B, npoint, 3) 305 | features : torch.Tensor 306 | Descriptors of the features (B, C, N) 307 | 308 | Returns 309 | ------- 310 | new_features : torch.Tensor 311 | (B, 3 + C, npoint, nsample) tensor 312 | """ 313 | 314 | idx = ball_query(self.radius, self.nsample, xyz, new_xyz) 315 | xyz_trans = xyz.transpose(1, 2).contiguous() 316 | grouped_xyz = grouping_operation(xyz_trans, idx) # (B, 3, npoint, nsample) 317 | grouped_xyz -= new_xyz.transpose(1, 2).unsqueeze(-1) 318 | 319 | if features is not None: 320 | grouped_features = grouping_operation(features, idx) 321 | if self.use_xyz: 322 | new_features = torch.cat( 323 | [grouped_xyz, grouped_features], dim=1 324 | ) # (B, C + 3, npoint, nsample) 325 | else: 326 | new_features = grouped_features 327 | else: 328 | assert ( 329 | self.use_xyz 330 | ), "Cannot have not features and not use xyz as a feature!" 331 | new_features = grouped_xyz 332 | 333 | return new_features 334 | 335 | 336 | class GroupAll(nn.Module): 337 | r""" 338 | Groups all features 339 | 340 | Parameters 341 | --------- 342 | """ 343 | 344 | def __init__(self, use_xyz=True): 345 | # type: (GroupAll, bool) -> None 346 | super(GroupAll, self).__init__() 347 | self.use_xyz = use_xyz 348 | 349 | def forward(self, xyz, new_xyz, features=None): 350 | # type: (GroupAll, torch.Tensor, torch.Tensor, torch.Tensor) -> Tuple[torch.Tensor] 351 | r""" 352 | Parameters 353 | ---------- 354 | xyz : torch.Tensor 355 | xyz coordinates of the features (B, N, 3) 356 | new_xyz : torch.Tensor 357 | Ignored 358 | features : torch.Tensor 359 | Descriptors of the features (B, C, N) 360 | 361 | Returns 362 | ------- 363 | new_features : torch.Tensor 364 | (B, C + 3, 1, N) tensor 365 | """ 366 | 367 | grouped_xyz = xyz.transpose(1, 2).unsqueeze(2) 368 | if features is not None: 369 | grouped_features = features.unsqueeze(2) 370 | if self.use_xyz: 371 | new_features = torch.cat( 372 | [grouped_xyz, grouped_features], dim=1 373 | ) # (B, 3 + C, 1, N) 374 | else: 375 | new_features = grouped_features 376 | else: 377 | new_features = grouped_xyz 378 | 379 | return new_features 380 | -------------------------------------------------------------------------------- /pointnet2_ops_lib/setup.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import os.path as osp 4 | 5 | from setuptools import find_packages, setup 6 | from torch.utils.cpp_extension import BuildExtension, CUDAExtension 7 | 8 | this_dir = osp.dirname(osp.abspath(__file__)) 9 | _ext_src_root = osp.join("pointnet2_ops", "_ext-src") 10 | _ext_sources = glob.glob(osp.join(_ext_src_root, "src", "*.cpp")) + glob.glob( 11 | osp.join(_ext_src_root, "src", "*.cu") 12 | ) 13 | _ext_headers = glob.glob(osp.join(_ext_src_root, "include", "*")) 14 | 15 | requirements = ["torch>=1.4"] 16 | 17 | exec(open(osp.join("pointnet2_ops", "_version.py")).read()) 18 | 19 | os.environ["TORCH_CUDA_ARCH_LIST"] = "3.7+PTX;5.0;6.0;6.1;6.2;7.0;7.5" 20 | setup( 21 | name="pointnet2_ops", 22 | version=__version__, 23 | author="Erik Wijmans", 24 | packages=find_packages(), 25 | install_requires=requirements, 26 | ext_modules=[ 27 | CUDAExtension( 28 | name="pointnet2_ops._ext", 29 | sources=_ext_sources, 30 | extra_compile_args={ 31 | "cxx": ["-O3"], 32 | "nvcc": ["-O3", "-Xfatbin", "-compress-all"], 33 | }, 34 | include_dirs=[osp.join(this_dir, _ext_src_root, "include")], 35 | ) 36 | ], 37 | cmdclass={"build_ext": BuildExtension}, 38 | include_package_data=True, 39 | ) 40 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | einops==0.3.0 3 | h5py==3.2.1 4 | matplotlib==3.4.2 5 | numpy>=1.21 6 | pyyaml==5.4.1 7 | scikit-learn==0.24.2 8 | scipy==1.6.3 9 | tqdm==4.61.1 10 | open3d 11 | easydict 12 | tensorboard 13 | pointnet2_ops_lib/. 14 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import argparse 4 | import datetime 5 | import numpy as np 6 | import random 7 | import shutil 8 | 9 | from glob import glob 10 | from tqdm import tqdm 11 | from torch.utils.data import DataLoader 12 | 13 | from core.builders import build_dataset, build_network, build_optimizer 14 | from utils.runtime_utils import cfg, cfg_from_yaml_file, validate 15 | from utils.vis_utils import visualize_numpy 16 | 17 | def parse_config(): 18 | parser = argparse.ArgumentParser(description='arg parser') 19 | parser.add_argument('--cfg_file', type=str, default=None, help='specify the config for training') 20 | parser.add_argument('--ckpt', type=str, default=None, help='checkpoint to start from') 21 | 22 | args = parser.parse_args() 23 | 24 | cfg_from_yaml_file(args.cfg_file, cfg) 25 | 26 | return args, cfg 27 | 28 | args, cfg = parse_config() 29 | exp_dir = ('/').join(args.ckpt.split('/')[:-2]) 30 | 31 | random_seed = cfg.RANDOM_SEED # Setup seed for reproducibility 32 | torch.manual_seed(random_seed) 33 | torch.cuda.manual_seed(random_seed) 34 | np.random.seed(random_seed) 35 | random.seed(random_seed) 36 | 37 | # Build Dataloader 38 | val_dataset = build_dataset(cfg, split='val') 39 | val_dataloader = DataLoader(val_dataset, batch_size=1, shuffle=False, drop_last=False) 40 | 41 | # Build Network and Optimizer 42 | net = build_network(cfg) 43 | state_dict = torch.load(args.ckpt) 44 | epoch = state_dict['epoch'] 45 | net.load_state_dict(state_dict['model_state_dict']) 46 | net = net.cuda() 47 | net.eval() 48 | 49 | print('Evaluating Epoch: ', epoch) 50 | val_dict = validate(net, val_dataloader, net.get_loss, 'cuda', is_segmentation = cfg.DATASET.IS_SEGMENTATION) 51 | 52 | if cfg.DATASET.IS_SEGMENTATION: 53 | miou = np.round(val_dict['miou'], 4) 54 | print('miou', miou) 55 | 56 | else: 57 | val_loss = np.round(val_dict['loss'], 4) 58 | val_acc = np.round(val_dict['acc'], 2) 59 | val_acc_avg = np.round(val_dict['acc_avg'], 2) 60 | 61 | print('val_loss', val_loss) 62 | print('val_acc', val_acc) 63 | print('val_acc_avg', val_acc_avg) 64 | 65 | 66 | if cfg.DATASET.IS_SEGMENTATION: 67 | with open(exp_dir + '/eval_best.txt', 'w') as f: 68 | f.write('Best Epoch: ' + str(epoch)) 69 | f.write('\nBest miou: ' + str(miou)) 70 | 71 | else: 72 | with open(exp_dir + '/eval_best.txt', 'w') as f: 73 | f.write('Best Epoch: ' + str(epoch)) 74 | f.write('\nBest Acc: ' + str(val_acc)) 75 | f.write('\nBest Mean Acc: ' + str(val_acc_avg)) 76 | f.write('\nBest Loss: ' + str(val_loss)) 77 | 78 | 79 | torch.save(state_dict['model_state_dict'], exp_dir + '/ckpt_model_only.pth') 80 | 81 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import argparse 4 | import datetime 5 | import numpy as np 6 | import random 7 | import shutil 8 | 9 | from glob import glob 10 | from tqdm import tqdm 11 | from torch.utils.data import DataLoader 12 | 13 | from core.builders import build_dataset, build_network, build_optimizer 14 | from utils.runtime_utils import cfg, cfg_from_yaml_file, validate 15 | from utils.vis_utils import visualize_numpy 16 | 17 | def parse_config(): 18 | parser = argparse.ArgumentParser(description='arg parser') 19 | parser.add_argument('--cfg_file', type=str, default=None, help='specify the config for training') 20 | parser.add_argument('--exp_name', type=str, default=None, help='specify experiment name for saving outputs') 21 | parser.add_argument('--ckpt', type=str, default=None, help='checkpoint to start from') 22 | parser.add_argument('--random_seed', type=int, default=0, help='random seed number') 23 | parser.add_argument('--val_steps', type=int, default=1, help='perform validation every n steps') 24 | parser.add_argument('--pretrained_ckpt', type = str, default = None, help='path to pretrained ckpt') 25 | args = parser.parse_args() 26 | 27 | cfg_from_yaml_file(args.cfg_file, cfg) 28 | exp_dir = cfg.ROOT_DIR / 'experiments' / cfg.DATASET.NAME / args.exp_name 29 | os.makedirs(exp_dir, exist_ok=True) 30 | shutil.copy2(args.cfg_file, exp_dir) 31 | 32 | return args, cfg 33 | 34 | args, cfg = parse_config() 35 | 36 | random_seed = cfg.RANDOM_SEED # Setup seed for reproducibility 37 | torch.manual_seed(random_seed) 38 | torch.cuda.manual_seed(random_seed) 39 | np.random.seed(random_seed) 40 | random.seed(random_seed) 41 | 42 | # Build Dataloader 43 | train_dataset = build_dataset(cfg, split = 'train') 44 | train_dataloader = DataLoader(train_dataset, batch_size=cfg.OPTIMIZER.BATCH_SIZE, shuffle=True, drop_last=True) 45 | 46 | val_dataset = build_dataset(cfg, split='val') 47 | val_dataloader = DataLoader(val_dataset, batch_size=1, shuffle=False, drop_last=False) 48 | 49 | # Build Network and Optimizer 50 | net = build_network(cfg) 51 | if args.pretrained_ckpt is not None: 52 | pretrained_state_dict = torch.load(args.pretrained_ckpt)['model_state_dict'] 53 | 54 | for k, v in net.state_dict().items(): 55 | if (v.shape != pretrained_state_dict[k].shape): 56 | del pretrained_state_dict[k] 57 | 58 | net.load_state_dict(pretrained_state_dict, strict = False) 59 | 60 | net = net.cuda() 61 | opt, scheduler = build_optimizer(cfg, net.parameters(), len(train_dataloader)) 62 | 63 | 64 | from torch.utils.tensorboard import SummaryWriter 65 | ckpt_dir = cfg.ROOT_DIR / 'experiments' / cfg.DATASET.NAME / args.exp_name / 'ckpt' 66 | tensorboard_dir = cfg.ROOT_DIR / 'experiments' / cfg.DATASET.NAME / args.exp_name / 'tensorboard' 67 | 68 | os.makedirs(ckpt_dir, exist_ok=True) 69 | os.makedirs(tensorboard_dir, exist_ok=True) 70 | 71 | writer = SummaryWriter(tensorboard_dir) 72 | 73 | min_loss = 1e20 74 | max_acc = 0 75 | 76 | steps_cnt = 0 77 | epoch_cnt = 0 78 | 79 | 80 | for epoch in tqdm(range(1, cfg.OPTIMIZER.MAX_EPOCH + 1)): 81 | opt.zero_grad() 82 | net.zero_grad() 83 | net.train() 84 | loss = 0 85 | for data_dic in tqdm(train_dataloader): 86 | 87 | data_dic = net(data_dic) 88 | loss, loss_dict = net.get_loss(data_dic, smoothing = True, is_segmentation = cfg.DATASET.IS_SEGMENTATION) 89 | loss = loss 90 | loss.backward() 91 | steps_cnt += 1 92 | 93 | # if (steps_cnt)%(cfg.OPTIMIZER.GRAD_ACCUMULATION) == 0: 94 | torch.nn.utils.clip_grad_norm_(net.parameters(), cfg.OPTIMIZER.GRAD_CLIP) 95 | opt.step() 96 | opt.zero_grad() 97 | lr = scheduler.get_last_lr()[0] 98 | scheduler.step() 99 | writer.add_scalar('steps/loss', loss, steps_cnt) 100 | writer.add_scalar('steps/lr', lr, steps_cnt) 101 | 102 | for k,v in loss_dict.items(): 103 | writer.add_scalar('steps/loss_' + k, v, steps_cnt) 104 | 105 | if (epoch % args.val_steps) == 0: 106 | val_dict = validate(net, val_dataloader, net.get_loss, 'cuda', is_segmentation = cfg.DATASET.IS_SEGMENTATION) 107 | 108 | print('='*20, 'Epoch ' + str(epoch+1), '='*20) 109 | 110 | if cfg.DATASET.IS_SEGMENTATION: 111 | writer.add_scalar('epochs/val_miou', val_dict['miou'], epoch_cnt) 112 | print('Val mIoU: ', val_dict['miou']) 113 | 114 | else: 115 | writer.add_scalar('epochs/val_loss', val_dict['loss'], epoch_cnt) 116 | writer.add_scalar('epochs/val_acc', val_dict['acc'], epoch_cnt) 117 | writer.add_scalar('epochs/val_acc_avg', val_dict['acc_avg'], epoch_cnt) 118 | print('Val Loss: ', val_dict['loss'], 'Val Accuracy: ', val_dict['acc'], 'Val Avg Accuracy: ', val_dict['acc_avg']) 119 | 120 | for k,v in val_dict['loss_dic'].items(): 121 | writer.add_scalar('epochs/val_loss_'+ k, v, epoch_cnt) 122 | 123 | epoch_cnt += 1 124 | 125 | 126 | if cfg.DATASET.IS_SEGMENTATION: 127 | if val_dict['miou'] > max_acc: 128 | torch.save({ 129 | 'epoch': epoch, 130 | 'model_state_dict': net.state_dict(), 131 | 'optimizer_state_dict': opt.state_dict(), 132 | }, ckpt_dir / 'ckpt-best.pth') 133 | 134 | max_acc = val_dict['miou'] 135 | else: 136 | 137 | if val_dict['acc'] > max_acc: 138 | torch.save({ 139 | 'epoch': epoch, 140 | 'model_state_dict': net.state_dict(), 141 | 'optimizer_state_dict': opt.state_dict(), 142 | 'loss': val_dict['loss'], 143 | }, ckpt_dir / 'ckpt-best.pth') 144 | 145 | max_acc = val_dict['acc'] 146 | 147 | torch.save({ 148 | 'epoch': epoch, 149 | 'model_state_dict': net.state_dict(), 150 | 'optimizer_state_dict': opt.state_dict(), 151 | }, ckpt_dir / 'ckpt-last.pth') 152 | -------------------------------------------------------------------------------- /utils/runtime_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import torch 4 | import datetime 5 | 6 | import sklearn.metrics as metrics 7 | import numpy as np 8 | from tqdm import tqdm 9 | from easydict import EasyDict 10 | from pathlib import Path 11 | 12 | import torch.nn.functional as F 13 | 14 | def merge_new_config(config, new_config): 15 | if '_BASE_CONFIG_' in new_config: 16 | with open(new_config['_BASE_CONFIG_'], 'r') as f: 17 | try: 18 | yaml_config = yaml.safe_load(f, Loader=yaml.FullLoader) 19 | except: 20 | yaml_config = yaml.safe_load(f) 21 | config.update(EasyDict(yaml_config)) 22 | 23 | for key, val in new_config.items(): 24 | if not isinstance(val, dict): 25 | config[key] = val 26 | continue 27 | if key not in config: 28 | config[key] = EasyDict() 29 | merge_new_config(config[key], val) 30 | 31 | return config 32 | 33 | def cfg_from_yaml_file(cfg_file, config): 34 | with open(cfg_file, 'r') as f: 35 | try: 36 | new_config = yaml.safe_load(f, Loader=yaml.FullLoader) 37 | except: 38 | new_config = yaml.safe_load(f) 39 | 40 | merge_new_config(config=config, new_config=new_config) 41 | 42 | return config 43 | 44 | 45 | cfg = EasyDict() 46 | cfg.ROOT_DIR = (Path(__file__).resolve().parent / '../').resolve() 47 | cfg.LOCAL_RANK = 0 48 | 49 | class AverageMeter(object): 50 | """Computes and stores the average and current value""" 51 | def __init__(self): 52 | self.reset() 53 | 54 | def reset(self): 55 | self.val = 0 56 | self.avg = 0 57 | self.sum = 0 58 | self.count = 0 59 | 60 | def update(self, val, n=1): 61 | self.val = val 62 | self.sum += val * n 63 | self.count += n 64 | self.avg = self.sum / self.count 65 | 66 | def get_n_params(model): 67 | pp=0 68 | for p in list(model.parameters()): 69 | nn=1 70 | for s in list(p.size()): 71 | nn = nn*s 72 | pp += nn 73 | return pp 74 | 75 | def validate(net, testloader, criterion, device, is_segmentation = False): 76 | net.eval() 77 | num_params = get_n_params(net) 78 | 79 | test_loss = 0 80 | correct = 0 81 | total = 0 82 | test_true = [] 83 | test_pred = [] 84 | time_cost = [] 85 | mious = [] 86 | 87 | accuracy = [] 88 | shape_ious = 0.0 89 | count = 0.0 90 | with torch.no_grad(): 91 | for batch_idx, data_dic in enumerate(tqdm(testloader)): 92 | start_time = datetime.datetime.now() 93 | data_dic = net(data_dic) 94 | time_cost.append(float((datetime.datetime.now() - start_time).total_seconds())) 95 | 96 | 97 | if is_segmentation: 98 | miou = net.compute_overall_iou(data_dic['pred_score_logits'], data_dic['seg_id'], num_classes = 50) 99 | # total iou of current batch in each process: 100 | batch_ious = data_dic['pred_score_logits'].new_tensor([np.sum(miou)], dtype=torch.float64) # same device with seg_pred 101 | 102 | # prepare seg_pred and target for later calculating loss and acc: 103 | seg_pred = data_dic['pred_score_logits'].contiguous().view(-1, 50) # ShapeNetPart has 50 classes 104 | 105 | target = data_dic['seg_id'].view(-1, 1)[:, 0] 106 | # Loss 107 | 108 | pred_choice = seg_pred.data.max(1)[1] # b*n 109 | correct = pred_choice.eq(target.data).sum() # torch.int64: total number of correct-predict pts 110 | 111 | shape_ious += batch_ious.item() # count the sum of ious in each iteration 112 | count += data_dic['pred_score_logits'].shape[0] # count the total number of samples in each iteration 113 | accuracy.append(correct.item() / (data_dic['pred_score_logits'].shape[0] * data_dic['points'].shape[1])) # append the accuracy of each iteration 114 | mious.append(miou) 115 | 116 | else: 117 | preds = data_dic['pred_score_logits'].max(dim=1)[1] 118 | test_true.append(data_dic['cls_id'].cpu().numpy()) 119 | test_pred.append(preds.detach().cpu().numpy()) 120 | total += data_dic['cls_id'].size(0) 121 | correct += preds.eq(data_dic['cls_id']).sum().item() 122 | loss, loss_dict = criterion(data_dic) 123 | test_loss += loss.item() 124 | 125 | 126 | 127 | total_samples = len(time_cost) 128 | time_cost = np.mean(time_cost[total_samples//5:total_samples*4//5]) 129 | 130 | if is_segmentation: 131 | overall_miou = np.mean(mious) 132 | overall_acc = np.mean(accuracy) 133 | overall_ins_acc = shape_ious * 1.0 / count 134 | return { 135 | "miou": float("%.3f" % (100. * overall_miou)), 136 | "time": time_cost, 137 | "num_params": num_params, 138 | 'peak_memory': torch.cuda.memory_stats('cuda')['allocated_bytes.all.peak'] 139 | } 140 | else: 141 | test_true = np.concatenate(test_true) 142 | test_pred = np.concatenate(test_pred) 143 | 144 | 145 | return { 146 | "loss": float("%.3f" % (test_loss / (batch_idx + 1))), 147 | "loss_dic": loss_dict, 148 | "acc": float("%.3f" % (100. * metrics.accuracy_score(test_true, test_pred))), 149 | "acc_avg": float("%.3f" % (100. * metrics.balanced_accuracy_score(test_true, test_pred))), 150 | "time": time_cost, 151 | "num_params": num_params, 152 | 'peak_memory': torch.cuda.memory_stats('cuda')['allocated_bytes.all.peak'] 153 | } 154 | 155 | def validate_voting(net, testloader, device = 'cuda', num_repeat = 300, num_vote = 10): 156 | net.eval() 157 | best_acc = 0 158 | best_mean_acc = 0 159 | pointscale = PointcloudScale(scale_low=0.85, scale_high=1.15) 160 | 161 | for i in tqdm(range(num_repeat)): 162 | test_true = [] 163 | test_pred = [] 164 | 165 | for batch_idx, data_dic in enumerate(tqdm(testloader)): 166 | pred = 0 167 | for v in range(num_vote): 168 | new_data = data_dic 169 | if v > 0: 170 | new_data['points'] = pointscale(new_data['points']) 171 | with torch.no_grad(): 172 | pred += F.softmax(net(data_dic)['pred_score_logits'], dim=1) # sum 10 preds 173 | pred /= num_vote # avg the preds! 174 | pred_choice = pred.max(dim=1)[1] 175 | test_true.append(data_dic['cls_id'].cpu().numpy()) 176 | test_pred.append(pred_choice.detach().cpu().numpy()) 177 | test_true = np.concatenate(test_true) 178 | test_pred = np.concatenate(test_pred) 179 | test_acc = 100. * metrics.accuracy_score(test_true, test_pred) 180 | test_mean_acc = 100. * metrics.balanced_accuracy_score(test_true, test_pred) 181 | if test_acc > best_acc: 182 | best_acc = test_acc 183 | if test_mean_acc > best_mean_acc: 184 | best_mean_acc = test_mean_acc 185 | outstr = 'Voting %d, test acc: %.3f, test mean acc: %.3f, [current best(all_acc: %.3f mean_acc: %.3f)]' % \ 186 | (i, test_acc, test_mean_acc, best_acc, best_mean_acc) 187 | print(outstr) 188 | best_acc = best_acc*100 189 | best_mean_acc = best_mean_acc*100 190 | print('Final Test Acc: ', best_acc) 191 | print('Final Test Avg Acc: ', best_mean_acc) 192 | 193 | val_dict = { 194 | 'acc': best_acc, 195 | 'acc_avg': best_mean_acc 196 | } 197 | 198 | return val_dict 199 | 200 | class PointcloudScale(object): # input random scaling 201 | def __init__(self, scale_low=2. / 3., scale_high=3. / 2.): 202 | self.scale_low = scale_low 203 | self.scale_high = scale_high 204 | 205 | def __call__(self, pc): 206 | bsize = pc.size()[0] 207 | for i in range(bsize): 208 | xyz1 = np.random.uniform(low=self.scale_low, high=self.scale_high, size=[3]) 209 | pc[i, :, 0:3] = torch.mul(pc[i, :, 0:3], torch.from_numpy(xyz1).float().cuda()) 210 | 211 | return pc 212 | -------------------------------------------------------------------------------- /utils/vis_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import open3d as o3d 3 | 4 | import torch 5 | from tqdm import tqdm 6 | import datetime 7 | 8 | def visualize_numpy(pc_numpy, colors = None): 9 | point_cloud = o3d.geometry.PointCloud() 10 | point_cloud.points = o3d.utility.Vector3dVector(pc_numpy) 11 | try: 12 | point_cloud.colors = o3d.utility.Vector3dVector(colors) 13 | except: 14 | pass 15 | 16 | vis = o3d.visualization.Visualizer() 17 | vis.create_window() 18 | vis.add_geometry(point_cloud) 19 | ctr = vis.get_view_control() 20 | ctr.set_up((1, 0, 0)) 21 | ctr.set_front((0, 1, 0)) 22 | 23 | vis.run() 24 | 25 | # o3d.visualization.draw_geometries([point_cloud]) 26 | 27 | def visualize_part(net, testloader): 28 | color_choices = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), #3 29 | (1, 0, 0), (0, 1, 0), #5 30 | (1, 0, 0), (0, 1, 0), #7 31 | (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), #11 32 | (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), #15 33 | (1, 0, 0), (0, 1, 0), (0, 0, 1), # 18 34 | (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), #21 35 | (1, 0, 0), (0, 1, 0), #23 36 | (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), #27 37 | (1, 0, 0), (0, 1, 0), #29 38 | (1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (0, 1, 1), (1, 0, 1), # 35 39 | (1, 0, 0), (0, 1, 0), #37 40 | (1, 0, 0), (0, 1, 0), (0, 0, 1), #40 41 | (1, 0, 0), (0, 1, 0), (0, 0, 1), #43 42 | (1, 0, 0), (0, 1, 0), (0, 0, 1), #46 43 | (1, 0, 0), (0, 1, 0), (0, 0, 1), #49 44 | ] 45 | 46 | net.eval() 47 | 48 | with torch.no_grad(): 49 | for batch_idx, data_dic in enumerate(tqdm(testloader)): 50 | if ((batch_idx % 10 == 0) and (batch_idx > 1260)): 51 | data_dic = net(data_dic) 52 | points = data_dic['points'].squeeze(0).cpu().numpy() 53 | label = data_dic['seg_id'].squeeze(0).cpu().numpy() 54 | pred = torch.argmax(data_dic['pred_score_logits'], dim = -1).squeeze(0).cpu().numpy() 55 | 56 | color_list = np.zeros_like(points) 57 | for i, pred_id in enumerate(pred): 58 | color_list[i] = color_choices[int(pred_id)] 59 | visualize_numpy(points, colors=color_list) 60 | 61 | color_list = np.zeros_like(points) 62 | for i, label_id in enumerate(label): 63 | color_list[i] = color_choices[int(label_id)] 64 | visualize_numpy(points, colors=color_list) 65 | -------------------------------------------------------------------------------- /visualize_part.py: -------------------------------------------------------------------------------- 1 | import os 2 | from idna import valid_contextj 3 | import torch 4 | import argparse 5 | import datetime 6 | import numpy as np 7 | import random 8 | import shutil 9 | 10 | from glob import glob 11 | from tqdm import tqdm 12 | from torch.utils.data import DataLoader 13 | 14 | from core.builders import build_dataset, build_network, build_optimizer 15 | from utils.runtime_utils import cfg, cfg_from_yaml_file, validate 16 | from utils.vis_utils import visualize_numpy, visualize_part 17 | 18 | def parse_config(): 19 | parser = argparse.ArgumentParser(description='arg parser') 20 | parser.add_argument('--cfg_file', type=str, default=None, help='specify the config for training') 21 | parser.add_argument('--ckpt', type=str, default=None, help='checkpoint to start from') 22 | 23 | args = parser.parse_args() 24 | 25 | cfg_from_yaml_file(args.cfg_file, cfg) 26 | 27 | return args, cfg 28 | 29 | args, cfg = parse_config() 30 | exp_dir = ('/').join(args.ckpt.split('/')[:-2]) 31 | 32 | 33 | # Build Dataloader 34 | val_dataset = build_dataset(cfg, split='val') 35 | val_dataloader = DataLoader(val_dataset, batch_size=1, shuffle=False, drop_last=False) 36 | 37 | # Build Network 38 | net = build_network(cfg) 39 | state_dict = torch.load(args.ckpt) 40 | epoch = state_dict['epoch'] 41 | net.load_state_dict(state_dict['model_state_dict']) 42 | net = net.cuda() 43 | net.eval() 44 | 45 | print('Evaluating Epoch: ', epoch) 46 | visualize_part(net, val_dataloader) --------------------------------------------------------------------------------