# %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)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[3], line 6 4 sdf_tensor = tensor(sdf_np, spatial('x,y')) 5 sdf = geom.SDFGrid(sdf_tensor, bounds) ----> 6 plot(sdf_tensor, sdf) File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phi/vis/_vis.py:291, in plot(lib, row_dims, col_dims, animate, overlay, title, size, same_scale, log_dims, show_color_bar, color, alpha, err, frame_time, repeat, plt_params, max_subfigures, *fields) 289 min_val = max_val = None 290 # --- Layout --- --> 291 subplots = {pos: _space(*fields, ignore_dims=animate, log_dims=log_dims, errs=[err[i] for i in indices[pos]]) for pos, fields in positioning.items()} 292 subplots = {pos: _insert_value_dim(space, pos, subplots, min_val, max_val) for pos, space in subplots.items()} 293 if same_scale: File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phi/vis/_vis.py:400, in _space(ignore_dims, log_dims, errs, *values) 398 all_dims = [] 399 for f, e in zip(values, errs): --> 400 for dim in get_default_limits(f, None, log_dims, e).vector.item_names: 401 if dim not in all_dims and dim not in ignore_dims: 402 all_dims.append(dim) File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1257, in broadcast.<locals>.broadcast_(*args, **kwargs) 1255 @wraps(function) 1256 def broadcast_(*args, **kwargs): -> 1257 return map_(function, *args, dims=dims, range=range, unwrap_scalars=unwrap_scalars, simplify=simplify, map_name=name, **kwargs) File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1389, in map_(function, dims, range, unwrap_scalars, expand_results, simplify, map_name, *args, **kwargs) 1387 idx_extra_args = list(extra_args) 1388 idx_all_args = [idx_args.pop(0) if isinstance(v, Shapable) else idx_extra_args.pop(0) for v in args] -> 1389 f_output = function(*idx_all_args, **idx_kwargs, **extra_kwargs) 1390 results.append(f_output) 1391 if isinstance(results[0], tuple): File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phi/vis/_vis_base.py:546, in get_default_limits(f, all_dims, log_dims, err) 544 return data_bounds(f) * value_limits 545 # --- Determine element size --- --> 546 f_dims = f.geometry.vector.item_names 547 value_axis = f.spatial_rank <= 1 548 if value_axis: AttributeError: 'SDFGrid' object has no attribute 'vector'
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)
/opt/hostedtoolcache/Python/3.12.11/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
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.11/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.11/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1148, in grid_sample(grid, coordinates, extrap, **kwargs) 1146 assert channel(coordinates).rank == 1, f"coordinates must have at most one channel dimension but got {channel(coordinates)}" 1147 coordinates = rename_dims(coordinates, channel, 'vector') -> 1148 result = broadcast_op(functools.partial(_grid_sample, extrap=extrap, pad_kwargs=kwargs), [grid, coordinates]) 1149 return result File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1220, in broadcast_op(operation, tensors, iter_dims, no_return) 1218 iter_dims = broadcast_dims(*tensors) if iter_dims is None else iter_dims 1219 if len(iter_dims) == 0: -> 1220 return operation(*tensors) 1221 else: 1222 if isinstance(iter_dims, SHAPE_TYPES): File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1161, in _grid_sample(grid, coordinates, extrap, pad_kwargs) 1153 """ 1154 Args: 1155 grid: (...) 1158 pad_kwargs: 1159 """ 1160 dim_names = channel(coordinates).labels[0] or grid.shape.spatial.names -> 1161 dims = grid.shape.only(dim_names, reorder=True) 1162 assert len(dims) == len(dim_names), f"all grid dims {dim_names} must be present on grid but got shape {grid.shape}" 1163 if grid.shape.batch == coordinates.shape.batch or grid.shape.batch.volume == 1 or coordinates.shape.batch.volume == 1: 1164 # call backend.grid_sample() AttributeError: 'NoneType' object has no attribute 'shape'