Summary
get_shcoeffs() currently accepts only binary images. Internally, the library already converts the image to a vtkPolyData mesh before performing the actual spherical-harmonics fitting — but that mesh capability is never exposed to the user.
Users who already have surface meshes (from meshing pipelines, simulation outputs, or computational geometry tools) must voxelize them back into images as a workaround. That round-trip is lossy, slow, and unnecessary.
I would like to contribute two new public entry points that expose the existing mesh path directly:
get_shcoeffs_from_mesh(mesh, lmax, alignment_2d=True, make_unique=False)
get_shcoeffs_from_vertices_faces(vertices, faces, lmax, alignment_2d=True, make_unique=False)
I have a working implementation on my fork (branch feature/mesh-input-api) and am happy to open a PR.
Motivation
The internal flow of get_shcoeffs is already:
binary image → vtkPolyData → spherical coordinates → SH expansion
The second half of that pipeline (mesh → SH) is exactly what mesh users need. Exposing it avoids:
- Voxelization artifacts when converting a clean mesh back to an image
- Unnecessary dependency on image resolution
- Code duplication for users who manage their own mesh pipeline
Proposed API
get_shcoeffs_from_mesh(mesh, lmax, ...)
Accepts a vtk.vtkPolyData object. Returns the same nested-tuple structure as get_shcoeffs for drop-in compatibility, with image_ set to None:
from aicsshparam import shparam, shtools
mesh = shtools.get_mesh_from_vertices_faces(vertices, faces)
(coeffs, grid_rec), (_, mesh_out, grid, transform) = \
shparam.get_shcoeffs_from_mesh(mesh=mesh, lmax=4)
mse = shtools.get_reconstruction_error(grid, grid_rec)
get_shcoeffs_from_vertices_faces(vertices, faces, lmax, ...)
Convenience wrapper for users who do not work with VTK directly:
import numpy as np
from aicsshparam import shparam
# vertices: (N, 3) float array; faces: (M, 3) int array
(coeffs, grid_rec), (_, mesh_out, grid, transform) = \
shparam.get_shcoeffs_from_vertices_faces(
vertices=vertices, faces=faces, lmax=4
)
Implementation outline
New public functions in shparam.py:
get_shcoeffs_from_mesh — centers mesh on centroid, optionally aligns via PCA on vertex (x, y), then delegates to shared core
get_shcoeffs_from_vertices_faces — builds a vtkPolyData from numpy arrays via a new helper and calls the above
_get_shcoeffs_from_mesh_coords — private shared core extracted from get_shcoeffs; both the image path and mesh path delegate here (eliminates duplication)
New helpers in shtools.py:
get_mesh_from_vertices_faces(vertices, faces, center=True) — builds and validates a vtkPolyData
check_mesh_for_parametrization(mesh) — non-raising warnings for geometric assumption violations (non-watertight surface; centroid outside mesh)
align_points_2d(x, y, make_unique=False) — PCA-based 2D alignment on a point cloud; refactors the alignment logic shared between image and mesh paths
Validation approach:
| Situation |
Behaviour |
| Empty mesh / zero points |
ValueError raised |
| Invalid vertex array shape, NaN/Inf values, out-of-range face indices |
ValueError raised |
| Non-watertight / non-manifold surface |
UserWarning (computation proceeds) |
| Centroid outside mesh (non-star-shaped proxy) |
UserWarning (computation proceeds) |
Important design note: alignment difference between image and mesh paths
The 2D alignment angle is not guaranteed to be bit-identical between get_shcoeffs and get_shcoeffs_from_mesh for the same object, because they run PCA on different point sets:
- Image path: PCA on all foreground voxel (x, y) coordinates (a volume point cloud), then rotates the whole image with interpolation, then re-meshes.
- Mesh path: PCA on surface vertex (x, y) coordinates, then rotates the vertices directly (no interpolation).
The angle formula is shared, but the input point sets differ, so the resulting angle can differ slightly. For clean, well-aligned shapes the two paths agree closely.
Reproducing the image path exactly from a mesh input would require voxelizing the mesh first, which defeats the purpose. I wanted to flag this explicitly in case it affects downstream users comparing coefficients across the two paths.
Summary
get_shcoeffs()currently accepts only binary images. Internally, the library already converts the image to avtkPolyDatamesh before performing the actual spherical-harmonics fitting — but that mesh capability is never exposed to the user.Users who already have surface meshes (from meshing pipelines, simulation outputs, or computational geometry tools) must voxelize them back into images as a workaround. That round-trip is lossy, slow, and unnecessary.
I would like to contribute two new public entry points that expose the existing mesh path directly:
I have a working implementation on my fork (branch
feature/mesh-input-api) and am happy to open a PR.Motivation
The internal flow of
get_shcoeffsis already:The second half of that pipeline (mesh → SH) is exactly what mesh users need. Exposing it avoids:
Proposed API
get_shcoeffs_from_mesh(mesh, lmax, ...)Accepts a
vtk.vtkPolyDataobject. Returns the same nested-tuple structure asget_shcoeffsfor drop-in compatibility, withimage_set toNone:get_shcoeffs_from_vertices_faces(vertices, faces, lmax, ...)Convenience wrapper for users who do not work with VTK directly:
Implementation outline
New public functions in
shparam.py:get_shcoeffs_from_mesh— centers mesh on centroid, optionally aligns via PCA on vertex (x, y), then delegates to shared coreget_shcoeffs_from_vertices_faces— builds avtkPolyDatafrom numpy arrays via a new helper and calls the above_get_shcoeffs_from_mesh_coords— private shared core extracted fromget_shcoeffs; both the image path and mesh path delegate here (eliminates duplication)New helpers in
shtools.py:get_mesh_from_vertices_faces(vertices, faces, center=True)— builds and validates avtkPolyDatacheck_mesh_for_parametrization(mesh)— non-raising warnings for geometric assumption violations (non-watertight surface; centroid outside mesh)align_points_2d(x, y, make_unique=False)— PCA-based 2D alignment on a point cloud; refactors the alignment logic shared between image and mesh pathsValidation approach:
ValueErrorraisedValueErrorraisedUserWarning(computation proceeds)UserWarning(computation proceeds)Important design note: alignment difference between image and mesh paths
The 2D alignment angle is not guaranteed to be bit-identical between
get_shcoeffsandget_shcoeffs_from_meshfor the same object, because they run PCA on different point sets:The angle formula is shared, but the input point sets differ, so the resulting angle can differ slightly. For clean, well-aligned shapes the two paths agree closely.
Reproducing the image path exactly from a mesh input would require voxelizing the mesh first, which defeats the purpose. I wanted to flag this explicitly in case it affects downstream users comparing coefficients across the two paths.