porespy icon indicating copy to clipboard operation
porespy copied to clipboard

`points_to_spheres` not adding spheres correctly for 3D images

Open heinsimon opened this issue 11 months ago • 1 comments

The function points_to_spheres is not correctly adding spheres for a 3D image.

radius=10
im=np.full((41,41,41),0)
im[20,20,20]=radius

res_ps=ps.tools.points_to_spheres(im=im)
res_fixed = points_to_spheres_fixed(im=im)
print(f'Number voxels porespy: {np.sum(res_ps)}') # Number voxels porespy: 1
print(f'Number voxels fixed: {np.sum(res_fixed)}') # Number voxels fixed: 4169
print(f'Volume analytic: {4/3*radius**3 *np.pi}') # Volume analytic: 4188.790204786391

fig, ax = plt.subplots(3, 2, figsize=[12, 6])
ax[0,0].imshow(ps.visualization.sem(~res_ps, axis=0), interpolation='none', origin='lower')
ax[0,0].axis(False)
ax[1,0].imshow(ps.visualization.sem(~res_ps, axis=2).T, interpolation='none', origin='lower')
ax[1,0].axis(False);
ax[2,0].imshow(ps.visualization.sem(~res_ps, axis=1).T, interpolation='none', origin='lower')
ax[2,0].axis(False);

ax[0,1].imshow(ps.visualization.sem(~res_fixed, axis=0), interpolation='none', origin='lower')
ax[0,1].axis(False)
ax[1,1].imshow(ps.visualization.sem(~res_fixed, axis=2).T, interpolation='none', origin='lower')
ax[1,1].axis(False);
ax[2,1].imshow(ps.visualization.sem(~res_fixed, axis=1).T, interpolation='none', origin='lower')
ax[2,1].axis(False);

Potential fix:

Fix used for example above

def points_to_spheres_fixed(im):
    r"""
    Inserts disks/spheres into an image at locations indicated by non-zero values

    Parameters
    ----------
    im : ndarray
        The image containing nonzeros indicating the locations to insert spheres.
        If the non-zero values are `bool`, then the maximal size is found and used;
        if the non-zeros are `int` then these values are used as the radii.

    Returns
    -------
    spheres : ndarray
        A `bool` array with disks/spheres inserted at each nonzero location in `im`.
    """
    from scipy.spatial import distance_matrix
    if im.ndim == 3:
        x, y, z = np.where(im > 0)
        coords = np.vstack((x, y, z))
    else:
        x, y = np.where(im > 0)
        coords = np.vstack((x, y))
    if im.dtype == bool:
        dmap = distance_matrix(coords.T, coords.T)
        mask = dmap < 1
        dmap[mask] = np.inf
        r = np.around(dmap.min(axis=0)/2, decimals=0).astype(int)
    else:
        if im.ndim == 3:
            r = im[x, y, z].flatten()
        else:
            r = im[x, y].flatten()
    im_spheres = np.zeros_like(im, dtype=bool)
    im_spheres = ps.tools._insert_disks_at_points_parallel(
        im_spheres,
        coords=coords,
        radii=r,
        v=True,
        smooth=False,
    )
    return im_spheres
    ```

heinsimon avatar Mar 08 '24 12:03 heinsimon

Thanks for the detailed bug report and solution. Much appreciated. Would you be willing to do a pull request? Those changes are pretty minor so it should be a quick and easy.

jgostick avatar Mar 08 '24 16:03 jgostick