Skip to content

Commit

Permalink
Merge pull request #253 from hansen7/hc_dev
Browse files Browse the repository at this point in the history
add farthest point sampling for points sampler, and modify the uniform sampler for the meshes
  • Loading branch information
daavoo authored Aug 26, 2019
2 parents 9184333 + 2facd64 commit 3142da3
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 8 deletions.
2 changes: 1 addition & 1 deletion pyntcloud/io/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def read_obj(filename):
for i in range(sum(c.isdigit() for c in f[0].split(" "))):
mesh_columns.append("v{}".format(i + 1))

mesh = pd.DataFrame([re.split(r'\D+', x) for x in f], dtype='i4', columns=mesh_columns)
mesh = pd.DataFrame([re.split(r'\D+', x) for x in f], dtype='i4', columns=mesh_columns).astype('i4')
mesh -= 1 # index starts with 1 in obj file

data["mesh"] = mesh
Expand Down
3 changes: 2 additions & 1 deletion pyntcloud/samplers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
HAKUNA MATATA
"""

from .points import RandomPointsSampler
from .points import RandomPointsSampler, FarthestPointsSampler
from .mesh import RandomMeshSampler
from .voxelgrid import (
VoxelgridCentersSampler,
Expand All @@ -17,6 +17,7 @@
'mesh_random': RandomMeshSampler,
# Points
'points_random': RandomPointsSampler,
'points_farthest': FarthestPointsSampler,
# Voxelgrid
'voxelgrid_centers': VoxelgridCentersSampler,
'voxelgrid_centroids': VoxelgridCentroidsSampler,
Expand Down
8 changes: 2 additions & 6 deletions pyntcloud/samplers/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,8 @@ def compute(self):
v3_xyz = self.v3_xyz[random_idx]

# (n, 1) the 1 is for broadcasting
u = np.random.rand(self.n, 1)
v = np.random.rand(self.n, 1)
is_a_problem = u + v > 1

u[is_a_problem] = 1 - u[is_a_problem]
v[is_a_problem] = 1 - v[is_a_problem]
u = np.random.uniform(low=0., high=1., size=(self.n, 1))
v = np.random.uniform(low=0., high=1-u, size=(self.n, 1))

result = pd.DataFrame()

Expand Down
56 changes: 56 additions & 0 deletions pyntcloud/samplers/points.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from .base import Sampler
import random
import numpy as np
import pandas as pd


class PointsSampler(Sampler):
Expand All @@ -24,3 +27,56 @@ def compute(self):
if self.n > len(self.points):
raise ValueError("n can't be higher than the number of points in the PyntCloud.")
return self.points.sample(self.n).reset_index(drop=True)


class FarthestPointsSampler(PointsSampler):

"""
Parameters
----------
n: int
Number of unique points that will be chosen.
d_metric: 3*3 numpy array
a positive semi-definite matrix which defines a distance metric
"""

def __init__(self, *, pyntcloud, n, d_metric=np.eye(3)):
"""d_metric -> Euclidean distance space by default, can be modified to other Mahalanobis distance as well"""
super().__init__(pyntcloud=pyntcloud)
self.n = n
if not np.all(np.linalg.eigvals(d_metric) >= 0):
raise ValueError("the distance metric must be positive semi-definite")
self.d_metric = d_metric

def cal_distance(self, point, solution_set):
"""
:param point: points which is not sampled yet, N*10 or N*3 numpy array
:param solution_set: the points which has been selected, M*3 or M*10 array
:return: a (N, ) array, where each element is equal to the sum of distance
of all points in 'solution_set' w.r.t the unselected point in the 'point'
"""
distance_sum = np.zeros(len(point))

for pt in solution_set:
distance_sum += np.diag(np.dot((point[:, :3]-pt[:3]), self.d_metric@(point[:, :3]-pt[:3]).T))
return distance_sum

def compute(self):
"incremental farthest search"
if self.n > len(self.points):
raise ValueError("sampled points can't be more than the original input")
remaining_points = self.points.values

# the sampled points set as the return
select_idx = np.random.randint(low=0, high=len(self.points))
# to remain the shape as (1, n) instead of (n, )
solution_set = remaining_points[select_idx: select_idx+1]
remaining_points = np.delete(remaining_points, select_idx, 0)

for _ in range(self.n - 1):
distance_sum = self.cal_distance(remaining_points, solution_set)
select_idx = np.argmax(distance_sum)
solution_set = np.concatenate([solution_set, remaining_points[select_idx:select_idx+1]], axis=0)
remaining_points = np.delete(remaining_points, select_idx, 0)

return pd.DataFrame(solution_set, columns=self.points.columns)
30 changes: 30 additions & 0 deletions tests/integration/samplers/test_points_samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_mesh_random_sampling_return_type(simple_pyntcloud):
as_PyntCloud=True)
assert type(sample) == PyntCloud


@pytest.mark.parametrize("n", [
1,
5,
Expand Down Expand Up @@ -48,3 +49,32 @@ def test_RandomPointsSampler_sampled_points_are_from_original(simple_pyntcloud):
n=1)
assert point_in_array_2D(sample, simple_pyntcloud.xyz)


@pytest.mark.parametrize("n", [
1,
5,
6
])
@pytest.mark.usefixtures("simple_pyntcloud")
def test_FarthestPointsSampler_n_argument(simple_pyntcloud, n):
sample = simple_pyntcloud.get_sample(
"points_farthest",
n=n)
assert len(sample) == n


@pytest.mark.usefixtures("simple_pyntcloud")
def test_FarthestPointsSampler_raises_ValueError_on_invalid_n(simple_pyntcloud):
with pytest.raises(ValueError):
simple_pyntcloud.get_sample(
"points_farthest",
n=10)


@pytest.mark.usefixtures("simple_pyntcloud")
def test_FarthestPointsSampler_sampled_points_are_from_original(simple_pyntcloud):
for i in range(10):
sample = simple_pyntcloud.get_sample(
"points_farthest",
n=1)
assert point_in_array_2D(sample, simple_pyntcloud.xyz)

0 comments on commit 3142da3

Please sign in to comment.