Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 752d98c

Browse files
committed
Uses Voronoi clustering to remesh the voxelized shape
1 parent d16e7b0 commit 752d98c

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

mti_nma/steps/avgshape/avgshape.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from datastep import Step, log_run_params
1111
from .avgshape_utils import run_shcoeffs_analysis, save_mesh_as_stl
12+
from .avgshape_utils import get_smooth_and_coarse_mesh_from_voxelization
1213
from .avgshape_utils import save_mesh_as_obj, save_voxelization, save_displacement_map
1314

1415
###############################################################################
@@ -121,7 +122,7 @@ def run(self, sh_df=None, struct="Nuc", **kwargs):
121122
save_mesh_as_stl(mesh_avg, avg_data_dir / f"avgshape_{struct}.stl")
122123

123124
# Remesh voxelization
124-
remesh_avg, _, _ = shtools.get_mesh_from_image((domain > 0).astype(np.uint8))
125+
remesh_avg = get_smooth_and_coarse_mesh_from_voxelization(domain, sigma=3, npoints=2000)
125126

126127
# Save remesh as PLY
127128
shtools.save_polydata(

mti_nma/steps/avgshape/avgshape_utils.py

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from stl import mesh
88
from aicscytoparam import cytoparam
99
from aicsimageio import writers
10+
from skimage import filters as skfilters
11+
from vtk.util import numpy_support as vtknp
12+
import pyvista as pv
13+
import pyacvd
1014

1115
###############################################################################
1216

@@ -134,32 +138,89 @@ def save_mesh_as_stl(polydata, fname):
134138

135139
# Write the mesh to file"
136140
nuc_mesh.save(fname)
137-
141+
142+
138143
def save_mesh_as_obj(polydata, fname):
139-
144+
140145
writer = vtk.vtkOBJWriter()
141146
writer.SetInputData(polydata)
142147
writer.SetFileName(str(fname))
143148
writer.Write()
144149

150+
145151
def save_voxelization(polydata, fname):
146-
152+
147153
domain, _ = cytoparam.voxelize_meshes([polydata])
148154
with writers.ome_tiff_writer.OmeTiffWriter(fname, overwrite_file=True) as writer:
149155
writer.save(
150-
255*domain,
156+
255 * domain,
151157
dimension_order='ZYX',
152158
image_name=fname.stem
153159
)
154160
return domain
155161

162+
156163
def save_displacement_map(grid, fname):
157-
164+
158165
grid = grid.reshape(1, *grid.shape).astype(np.float32)
159-
166+
160167
with writers.ome_tiff_writer.OmeTiffWriter(fname, overwrite_file=True) as writer:
161-
writer.save(
162-
grid,
163-
dimension_order='ZYX',
164-
image_name=fname.stem
165-
)
168+
writer.save(
169+
grid,
170+
dimension_order='ZYX',
171+
image_name=fname.stem
172+
)
173+
174+
175+
def get_smooth_and_coarse_mesh_from_voxelization(img, sigma, npoints):
176+
"""
177+
Converts an image into a triangle mesh with even distributed points.
178+
First we use a Gaussian kernel with size (sigma**3) to smooth the
179+
input image. Next we apply marching cubes (vtkContourFilter) to obtain
180+
a first mesh, which is used as input to a Voronoi-based clustering
181+
that is responsible for remeshing. Details can be found here:
182+
https://github.com/pyvista/pyacvd
183+
184+
Parameters
185+
----------
186+
img: np.array
187+
Input image corresponding to the voxelized version of the original
188+
average mesh.
189+
sigma: float
190+
Gaussian kernel size.
191+
npoints: int
192+
Number of points used to create the Voronoi clustering. The larger
193+
this value the more points the final mesh will have.
194+
Returns
195+
-------
196+
remesh_vtk: vtkPolyData
197+
Triangle with even distirbuted points.
198+
"""
199+
200+
201+
rad = 5
202+
img = np.pad(img, ((rad, rad), (rad, rad), (rad, rad)))
203+
d, h, w = img.shape
204+
img = skfilters.gaussian(img > 0, sigma=sigma, preserve_range=True)
205+
imagedata = vtk.vtkImageData()
206+
imagedata.SetDimensions([w, h, d])
207+
imagedata.SetExtent(0, w - 1, 0, h - 1, 0, d - 1)
208+
imagedata.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
209+
values = (255 * img).ravel().astype(np.uint8)
210+
values = vtknp.numpy_to_vtk(values, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR)
211+
imagedata.GetPointData().SetScalars(values)
212+
cf = vtk.vtkContourFilter()
213+
cf.SetInputData(imagedata)
214+
cf.SetValue(0, 255.0 / np.exp(1.0))
215+
cf.Update()
216+
mesh = cf.GetOutput()
217+
218+
pv_temp = pv.PolyData(mesh)
219+
cluster = pyacvd.Clustering(pv_temp)
220+
cluster.cluster(npoints)
221+
remesh = cluster.create_mesh()
222+
remesh_vtk = vtk.vtkPolyData()
223+
remesh_vtk.SetPoints(remesh.GetPoints())
224+
remesh_vtk.SetVerts(remesh.GetVerts())
225+
remesh_vtk.SetPolys(remesh.GetPolys())
226+
return remesh_vtk

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
"aicsimageio>=3.1.2",
5050
"aicsshparam>=0.1.1",
5151
"aicscytoparam>=0.1.2",
52+
"pyvista>=0.29.1",
53+
"pyacvd>=0.2.5",
5254
"bokeh",
5355
"datastep>=0.1.5",
5456
"dask[bag]",

0 commit comments

Comments
 (0)