Working with Occupancy Grids¶
isoext is built for scalar fields, but it also works on binary occupancy grids: voxel data where each sample is occupied (1) or empty (0), as produced by 3D scans, segmentation masks, or boolean voxel operations. Extraction works the same way; what changes is the choice of level and the need for smoothing.
import torch
import isoext
from isoext.sdf import SphereSDF
from isoext import viewer
Creating an Occupancy Grid¶
Let’s create a binary occupancy grid from a sphere SDF. Values > 0 are outside, so we threshold to get a binary mask:
# Create a grid and compute sphere SDF
grid = isoext.UniformGrid([64, 64, 64])
sdf_values = SphereSDF(radius=0.7)(grid.get_points())
# Convert to binary occupancy: 0 = outside, 1 = inside
occupancy = (sdf_values < 0.0).float()
print(f"Occupancy values: {occupancy.unique().tolist()}")
Occupancy values: [0.0, 1.0]
Direct Extraction¶
Marching cubes runs directly on the binary grid with level=0.5,
halfway between empty and occupied. The result is jagged: with only
two values there is nothing to interpolate, so every vertex lands
exactly halfway across a voxel face.
grid.set_values(occupancy)
v_jagged, f_jagged = isoext.marching_cubes(grid, level=0.5)
print(f"Jagged mesh: {v_jagged.shape[0]} vertices, {f_jagged.shape[0]} faces")
viewer.embed(v_jagged, f_jagged, flat_shading=True)
Jagged mesh: 9168 vertices, 18332 faces
Smoothed Extraction¶
Gaussian smoothing before extraction turns the hard 0/1 steps into gradients the interpolation can work with. The mesh comes out smooth, at the cost of rounding any genuinely sharp corners in the data.
# Smooth the occupancy grid
smoothed = isoext.gaussian_smooth(occupancy, sigma=5.0)
grid.set_values(smoothed)
v_smooth, f_smooth = isoext.marching_cubes(grid, level=0.5)
print(f"Smooth mesh: {v_smooth.shape[0]} vertices, {f_smooth.shape[0]} faces")
viewer.embed(v_smooth, f_smooth)
Smooth mesh: 8232 vertices, 16460 faces