Skip to content

Commit f4934fc

Browse files
committed
simplify fix and points test
1 parent c5fb85d commit f4934fc

5 files changed

Lines changed: 90 additions & 73 deletions

File tree

tests/test_points.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
class PointsTest(g.unittest.TestCase):
88

99
def test_pointcloud(self):
10-
'''
11-
Test pointcloud object
12-
'''
10+
"""
11+
Test PointCloud object
12+
"""
1313
shape = (100, 3)
1414
# random points
1515
points = g.np.random.random(shape)
@@ -22,16 +22,20 @@ def test_pointcloud(self):
2222
# create a pointcloud object
2323
cloud = g.trimesh.points.PointCloud(points)
2424

25-
# set some random colors
26-
cloud.colors = g.np.random.random((shape[0], 4))
25+
initial_md5 = cloud.md5()
2726

2827
assert cloud.convex_hull.volume > 0.0
2928

3029
# check shapes of data
3130
assert cloud.vertices.shape == shape
31+
assert cloud.shape == shape
3232
assert cloud.extents.shape == (3,)
3333
assert cloud.bounds.shape == (2, 3)
3434

35+
assert cloud.md5() == initial_md5
36+
37+
# set some random colors
38+
cloud.colors = g.np.random.random((shape[0], 4))
3539
# remove the duplicates we created
3640
cloud.merge_vertices()
3741

@@ -41,6 +45,11 @@ def test_pointcloud(self):
4145
# make sure vertices and colors are new shape
4246
assert cloud.vertices.shape == new_shape
4347
assert len(cloud.colors) == new_shape[0]
48+
assert cloud.md5() != initial_md5
49+
50+
# check getitem and setitem
51+
cloud[0] = [10, 10, 10]
52+
assert g.np.allclose(cloud[0], [10, 10, 10])
4453

4554
def test_vertex_only(self):
4655
"""
@@ -79,6 +88,30 @@ def test_plane(self):
7988
# sign of normal is arbitrary on fit so check both
8089
assert g.np.allclose(truth, N) or g.np.allclose(truth, -N)
8190

91+
def test_kmeans(self,
92+
cluster_count=5,
93+
points_per_cluster=100):
94+
"""
95+
Test K-means clustering
96+
"""
97+
clustered = []
98+
for i in range(cluster_count):
99+
clustered.append(
100+
g.np.random.random((points_per_cluster, 3)) + (i * 10.0))
101+
clustered = g.np.vstack(clustered)
102+
103+
# run k- means clustering on our nicely separated data
104+
centroids, klabel = g.trimesh.points.k_means(points=clustered,
105+
k=cluster_count)
106+
107+
# reshape to make sure all groups have the same index
108+
variance = klabel.reshape(
109+
(cluster_count, points_per_cluster)).ptp(
110+
axis=1)
111+
112+
assert len(centroids) == cluster_count
113+
assert (variance == 0).all()
114+
82115

83116
if __name__ == '__main__':
84117
g.trimesh.util.attach_to_log()

tests/test_simplify.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ def test_simplify(self):
6868
self.polygon_simplify(polygon=polygon,
6969
arc_count=arc_count)
7070

71+
def test_spline(self):
72+
"""
73+
Test basic spline simplification of Path2D objects
74+
"""
75+
scene = g.get_mesh('cycloidal.3DXML')
76+
m = scene.geometry['disc_cam_A']
77+
78+
path_3D = m.outline(m.facets[m.facets_area.argmax()])
79+
path_2D, to_3D = path_3D.to_planar()
80+
81+
simple = g.trimesh.path.simplify.simplify_spline(path_2D,
82+
smooth=.01,
83+
verbose=True)
84+
assert g.np.isclose(path_2D.area, simple.area, rtol=.01)
7185

7286
if __name__ == '__main__':
7387
g.trimesh.util.attach_to_log()

trimesh/path/simplify.py

Lines changed: 32 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def resample_spline(points, smooth=.001, count=None, degree=3):
232232
return resampled
233233

234234

235-
def points_to_spline_entity(points, smooth=.0005, count=None):
235+
def points_to_spline_entity(points, smooth=None, count=None):
236236
"""
237237
Create a spline entity from a curve in space
238238
@@ -251,7 +251,10 @@ def points_to_spline_entity(points, smooth=.0005, count=None):
251251
from scipy.interpolate import splprep
252252
if count is None:
253253
count = len(points)
254-
points = np.asanyarray(points)
254+
if smooth is None:
255+
smooth = 0.002
256+
257+
points = np.asanyarray(points, dtype=np.float64)
255258
closed = np.linalg.norm(points[0] - points[-1]) < tol.merge
256259

257260
knots, control, degree = splprep(points.T, s=smooth)[0]
@@ -270,25 +273,6 @@ def points_to_spline_entity(points, smooth=.0005, count=None):
270273
return entity, control
271274

272275

273-
def three_point(indices):
274-
"""
275-
Given a long list of ordered indices,
276-
return the first, middle and last.
277-
278-
Parameters
279-
-----------
280-
indices: (n,) array
281-
282-
Returns
283-
----------
284-
three: (3,) array
285-
"""
286-
three = [indices[0],
287-
indices[int(len(indices) / 2)],
288-
indices[-1]]
289-
return np.asanyarray(three)
290-
291-
292276
def simplify_basic(drawing, process=False, **kwargs):
293277
"""
294278
Merge colinear segments and fit circles.
@@ -367,51 +351,51 @@ def simplify_basic(drawing, process=False, **kwargs):
367351
return simplified
368352

369353

370-
def simplify_spline(path, smooth, path_indexes=None):
354+
def simplify_spline(path, smooth=None, verbose=False):
371355
"""
372-
Replace discrete curves with b-spline curves, and
356+
Replace discrete curves with b-spline or Arc and
373357
return the result as a new Path2D object.
374358
375359
Parameters
376360
------------
377-
path: Path2D object
378-
smooth: float, amount to smooth
379-
path_indexes: (n,) int, indexes of path.paths to simplify
361+
path : trimesh.path.Path2D
362+
Input geometry
363+
smooth : float
364+
Distance to smooth
380365
381366
Returns
382367
------------
383-
simplified: Path2D object, with specified entities replaced
368+
simplified : Path2D
369+
Consists of Arc and BSpline entities
384370
"""
385-
# if we aren't simplifying specific indexes
386-
# simplify all indexes in the path object
387-
if path_indexes is None:
388-
path_indexes = np.arange(len(path.paths))
389371

390-
entities_keep = np.ones(len(path.entities),
391-
dtype=np.bool)
392372
new_vertices = []
393373
new_entities = []
374+
scale = path.scale
375+
376+
for discrete in path.discrete:
377+
circle = is_circle(discrete,
378+
scale=scale,
379+
verbose=verbose)
380+
if circle is not None:
381+
# the points are circular enough for our high standards
382+
# so replace them with a closed Arc entity
383+
new_entities.append(entities.Arc(points=np.arange(3) +
384+
len(new_vertices),
385+
closed=True))
386+
new_vertices.extend(circle)
387+
continue
394388

395-
for i in path_indexes:
396389
# entities for this path
397-
entity, vertices = points_to_spline_entity(path.discrete[i])
390+
entity, vertices = points_to_spline_entity(discrete, smooth=smooth)
398391
# reindex returned control points
399-
entity.points += len(path.vertices) + len(new_vertices)
392+
entity.points += len(new_vertices)
400393
# save entity and vertices
401-
new_vertices.append(vertices)
394+
new_vertices.extend(vertices)
402395
new_entities.append(entity)
403-
# we don't need any of the entities from the
404-
# path we just consumed and replaced
405-
entities_keep[path.paths[i]] = False
406-
407-
# flatten entities and vertices
408-
entities = np.append(path.entities[entities_keep],
409-
new_entities)
410-
vertices = np.vstack((path.vertices,
411-
np.vstack(new_vertices)))
412396

413397
# create the Path2D object for the result
414-
simplified = type(path)(entities=entities,
415-
vertices=vertices)
398+
simplified = type(path)(entities=new_entities,
399+
vertices=new_vertices)
416400

417401
return simplified

trimesh/points.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,6 @@ def remove_close(points, radius):
187187
return points[unique], unique
188188

189189

190-
def remove_close_set(points_fixed, points_reduce, radius):
191-
"""
192-
Given two sets of points and a radius, return a set of points
193-
that is the subset of points_reduce where no point is within
194-
radius of any point in points_fixed
195-
"""
196-
from scipy.spatial import cKDTree as KDTree
197-
198-
tree_fixed = KDTree(points_fixed)
199-
tree_reduce = KDTree(points_reduce)
200-
reduce_duplicates = tree_fixed.query_ball_tree(tree_reduce, r=radius)
201-
reduce_duplicates = np.unique(np.hstack(reduce_duplicates).astype(int))
202-
reduce_mask = np.ones(len(points_reduce), dtype=np.bool)
203-
reduce_mask[reduce_duplicates] = False
204-
points_clean = points_reduce[reduce_mask]
205-
return points_clean
206-
207-
208190
def k_means(points, k, **kwargs):
209191
"""
210192
Find k centroids that attempt to minimize the k- means problem:
@@ -229,13 +211,16 @@ def k_means(points, k, **kwargs):
229211
from scipy.cluster.vq import kmeans
230212
from scipy.spatial import cKDTree
231213

232-
points = np.asanyarray(points)
214+
points = np.asanyarray(points, dtype=np.float64)
233215
points_std = points.std(axis=0)
234216
whitened = points / points_std
235217
centroids_whitened, distortion = kmeans(whitened, k, **kwargs)
236218
centroids = centroids_whitened * points_std
219+
220+
# find which centroid each point is closest to
237221
tree = cKDTree(centroids)
238222
labels = tree.query(points, k=1)[1]
223+
239224
return centroids, labels
240225

241226

@@ -297,6 +282,7 @@ def __setitem__(self, *args, **kwargs):
297282
def __getitem__(self, *args, **kwargs):
298283
return self.vertices.__getitem__(*args, **kwargs)
299284

285+
@property
300286
def shape(self):
301287
"""
302288
Get the shape of the pointcloud

trimesh/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.33.23'
1+
__version__ = '2.33.24'

0 commit comments

Comments
 (0)