Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Mapping/point_cloud_sampling/point_cloud_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- Poisson disk sampling

"""
import operator

import matplotlib.pyplot as plt
import numpy as np
import numpy.typing as npt
Expand Down Expand Up @@ -48,16 +50,28 @@ def farthest_point_sampling(orig_points: npt.NDArray,
----------
orig_points : (M, N) N-dimensional points for sampling.
The number of points is M.
n_points : number of points for sampling
n_points : integer number of points for sampling, from 0 to M inclusive.
seed : random seed number

Returns
-------
sampled points (n_points, N)
sampled points (n_points, N), with the input dtype.

Raises
------
TypeError
If n_points is not an integer.
ValueError
If n_points is negative or exceeds the number of input rows.

"""
rng = np.random.default_rng(seed)
n_points = operator.index(n_points)
n_orig_points = orig_points.shape[0]
if not 0 <= n_points <= n_orig_points:
raise ValueError("n_points must be between 0 and the number of input rows")
if n_points == 0:
return orig_points[:0].copy()
rng = np.random.default_rng(seed)
first_point_id = rng.choice(range(n_orig_points))
min_distances = np.ones(n_orig_points) * float("inf")
selected_ids = [first_point_id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ possible.
This method is useful for machine learning and other situations where
you want to obtain a specified number of points from point cloud.

The requested count must be an integer between zero and the number of input
rows, inclusive. Each input row is selected at most once, even when multiple
rows have identical coordinates. A zero count returns an empty array with the
same coordinate dimension and dtype as the input. Negative or excessive counts
raise ``ValueError`` instead of starting a sampling loop that cannot finish.

API
=====

Expand Down
45 changes: 45 additions & 0 deletions tests/test_point_cloud_sampling.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import numpy as np
import pytest

import conftest # Add root path to sys.path
from Mapping.point_cloud_sampling import point_cloud_sampling as m

Expand All @@ -11,5 +14,47 @@ def test_1(capsys):
assert "poisson_disk_points.shape=(20, 3)" in captured.out


@pytest.mark.parametrize("points", [np.empty((0, 3)), np.ones((4, 2), dtype=np.int32)])
def test_farthest_sampling_zero_count(points):
sampled = m.farthest_point_sampling(points, 0, seed=0)
assert sampled.shape == (0, points.shape[1])
assert sampled.dtype == points.dtype


@pytest.mark.parametrize("count", [-1, 3])
def test_farthest_sampling_rejects_unavailable_count(count):
with pytest.raises(ValueError, match="between 0 and"):
m.farthest_point_sampling(np.zeros((2, 3)), count, seed=0)


def test_farthest_sampling_rejects_nonempty_sample_from_empty_cloud():
with pytest.raises(ValueError, match="between 0 and"):
m.farthest_point_sampling(np.empty((0, 3)), 1, seed=0)


@pytest.mark.parametrize("count", [1.5, 2.0, "2", None])
def test_farthest_sampling_requires_integer_count(count):
with pytest.raises(TypeError):
m.farthest_point_sampling(np.zeros((3, 2)), count, seed=0)


@pytest.mark.parametrize("seed, expected", [
(0, [[16, 0], [0, 0], [9, 0], [4, 0]]),
(42, [[0, 0], [16, 0], [9, 0], [4, 0]]),
])
def test_farthest_sampling_preserves_seeded_order(seed, expected):
points = np.array([[0, 0], [1, 0], [4, 0], [9, 0], [16, 0]])
np.testing.assert_array_equal(
m.farthest_point_sampling(points, np.int64(4), seed), expected)


def test_farthest_sampling_duplicate_points_use_distinct_rows():
points = np.array([[0, 0], [0, 0], [1, 0], [1, 0]])
original = points.copy()
sampled = m.farthest_point_sampling(points, len(points), seed=0)
np.testing.assert_array_equal(np.sort(sampled, axis=0), points)
np.testing.assert_array_equal(points, original)


if __name__ == '__main__':
conftest.run_this_test(__file__)