Skip to content

Commit

Permalink
Merge pull request #34 from Kramer84/exposing-other-methods
Browse files Browse the repository at this point in the history
Exposed simplify_mesh_lossless method and updated README to reflect recent changes, notably the verbose/logging in the example.  Updated the tests and passed to version 0.3.0
  • Loading branch information
Kramer84 authored Oct 20, 2024
2 parents 6de5286 + d55831d commit 9be728c
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 87 deletions.
70 changes: 50 additions & 20 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pyfqmr can be installed via `pip <https://pypi.org/project/pyfqmr/0.1.1/>`_ :
Compilation :
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~

Run:

Expand All @@ -34,31 +34,37 @@ Usage:

.. code:: python
>>> #We assume you have a numpy based mesh processing software
>>> #Where you can get the vertices and faces of the mesh as numpy arrays.
>>> #For example Trimesh or meshio
>>> # We assume you have a numpy based mesh processing software
>>> # Where you can get the vertices and faces of the mesh as numpy arrays.
>>> # For example Trimesh or meshio
>>> import pyfqmr
>>> import trimesh as tr
>>> bunny = tr.load_mesh('example/Stanford_Bunny_sample.stl')
>>> #Simplify object
>>> # Simplify object
>>> mesh_simplifier = pyfqmr.Simplify()
>>> mesh_simplifier.setMesh(bunny.vertices, bunny.faces)
>>> mesh_simplifier.simplify_mesh(target_count = 1000, aggressiveness=7, preserve_border=True, verbose=10)
iteration 0 - triangles 112402 threshold 2.187e-06
iteration 5 - triangles 62674 threshold 0.00209715
iteration 10 - triangles 21518 threshold 0.0627485
iteration 15 - triangles 9086 threshold 0.61222
iteration 20 - triangles 4692 threshold 3.40483
iteration 25 - triangles 2796 threshold 13.4929
iteration 30 - triangles 1812 threshold 42.6184
iteration 35 - triangles 1262 threshold 114.416
simplified mesh in 0.2518 seconds
>>> mesh_simplifier.simplify_mesh(target_count = 1000, aggressiveness=7, preserve_border=True, verbose=True)
>>> vertices, faces, normals = mesh_simplifier.getMesh()
>>>
>>> # To make verbose visible, use logging module :
>>> import logging
>>>
>>> # Configure the logger to show debug messages
>>> logging.basicConfig(level=logging.DEBUG)
>>> logger = logging.getLogger("pyfqmr")
>>>
>>> # Optionally, log to a file:
>>> # logging.basicConfig(filename='mesh_simplification.log', level=logging.DEBUG)
>>>
>>> # Now, when `simplify_mesh(verbose=True)` is called,
>>> # messages will appear in the console or the log file
Controlling the reduction algorithm
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Parameters of the '''simplify\_mesh''' method that can be tuned.
Parameters of the **`simplify\_mesh`** method that can be tuned.

- **target\_count**
Target number of triangles.
Expand All @@ -79,14 +85,38 @@ Parameters of the '''simplify\_mesh''' method that can be tuned.
- **threshold\_lossless**
Maximal error after which a vertex is not deleted, only when the lossless flag is set to True.
- **verbose**
Controls verbosity
Falg controlling verbosity

Controlling the lossless reduction algorithm
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Parameters of the **`simplify\_mesh\_lossless`** method that can be tuned.

- **verbose**
Falg controlling verbosity
- **epsilon**
Maximal error after which a vertex is not deleted.
- **max\_iterations**
Maximum number of iterations.

Note
~~~~

- The **`simplify\_mesh\_lossless`** method is different from the **`simplify\_mesh`** method with the lossless flag enabled, and should be prefered when quality is the aim and not a precise number of target triangles.
- Tests have shown that the **threshold\_lossless** argument has little to no influence on the reduction of the meshes.
- On the other hand, only the basic algorithm has the option (yet) to preserve open borders.


Implications of the parameters for the threshold growth rate :
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
*(`simplify\_mesh` method when not in lossless mode)*

Implications of the parameters of the threshold growth rate (when not in lossless mode) :
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
$$threshold = alpha \* (iteration + K)^{agressiveness}$$

\

More information is to be found on Sp4cerat's repository : `Fast-Quadric-Mesh-Simplification <https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification>`__
More information is to be found on Sp4cerat's repository :
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
`Fast-Quadric-Mesh-Simplification <https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification>`__

Huge thanks to Sp4cerat for making his code available!
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.1
0.3.0
22 changes: 12 additions & 10 deletions pyfqmr/Simplify.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// License : MIT
// http://opensource.org/licenses/MIT
//
//https://github.com/sp4cerat/Fast-Quadric-Mesh-S;
//https://github.com/sp4cerat/Fast-Quadric-Mesh-S;
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile

//#include <iostream>
Expand Down Expand Up @@ -341,7 +341,7 @@ namespace Simplify
// more iterations yield higher quality
//

void simplify_mesh(int target_count, int update_rate=5, double agressiveness=7,
void simplify_mesh(int target_count, int update_rate=5, double agressiveness=7,
void (*log)(char*, int)=NULL, int max_iterations=100, double alpha = 0.000000001,
int K = 3, bool lossless=false, double threshold_lossless = 0.0001,
bool preserve_border = false)
Expand Down Expand Up @@ -399,7 +399,7 @@ namespace Simplify
{
int i0=t.v[ j ]; Vertex &v0 = vertices[i0];
int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
// Border check //Added preserve_border method from issue 14
// Border check //Added preserve_border method from issue 14
if(preserve_border){
if (v0.border || v1.border) continue; // should keep border vertices
}
Expand Down Expand Up @@ -444,20 +444,20 @@ namespace Simplify
break;
}
// done?
if (lossless && (deleted_triangles<=0)){ break;
if (lossless && (deleted_triangles<=0)){ break;
} else if (!lossless && (triangle_count-deleted_triangles<=target_count)){break;}

if (lossless) deleted_triangles = 0;
}
}
// clean up mesh
compact_mesh();
} //simplify_mesh()

void simplify_mesh_lossless(bool verbose=false, double epsilon=1e-3, int max_iterations = 9999)
void simplify_mesh_lossless(void (*log)(char*, int)=NULL, double epsilon=1e-3, int max_iterations = 9999)
{
// init
loopi(0,triangles.size())
loopi(0,triangles.size())
{
triangles[i].deleted=0;
}
Expand All @@ -480,8 +480,10 @@ namespace Simplify
// If it does not, try to adjust the 3 parameters
//
double threshold = epsilon; //1.0E-3 EPS;
if (verbose) {
printf("lossless iteration %d\n", iteration);
if (log) {
char message[128];
snprintf(message, 127, "lossless iteration %d\n", iteration);
log(message, 128);
}

// remove vertices & mark deleted triangles
Expand Down Expand Up @@ -1115,4 +1117,4 @@ namespace Simplify
fclose(file);
}
};
///////////////////////////////////////////
///////////////////////////////////////////
108 changes: 55 additions & 53 deletions pyfqmr/Simplify.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ class _hidden_ref(object):
self.faces = None
self.verts = None

_REF = _hidden_ref()
_REF = _hidden_ref()

cdef extern from "Simplify.h" namespace "Simplify" :
void simplify_mesh( int target_count, int update_rate, double aggressiveness,
void simplify_mesh( int target_count, int update_rate, double aggressiveness,
void (*log)(char*, int), int max_iterations,double alpha, int K,
bool lossless, double threshold_lossless, bool preserve_border)
void simplify_mesh_lossless(void (*log)(char*, int), double epsilon, int max_iterations)
void setMeshFromExt(vector[vector[double]] vertices, vector[vector[int]] faces)
vector[vector[int]] getFaces()
vector[vector[double]] getVertices()
Expand All @@ -34,7 +35,7 @@ cdef void log_message(char* message, int length) noexcept:

getLogger("pyfqmr").debug(message.decode("utf-8"))

cdef class Simplify :
cdef class Simplify :

cdef int[:,:] faces_mv
cdef double[:,:] vertices_mv
Expand All @@ -43,7 +44,7 @@ cdef class Simplify :
cdef vector[vector[int]] triangles_cpp
cdef vector[vector[double]] vertices_cpp
cdef vector[vector[double]] normals_cpp

def __cinit__(self):
pass

Expand Down Expand Up @@ -88,7 +89,7 @@ cdef class Simplify :

cpdef void setMesh(self, vertices, faces, face_colors=None):
"""Method to set the mesh of the simplifier object.
Arguments
---------
vertices : numpy.ndarray
Expand All @@ -99,7 +100,7 @@ cdef class Simplify :
array of face_colors of shape (n_faces,3)
this is not yet implemented
"""
_REF.faces = faces
_REF.faces = faces
_REF.verts = vertices
# We have to clear the vectors to avoid overflow when using the simplify object
# multiple times
Expand All @@ -113,9 +114,9 @@ cdef class Simplify :
self.vertices_cpp = setVerticesNogil(self.vertices_mv, self.vertices_cpp)
setMeshFromExt(self.vertices_cpp, self.triangles_cpp)

cpdef void simplify_mesh(self, int target_count = 100, int update_rate = 5,
double aggressiveness=7., max_iterations = 100, bool verbose=True,
bool lossless = False, double threshold_lossless=1e-3, double alpha = 1e-9,
cpdef void simplify_mesh(self, int target_count = 100, int update_rate = 5,
double aggressiveness=7., max_iterations = 100, bool verbose=True,
bool lossless = False, double threshold_lossless=1e-3, double alpha = 1e-9,
int K = 3, bool preserve_border = True):
"""Simplify mesh
Expand All @@ -124,23 +125,23 @@ cdef class Simplify :
target_count : int
Target number of triangles, not used if lossless is True
update_rate : int
Number of iterations between each update.
Number of iterations between each update.
If lossless flag is set to True, rate is 1
aggressiveness : float
Parameter controlling the growth rate of the threshold at each
iteration when lossless is False.
Parameter controlling the growth rate of the threshold at each
iteration when lossless is False.
max_iterations : int
Maximal number of iterations
Maximal number of iterations
verbose : bool
control verbosity
lossless : bool
Use the lossless simplification method
Use the lossless simplification method
threshold_lossless : float
Maximal error after which a vertex is not deleted, only for
lossless method.
alpha : float
Maximal error after which a vertex is not deleted, only for
lossless method.
alpha : float
Parameter for controlling the threshold growth
K : int
K : int
Parameter for controlling the thresold growth
preserve_border : Bool
Flag for preserving vertices on open border
Expand Down Expand Up @@ -170,21 +171,52 @@ cdef class Simplify :
round(t_end-t_start,4), N_start, N_end)
)

cpdef void simplify_mesh_lossless(self, bool verbose = True, double epsilon=1e-3, int max_iterations=9999):
"""Simplify mesh using lossless method
Parameters
----------
verbose : bool
Control verbosity
epsilon : float
Maximal error after which a vertex is not deleted
max_iterations : int
Maximum number of iterations
"""
cdef void (*log)(char*, int) noexcept

log = NULL
if verbose:
log = log_message

N_start = self.faces_mv.shape[0]
t_start = _time()
simplify_mesh_lossless(log, epsilon, max_iterations)
t_end = _time()
N_end = getFaces().size()

if verbose:
from logging import getLogger

getLogger("pyfqmr").debug('simplified mesh in {} seconds from {} to {} triangles'.format(
round(t_end-t_start,4), N_start, N_end)
)

@cython.boundscheck(False)
@cython.wraparound(False) # turn off negative index wrapping for entire function
@cython.nonecheck(False)
cdef vector[vector[double]] setVerticesNogil(double[:,:] vertices, vector[vector[double]] vector_vertices )nogil:
"""nogil function for filling the vector of vertices, "vector_vertices",
with the data found in the memory view of the array "vertices"
with the data found in the memory view of the array "vertices"
"""
cdef vector[double] vertex
cdef vector[double] vertex
vector_vertices.reserve(vertices.shape[0])

cdef size_t i = 0
cdef size_t j = 0
for i in range(vertices.shape[0]):
vertex.clear()
for j in range(3):
for j in range(3):
vertex.push_back(vertices[i,j])
vector_vertices.push_back(vertex)
return vector_vertices
Expand All @@ -196,44 +228,14 @@ cdef vector[vector[int]] setFacesNogil(int[:,:] faces, vector[vector[int]] vecto
"""nogil function for filling the vector of faces, "vector_faces",
with the data found in the memory view of the array "faces"
"""
cdef vector[int] triangle
cdef vector[int] triangle
vector_faces.reserve(faces.shape[0]);

cdef size_t i = 0
cdef size_t j = 0
for i in range(faces.shape[0]):
triangle.clear()
for j in range(3):
for j in range(3):
triangle.push_back(faces[i,j])
vector_faces.push_back(triangle)
return vector_faces





"""Example:
#We assume you have a numpy based mesh processing software
#Where you can get the vertices and faces of the mesh as numpy arrays.
#For example Trimesh or meshio
import pyfqmr
import trimesh as tr
bunny = tr.load_mesh('Stanford_Bunny_sample.stl')
#Simplify object
simp = pyfqmr.Simplify()
simp.setMesh(bunny.vertices, bunny.faces)
simp.simplify_mesh(target_count = 1000, aggressiveness=7, preserve_border=True, verbose=10)
vertices, faces, normals = simp.getMesh()
"""

"""Example2:
import trimesh as tr
import pyfqmr as fmr
mesh = tr.load_mesh('Stanford_Bunny_sample.stl')
simpl = fmr.Simplify()
verts, faces = mesh.vertices, mesh.faces
simpl.setMesh(verts, faces)
simpl.getMesh()
faces.shape
"""
Loading

0 comments on commit 9be728c

Please sign in to comment.