def propagate_sign(vol, sigma=5, certainty_threshold=13):
    """
    iterative propagation of SDF signs from 'seed' voxels to get a dense, truncated volume
    :param vol:
    :param certainty_threshold: int in (0..5^3]
    :param sigma: neighborhood of propagation (kernel size)
    :return:
    """

    from scipy.ndimage.filters import convolve

    # # remove disconnected voxels as pre-processing
    # # e.g. a single outside voxel surrounded by inside voxels
    # sigma_disc = 3
    # kernel_neighbor_sum = np.ones((sigma_disc, sigma_disc, sigma_disc), dtype=np.float32)
    # kernel_center = int(sigma_disc / 2)
    # kernel_neighbor_sum[kernel_center, kernel_center, kernel_center] = 0.0
    # neighbor_sum = convolve(np.sign(vol), kernel_neighbor_sum, mode='nearest')
    # num_neighbors = sigma_disc**3.0 - 1.0
    # neighbors_have_same_sign = np.abs(neighbor_sum) == int(num_neighbors)
    # disconnected_voxels = np.logical_and(neighbors_have_same_sign, np.sign(neighbor_sum) != np.sign(vol))
    # vol[disconnected_voxels] = neighbor_sum[disconnected_voxels] / num_neighbors

    # smoothing as pre-processing helps with the reconstruction noise
    # quality should be ok as long as the surface stays at the same place
    # over-smoothing reduces the overall quality
    # sigma_smooth = 3
    # kernel_neighbor_sum = np.ones((sigma_smooth, sigma_smooth, sigma_smooth), dtype=np.float32)
    # vol = convolve(vol, kernel_neighbor_sum, mode='nearest') / sigma_smooth**3

    vol_sign_propagated = np.sign(vol)
    unknown_initially = vol_sign_propagated == 0
    kernel = np.ones((sigma, sigma, sigma), dtype=np.float32)

    # assume borders to be outside, reduce number of iterations
    vol[+0, :, :] = -1.0
    vol[-1, :, :] = -1.0
    vol[:, +0, :] = -1.0
    vol[:, -1, :] = -1.0
    vol[:, :, +0] = -1.0
    vol[:, :, -1] = -1.0

    while True:
        unknown_before = vol_sign_propagated == 0
        if unknown_before.sum() == 0:
            break

        # sum sign of neighboring voxels
        vol_sign_propagated_new = convolve(vol_sign_propagated, kernel, mode='nearest')

        # update only when the sign is certain
        vol_sign_propagated_new_unsure = np.abs(vol_sign_propagated_new) < certainty_threshold
        vol_sign_propagated_new[vol_sign_propagated_new_unsure] = 0.0

        # map again to [-1.0, 0.0, +1.0]
        vol_sign_propagated_new = np.sign(vol_sign_propagated_new)

        # stop when no changes happen
        unknown_after = vol_sign_propagated_new == 0
        if unknown_after.sum() >= unknown_before.sum():
            break  # no changes -> some values might be caught in a tie
        vol_sign_propagated[unknown_initially] = vol_sign_propagated_new[unknown_initially]  # add new values

    vol[vol == 0] = vol_sign_propagated[vol == 0]
    return vol

    iterative propagation of SDF signs from 'seed' voxels to get a dense, truncated volume
    :param vol:
    :param certainty_threshold: int in (0..5^3]
    :param sigma: neighborhood of propagation (kernel size)
    :return:
    
source/sdf.py