Skip to content

Commit

Permalink
feat: expose min error (#40)
Browse files Browse the repository at this point in the history
* fix: max simplification error int -> float

* feat: expose min_error to Python

* test: check that setting min_error to 0 gives closer to reduction factor

* fix: use C++ default value

* feat: add id to zmesh.Mesh
  • Loading branch information
william-silversmith authored Jul 27, 2024
1 parent 7ed0aab commit f554027
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 32 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
CHANGES
=======

* fix: max simplification error int -> float
* feat: support more types of obj files

1.7.1
-----

* fix: some setup issues for py312
* build: update cibuildwheel
* fix: re-simplification works
* fix(simplify): double multiplication by resolution
Expand Down
23 changes: 23 additions & 0 deletions automated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,26 @@ def test_simplified_meshes_remain_the_same(connectomics_labels, order):
assert np.all(np.isclose(np.sort(old_mesh.vertices[old_mesh.faces], axis=0), np.sort(new_mesh.vertices[new_mesh.faces], axis=0)))
print(lbl, "ok")


@pytest.mark.parametrize("reduction_factor", [2,5,10])
def test_min_error_skip(reduction_factor):
for order in ['C','F']:
labels = np.zeros((11, 17, 19), dtype=np.uint8, order=order)
labels[1:-1, 1:-1, 1:-1] = 1

mesher = zmesh.Mesher((4, 4, 40))
mesher.mesh(labels)

original_mesh = mesher.get_mesh(1, normals=False)
mesh = mesher.simplify(
original_mesh,
reduction_factor=reduction_factor,
max_error=40,
min_error=0,
compute_normals=True
)
factor = len(original_mesh.faces) / len(mesh.faces)
assert abs(factor - reduction_factor) < 1



14 changes: 10 additions & 4 deletions cMesher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class CMesher {
LabelType segid,
bool generate_normals,
int simplification_factor,
int max_simplification_error,
float max_simplification_error,
float min_simplification_error,
bool transpose = true
) {

Expand All @@ -71,6 +72,7 @@ class CMesher {
generate_normals,
simplification_factor,
max_simplification_error,
min_simplification_error,
transpose
);
}
Expand All @@ -79,7 +81,8 @@ class CMesher {
const std::vector< zi::vl::vec< PositionType, 3> >& triangles,
bool generate_normals,
int simplification_factor,
int max_simplification_error,
float max_simplification_error,
float min_simplification_error,

// This is to support the old broken version
// w/ backwards compatibility
Expand Down Expand Up @@ -111,7 +114,8 @@ class CMesher {
// This is the most cpu intensive line
simplifier_.optimize(
simplifier_.face_count() / simplification_factor,
max_simplification_error
max_simplification_error,
min_simplification_error
);
}

Expand Down Expand Up @@ -178,7 +182,8 @@ class CMesher {
const size_t Nv,
bool generate_normals,
int simplification_factor,
int max_simplification_error
float max_simplification_error,
float min_simplification_error
) {

std::vector< zi::vl::vec< PositionType, 3> > triangles;
Expand All @@ -199,6 +204,7 @@ class CMesher {
generate_normals,
simplification_factor,
max_simplification_error,
min_simplification_error,
false
);
}
Expand Down
4 changes: 2 additions & 2 deletions templates/mesherclass.j2
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ cdef class Mesher{{ position_type }}{{ '%02d' % label_type }}:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down
5 changes: 4 additions & 1 deletion zi_lib/zi/mesh/quadratic_simplifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,11 @@ class simplifier : non_copyable

std::size_t
optimize(std::size_t target_faces, Float max_error,
Float min_error = std::numeric_limits<Float>::epsilon() * 25)
Float min_error = -1.0)
{
if (min_error < 0) {
min_error = std::numeric_limits<Float>::epsilon() * 25;
}

// double no_faces = static_cast< double >( mesh_.face_count() );

Expand Down
66 changes: 42 additions & 24 deletions zmesh/_zmesh.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ from libcpp cimport bool

import operator
from functools import reduce
import sys

cimport numpy as cnp
import numpy as np
Expand All @@ -33,7 +34,8 @@ cdef extern from "cMesher.hpp":
L segid,
bool normals,
int simplification_factor,
int max_simplification_error,
float max_simplification_error,
float min_simplification_error,
bool transpose
)
# NOTE: need to define triangle_t
Expand All @@ -42,7 +44,8 @@ cdef extern from "cMesher.hpp":
size_t Nv,
bool normals,
int simplification_factor,
int max_simplification_error
float max_simplification_error,
float min_simplification_error,
)
bool erase(L segid)
void clear()
Expand Down Expand Up @@ -180,7 +183,9 @@ class Mesher:
transpose=False
)

return self._normalize_simplified_mesh(mesh, voxel_centered, physical=True)
mesh = self._normalize_simplified_mesh(mesh, voxel_centered, physical=True)
mesh.id = int(label)
return mesh

def _normalize_simplified_mesh(self, mesh, voxel_centered, physical):
points = np.array(mesh['points'], dtype=np.float32)
Expand Down Expand Up @@ -209,16 +214,21 @@ class Mesher:
"""
return self.simplify(mesh, reduction_factor=0, max_error=0, compute_normals=True)

# adding this causes a cython compilation error as of 3.0.10
# @cython.binding(True)
def simplify(
self, mesh, int reduction_factor=0,
int max_error=40, compute_normals=False,
voxel_centered=False
self, mesh,
int reduction_factor = 0,
float max_error = 40,
compute_normals = False,
voxel_centered = False,
float min_error = -1,
):
"""
Mesh simplify(
mesh, reduction_factor=0,
max_error=40, compute_normals=False,
voxel_centered=False
voxel_centered=False, min_error = -1
)
Given a mesh object (either zmesh.Mesh or another object that has
Expand All @@ -234,6 +244,13 @@ class Mesher:
stop short of this target.
max_error: The maximum allowed displacement of a vertex in physical
distance.
min_error: Continue simplifying until this error target is met OR secondarily
the target number of triangles is met. Set this to 0 to force the
reduction_factor to take precedence. -1 indicates that the c++ default
value should be used.
The c++ default value is std::numeric_limits<Float>::epsilon() * 25
compute_normals: whether or not to also compute the vertex normals
voxel_centered: By default, the meshes produced will be centered at
0,0,0. If enabled, the meshes will be centered in the voxel at
Expand Down Expand Up @@ -278,7 +295,8 @@ class Mesher:

cdef MeshObject result = mesher.simplify_points(
<uint64_t*>&packed_triangles[0,0], Nv,
<bool>compute_normals, reduction_factor, max_error
<bool>compute_normals, reduction_factor,
max_error, min_error
)
del mesher

Expand Down Expand Up @@ -357,8 +375,8 @@ cdef class Mesher3208:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -386,8 +404,8 @@ cdef class Mesher3216:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -415,8 +433,8 @@ cdef class Mesher3232:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -444,8 +462,8 @@ cdef class Mesher3264:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -473,8 +491,8 @@ cdef class Mesher6408:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -502,8 +520,8 @@ cdef class Mesher6416:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -531,8 +549,8 @@ cdef class Mesher6432:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down Expand Up @@ -560,8 +578,8 @@ cdef class Mesher6464:
def ids(self):
return self.ptr.ids()

def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, transpose)
def get_mesh(self, mesh_id, normals=False, simplification_factor=0, max_simplification_error=40, min_simplification_error=(25 * sys.float_info.epsilon), transpose=True):
return self.ptr.get_mesh(mesh_id, normals, simplification_factor, max_simplification_error, min_simplification_error, transpose)

def clear(self):
self.ptr.clear()
Expand Down
3 changes: 2 additions & 1 deletion zmesh/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ class Mesh:
ndarray[float32, ndim=2] self.normals: [ [nx,ny,nz], ... ]
"""
def __init__(self, vertices, faces, normals):
def __init__(self, vertices, faces, normals, id=None):
self.vertices = vertices
self.faces = faces
self.normals = normals
self.id = id

def __len__(self):
return self.vertices.shape[0]
Expand Down

0 comments on commit f554027

Please sign in to comment.