-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding sample_mesh.py function (#29)
- Loading branch information
Showing
5 changed files
with
197 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import numpy as np | ||
from .doublearea import doublearea | ||
|
||
def random_points_on_mesh(V,F, | ||
n, | ||
rng=np.random.default_rng(), | ||
distribution='uniform', | ||
return_indices=False): | ||
"""Samples a mesh V,F according to a given distribution. | ||
Valid meshes are polylines or triangle meshes. | ||
Parameters | ||
---------- | ||
V : (n_v,d) numpy array | ||
vertex list of vertex positions | ||
F : (m,k) numpy int array | ||
if k==2, interpret input as ordered polyline; | ||
if k==3 numpy int array, interpred as face index list of a triangle | ||
mesh | ||
rng : numpy rng, optional (default: new `np.random.default_rng()`) | ||
which numpy random number generator to use | ||
n : int | ||
how many points to sample | ||
method : string, optional (default: 'uniform') | ||
According to which distribution to sample. | ||
Currently, only uniform is supported. | ||
return_indices : bool, optional (default: False) | ||
Whether to return the indices for each element along with the | ||
barycentric coordinates of the sampled points within each element | ||
Returns | ||
------- | ||
x : (n,d) numpy array | ||
the n sampled points | ||
I : (n,) numpy int array | ||
element indices where sampled points lie (if requested) | ||
u : (n,k) numpy array | ||
barycentric coordinates for sampled points in I | ||
Examples | ||
-------- | ||
TODO | ||
""" | ||
|
||
assert n>=0 | ||
|
||
if n==0: | ||
if return_indices: | ||
return np.zeros((0,), dtype=V.dtype), \ | ||
np.zeros((0,), dtype=F.dtype), \ | ||
np.zeros((0,), dtype=V.dtype) | ||
else: | ||
return np.zeros((0,), dtype=V.dtype) | ||
|
||
k = F.shape[1] | ||
if k==2: | ||
if distribution=='uniform': | ||
I,u = _uniform_sample_polyline(V,F,n,rng,distribution) | ||
else: | ||
assert False, "distribution not supported" | ||
x = u[:,0][:,None]*V[F[I,0],:] + \ | ||
u[:,1][:,None]*V[F[I,1],:] | ||
elif k==3: | ||
if distribution=='uniform': | ||
I,u = _uniform_sample_triangle_mesh(V,F,n,rng,distribution) | ||
else: | ||
assert False, "distribution not supported" | ||
x = u[:,0][:,None]*V[F[I,0],:] + \ | ||
u[:,1][:,None]*V[F[I,1],:] + \ | ||
u[:,2][:,None]*V[F[I,2],:] | ||
else: | ||
assert False, "Only polylines and triangles supported" | ||
|
||
if return_indices: | ||
return x, I, u | ||
else: | ||
return x | ||
|
||
|
||
def _uniform_sample_polyline(V,E,n,rng,distribution): | ||
l = np.linalg.norm(V[E[:,1],:] - V[E[:,0],:], axis=1) | ||
w = l / np.sum(l) | ||
|
||
I = rng.choice(w.shape[0], size=(n,), p=w) | ||
|
||
r = rng.uniform(size=(n,)) | ||
u = np.stack([r, 1.-r], axis=-1) | ||
|
||
return I, u | ||
|
||
|
||
def _uniform_sample_triangle_mesh(V,F,n,rng,distribution): | ||
# Adapted partially from code by Justin Solomon, and math from | ||
# https://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle-in-3d | ||
|
||
A = doublearea(V,F) | ||
w = A / np.sum(A) | ||
|
||
I = rng.choice(w.shape[0], size=(n,), p=w) | ||
|
||
r = rng.uniform(size=(n,2)) | ||
r2 = r[:,0] | ||
sqrtr = np.sqrt(r[:,1]) | ||
u = np.stack([1.-sqrtr, sqrtr * (1.-r2), r2*sqrtr], axis=-1) | ||
|
||
return I, u |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from .context import gpytoolbox as gpy | ||
from .context import unittest | ||
from .context import numpy as np | ||
|
||
|
||
class TestRandomPointsOnMesh(unittest.TestCase): | ||
def test_is_uniform(self): | ||
|
||
# Do not error on zero input | ||
x = gpy.random_points_on_mesh(np.array([]), np.array([], dtype=int), n=0) | ||
self.assertTrue(len(x)==0) | ||
|
||
# Test 1: Straight line | ||
V = np.array([[0.0,0.0],[1.0,1.0],[2.0,2.0]]) | ||
E = gpy.edge_indices(V.shape[0],closed=False) | ||
n = 100000 | ||
rng = np.random.default_rng(5) | ||
x = gpy.random_points_on_mesh(V, E, n, rng=rng) | ||
rng = np.random.default_rng(5) | ||
y,I,u = gpy.random_points_on_mesh(V, E, n, rng=rng, return_indices=True) | ||
|
||
self.check_consistency(V, E, [x,y], I, u) | ||
for d in range(2): | ||
hist, bin_edges = np.histogram(x[:,d],bins=20, density=True) | ||
self.assertTrue(np.std(hist)<0.01) | ||
self.assertTrue(np.isclose(np.mean(hist),0.5,atol=1e-3)) | ||
self.assertTrue(np.isclose(np.mean(x[:,d]),1.,atol=1e-2)) | ||
|
||
# Sample grid. | ||
V,F = gpy.regular_square_mesh(30) | ||
n = 100000 | ||
rng = np.random.default_rng(526) | ||
x = gpy.random_points_on_mesh(V, F, n, rng=rng) | ||
rng = np.random.default_rng(526) | ||
y,I,u = gpy.random_points_on_mesh(V, F, n, rng=rng, return_indices=True) | ||
|
||
self.check_consistency(V, F, [x,y], I, u) | ||
for d in range(2): | ||
hist, bin_edges = np.histogram(x[:,d],bins=20, density=True) | ||
self.assertTrue(np.std(hist)<0.01) | ||
self.assertTrue(np.isclose(np.mean(hist),0.5,atol=1e-3)) | ||
self.assertTrue(np.isclose(np.mean(x[:,d]),0.,atol=1e-2)) | ||
|
||
# Sample cube. This time, no histogram test, just mean of output | ||
V,F = gpy.read_mesh("test/unit_tests_data/cube.obj") | ||
n = 100000 | ||
rng = np.random.default_rng(80) | ||
x = gpy.random_points_on_mesh(V, F, n, rng=rng) | ||
rng = np.random.default_rng(80) | ||
y,I,u = gpy.random_points_on_mesh(V, F, n, rng=rng, return_indices=True) | ||
|
||
self.check_consistency(V, F, [x,y], I, u) | ||
for d in range(3): | ||
self.assertTrue(np.isclose(np.mean(x[:,d]),0.,atol=1e-2)) | ||
|
||
|
||
def check_consistency(self, V, F, xs, I=None, u=None): | ||
if len(xs)<1: | ||
return None | ||
for x in xs: | ||
self.assertTrue(np.isclose(x,xs[0]).all()) | ||
if I is not None and u is not None: | ||
y = 0. | ||
for d in range(F.shape[1]): | ||
y = y + u[:,d][:,None]*V[F[I,d],:] | ||
self.assertTrue(np.isclose(y,xs[0]).all()) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |