Source code for geomfum.sample

"""Sampling methods."""

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): """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)