seung-lab / zmesh

Marching Cubes & Mesh Simplification on multi-label 3D images.
GNU General Public License v3.0
58 stars 8 forks source link

Simplification is independent of reduction_factor #39

Closed davidackerman closed 4 months ago

davidackerman commented 4 months ago

Regardless of the reduction factor, mesher.simplify seems to simplify what seems like as much as possible:


import zmesh
import numpy as np

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=2, max_error=40, compute_normals=True
    )
    print(f"Actual reduction factor for order {order}: {len(original_mesh.faces)/len(mesh.faces)}")

Actual reduction factor for order C: 24.636363636363637 Actual reduction factor for order F: 20.452830188679247

The actual reduction seems to be slightly affected by whether it is order C or F.

william-silversmith commented 4 months ago

Thank you for the bug report! I'm currently on vacation but I'll take a look when I get a chance.

william-silversmith commented 4 months ago

I did a little investigation. It seems that there is an if statement that has some odd behavior.

(
    (mesh_.face_count() <= target_faces) && (heap_.top().value_ >= min_error)
) ||
    (heap_.top().value_ > max_error)

If the heap value is 0, it will be below min_error and so the face target condition will be inoperative. I am trying to figure out why it was written this way.

william-silversmith commented 4 months ago

@zlateski would you have any insight into this? Sorry to bother you!

zlateski commented 4 months ago

@william-silversmith

The errors are always increasing, so this condition is something like 1) stop when you hit certain number of faces or reach certain error (whatever comes second) -> min_error can be set to 0 to always hit certain number of faces, or 2) Just stop when you hit certain error (max_error), regardless of the number of faces

Naming might be unfortunate, but it was consistent with other naming we had at the time with the watershed etc... (as far as I remember)

zlateski commented 4 months ago

@william-silversmith

The rationale for adding min_error to the first condition was something like: We know the meshes will look good up to some error, so we can reduce even more than the target number of faces. Setting error to something close to 0 (or 0) and simplifying a cube with a lot of triangles should result in only 12 triangles and look more or less the same.

zlateski commented 4 months ago

This is not a bug, it's a feature. Make sure you pass min_error from pyton to C++. The floating point computation error will always be there - but will be proportional to the absolute size of the object passed to the simplifier (e.g. difference between max and min values along an axis).

The c++ code has some factor of 25*std::numeric_limits::epsilon() used as a default, which was probably appropriate for some meshes at some point in time - and is definitely not to be always used.

william-silversmith commented 4 months ago

This is really helpful info @zlateski! Really appreciate you taking the time. I'll expose min_error to the Python code.