|
| 1 | +""" |
| 2 | +Point cloud sampling example codes. This code supports |
| 3 | +- Voxel point sampling |
| 4 | +- Farthest point sampling |
| 5 | +- Poisson disk sampling |
| 6 | +
|
| 7 | +""" |
| 8 | +import matplotlib.pyplot as plt |
| 9 | +import numpy as np |
| 10 | +import numpy.typing as npt |
| 11 | +from collections import defaultdict |
| 12 | + |
| 13 | +do_plot = True |
| 14 | + |
| 15 | + |
| 16 | +def voxel_point_sampling(original_points: npt.NDArray, voxel_size: float): |
| 17 | + """ |
| 18 | + Voxel Point Sampling function. |
| 19 | + This function sample N-dimensional points with voxel grid. |
| 20 | + Points in a same voxel grid will be merged by mean operation for sampling. |
| 21 | +
|
| 22 | + Parameters |
| 23 | + ---------- |
| 24 | + original_points : (M, N) N-dimensional points for sampling. |
| 25 | + The number of points is M. |
| 26 | + voxel_size : voxel grid size |
| 27 | +
|
| 28 | + Returns |
| 29 | + ------- |
| 30 | + sampled points (M', N) |
| 31 | + """ |
| 32 | + voxel_dict = defaultdict(list) |
| 33 | + for i in range(original_points.shape[0]): |
| 34 | + xyz = original_points[i, :] |
| 35 | + xyz_index = tuple(xyz // voxel_size) |
| 36 | + voxel_dict[xyz_index].append(xyz) |
| 37 | + points = np.vstack([np.mean(v, axis=0) for v in voxel_dict.values()]) |
| 38 | + return points |
| 39 | + |
| 40 | + |
| 41 | +def farthest_point_sampling(orig_points: npt.NDArray, |
| 42 | + n_points: int, seed: int): |
| 43 | + """ |
| 44 | + Farthest point sampling function |
| 45 | + This function sample N-dimensional points with the farthest point policy. |
| 46 | +
|
| 47 | + Parameters |
| 48 | + ---------- |
| 49 | + orig_points : (M, N) N-dimensional points for sampling. |
| 50 | + The number of points is M. |
| 51 | + n_points : number of points for sampling |
| 52 | + seed : random seed number |
| 53 | +
|
| 54 | + Returns |
| 55 | + ------- |
| 56 | + sampled points (n_points, N) |
| 57 | +
|
| 58 | + """ |
| 59 | + rng = np.random.default_rng(seed) |
| 60 | + n_orig_points = orig_points.shape[0] |
| 61 | + first_point_id = rng.choice(range(n_orig_points)) |
| 62 | + min_distances = np.ones(n_orig_points) * float("inf") |
| 63 | + selected_ids = [first_point_id] |
| 64 | + while len(selected_ids) < n_points: |
| 65 | + base_point = orig_points[selected_ids[-1], :] |
| 66 | + distances = np.linalg.norm(orig_points[np.newaxis, :] - base_point, |
| 67 | + axis=2).flatten() |
| 68 | + min_distances = np.minimum(min_distances, distances) |
| 69 | + distances_rank = np.argsort(-min_distances) # Farthest order |
| 70 | + for i in distances_rank: # From the farthest point |
| 71 | + if i not in selected_ids: # if not selected yes, select it |
| 72 | + selected_ids.append(i) |
| 73 | + break |
| 74 | + return orig_points[selected_ids, :] |
| 75 | + |
| 76 | + |
| 77 | +def poisson_disk_sampling(orig_points: npt.NDArray, n_points: int, |
| 78 | + min_distance: float, seed: int, MAX_ITER=1000): |
| 79 | + """ |
| 80 | + Poisson disk sampling function |
| 81 | + This function sample N-dimensional points randomly until the number of |
| 82 | + points keeping minimum distance between selected points. |
| 83 | +
|
| 84 | + Parameters |
| 85 | + ---------- |
| 86 | + orig_points : (M, N) N-dimensional points for sampling. |
| 87 | + The number of points is M. |
| 88 | + n_points : number of points for sampling |
| 89 | + min_distance : minimum distance between selected points. |
| 90 | + seed : random seed number |
| 91 | + MAX_ITER : Maximum number of iteration. Default is 1000. |
| 92 | +
|
| 93 | + Returns |
| 94 | + ------- |
| 95 | + sampled points (n_points or less, N) |
| 96 | + """ |
| 97 | + rng = np.random.default_rng(seed) |
| 98 | + selected_id = rng.choice(range(orig_points.shape[0])) |
| 99 | + selected_ids = [selected_id] |
| 100 | + loop = 0 |
| 101 | + while len(selected_ids) < n_points and loop <= MAX_ITER: |
| 102 | + selected_id = rng.choice(range(orig_points.shape[0])) |
| 103 | + base_point = orig_points[selected_id, :] |
| 104 | + distances = np.linalg.norm( |
| 105 | + orig_points[np.newaxis, selected_ids] - base_point, |
| 106 | + axis=2).flatten() |
| 107 | + if min(distances) >= min_distance: |
| 108 | + selected_ids.append(selected_id) |
| 109 | + loop += 1 |
| 110 | + if len(selected_ids) != n_points: |
| 111 | + print("Could not find the specified number of points...") |
| 112 | + |
| 113 | + return orig_points[selected_ids, :] |
| 114 | + |
| 115 | + |
| 116 | +def plot_sampled_points(original_points, sampled_points, method_name): |
| 117 | + fig = plt.figure() |
| 118 | + ax = fig.add_subplot(projection='3d') |
| 119 | + ax.scatter(original_points[:, 0], original_points[:, 1], |
| 120 | + original_points[:, 2], marker=".", label="Original points") |
| 121 | + ax.scatter(sampled_points[:, 0], sampled_points[:, 1], |
| 122 | + sampled_points[:, 2], marker="o", |
| 123 | + label="Filtered points") |
| 124 | + plt.legend() |
| 125 | + plt.title(method_name) |
| 126 | + plt.axis("equal") |
| 127 | + |
| 128 | + |
| 129 | +def main(): |
| 130 | + n_points = 1000 |
| 131 | + seed = 1234 |
| 132 | + rng = np.random.default_rng(seed) |
| 133 | + |
| 134 | + x = rng.normal(0.0, 10.0, n_points) |
| 135 | + y = rng.normal(0.0, 1.0, n_points) |
| 136 | + z = rng.normal(0.0, 10.0, n_points) |
| 137 | + original_points = np.vstack((x, y, z)).T |
| 138 | + print(f"{original_points.shape=}") |
| 139 | + print("Voxel point sampling") |
| 140 | + voxel_size = 20.0 |
| 141 | + voxel_sampling_points = voxel_point_sampling(original_points, voxel_size) |
| 142 | + print(f"{voxel_sampling_points.shape=}") |
| 143 | + |
| 144 | + print("Farthest point sampling") |
| 145 | + n_points = 20 |
| 146 | + farthest_sampling_points = farthest_point_sampling(original_points, |
| 147 | + n_points, seed) |
| 148 | + print(f"{farthest_sampling_points.shape=}") |
| 149 | + |
| 150 | + print("Poisson disk sampling") |
| 151 | + n_points = 20 |
| 152 | + min_distance = 10.0 |
| 153 | + poisson_disk_points = poisson_disk_sampling(original_points, n_points, |
| 154 | + min_distance, seed) |
| 155 | + print(f"{poisson_disk_points.shape=}") |
| 156 | + |
| 157 | + if do_plot: |
| 158 | + plot_sampled_points(original_points, voxel_sampling_points, |
| 159 | + "Voxel point sampling") |
| 160 | + plot_sampled_points(original_points, farthest_sampling_points, |
| 161 | + "Farthest point sampling") |
| 162 | + plot_sampled_points(original_points, poisson_disk_points, |
| 163 | + "poisson disk sampling") |
| 164 | + plt.show() |
| 165 | + |
| 166 | + |
| 167 | +if __name__ == '__main__': |
| 168 | + main() |
0 commit comments