From 7ac34138c40cce37b9cb5ae89767c2520d13c19b Mon Sep 17 00:00:00 2001 From: yunyv <62236062+yunyv@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:02:06 -0600 Subject: [PATCH 1/3] fix(mapping): validate farthest-point sampling count Validate counts before selecting points and preserve empty results and existing valid sampling behavior. --- .../point_cloud_sampling.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Mapping/point_cloud_sampling/point_cloud_sampling.py b/Mapping/point_cloud_sampling/point_cloud_sampling.py index df7cde41c0..e126346809 100644 --- a/Mapping/point_cloud_sampling/point_cloud_sampling.py +++ b/Mapping/point_cloud_sampling/point_cloud_sampling.py @@ -5,6 +5,8 @@ - Poisson disk sampling """ +import operator + import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt @@ -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] From aaf4fc86dee3be23c283b5b628ca3c891f0f3b76 Mon Sep 17 00:00:00 2001 From: yunyv <62236062+yunyv@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:07:45 -0600 Subject: [PATCH 2/3] test(mapping): cover farthest-point sampling count boundaries Cover zero, invalid and NumPy integer counts, seeded ordering and duplicate input rows. --- tests/test_point_cloud_sampling.py | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_point_cloud_sampling.py b/tests/test_point_cloud_sampling.py index 8f6447c69c..0a9c87925d 100644 --- a/tests/test_point_cloud_sampling.py +++ b/tests/test_point_cloud_sampling.py @@ -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 @@ -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__) From 4d7e86879553955ba17dad5609c87d6947761bfa Mon Sep 17 00:00:00 2001 From: yunyv <62236062+yunyv@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:11:55 -0600 Subject: [PATCH 3/3] docs(mapping): explain farthest-point sampling count limits Document the integer count range, empty results, duplicate rows and invalid-count errors. --- .../point_cloud_sampling/point_cloud_sampling_main.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst b/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst index 8cb08d4b78..f15df69519 100644 --- a/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst +++ b/docs/modules/3_mapping/point_cloud_sampling/point_cloud_sampling_main.rst @@ -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 =====