XelToFab
API Reference

sdf_to_density

Convert an SDF array to a density array in [0, 1]

Import

from xeltofab import sdf_to_density
# or, for direct access to each primitive:
from xeltofab import heaviside, linear_ramp, sigmoid

Signature

def sdf_to_density(
    sdf: np.ndarray,
    method: Literal["heaviside", "linear", "sigmoid"] = "linear",
    *,
    bandwidth: float = 1.0,
    level: float = 0.0,
) -> np.ndarray

Parameters

ParameterTypeDefaultDescription
sdfarray-likeSigned-distance field. Any shape. Finite values only. Negative inside, positive outside.
method"heaviside" | "linear" | "sigmoid""linear"Conversion profile (see below).
bandwidthfloat1.0Transition scale in the same units as sdf. Must be > 0 for "linear" and "sigmoid"; ignored for "heaviside".
levelfloat0.0Iso-surface offset.

Return value

A float64 array with the same shape as sdf and values in [0, 1]. All three methods map sdf == level to density exactly 0.5, so extracting the density at iso=0.5 recovers the input iso-surface.

Methods

MethodFormulaWhen to use
"heaviside"{1 if sdf<level, 0.5 if sdf==level, 0 if sdf>level}Binary density when sub-voxel information is not needed.
"linear" (default)clip(0.5 - (sdf-level)/(2·bandwidth), 0, 1)Compact-support ramp — matches marching-cubes-at-0.5 behavior on band-limited density fields.
"sigmoid"1 / (1 + exp((sdf-level)/bandwidth))Smooth, differentiable profile for gradient-based optimizers (e.g. TO solvers with projection filters).

Visual comparison

See Bridging SDF and density for 1D profile plots and side-by-side mesh renders that show exactly how the three methods differ at finite grid resolution.

When to use this

The pipeline already accepts SDF arrays directly via PipelineParams(field_type="sdf") — you do not need sdf_to_density for native SDF processing. Use it when a downstream consumer expects density semantics:

  • Feeding an SDF-sourced field into a density-only TO solver or benchmarking harness (e.g. EngiBench).
  • Comparing SDF-derived meshes against density-pipeline meshes under matched defaults.
  • Converting once and caching a density grid for repeated density-mode runs.

Examples

Basic conversion with direct extraction

import numpy as np
from xeltofab import sdf_to_density, process, PipelineParams, PipelineState

# sdf_grid: ndarray, negative inside the shape, positive outside
density = sdf_to_density(sdf_grid, method="linear", bandwidth=1.0)

# direct_extraction=True skips the density preprocessor so the iso=0.5
# surface of the converted field is preserved exactly.
params = PipelineParams(
    field_type="density",
    direct_extraction=True,
    extraction_level=0.5,
)
result = process(PipelineState(field=density, params=params))

Choosing a bandwidth

bandwidth has the same units as your SDF.

  • SDF from scipy.ndimage.distance_transform_edtbandwidth is in voxels. bandwidth=1.0 gives a one-voxel transition band.
  • SDF from an analytic function on a world-space grid → bandwidth is in world units. Pick ~ (1-2) * voxel_spacing to keep the transition band at grid resolution.

Using a primitive directly

The three primitives are exported for callers that want an explicit function reference rather than a string dispatch:

from xeltofab import sigmoid

density = sigmoid(sdf_grid, bandwidth=0.5)

Errors

  • ValueErrorsdf contains NaN or Inf values.
  • ValueErrorbandwidth is not a finite positive number (for "linear" and "sigmoid").
  • ValueErrorlevel is not finite.
  • ValueError — unknown method string.

See also

Outline