forked from aboulch/ConvPoint
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexandre Boulch
committed
Apr 3, 2019
0 parents
commit deabcc4
Showing
9 changed files
with
831 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
# License information | ||
|
||
Code is released under dual license depending on applications, research or commercial. | ||
|
||
--- | ||
|
||
## COMMERCIAL PURPOSES | ||
|
||
Please contact the ONERA [www.onera.fr/en/contact-us](www.onera.fr/en/contact-us) for additional information or directly the author Alexandre Boulch. | ||
|
||
--- | ||
|
||
## RESEARCH AND NON COMMERCIAL PURPOSES | ||
|
||
For research and non commercial purposes, all the code and documentation of github.com/aboulch/ConvPoint is released under the GPLv3 license: | ||
|
||
ConvPoint: UGeneralizing discrete convolutions for unstructured point clouds | ||
Copyright (C) 2019 ONERA, Alexandre Boulch | ||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version. | ||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
|
||
PLEASE ACKNOWLEDGE THE AUTHORS AND PUBLICATION ACCORDING TO THE | ||
REPOSITORY github.com/aboulch/ConvPoint OR IF NOT AVAILABLE: | ||
"Generalizing discrete convolutions for unstructured point clouds", A.Boulch, | ||
Eurographics Workshop on 3D Object Retrieval 2019 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# ConvPoint: Generalizing discrete convolutions for unstructured point clouds | ||
|
||
## Introduction | ||
|
||
## License | ||
|
||
Code is released under dual license depending on applications, research or commercial. Reseach license is GPLv3. | ||
|
||
## Citation | ||
|
||
If you use this code in your research, please consider citing: | ||
(citation will be updated as soon as 3DOR proceedings will be released) | ||
|
||
``` | ||
@inproceedings{boulch2019, | ||
title={Generalizing discrete convolutions for unstructured point clouds}, | ||
author={Boulch, Alexandre}, | ||
booktitle={Eurographics Workshop on 3D Object Retrieval}, | ||
year={2019} | ||
} | ||
``` | ||
|
||
## Dependencies | ||
|
||
- Pytorch | ||
- Scikit-learn for confusion matrix computation, and efficient neighbors search | ||
- Trimesh (for Modelnet40) for loading triangular meshes and sampling points | ||
- TQDM for progress bars | ||
|
||
All these dependencies can be install via conda in an Anaconda environment or via pip. | ||
|
||
## The library | ||
|
||
## Usage | ||
|
||
We propose scripts for training on several point cloud datasets: | ||
- ModelNet40 (meshes can be found [here](http://modelnet.cs.princeton.edu/)). The meshes are sampled in the code using Trimesh. | ||
- ShapeNet *(code to be added)* | ||
- S3DIS *(code to be added)* | ||
- Semantic8 *(code to added)* | ||
|
||
### ModelNet40 | ||
|
||
#### Training | ||
``` | ||
python modelnet_classif.py --rootdir path_to_modelnet40_data | ||
``` | ||
|
||
#### Testing | ||
|
||
For testing with one tree per shape: | ||
``` | ||
python modelnet_classif.py --rootdir path_to_modelnet40_data --savedir path_to_statedict_directory --test | ||
``` | ||
For testing with more than one tree per shape: *(this code is not optimized at all and is very slow)* | ||
``` | ||
python modelnet_classif.py --rootdir path_to_modelnet40_data --savedir path_to_statedict_directory --test --ntree 2 | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .conv import PtConv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import torch | ||
import torch.nn as nn | ||
import torch.nn.functional as F | ||
import numpy as np | ||
import math | ||
|
||
class PtConv(nn.Module): | ||
def __init__(self, input_features, output_features, n_centers, dim, use_bias=True): | ||
super(PtConv, self).__init__() | ||
|
||
# Weight | ||
self.weight = nn.Parameter( | ||
torch.Tensor(input_features, n_centers, output_features), requires_grad=True) | ||
bound = math.sqrt(3.0) * math.sqrt(2.0 / (input_features + output_features)) | ||
self.weight.data.uniform_(-bound, bound) | ||
|
||
# bias | ||
self.use_bias = use_bias | ||
if use_bias: | ||
self.bias = nn.Parameter(torch.Tensor(output_features), requires_grad=True) | ||
self.bias.data.uniform_(0,0) | ||
|
||
# centers | ||
center_data = np.zeros((dim, n_centers)) | ||
for i in range(n_centers): | ||
coord = np.random.rand(dim)*2 - 1 | ||
while (coord**2).sum() > 1: | ||
coord = np.random.rand(dim)*2 - 1 | ||
center_data[:,i] = coord | ||
self.centers = nn.Parameter(torch.from_numpy(center_data).float(), | ||
requires_grad=True) | ||
|
||
# MLP | ||
self.l1 = nn.Linear(dim*n_centers, 2*n_centers) | ||
self.l2 = nn.Linear(2*n_centers, n_centers) | ||
self.l3 = nn.Linear(n_centers, n_centers) | ||
|
||
|
||
def forward(self, input, points, indices, next_points=None, normalize=True): | ||
|
||
batch_size = input.size(0) | ||
n_pts = input.size(1) | ||
|
||
# compute indices for indexing points | ||
add_indices = torch.arange(batch_size).type(indices.type()) * n_pts | ||
indices = indices + add_indices.view(-1,1,1) | ||
|
||
# get the features and point cooridnates associated with the indices | ||
features = input.view(-1, input.size(2))[indices] | ||
pts = points.view(-1, points.size(2))[indices] | ||
|
||
# if the projecting points is provided, use it, otherwise, it is gravity center | ||
if next_points is not None: | ||
pts = pts - next_points.unsqueeze(2) | ||
else: | ||
pts = pts - pts.sum(2, keepdim=True) / indices.size(2) | ||
|
||
# normalize to unit ball, or not | ||
if normalize: | ||
maxi = torch.sqrt((pts**2).sum(3).max(2)[0]) | ||
maxi[maxi==0] = 1 | ||
pts = pts / maxi.view(maxi.size()+(1,1,)) | ||
|
||
# compute the distances | ||
dists = pts.view(pts.size()+(1,)) - self.centers | ||
dists = dists.view(dists.size(0), dists.size(1), dists.size(2), -1) | ||
dists = F.relu(self.l1(dists)) | ||
dists = F.relu(self.l2(dists)) | ||
dists = F.relu(self.l3(dists)) | ||
dists = dists.unsqueeze(3) | ||
|
||
# compute features | ||
features = features.view(features.size()+(1,)) * dists | ||
features = features.mean(2) | ||
features = features.view(features.size()+(1,)) * self.weight | ||
features = features.sum([2,3]) | ||
|
||
# add a bias | ||
if self.use_bias: | ||
features = features + self.bias | ||
|
||
return features |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import numpy as np | ||
|
||
#============================================== | ||
#============================================== | ||
# STATS | ||
#============================================== | ||
#============================================== | ||
|
||
|
||
def stats_overall_accuracy(cm): | ||
"""Compute the overall accuracy. | ||
""" | ||
return np.trace(cm)/cm.sum() | ||
|
||
|
||
def stats_pfa_per_class(cm): | ||
"""Compute the probability of false alarms. | ||
""" | ||
sums = np.sum(cm, axis=0) | ||
mask = (sums>0) | ||
sums[sums==0] = 1 | ||
pfa_per_class = (cm.sum(axis=0)-np.diag(cm)) / sums | ||
pfa_per_class[np.logical_not(mask)] = -1 | ||
average_pfa = pfa_per_class[mask].mean() | ||
return average_pfa, pfa_per_class | ||
|
||
|
||
def stats_accuracy_per_class(cm): | ||
"""Compute the accuracy per class and average | ||
puts -1 for invalid values (division per 0) | ||
returns average accuracy, accuracy per class | ||
""" | ||
# equvalent to for class i to | ||
# number or true positive of class i (data[target==i]==i).sum()/ number of elements of i (target==i).sum() | ||
sums = np.sum(cm, axis=1) | ||
mask = (sums>0) | ||
sums[sums==0] = 1 | ||
accuracy_per_class = np.diag(cm) / sums #sum over lines | ||
accuracy_per_class[np.logical_not(mask)] = -1 | ||
average_accuracy = accuracy_per_class[mask].mean() | ||
return average_accuracy, accuracy_per_class | ||
|
||
|
||
def stats_iou_per_class(cm, ignore_missing_classes=True): | ||
"""Compute the iou per class and average iou | ||
Puts -1 for invalid values | ||
returns average iou, iou per class | ||
""" | ||
|
||
sums = (np.sum(cm, axis=1) + np.sum(cm, axis=0) - np.diag(cm)) | ||
mask = (sums>0) | ||
sums[sums==0] = 1 | ||
iou_per_class = np.diag(cm) / sums | ||
iou_per_class[np.logical_not(mask)] = -1 | ||
|
||
if mask.sum()>0: | ||
average_iou = iou_per_class[mask].mean() | ||
else: | ||
average_iou = 0 | ||
|
||
return average_iou, iou_per_class | ||
|
||
|
||
def stats_f1score_per_class(cm): | ||
"""Compute f1 scores per class and mean f1. | ||
puts -1 for invalid classes | ||
returns average f1 score, f1 score per class | ||
""" | ||
# defined as 2 * recall * prec / recall + prec | ||
sums = (np.sum(cm, axis=1) + np.sum(cm, axis=0)) | ||
mask = (sums>0) | ||
sums[sums==0] = 1 | ||
f1score_per_class = 2 * np.diag(cm) / sums | ||
f1score_per_class[np.logical_not(mask)] = -1 | ||
average_f1_score = f1score_per_class[mask].mean() | ||
return average_f1_score, f1score_per_class |
Oops, something went wrong.