CofeehousePy/deps/scikit-image/skimage/restoration/_rolling_ball_cy.pyx

241 lines
8.7 KiB
Cython

import numpy as np
cimport cython
from libc.math cimport isnan, INFINITY
from cython.parallel cimport prange
from .._shared.fused_numerics cimport np_floats
ctypedef np_floats DTYPE_FLOAT
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef inline Py_ssize_t ind2ind(
Py_ssize_t from_index,
Py_ssize_t offset,
Py_ssize_t[::1] from_shape,
Py_ssize_t[::1] to_shape) nogil:
"""Convert the flat index of one array to a flat index of another array.
The primary use case for this is if one array is a view into the other
array and the dimensionality is unknown at compile time (and, hence, typed
memoryviews can't be used).
This is the cython equivalent of
.. code-block:: python
to_index = np.ravel_multi_index(
np.unravel_index(from_index, shape=from_shape),
shape=to_shape
) + offset
However, it doesn't perform boundary checks or type checks; if missused this
can cause a segfault.
Parameters
----------
from_index : intp
The index in the original array.
offset : intp
The distance (in number of elements) between the origins
of both arrays measured from the first element of
``from_shape``.
from_shape : (N) ndarray
The shape of the original array.
to_shape : (N) ndarray
The shape of the resulting array.
Returns
-------
to_index : intp
The index in the resulting array.
"""
cdef Py_ssize_t ndim = from_shape.shape[0]
cdef Py_ssize_t idx, modulo, pos
cdef Py_ssize_t multiple_kernel = 1
cdef Py_ssize_t multiple_img = 1
cdef Py_ssize_t to_index = offset
for idx in range(ndim-1, -1, -1):
modulo = from_index % (from_shape[idx] * multiple_kernel)
pos = modulo // multiple_kernel
multiple_kernel *= from_shape[idx]
to_index += pos * multiple_img
multiple_img *= to_shape[idx]
return to_index
@cython.boundscheck(False)
@cython.wraparound(False)
def apply_kernel_nan(DTYPE_FLOAT[::1] img not None,
DTYPE_FLOAT[::1] intensity_difference not None,
DTYPE_FLOAT[::1] out not None,
Py_ssize_t[::1] img_shape not None,
Py_ssize_t[::1] padded_img_shape not None,
Py_ssize_t[::1] kernel_shape not None,
Py_ssize_t num_threads=0):
"""Apply a ND kernel to an ND image.
This function is the critical piece of code for
`morphology.rolling_ellipsoid`. It was moved to cython for speed.
Parameters
----------
img : (I) ndarray
A flat view into a padded image, e.g., from ``numpy.reshape(-1)``.
intensity_difference : (K) ndarray
A flat view into an array containing the intensity difference
between ellipsoid[center] and ellipsoid[pos], e.g., from
``numpy.reshape(-1)``. Indicates the difference between the
height/intensity of the ellipsoid at position ``(x,y)`` and
the height/intensity at the center of the kernel.
out : (I) ndarray
A flat view into the output image. Note: does NOT support inplace
modification of img.
img_shape : (N) ndarray
The shape of the unflattened, unpadded image.
padded_img_shape : (N) ndarray
The shape of the unflattened, padded image.
kernel_shape : (N) ndarray
The shape of the unflattened kernel.
num_threads : int, optional
The number of threads used to compute the result. If no value is
provided (0, default) fall back to the number of threads that openMP
is currently configured to use.
Returns
-------
out_data : ndarray
The array passed into ``out``, reshaped to
``out_data.shape = img_shape`` (possibly a view) and filled with the
estimated background intensity.
See Also
--------
rolling_ball
"""
cdef Py_ssize_t offset, offset_idx, out_data_size, img_idx
cdef DTYPE_FLOAT min_value, tmp
out_data_size = out.size
cdef Py_ssize_t ndim = kernel_shape.shape[0]
cdef Py_ssize_t kernel_leading_dims = np.prod(kernel_shape[0:(ndim - 1)])
cdef Py_ssize_t kernel_last_dim = kernel_shape[ndim - 1]
cdef Py_ssize_t ker_idx_leading, ker_idx_last
for offset_idx in prange(
out_data_size,
num_threads=num_threads,
nogil=True):
offset = ind2ind(offset_idx, 0, img_shape, padded_img_shape)
min_value = INFINITY
for ker_idx_leading in range(kernel_leading_dims):
ker_idx_leading = ker_idx_leading * kernel_last_dim
img_idx = ind2ind(ker_idx_leading, offset, kernel_shape,
padded_img_shape)
# split into outer and inner loop for vectorization
# (the inner loop is contiguous in memory)
for ker_idx_last in range(kernel_last_dim):
tmp = (img[img_idx+ker_idx_last] +
intensity_difference[ker_idx_leading + ker_idx_last])
if min_value > tmp or isnan(tmp):
min_value = tmp
if isnan(min_value):
break
out[offset_idx] = min_value
return out.base.reshape(img_shape)
@cython.boundscheck(False)
@cython.wraparound(False)
def apply_kernel(DTYPE_FLOAT[::1] img not None,
DTYPE_FLOAT[::1] intensity_difference not None,
DTYPE_FLOAT[::1] out not None,
Py_ssize_t[::1] img_shape not None,
Py_ssize_t[::1] padded_img_shape not None,
Py_ssize_t[::1] kernel_shape not None,
Py_ssize_t num_threads=0):
"""Apply a ND kernel to an ND image.
This function is the critical piece of code for
`morphology.rolling_ellipsoid`. It was moved to cython for speed.
Parameters
----------
img : (I) ndarray
A flat view into a padded image, e.g., from ``numpy.reshape(-1)``.
intensity_difference : (K) ndarray
A flat view into an array containing the intensity difference
between ellipsoid[center] and ellipsoid[pos], e.g., from
``numpy.reshape(-1)``. Indicates the difference between the
height/intensity of the ellipsoid at position ``(x,y)`` and
the height/intensity at the center of the kernel.
out : (I) ndarray
A flat view into the output image. Note: does NOT support inplace
modification of img.
img_shape : (N) ndarray
The shape of the unflattened, unpadded image.
padded_img_shape : (N) ndarray
The shape of the unflattened, padded image.
kernel_shape : (N) ndarray
The shape of the unflattened kernel.
num_threads : int, optional
The number of threads used to compute the result. If no value is
provided (0, default) fall back to the number of threads that openMP
is currently configured to use.
Returns
-------
out_data : ndarray
The array passed into ``out``, reshaped to
``out_data.shape = img_shape`` (possibly a view) and filled with the
estimated background intensity.
See Also
--------
rolling_ball
Notes
-----
This function assumes that the image doesn't contain ``NaN``s; this
assumption allows for faster code (better compiler optimization).
"""
cdef Py_ssize_t offset, offset_idx, out_data_size, ker_idx, img_idx
cdef DTYPE_FLOAT min_value, tmp
out_data_size = out.size
cdef Py_ssize_t ndim = kernel_shape.shape[0]
cdef Py_ssize_t kernel_leading_dims = np.prod(kernel_shape[0:(ndim - 1)])
cdef Py_ssize_t kernel_last_dim = kernel_shape[ndim - 1]
cdef Py_ssize_t ker_idx_leading, ker_idx_last
for offset_idx in prange(
out_data_size,
num_threads=num_threads,
nogil=True):
offset = ind2ind(offset_idx, 0, img_shape, padded_img_shape)
min_value = INFINITY
# split into outer and inner loop for vectorization
# (the inner loop is contiguous in memory)
for ker_idx_leading in range(kernel_leading_dims):
ker_idx_leading = ker_idx_leading * kernel_last_dim
img_idx = ind2ind(ker_idx_leading, offset, kernel_shape,
padded_img_shape)
for ker_idx_last in range(kernel_last_dim):
ker_idx = ker_idx_leading + ker_idx_last
tmp = (img[img_idx+ker_idx_last] + intensity_difference[ker_idx])
if min_value > tmp:
min_value = tmp
out[offset_idx] = min_value
return out.base.reshape(img_shape)