Source code for geomfum.sample
"""Sampling methods. This module defines various sampling strategies for geometric shapes, including Poisson disk sampling and farthest point sampling to sample sets of point following geometric indications."""
import abc
import warnings
import numpy as np
from sklearn.neighbors import NearestNeighbors
import geomfum.wrap as _wrap # noqa (for register)
from geomfum._registry import (
PoissonSamplerRegistry,
WhichRegistryMixins,
)
[docs]
class BaseSampler(abc.ABC):
"""Abstract Sampler."""
[docs]
@abc.abstractmethod
def sample(self, shape):
"""Sample shape."""
[docs]
class PoissonSampler(WhichRegistryMixins):
"""Poisson disk sampling."""
_Registry = PoissonSamplerRegistry
[docs]
class FarthestPointSampler(BaseSampler):
"""Farthest point sampling.
Parameters
----------
min_n_samples : int
Minimum number of samples to target.
"""
def __init__(self, min_n_samples):
super().__init__()
self.min_n_samples = min_n_samples
[docs]
def sample(self, shape, first_point=None, points_pool=None):
"""
Perform farthest point sampling on a mesh.
Parameters
----------
shape : TriangleMesh
The mesh to sample points from.
first_point : int, optional
Index of the initial point to start sampling from. If None, a random point is chosen.
points_pool : array-like or int, optional
Pool of candidate points to sample from. If None, all vertices in the mesh are used.
Returns
-------
samples : array-like of shape (min_n_samples,)
Indices of sampled points.
"""
if shape.metric is None:
raise ValueError("d_func should be a callable")
dist_func = shape.metric.dist_from_source
sub_points = (
np.arange(shape.n_vertices)
if points_pool is None
else np.array(points_pool)
)
if first_point is None:
rng = np.random.default_rng()
inds = [rng.choice(sub_points)]
else:
if first_point not in sub_points:
warnings.warn(
f"First index {first_point} is not in the points pool {sub_points}.",
UserWarning,
)
sub_points = np.append(sub_points, first_point)
inds = [first_point]
dists = dist_func(inds[0])[0][sub_points]
for i in range(self.min_n_samples - 1):
if i == self.min_n_samples - 1:
continue
new_subid = np.argmax(dists)
newid = sub_points[new_subid]
inds.append(newid)
dists = np.minimum(dists, dist_func(newid)[0][sub_points])
return np.asarray(inds)
[docs]
class VertexProjectionSampler(BaseSampler):
"""Sample by projecting samples to the closest vertex.
Uses nearest neighbor to get indices of sample coordinates
resulting from another sampler.
Parameters
----------
min_n_samples : int
Minimum number of samples to target.
Ignored if ``sampler`` is not None.
Not guaranteed if ``unique`` is True.
sampler : BaseSampler
Coordinates sampler.
neighbor_finder : sklearn.NearestNeighbors
Nearest neighbors finder.
unique : bool
Whether to remove duplicates.
"""
def __init__(
self, min_n_samples=100, sampler=None, neighbor_finder=None, unique=False
):
super().__init__()
if sampler is None:
sampler = PoissonSampler.from_registry(min_n_samples=min_n_samples)
if neighbor_finder is None:
neighbor_finder = NearestNeighbors(
n_neighbors=1, leaf_size=40, algorithm="kd_tree", n_jobs=1
)
if neighbor_finder.n_neighbors > 1:
raise ValueError("Expects `n_neighbors = 1`.")
self.neighbor_finder = neighbor_finder
self.sampler = sampler
self.unique = unique
[docs]
def sample(self, shape):
"""Sample using Poisson disk sampling.
Parameters
----------
shape : Shape
Shape to be sampled.
Returns
-------
samples : array-like, shape=[n_samples]
Vertex indices of samples.
"""
sampled_points = self.sampler.sample(shape)
self.neighbor_finder.fit(shape.vertices)
_, neighbor_indices = self.neighbor_finder.kneighbors(sampled_points)
if self.unique:
return np.unique(neighbor_indices)
return np.squeeze(neighbor_indices)