# %pip install phiflow
from phi.flow import *
Signed distance fields can easily be created from existing geometry. The next cell creates a SDF from a pair of spheres.
spheres = Sphere(vec(x=[1, 2], y=1), radius=.8)
bounds = Box(x=3, y=2)
sdf = geom.sdf_from_geometry(spheres, bounds, x=10, y=10)
plot({"SDF": sdf.values, "Surface": [spheres, sdf]}, overlay='list')
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[2], line 3 1 spheres = Sphere(vec(x=[1, 2], y=1), radius=.8) 2 bounds = Box(x=3, y=2) ----> 3 sdf = geom.sdf_from_geometry(spheres, bounds, x=10, y=10) 4 plot({"SDF": sdf.values, "Surface": [spheres, sdf]}, overlay='list') AttributeError: module 'phi.geom' has no attribute 'sdf_from_geometry'
Next, let's construct a SDF from a 2D NumPy array.
bounds = Box(x=(-10, 10), y=(-10, 10))
grid_x, grid_y = np.meshgrid(np.linspace(-10, 10, 100), np.linspace(-10, 10, 100))
sdf_np = np.sqrt(grid_x**2 + grid_y**2) - 5
sdf_tensor = tensor(sdf_np, spatial('x,y'))
sdf = geom.SDFGrid(sdf_tensor, bounds)
plot(sdf_tensor, sdf)
/opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phiml/backend/_backend.py:1679: RuntimeWarning: invalid value encountered in power return base ** exp /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phi/vis/_matplotlib/_matplotlib_plots.py:167: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect. plt.tight_layout() # because subplot titles can be added after figure creation
SDFs behave like any other geomoetry. They can be resampled to grids and other fields.
to_grid = CenteredGrid(sdf, 0, bounds, x=40, y=40)
soft = resample(sdf, to_grid, soft=True)
plot(to_grid, soft)
sdf.lies_inside(vec(x=0, y=0))
True
SDFs support querying the closest surface point and normal vector.
loc = geom.UniformGrid(sdf.resolution.with_sizes(10), sdf.bounds).center
sgn_dist, delta, normal, *_ = sdf.approximate_closest_surface(loc)
plot(PointCloud(loc, delta))
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[6], line 2 1 loc = geom.UniformGrid(sdf.resolution.with_sizes(10), sdf.bounds).center ----> 2 sgn_dist, delta, normal, *_ = sdf.approximate_closest_surface(loc) 3 plot(PointCloud(loc, delta)) File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phi/geom/_sdf_grid.py:166, in SDFGrid.approximate_closest_surface(self, location) 164 to_surf = math.grid_sample(self._to_surface, float_idx - .5, math.extrapolation.ZERO_GRADIENT) 165 else: --> 166 sdf_grad = math.grid_sample(self._grad, float_idx - 1, math.extrapolation.ZERO_GRADIENT) 167 sdf_grad = math.vec_normalize(sdf_grad) # theoretically not necessary 168 to_surf = sgn_dist * -sdf_grad File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1103, in grid_sample(grid, coordinates, extrap, **kwargs) 1101 assert channel(coordinates).rank == 1, f"coordinates must have at most one channel dimension but got {channel(coordinates)}" 1102 coordinates = rename_dims(coordinates, channel, 'vector') -> 1103 result = broadcast_op(functools.partial(_grid_sample, extrap=extrap, pad_kwargs=kwargs), [grid, coordinates]) 1104 return result File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1177, in broadcast_op(operation, tensors, iter_dims, no_return) 1175 iter_dims = broadcast_dims(*tensors) if iter_dims is None else iter_dims 1176 if len(iter_dims) == 0: -> 1177 return operation(*tensors) 1178 else: 1179 if isinstance(iter_dims, Shape): File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1116, in _grid_sample(grid, coordinates, extrap, pad_kwargs) 1108 """ 1109 Args: 1110 grid: (...) 1113 pad_kwargs: 1114 """ 1115 dim_names = channel(coordinates).item_names[0] or grid.shape.spatial.names -> 1116 dims = grid.shape.only(dim_names, reorder=True) 1117 assert len(dims) == len(dim_names), f"all grid dims {dim_names} must be present on grid but got shape {grid.shape}" 1118 if grid.shape.batch == coordinates.shape.batch or grid.shape.batch.volume == 1 or coordinates.shape.batch.volume == 1: 1119 # call backend.grid_sample() AttributeError: 'NoneType' object has no attribute 'shape'