-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: C++ implementation of iterative farthest point sampling. Reviewed By: jcjohnson Differential Revision: D30349887 fbshipit-source-id: d25990f857752633859fe00283e182858a870269
- Loading branch information
1 parent
3b7d78c
commit d9f7611
Showing
6 changed files
with
346 additions
and
19 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
107 changes: 107 additions & 0 deletions
107
pytorch3d/csrc/sample_farthest_points/sample_farthest_points.cpp
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,107 @@ | ||
/* | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
#include <torch/extension.h> | ||
#include <iterator> | ||
#include <random> | ||
#include <vector> | ||
|
||
at::Tensor FarthestPointSamplingCpu( | ||
const at::Tensor& points, | ||
const at::Tensor& lengths, | ||
const at::Tensor& K, | ||
const bool random_start_point) { | ||
// Get constants | ||
const int64_t N = points.size(0); | ||
const int64_t P = points.size(1); | ||
const int64_t D = points.size(2); | ||
const int64_t max_K = torch::max(K).item<int64_t>(); | ||
|
||
// Initialize an output array for the sampled indices | ||
// of shape (N, max_K) | ||
auto long_opts = lengths.options(); | ||
torch::Tensor sampled_indices = torch::full({N, max_K}, -1, long_opts); | ||
|
||
// Create accessors for all tensors | ||
auto points_a = points.accessor<float, 3>(); | ||
auto lengths_a = lengths.accessor<int64_t, 1>(); | ||
auto k_a = K.accessor<int64_t, 1>(); | ||
auto sampled_indices_a = sampled_indices.accessor<int64_t, 2>(); | ||
|
||
// Initialize a mask to prevent duplicates | ||
// If true, the point has already been selected. | ||
std::vector<unsigned char> selected_points_mask(P, false); | ||
|
||
// Initialize to infinity a vector of | ||
// distances from each point to any of the previously selected points | ||
std::vector<float> dists(P, std::numeric_limits<float>::max()); | ||
|
||
// Initialize random number generation for random starting points | ||
std::random_device rd; | ||
std::default_random_engine eng(rd()); | ||
|
||
for (int64_t n = 0; n < N; ++n) { | ||
// Resize and reset points mask and distances for each batch | ||
selected_points_mask.resize(lengths_a[n]); | ||
dists.resize(lengths_a[n]); | ||
std::fill(selected_points_mask.begin(), selected_points_mask.end(), false); | ||
std::fill(dists.begin(), dists.end(), std::numeric_limits<float>::max()); | ||
|
||
// Select a starting point index and save it | ||
std::uniform_int_distribution<int> distr(0, lengths_a[n] - 1); | ||
int64_t last_idx = random_start_point ? distr(eng) : 0; | ||
sampled_indices_a[n][0] = last_idx; | ||
|
||
// Set the value of the mask at this point to false | ||
selected_points_mask[last_idx] = true; | ||
|
||
// For heterogeneous pointclouds, use the minimum of the | ||
// length for that cloud compared to K as the number of | ||
// points to sample | ||
const int64_t batch_k = std::min(lengths_a[n], k_a[n]); | ||
|
||
// Iteratively select batch_k points per batch | ||
for (int64_t k = 1; k < batch_k; ++k) { | ||
// Iterate through all the points | ||
for (int64_t p = 0; p < lengths_a[n]; ++p) { | ||
if (selected_points_mask[p]) { | ||
// For already selected points set the distance to 0.0 | ||
dists[p] = 0.0; | ||
continue; | ||
} | ||
|
||
// Calculate the distance to the last selected point | ||
float dist2 = 0.0; | ||
for (int64_t d = 0; d < D; ++d) { | ||
float diff = points_a[n][last_idx][d] - points_a[n][p][d]; | ||
dist2 += diff * diff; | ||
} | ||
|
||
// If the distance of this point to the last selected point is closer | ||
// than the distance to any of the previously selected points, then | ||
// update this distance | ||
if (dist2 < dists[p]) { | ||
dists[p] = dist2; | ||
} | ||
} | ||
|
||
// The aim is to pick the point that has the largest | ||
// nearest neighbour distance to any of the already selected points | ||
auto itr = std::max_element(dists.begin(), dists.end()); | ||
last_idx = std::distance(dists.begin(), itr); | ||
|
||
// Save selected point | ||
sampled_indices_a[n][k] = last_idx; | ||
|
||
// Set the mask value to true to prevent duplicates. | ||
selected_points_mask[last_idx] = true; | ||
} | ||
} | ||
|
||
return sampled_indices; | ||
} |
56 changes: 56 additions & 0 deletions
56
pytorch3d/csrc/sample_farthest_points/sample_farthest_points.h
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,56 @@ | ||
/* | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
#pragma once | ||
#include <torch/extension.h> | ||
#include <tuple> | ||
#include "utils/pytorch3d_cutils.h" | ||
|
||
// Iterative farthest point sampling algorithm [1] to subsample a set of | ||
// K points from a given pointcloud. At each iteration, a point is selected | ||
// which has the largest nearest neighbor distance to any of the | ||
// already selected points. | ||
|
||
// Farthest point sampling provides more uniform coverage of the input | ||
// point cloud compared to uniform random sampling. | ||
|
||
// [1] Charles R. Qi et al, "PointNet++: Deep Hierarchical Feature Learning | ||
// on Point Sets in a Metric Space", NeurIPS 2017. | ||
|
||
// Args: | ||
// points: (N, P, D) float32 Tensor containing the batch of pointclouds. | ||
// lengths: (N,) long Tensor giving the number of points in each pointcloud | ||
// (to support heterogeneous batches of pointclouds). | ||
// K: a tensor of length (N,) giving the number of | ||
// samples to select for each element in the batch. | ||
// The number of samples is typically << P. | ||
// random_start_point: bool, if True, a random point is selected as the | ||
// starting point for iterative sampling. | ||
// Returns: | ||
// selected_indices: (N, K) array of selected indices. If the values in | ||
// K are not all the same, then the shape will be (N, max(K), D), and | ||
// padded with -1 for batch elements where k_i < max(K). The selected | ||
// points are gathered in the pytorch autograd wrapper. | ||
|
||
at::Tensor FarthestPointSamplingCpu( | ||
const at::Tensor& points, | ||
const at::Tensor& lengths, | ||
const at::Tensor& K, | ||
const bool random_start_point); | ||
|
||
// Exposed implementation. | ||
at::Tensor FarthestPointSampling( | ||
const at::Tensor& points, | ||
const at::Tensor& lengths, | ||
const at::Tensor& K, | ||
const bool random_start_point) { | ||
if (points.is_cuda() || lengths.is_cuda() || K.is_cuda()) { | ||
AT_ERROR("CUDA implementation not yet supported"); | ||
} | ||
return FarthestPointSamplingCpu(points, lengths, K, random_start_point); | ||
} |
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
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,37 @@ | ||
# Copyright (c) Facebook, Inc. and its affiliates. | ||
# All rights reserved. | ||
# | ||
# This source code is licensed under the BSD-style license found in the | ||
# LICENSE file in the root directory of this source tree. | ||
|
||
from itertools import product | ||
|
||
from fvcore.common.benchmark import benchmark | ||
from test_sample_farthest_points import TestFPS | ||
|
||
|
||
def bm_fps() -> None: | ||
kwargs_list = [] | ||
backends = ["cpu", "cuda:0"] | ||
Ns = [8, 32] | ||
Ps = [64, 256] | ||
Ds = [3] | ||
Ks = [24] | ||
test_cases = product(Ns, Ps, Ds, Ks, backends) | ||
for case in test_cases: | ||
N, P, D, K, d = case | ||
kwargs_list.append({"N": N, "P": P, "D": D, "K": K, "device": d}) | ||
|
||
benchmark( | ||
TestFPS.sample_farthest_points_naive, | ||
"FPS_NAIVE_PYTHON", | ||
kwargs_list, | ||
warmup_iters=1, | ||
) | ||
|
||
kwargs_list = [k for k in kwargs_list if k["device"] == "cpu"] | ||
benchmark(TestFPS.sample_farthest_points, "FPS_CPU", kwargs_list, warmup_iters=1) | ||
|
||
|
||
if __name__ == "__main__": | ||
bm_fps() |
Oops, something went wrong.