CofeehousePy/deps/scikit-image/skimage/restoration/rolling_ball.py

193 lines
6.3 KiB
Python

import numpy as np
from ._rolling_ball_cy import apply_kernel, apply_kernel_nan
def rolling_ball(image, *, radius=100, kernel=None,
nansafe=False, num_threads=None):
"""Estimate background intensity by rolling/translating a kernel.
This rolling ball algorithm estimates background intensity for a
ndimage in case of uneven exposure. It is a generalization of the
frequently used rolling ball algorithm [1]_.
Parameters
----------
image : ndarray
The image to be filtered.
radius : int, optional
Radius of a ball shaped kernel to be rolled/translated in the image.
Used if ``kernel = None``.
kernel : ndarray, optional
The kernel to be rolled/translated in the image. It must have the
same number of dimensions as ``image``. Kernel is filled with the
intensity of the kernel at that position.
nansafe: bool, optional
If ``False`` (default) assumes that none of the values in ``image``
are ``np.nan``, and uses a faster implementation.
num_threads: int, optional
The maximum number of threads to use. If ``None`` use the OpenMP
default value; typically equal to the maximum number of virtual cores.
Note: This is an upper limit to the number of threads. The exact number
is determined by the system's OpenMP library.
Returns
-------
background : ndarray
The estimated background of the image.
Notes
-----
For the pixel that has its background intensity estimated (without loss
of generality at ``center``) the rolling ball method centers ``kernel``
under it and raises the kernel until the surface touches the image umbra
at some ``pos=(y,x)``. The background intensity is then estimated
using the image intensity at that position (``image[pos]``) plus the
difference of ``kernel[center] - kernel[pos]``.
This algorithm assumes that dark pixels correspond to the background. If
you have a bright background, invert the image before passing it to the
function, e.g., using `utils.invert`. See the gallery example for details.
This algorithm is sensitive to noise (in particular salt-and-pepper
noise). If this is a problem in your image, you can apply mild
gaussian smoothing before passing the image to this function.
References
----------
.. [1] Sternberg, Stanley R. "Biomedical image processing." Computer 1
(1983): 22-34. :DOI:`10.1109/MC.1983.1654163`
Examples
--------
>>> import numpy as np
>>> from skimage import data
>>> from skimage.restoration import rolling_ball
>>> image = data.coins()
>>> background = rolling_ball(data.coins())
>>> filtered_image = image - background
>>> import numpy as np
>>> from skimage import data
>>> from skimage.restoration import rolling_ball, ellipsoid_kernel
>>> image = data.coins()
>>> kernel = ellipsoid_kernel((101, 101), 75)
>>> background = rolling_ball(data.coins(), kernel=kernel)
>>> filtered_image = image - background
"""
image = np.asarray(image)
img = image.astype(float)
if num_threads is None:
num_threads = 0
if kernel is None:
kernel = ball_kernel(radius, image.ndim)
kernel_shape = np.asarray(kernel.shape)
kernel_center = (kernel_shape // 2)
center_intensity = kernel[tuple(kernel_center)]
intensity_difference = center_intensity - kernel
intensity_difference[kernel == np.Inf] = np.Inf
intensity_difference = intensity_difference.astype(img.dtype)
intensity_difference = intensity_difference.reshape(-1)
img = np.pad(img, kernel_center[:, np.newaxis],
constant_values=np.Inf, mode="constant")
func = apply_kernel_nan if nansafe else apply_kernel
background = func(
img.reshape(-1),
intensity_difference,
np.zeros_like(image, dtype=img.dtype).reshape(-1),
np.array(image.shape, dtype=np.intp),
np.array(img.shape, dtype=np.intp),
kernel_shape.astype(np.intp),
num_threads
)
background = background.astype(image.dtype, copy=False)
return background
def ball_kernel(radius, ndim):
"""Create a ball kernel for restoration.rolling_ball.
Parameters
----------
radius : int
Radius of the ball.
ndim : int
Number of dimensions of the ball. ``ndim`` should match the
dimensionality of the image the kernel will be applied to.
Returns
-------
kernel : ndarray
The kernel containing the surface intensity of the top half
of the ellipsoid.
See Also
--------
rolling_ball
"""
kernel_coords = np.stack(
np.meshgrid(
*[np.arange(-x, x + 1) for x in [np.ceil(radius)] * ndim],
indexing='ij'
),
axis=-1
)
sum_of_squares = np.sum(kernel_coords ** 2, axis=-1)
distance_from_center = np.sqrt(sum_of_squares)
kernel = np.sqrt(np.clip(radius ** 2 - sum_of_squares, 0, None))
kernel[distance_from_center > radius] = np.Inf
return kernel
def ellipsoid_kernel(shape, intensity):
"""Create an ellipoid kernel for restoration.rolling_ball.
Parameters
----------
shape : arraylike
Length of the principal axis of the ellipsoid (excluding
the intensity axis). The kernel needs to have the same
dimensionality as the image it will be applied to.
intensity : int
Length of the intensity axis of the ellipsoid.
Returns
-------
kernel : ndarray
The kernel containing the surface intensity of the top half
of the ellipsoid.
See Also
--------
rolling_ball
"""
shape = np.asarray(shape)
semi_axis = np.clip(shape // 2, 1, None)
kernel_coords = np.stack(
np.meshgrid(
*[np.arange(-x, x + 1) for x in semi_axis],
indexing='ij'
),
axis=-1)
intensity_scaling = 1 - np.sum((kernel_coords / semi_axis) ** 2, axis=-1)
kernel = intensity * np.sqrt(np.clip(intensity_scaling, 0, None))
kernel[intensity_scaling < 0] = np.Inf
return kernel