add gradient_direction arg to lewiner mc algorithm

This commit is contained in:
Almar Klein
2016-07-25 12:26:28 +02:00
parent 8275ac07c2
commit f4f05107e7
3 changed files with 37 additions and 15 deletions
+18 -4
View File
@@ -14,7 +14,8 @@ from . import _marching_cubes_lewiner_cy
def marching_cubes_lewiner(volume, level=None, spacing=(1., 1., 1.),
step_size=1, allow_degenerate=True, use_classic=False):
gradient_direction='descent', step_size=1,
allow_degenerate=True, use_classic=False):
"""
Lewiner marching cubes algorithm to find surfaces in 3d volumetric data
@@ -31,14 +32,21 @@ def marching_cubes_lewiner(volume, level=None, spacing=(1., 1., 1.),
spacing : length-3 tuple of floats
Voxel spacing in spatial dimensions corresponding to numpy array
indexing dimensions (M, N, P) as in `volume`.
gradient_direction : string
Controls if the mesh was generated from an isosurface with gradient
descent toward objects of interest (the default), or the opposite,
considering the *left-hand* rule.
The two options are:
* descent : Object was greater than exterior
* ascent : Exterior was greater than object
step_size : int
Step size in voxels. Default 1. Larger steps yield faster but
coarser results. The result will always be topologically correct
though.
allow_degenerate : bool
Whether to allow degenerate triangles in the end-result. Default True.
If False, degenerate triangles are removed, making the algorithm
about twice as slow.
Whether to allow degenerate (i.e. zero-area) triangles in the
end-result. Default True. If False, degenerate triangles are
removed, at the cost of making the algorithm slower.
use_classic : bool
If given and True, the classic marching cubes by Lorensen (1987)
is used. This option is included for reference purposes. Note
@@ -120,6 +128,12 @@ def marching_cubes_lewiner(volume, level=None, spacing=(1., 1., 1.),
# Finishing touches to output
faces.shape = -1, 3
if gradient_direction == 'descent':
# MC implementation is right-handed, but gradient_direction is left-handed
faces = np.fliplr(faces)
elif not gradient_direction == 'ascent':
raise ValueError("Incorrect input %s in `gradient_direction`, see "
"docstring." % (gradient_direction))
if spacing != (1, 1, 1):
vertices = vertices * np.r_[spacing]
+13 -5
View File
@@ -9,10 +9,12 @@ import numpy as np
import visvis as vv
from skimage.measure import marching_cubes, marching_cubes_lewiner
from skimage.draw import ellipsoid
# Create test volume
SELECT = 1
SELECT = 4
gradient_dir = 'descent' # ascent or descent
if SELECT == 1:
# Medical data
@@ -45,20 +47,26 @@ elif SELECT == 3:
# Uncommenting the line below will yield different results for classic MC
#vol = -vol
elif SELECT == 4:
vol = ellipsoid(4, 3, 2, levelset=True)
isovalue = 0
# Get surface meshes
t0 = time.time()
vertices1, faces1, *_ = marching_cubes_lewiner(vol, isovalue, use_classic=False)
vertices1, faces1, *_ = marching_cubes_lewiner(vol, isovalue, gradient_direction=gradient_dir, use_classic=False)
print('finding surface lewiner took %1.0f ms' % (1000*(time.time()-t0)) )
t0 = time.time()
vertices2, faces2, *_ = marching_cubes(vol, isovalue)
vertices2, faces2, *_ = marching_cubes(vol, isovalue, gradient_direction=gradient_dir)
print('finding surface classic took %1.0f ms' % (1000*(time.time()-t0)) )
# Show
vv.figure(1); vv.clf()
a1 = vv.subplot(121); vv.mesh(np.fliplr(vertices1), faces1)
a2 = vv.subplot(122); vv.mesh(np.fliplr(vertices2), faces2)
a1 = vv.subplot(121); m1 = vv.mesh(np.fliplr(vertices1), faces1)
a2 = vv.subplot(122); m2 = vv.mesh(np.fliplr(vertices2), faces2)
a1.camera = a2.camera
# visvis uses right-hand rule, gradient_direction param uses left-hand rule
m1.cullFaces = m2.cullFaces = 'front' # None, front or back
vv.use().Run()
+6 -6
View File
@@ -106,21 +106,21 @@ def test_both_algs_same_result_ellipse():
vertices2, faces2, *_ = marching_cubes_lewiner(sphere_small, 0, allow_degenerate=False)
vertices3, faces3, *_ = marching_cubes_lewiner(sphere_small, 0, allow_degenerate=False, use_classic=True)
# Order id different, best we can do is test equal shape and same vertices present
# Order is different, best we can do is test equal shape and same vertices present
assert _same_mesh(vertices1, faces1, vertices2, faces2)
assert _same_mesh(vertices1, faces1, vertices3, faces3)
def _same_mesh(vertices1, faces1, vertices2, faces2):
faces1 = [sorted(f) for f in faces1]
faces2 = [sorted(f) for f in faces2]
rounder = lambda x: int(x*1000)/1000 # to take into account small variations
triangles1 = vertices1[np.array(faces1)]
triangles2 = vertices2[np.array(faces2)]
triang1 = set([tuple(t.flat) for t in triangles1])
triang2 = set([tuple(t.flat) for t in triangles2])
triang1 = [np.concatenate(sorted(t, key=lambda x:tuple(x))) for t in triangles1]
triang1 = set([tuple([rounder(i) for i in t]) for t in triang1])
triang2 = [np.concatenate(sorted(t, key=lambda x:tuple(x))) for t in triangles2]
triang2 = set([tuple([rounder(i) for i in t]) for t in triang2])
return triang1 == triang2
def test_both_algs_same_result_donut():
# Performing this test on data that does not have ambiguities