AllenCell / AllenCell/aics-shparam
Feature request: expose mesh-based API (get_shcoeffs_from_mesh / get_shcoeffs_from_vertices_faces)
- Langage dominant
- Python
- Étoiles
- 24
- Forks
- 7
- Métriques de merge des PR
- Aucune PR mergée en 30 j
Description
### 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:
```python
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`:
```python
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:
```python
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.
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.