Heightmaps¶

This notebook introduces surfaces parameterized by heightmaps.

In [1]:
# !pip install git+https://github.com/tum-pbs/PhiFlow@3.0
from phi.flow import *

Heightmaps can be used in 2D or 3D. They encode a grid surface made up of lines or planes, respectively. The grid vertices are equally spaced and the displacement (height) of each point is specified via a 1D or 2D tensor.

The following code creates a heightmap in 2D from a list of height values.

In [2]:
height = wrap([.1, .02, 0, 0, 1, .95, .8, .5, 0], spatial('x'))
bounds = Box(x=2, y=1)
heightmap = geom.Heightmap(height, bounds, max_dist=.1)
plot(heightmap)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[2], line 4
      2 bounds = Box(x=2, y=1)
      3 heightmap = geom.Heightmap(height, bounds, max_dist=.1)
----> 4 plot(heightmap)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis.py:322, 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)
    320     for i, f in enumerate(fields):
    321         idx = indices[pos][i]
--> 322         plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[pos][i][idx], alpha[idx], err[idx])
    323 plots.finalize(figure)
    324 LAST_FIGURE[0] = figure

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis_base.py:381, in PlottingLibrary.plot(self, data, figure, subplot, space, *args, **kwargs)
    379 for recipe in self.recipes:
    380     if recipe.can_plot(data, space):
--> 381         recipe.plot(data, figure, subplot, space, *args, **kwargs)
    382         return
    383 raise NotImplementedError(f"No {self.name} recipe found for {data}. Recipes: {self.recipes}")

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1270, in broadcast.<locals>.broadcast_(*args, **kwargs)
   1268 @wraps(function)
   1269 def broadcast_(*args, **kwargs):
-> 1270     return map_(function, *args, dims=dims, range=range, unwrap_scalars=unwrap_scalars, simplify=simplify, map_name=name, **kwargs)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1402, in map_(function, dims, range, unwrap_scalars, expand_results, simplify, map_name, *args, **kwargs)
   1400     idx_extra_args = list(extra_args)
   1401     idx_all_args = [idx_args.pop(0) if isinstance(v, Shapable) else idx_extra_args.pop(0) for v in args]
-> 1402     f_output = function(*idx_all_args, **idx_kwargs, **extra_kwargs)
   1403     results.append(f_output)
   1404 if isinstance(results[0], tuple):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_matplotlib/_matplotlib_plots.py:558, in Heightmap2D.plot(self, data, figure, subplot, space, min_val, max_val, show_color_bar, color, alpha, err)
    556     col = _plt_col(color)
    557 alpha_f = float(alpha)
--> 558 if heightmap._hdim == dims[1]:  # horizontal
    559     if heightmap._fill_below:
    560         y1, y2 = max(-1e10, float(heightmap.bounds[heightmap._hdim].lower)), y

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:201, in Heightmap.__getattr__(self, item)
    199 if item in self.shape:
    200     return BoundDim(self, item)
--> 201 raise AttributeError(f"{self.__class__.__name__} has no attribute '{item}'")

AttributeError: Heightmap has no attribute '_hdim'

The heightmap surface separates the inside from the outside. Whether the inside is below or above the height values can be set in the Heightmap constructor.

In [3]:
is_inside = CenteredGrid(lambda loc: heightmap.lies_inside(loc), x=100, y=100, bounds=Box(x=2, y=2))
plot(is_inside)
/opt/hostedtoolcache/Python/3.12.12/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
Out[3]:

While the inside check is always exact, the distance from the surface is an approximation. For small distances, the face directly underneath a point will usually be closest but for farther points this is not always true. To account for this, each face links to one other face that will be queried for larger distances. Finding these secondary faces is performed during heightmap construction where the parameter max_dist influences which other faces are the most important to link.

Above, we set max_dist=0.1. In the following cell, we visualize the distance from the surface.

In [4]:
distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
plot(distance, heightmap, overlay='args')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
      2 plot(distance, heightmap, overlay='args')

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/field/_grid.py:75, in CenteredGrid(values, boundary, bounds, resolution, extrapolation, convert, **resolution_)
     73     values = sample(values, elements)
     74 elif callable(values):
---> 75     values = sample_function(values, elements, 'center', extrapolation)
     76 else:
     77     if isinstance(values, (tuple, list)) and len(values) == resolution.rank:

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_geom.py:826, in sample_function(f, elements, at, extrapolation)
    824     values = math.map_s2b(f)(*pos.vector)
    825 else:
--> 826     values = math.map_s2b(f)(pos)
    827 assert isinstance(values, math.Tensor), f"values function must return a Tensor but returned {type(values)}"
    828 return values

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1216, in map_types.<locals>.retyped_f(*args, **kwargs)
   1214     retyped_kwarg, input_types = forward_retype(v, input_types)
   1215     retyped_kwargs[k] = retyped_kwarg
-> 1216 output = f(*retyped_args, **retyped_kwargs)
   1217 restored_output = reverse_retype(output, input_types)
   1218 return restored_output

Cell In[4], line 1, in <lambda>(loc)
----> 1 distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
      2 plot(distance, heightmap, overlay='args')

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:181, in Heightmap.approximate_signed_distance(self, location)
    180 def approximate_signed_distance(self, location: Tensor) -> Tensor:
--> 181     return self.approximate_closest_surface(location)[0]

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:135, in Heightmap.approximate_closest_surface(self, location)
    133 # --- use closest face from considered ---
    134 delta = math.where(projects_onto_face, proj_delta, delta_highest)
--> 135 return math.at_min((distances, delta, normals, offsets, face_idx), key=abs(distances), dim=batch('consider') & instance(self).as_batch())

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2170, in at_min(value, key, dim)
   2168 if not shape(key).only(dim):
   2169     return value
-> 2170 idx = argmin(key, dim)
   2171 return slice_(value, idx)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2263, in argmin(x, dim, index_dim)
   2261     multi_idx_native = choose_backend(idx_native).unravel_index(idx_native[:, 0], dims.sizes)
   2262     return reshaped_tensor(multi_idx_native, [keep - broadcast, index_dim.with_size(dims.name_list)])
-> 2263 return broadcast_op(uniform_argmin, [x], broadcast)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1236, in broadcast_op(operation, tensors, iter_dims, no_return)
   1234 iter_dims = broadcast_dims(*tensors) if iter_dims is None else iter_dims
   1235 if len(iter_dims) == 0:
-> 1236     return operation(*tensors)
   1237 else:
   1238     if isinstance(iter_dims, SHAPE_TYPES):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2261, in argmin.<locals>.uniform_argmin(x)
   2259 v_native = x._reshaped_native([keep - broadcast, dims])
   2260 idx_native = x.backend.argmin(v_native, 1, keepdims=True)
-> 2261 multi_idx_native = choose_backend(idx_native).unravel_index(idx_native[:, 0], dims.sizes)
   2262 return reshaped_tensor(multi_idx_native, [keep - broadcast, index_dim.with_size(dims.name_list)])

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/backend/_numpy_backend.py:360, in NumPyBackend.unravel_index(self, flat_index, shape)
    359 def unravel_index(self, flat_index, shape):
--> 360     return np.stack(np.unravel_index(flat_index, shape), -1)

TypeError: 'NoneType' object cannot be interpreted as an integer

With max_dist=0.1, the values further than 0.1 away from the cliff get inaccurate distances. Let's increase `max_dist´ to see the difference.

In [5]:
heightmap = geom.Heightmap(height, bounds, max_dist=1)
distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
plot(distance, heightmap, overlay='args')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 2
      1 heightmap = geom.Heightmap(height, bounds, max_dist=1)
----> 2 distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
      3 plot(distance, heightmap, overlay='args')

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/field/_grid.py:75, in CenteredGrid(values, boundary, bounds, resolution, extrapolation, convert, **resolution_)
     73     values = sample(values, elements)
     74 elif callable(values):
---> 75     values = sample_function(values, elements, 'center', extrapolation)
     76 else:
     77     if isinstance(values, (tuple, list)) and len(values) == resolution.rank:

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_geom.py:826, in sample_function(f, elements, at, extrapolation)
    824     values = math.map_s2b(f)(*pos.vector)
    825 else:
--> 826     values = math.map_s2b(f)(pos)
    827 assert isinstance(values, math.Tensor), f"values function must return a Tensor but returned {type(values)}"
    828 return values

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1216, in map_types.<locals>.retyped_f(*args, **kwargs)
   1214     retyped_kwarg, input_types = forward_retype(v, input_types)
   1215     retyped_kwargs[k] = retyped_kwarg
-> 1216 output = f(*retyped_args, **retyped_kwargs)
   1217 restored_output = reverse_retype(output, input_types)
   1218 return restored_output

Cell In[5], line 2, in <lambda>(loc)
      1 heightmap = geom.Heightmap(height, bounds, max_dist=1)
----> 2 distance = CenteredGrid(lambda loc: heightmap.approximate_signed_distance(loc), x=100, y=100, bounds=Box(x=2, y=2))
      3 plot(distance, heightmap, overlay='args')

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:181, in Heightmap.approximate_signed_distance(self, location)
    180 def approximate_signed_distance(self, location: Tensor) -> Tensor:
--> 181     return self.approximate_closest_surface(location)[0]

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:135, in Heightmap.approximate_closest_surface(self, location)
    133 # --- use closest face from considered ---
    134 delta = math.where(projects_onto_face, proj_delta, delta_highest)
--> 135 return math.at_min((distances, delta, normals, offsets, face_idx), key=abs(distances), dim=batch('consider') & instance(self).as_batch())

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2170, in at_min(value, key, dim)
   2168 if not shape(key).only(dim):
   2169     return value
-> 2170 idx = argmin(key, dim)
   2171 return slice_(value, idx)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2263, in argmin(x, dim, index_dim)
   2261     multi_idx_native = choose_backend(idx_native).unravel_index(idx_native[:, 0], dims.sizes)
   2262     return reshaped_tensor(multi_idx_native, [keep - broadcast, index_dim.with_size(dims.name_list)])
-> 2263 return broadcast_op(uniform_argmin, [x], broadcast)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:1236, in broadcast_op(operation, tensors, iter_dims, no_return)
   1234 iter_dims = broadcast_dims(*tensors) if iter_dims is None else iter_dims
   1235 if len(iter_dims) == 0:
-> 1236     return operation(*tensors)
   1237 else:
   1238     if isinstance(iter_dims, SHAPE_TYPES):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_ops.py:2261, in argmin.<locals>.uniform_argmin(x)
   2259 v_native = x._reshaped_native([keep - broadcast, dims])
   2260 idx_native = x.backend.argmin(v_native, 1, keepdims=True)
-> 2261 multi_idx_native = choose_backend(idx_native).unravel_index(idx_native[:, 0], dims.sizes)
   2262 return reshaped_tensor(multi_idx_native, [keep - broadcast, index_dim.with_size(dims.name_list)])

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/backend/_numpy_backend.py:360, in NumPyBackend.unravel_index(self, flat_index, shape)
    359 def unravel_index(self, flat_index, shape):
--> 360     return np.stack(np.unravel_index(flat_index, shape), -1)

TypeError: 'NoneType' object cannot be interpreted as an integer

Now larger distances are covered as well. This runs with the same number of computations but trades off accuracy closer to the surface.

We can additionally query the direction to the closest surface point as well as the corresponding normal vector.

Let's plot these vectors for random points inside (orange) and outside (red) of the heightmap.

In [6]:
points = Box(x=3, y=(-1, 1)).sample_uniform(instance(points=200))
sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
plot({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])
/opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_tensors.py:1155: RuntimeWarning: invalid value encountered in power
  result = op(n1, n2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[6], line 3
      1 points = Box(x=3, y=(-1, 1)).sample_uniform(instance(points=200))
      2 sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
----> 3 plot({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      4       'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis.py:322, 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)
    320     for i, f in enumerate(fields):
    321         idx = indices[pos][i]
--> 322         plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[pos][i][idx], alpha[idx], err[idx])
    323 plots.finalize(figure)
    324 LAST_FIGURE[0] = figure

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis_base.py:381, in PlottingLibrary.plot(self, data, figure, subplot, space, *args, **kwargs)
    379 for recipe in self.recipes:
    380     if recipe.can_plot(data, space):
--> 381         recipe.plot(data, figure, subplot, space, *args, **kwargs)
    382         return
    383 raise NotImplementedError(f"No {self.name} recipe found for {data}. Recipes: {self.recipes}")

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1270, in broadcast.<locals>.broadcast_(*args, **kwargs)
   1268 @wraps(function)
   1269 def broadcast_(*args, **kwargs):
-> 1270     return map_(function, *args, dims=dims, range=range, unwrap_scalars=unwrap_scalars, simplify=simplify, map_name=name, **kwargs)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1402, in map_(function, dims, range, unwrap_scalars, expand_results, simplify, map_name, *args, **kwargs)
   1400     idx_extra_args = list(extra_args)
   1401     idx_all_args = [idx_args.pop(0) if isinstance(v, Shapable) else idx_extra_args.pop(0) for v in args]
-> 1402     f_output = function(*idx_all_args, **idx_kwargs, **extra_kwargs)
   1403     results.append(f_output)
   1404 if isinstance(results[0], tuple):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_matplotlib/_matplotlib_plots.py:558, in Heightmap2D.plot(self, data, figure, subplot, space, min_val, max_val, show_color_bar, color, alpha, err)
    556     col = _plt_col(color)
    557 alpha_f = float(alpha)
--> 558 if heightmap._hdim == dims[1]:  # horizontal
    559     if heightmap._fill_below:
    560         y1, y2 = max(-1e10, float(heightmap.bounds[heightmap._hdim].lower)), y

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:201, in Heightmap.__getattr__(self, item)
    199 if item in self.shape:
    200     return BoundDim(self, item)
--> 201 raise AttributeError(f"{self.__class__.__name__} has no attribute '{item}'")

AttributeError: Heightmap has no attribute '_hdim'

Vertical Heightmaps¶

The grid orientation is determined by which spatial dimension is not part of the grid. E.g. to create a vertical heightmap, the displacement values should be listed along the y direction.

In [7]:
height = wrap([.1, .02, 0, 0, 1, .95, .8, .5, 0], spatial('y'))
bounds = Box(x=1, y=2)
plot(geom.Heightmap(height, bounds, max_dist=.1))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[7], line 3
      1 height = wrap([.1, .02, 0, 0, 1, .95, .8, .5, 0], spatial('y'))
      2 bounds = Box(x=1, y=2)
----> 3 plot(geom.Heightmap(height, bounds, max_dist=.1))

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis.py:322, 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)
    320     for i, f in enumerate(fields):
    321         idx = indices[pos][i]
--> 322         plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[pos][i][idx], alpha[idx], err[idx])
    323 plots.finalize(figure)
    324 LAST_FIGURE[0] = figure

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis_base.py:381, in PlottingLibrary.plot(self, data, figure, subplot, space, *args, **kwargs)
    379 for recipe in self.recipes:
    380     if recipe.can_plot(data, space):
--> 381         recipe.plot(data, figure, subplot, space, *args, **kwargs)
    382         return
    383 raise NotImplementedError(f"No {self.name} recipe found for {data}. Recipes: {self.recipes}")

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1270, in broadcast.<locals>.broadcast_(*args, **kwargs)
   1268 @wraps(function)
   1269 def broadcast_(*args, **kwargs):
-> 1270     return map_(function, *args, dims=dims, range=range, unwrap_scalars=unwrap_scalars, simplify=simplify, map_name=name, **kwargs)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_functional.py:1402, in map_(function, dims, range, unwrap_scalars, expand_results, simplify, map_name, *args, **kwargs)
   1400     idx_extra_args = list(extra_args)
   1401     idx_all_args = [idx_args.pop(0) if isinstance(v, Shapable) else idx_extra_args.pop(0) for v in args]
-> 1402     f_output = function(*idx_all_args, **idx_kwargs, **extra_kwargs)
   1403     results.append(f_output)
   1404 if isinstance(results[0], tuple):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_matplotlib/_matplotlib_plots.py:558, in Heightmap2D.plot(self, data, figure, subplot, space, min_val, max_val, show_color_bar, color, alpha, err)
    556     col = _plt_col(color)
    557 alpha_f = float(alpha)
--> 558 if heightmap._hdim == dims[1]:  # horizontal
    559     if heightmap._fill_below:
    560         y1, y2 = max(-1e10, float(heightmap.bounds[heightmap._hdim].lower)), y

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:201, in Heightmap.__getattr__(self, item)
    199 if item in self.shape:
    200     return BoundDim(self, item)
--> 201 raise AttributeError(f"{self.__class__.__name__} has no attribute '{item}'")

AttributeError: Heightmap has no attribute '_hdim'

2D Heightmaps¶

Adding a third dimension z yields a 2D heightmap in 3D space. Here, we create height values from a function, but the rest stays the same.

In [8]:
bounds = Box(x=2, y=2, z=1)
height = CenteredGrid(lambda pos: math.exp(-math.vec_squared(pos-1) * 3), 0, bounds['x,y'], x=10, y=10).values
heightmap = geom.Heightmap(height, bounds, max_dist=.1)
show(heightmap)
/tmp/ipykernel_2909/959595594.py:2: DeprecationWarning: phiml.math.vec_squared is deprecated in favor of phiml.math.squared_norm
  height = CenteredGrid(lambda pos: math.exp(-math.vec_squared(pos-1) * 3), 0, bounds['x,y'], x=10, y=10).values

Again, we can sample directions and distances from the surface.

In [9]:
points = bounds.sample_uniform(instance(points=200))
sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
show({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[9], line 3
      1 points = bounds.sample_uniform(instance(points=200))
      2 sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
----> 3 show({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      4       'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis.py:58, in show(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)
     56 kwargs = locals()
     57 del kwargs['fields']
---> 58 fig = plot(*fields, **kwargs)
     59 plots = get_plots_by_figure(fig)
     60 if isinstance(fig, Tensor):

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis.py:322, 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)
    320     for i, f in enumerate(fields):
    321         idx = indices[pos][i]
--> 322         plots.plot(f, figure, axes[pos], subplots[pos], min_val, max_val, show_color_bar, color[pos][i][idx], alpha[idx], err[idx])
    323 plots.finalize(figure)
    324 LAST_FIGURE[0] = figure

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_vis_base.py:381, in PlottingLibrary.plot(self, data, figure, subplot, space, *args, **kwargs)
    379 for recipe in self.recipes:
    380     if recipe.can_plot(data, space):
--> 381         recipe.plot(data, figure, subplot, space, *args, **kwargs)
    382         return
    383 raise NotImplementedError(f"No {self.name} recipe found for {data}. Recipes: {self.recipes}")

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/vis/_dash/_plotly_plots.py:325, in VectorCloud3D.plot(self, data, figure, subplot, space, min_val, max_val, show_color_bar, color, alpha, err)
    323 x, y, z = math.reshaped_numpy(data.points.vector[dims], [vector, data.shape.non_channel])
    324 u, v, w = math.reshaped_numpy(data.values.vector[dims], [vector, extra_channels, data.shape.non_channel])
--> 325 figure.add_cone(x=x.flatten(), y=y.flatten(), z=z.flatten(), u=u.flatten(), v=v.flatten(), w=w.flatten(),
    326                 colorscale=colorscale,
    327                 sizemode='raw', anchor='tail',
    328                 row=row, col=col, opacity=float(alpha))

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/graph_objs/_figure.py:4659, in Figure.add_cone(self, anchor, autocolorscale, cauto, cmax, cmid, cmin, coloraxis, colorbar, colorscale, customdata, customdatasrc, hoverinfo, hoverinfosrc, hoverlabel, hovertemplate, hovertemplatefallback, hovertemplatesrc, hovertext, hovertextsrc, ids, idssrc, legend, legendgroup, legendgrouptitle, legendrank, legendwidth, lighting, lightposition, meta, metasrc, name, opacity, reversescale, scene, showlegend, showscale, sizemode, sizeref, stream, text, textsrc, u, uhoverformat, uid, uirevision, usrc, v, vhoverformat, visible, vsrc, w, whoverformat, wsrc, x, xhoverformat, xsrc, y, yhoverformat, ysrc, z, zhoverformat, zsrc, row, col, **kwargs)
   4284 """
   4285 Add a new Cone trace
   4286 
   (...)   4655 Figure
   4656 """
   4657 from plotly.graph_objs import Cone
-> 4659 new_trace = Cone(
   4660     anchor=anchor,
   4661     autocolorscale=autocolorscale,
   4662     cauto=cauto,
   4663     cmax=cmax,
   4664     cmid=cmid,
   4665     cmin=cmin,
   4666     coloraxis=coloraxis,
   4667     colorbar=colorbar,
   4668     colorscale=colorscale,
   4669     customdata=customdata,
   4670     customdatasrc=customdatasrc,
   4671     hoverinfo=hoverinfo,
   4672     hoverinfosrc=hoverinfosrc,
   4673     hoverlabel=hoverlabel,
   4674     hovertemplate=hovertemplate,
   4675     hovertemplatefallback=hovertemplatefallback,
   4676     hovertemplatesrc=hovertemplatesrc,
   4677     hovertext=hovertext,
   4678     hovertextsrc=hovertextsrc,
   4679     ids=ids,
   4680     idssrc=idssrc,
   4681     legend=legend,
   4682     legendgroup=legendgroup,
   4683     legendgrouptitle=legendgrouptitle,
   4684     legendrank=legendrank,
   4685     legendwidth=legendwidth,
   4686     lighting=lighting,
   4687     lightposition=lightposition,
   4688     meta=meta,
   4689     metasrc=metasrc,
   4690     name=name,
   4691     opacity=opacity,
   4692     reversescale=reversescale,
   4693     scene=scene,
   4694     showlegend=showlegend,
   4695     showscale=showscale,
   4696     sizemode=sizemode,
   4697     sizeref=sizeref,
   4698     stream=stream,
   4699     text=text,
   4700     textsrc=textsrc,
   4701     u=u,
   4702     uhoverformat=uhoverformat,
   4703     uid=uid,
   4704     uirevision=uirevision,
   4705     usrc=usrc,
   4706     v=v,
   4707     vhoverformat=vhoverformat,
   4708     visible=visible,
   4709     vsrc=vsrc,
   4710     w=w,
   4711     whoverformat=whoverformat,
   4712     wsrc=wsrc,
   4713     x=x,
   4714     xhoverformat=xhoverformat,
   4715     xsrc=xsrc,
   4716     y=y,
   4717     yhoverformat=yhoverformat,
   4718     ysrc=ysrc,
   4719     z=z,
   4720     zhoverformat=zhoverformat,
   4721     zsrc=zsrc,
   4722     **kwargs,
   4723 )
   4724 return self.add_trace(new_trace, row=row, col=col)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/graph_objs/_cone.py:2251, in Cone.__init__(self, arg, anchor, autocolorscale, cauto, cmax, cmid, cmin, coloraxis, colorbar, colorscale, customdata, customdatasrc, hoverinfo, hoverinfosrc, hoverlabel, hovertemplate, hovertemplatefallback, hovertemplatesrc, hovertext, hovertextsrc, ids, idssrc, legend, legendgroup, legendgrouptitle, legendrank, legendwidth, lighting, lightposition, meta, metasrc, name, opacity, reversescale, scene, showlegend, showscale, sizemode, sizeref, stream, text, textsrc, u, uhoverformat, uid, uirevision, usrc, v, vhoverformat, visible, vsrc, w, whoverformat, wsrc, x, xhoverformat, xsrc, y, yhoverformat, ysrc, z, zhoverformat, zsrc, **kwargs)
   2249 self._set_property("coloraxis", arg, coloraxis)
   2250 self._set_property("colorbar", arg, colorbar)
-> 2251 self._set_property("colorscale", arg, colorscale)
   2252 self._set_property("customdata", arg, customdata)
   2253 self._set_property("customdatasrc", arg, customdatasrc)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/basedatatypes.py:4403, in BasePlotlyType._set_property(self, name, arg, provided)
   4397 def _set_property(self, name, arg, provided):
   4398     """
   4399     Initialize a property of this object using the provided value
   4400     or a value popped from the arguments dictionary. If neither
   4401     is available, do not set the property.
   4402     """
-> 4403     _set_property_provided_value(self, name, arg, provided)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/basedatatypes.py:398, in _set_property_provided_value(obj, name, arg, provided)
    396 val = provided if provided is not None else val
    397 if val is not None:
--> 398     obj[name] = val

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/basedatatypes.py:4932, in BasePlotlyType.__setitem__(self, prop, value)
   4928         self._set_array_prop(prop, value)
   4930     # ### Handle simple property ###
   4931     else:
-> 4932         self._set_prop(prop, value)
   4933 else:
   4934     # Make sure properties dict is initialized
   4935     self._init_props()

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/basedatatypes.py:5276, in BasePlotlyType._set_prop(self, prop, val)
   5274         return
   5275     else:
-> 5276         raise err
   5278 # val is None
   5279 # -----------
   5280 if val is None:
   5281     # Check if we should send null update

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/plotly/basedatatypes.py:5271, in BasePlotlyType._set_prop(self, prop, val)
   5268 validator = self._get_validator(prop)
   5270 try:
-> 5271     val = validator.validate_coerce(val)
   5272 except ValueError as err:
   5273     if self._skip_invalid:

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/_plotly_utils/basevalidators.py:1650, in ColorscaleValidator.validate_coerce(self, v)
   1645             v = [
   1646                 [e[0], ColorValidator.perform_validate_coerce(e[1])] for e in v
   1647             ]
   1649 if not v_valid:
-> 1650     self.raise_invalid_val(v)
   1652 return v

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/_plotly_utils/basevalidators.py:312, in BaseValidator.raise_invalid_val(self, v, inds)
    309             for i in inds:
    310                 name += "[" + str(i) + "]"
--> 312         raise ValueError(
    313             """
    314     Invalid value of type {typ} received for the '{name}' property of {pname}
    315         Received value: {v}
    316 
    317 {valid_clr_desc}""".format(
    318                 name=name,
    319                 pname=self.parent_name,
    320                 typ=type_str(v),
    321                 v=repr(v),
    322                 valid_clr_desc=self.description(),
    323             )
    324         )

ValueError: 
    Invalid value of type 'builtins.list' received for the 'colorscale' property of cone
        Received value: [[0, ['rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)']], [1, ['rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(214, 39, 40)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)', 'rgb(255, 127, 14)']]]

    The 'colorscale' property is a colorscale and may be
    specified as:
      - A list of colors that will be spaced evenly to create the colorscale.
        Many predefined colorscale lists are included in the sequential, diverging,
        and cyclical modules in the plotly.colors package.
      - A list of 2-element lists where the first element is the
        normalized color level value (starting at 0 and ending at 1),
        and the second item is a valid color string.
        (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']])
      - One of the following named colorscales:
            ['aggrnyl', 'agsunset', 'algae', 'amp', 'armyrose', 'balance',
             'blackbody', 'bluered', 'blues', 'blugrn', 'bluyl', 'brbg',
             'brwnyl', 'bugn', 'bupu', 'burg', 'burgyl', 'cividis', 'curl',
             'darkmint', 'deep', 'delta', 'dense', 'earth', 'edge', 'electric',
             'emrld', 'fall', 'geyser', 'gnbu', 'gray', 'greens', 'greys',
             'haline', 'hot', 'hsv', 'ice', 'icefire', 'inferno', 'jet',
             'magenta', 'magma', 'matter', 'mint', 'mrybm', 'mygbm', 'oranges',
             'orrd', 'oryel', 'oxy', 'peach', 'phase', 'picnic', 'pinkyl',
             'piyg', 'plasma', 'plotly3', 'portland', 'prgn', 'pubu', 'pubugn',
             'puor', 'purd', 'purp', 'purples', 'purpor', 'rainbow', 'rdbu',
             'rdgy', 'rdpu', 'rdylbu', 'rdylgn', 'redor', 'reds', 'solar',
             'spectral', 'speed', 'sunset', 'sunsetdark', 'teal', 'tealgrn',
             'tealrose', 'tempo', 'temps', 'thermal', 'tropic', 'turbid',
             'turbo', 'twilight', 'viridis', 'ylgn', 'ylgnbu', 'ylorbr',
             'ylorrd'].
        Appending '_r' to a named colorscale reverses it.

Combining Heightmaps¶

Like all Geometry objects, heightmaps can be merged using the union function. If we only pass Heightmap objects with the same grid resolution, ΦFlow will automatically vectorize computations so that the latency does not increase.

We can use this to create complex obstacles with detailed surfaces on all sides or to create cavities.

In [10]:
height = (1 - math.linspace(-1, 1, spatial(x=10)) ** 2) ** .5
upper_heightmap = geom.Heightmap(height, Box(x=(-1, 1), y=None), max_dist=.1, fill_below=False, extrapolation=0)
lower_heightmap = geom.Heightmap(-height, Box(x=(-1, 1), y=None), max_dist=.1, fill_below=True, extrapolation=0)
heightmap = union(lower_heightmap, upper_heightmap)

points = Box(x=(-2, 2), y=(-2, 2)).sample_uniform(instance(points=200))
sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
show({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[10], line 7
      4 heightmap = union(lower_heightmap, upper_heightmap)
      6 points = Box(x=(-2, 2), y=(-2, 2)).sample_uniform(instance(points=200))
----> 7 sgn_dist, delta, normal, _, face_index = heightmap.approximate_closest_surface(points)
      8 show({'closest': [heightmap, PointCloud(points, .1 * math.vec_normalize(delta))],
      9       'normal': [heightmap, PointCloud(points, .2 * normal)]}, overlay='list', color=[0, heightmap.lies_inside(points)*2+1])

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:111, in Heightmap.approximate_closest_surface(self, location)
    109 def approximate_closest_surface(self, location: Tensor) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor]:
    110     grid_bounds = math.i2b(self.grid_bounds)
--> 111     faces = math.i2b(self.face_cache)
    112     cell_idx = cell_index(location, grid_bounds, self.resolution, clip=True)
    113     # --- gather face infos at projected cell ---

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/functools.py:998, in cached_property.__get__(self, instance, owner)
    996 val = cache.get(self.attrname, _NOT_FOUND)
    997 if val is _NOT_FOUND:
--> 998     val = self.func(instance)
    999     try:
   1000         cache[self.attrname] = val

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:77, in Heightmap.face_cache(self)
     75 @cached_property
     76 def face_cache(self):
---> 77     proj_faces = build_faces(self)
     78     with numpy.errstate(divide='ignore', invalid='ignore'):
     79         secondary_idx = math.map(find_most_important_neighbor, proj_faces, self.dx, self.resolution, self.hdim, self.fill_below, self.max_dist, dims=instance, unwrap_scalars=False)

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:219, in build_faces(heightmap)
    217 height = heightmap.height
    218 flat_space = spatial(height).name_list
--> 219 pos = heightmap.vertices
    220 center = math.neighbor_mean(pos)
    221 face_slopes = {}

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phi/geom/_heightmap.py:89, in Heightmap.vertices(self)
     87 space = self.vector.item_names
     88 pos = self.grid_bounds.local_to_global(math.meshgrid(spatial(self.height)) / self.resolution)
---> 89 vert = stack({dim: self.height if dim == hdim else pos[dim] for dim in space}, channel('vector'))
     90 return vert

File /opt/hostedtoolcache/Python/3.12.12/x64/lib/python3.12/site-packages/phiml/math/_magic_ops.py:148, in stack(values, dim, expand_values, simplify, layout_non_matching, **kwargs)
    146                 from ._tensors import layout
    147                 return layout(values, dim)
--> 148             raise ValueError(f"Non-batch dims must match but got: {v0_dims} and {s.non_batch.names}. Manually expand tensors or set expand_values=True")
    149 # --- Add missing dims ---
    150 if expand_values:

ValueError: Non-batch dims must match but got: {'x'} and ('union', 'x'). Manually expand tensors or set expand_values=True

Here, we set extrapolation=0 to extend the heightmap beyond its bounds. Consequently, points with x < -1 or x > 1 count as inside the mesh and are colored red correspondingly.