fix: contrast formulation cause leak of high value fix: empty series_id cause fail to read chore: restructure folder chore!: change layer vars BREAKING CHANGE: not support 4.1 any more
319 lines
11 KiB
Python
319 lines
11 KiB
Python
import warnings
|
|
import numpy as np
|
|
from .. import scipy as ndi
|
|
from ._utils import convert_to_float
|
|
|
|
def _clip_warp_output(input_image, output_image, mode, cval, clip):
|
|
"""Clip output image to range of values of input image.
|
|
|
|
Note that this function modifies the values of `output_image` in-place
|
|
and it is only modified if ``clip=True``.
|
|
|
|
Parameters
|
|
----------
|
|
input_image : ndarray
|
|
Input image.
|
|
output_image : ndarray
|
|
Output image, which is modified in-place.
|
|
|
|
Other parameters
|
|
----------------
|
|
mode : {'constant', 'edge', 'symmetric', 'reflect', 'wrap'}
|
|
Points outside the boundaries of the input are filled according
|
|
to the given mode. Modes match the behaviour of `numpy.pad`.
|
|
cval : float
|
|
Used in conjunction with mode 'constant', the value outside
|
|
the image boundaries.
|
|
clip : bool
|
|
Whether to clip the output to the range of values of the input image.
|
|
This is enabled by default, since higher order interpolation may
|
|
produce values outside the given input range.
|
|
|
|
"""
|
|
if clip:
|
|
min_val = np.min(input_image)
|
|
if np.isnan(min_val):
|
|
# NaNs detected, use NaN-safe min/max
|
|
min_func = np.nanmin
|
|
max_func = np.nanmax
|
|
min_val = min_func(input_image)
|
|
else:
|
|
min_func = np.min
|
|
max_func = np.max
|
|
max_val = max_func(input_image)
|
|
|
|
# Check if cval has been used such that it expands the effective input
|
|
# range
|
|
preserve_cval = (
|
|
mode == 'constant'
|
|
and not min_val <= cval <= max_val
|
|
and min_func(output_image) <= cval <= max_func(output_image)
|
|
)
|
|
|
|
# expand min/max range to account for cval
|
|
if preserve_cval:
|
|
# cast cval to the same dtype as the input image
|
|
cval = input_image.dtype.type(cval)
|
|
min_val = min(min_val, cval)
|
|
max_val = max(max_val, cval)
|
|
|
|
# Convert array-like types to ndarrays (gh-7159)
|
|
min_val, max_val = np.asarray(min_val), np.asarray(max_val)
|
|
np.clip(output_image, min_val, max_val, out=output_image)
|
|
|
|
|
|
def _validate_interpolation_order(image_dtype, order):
|
|
"""Validate and return spline interpolation's order.
|
|
|
|
Parameters
|
|
----------
|
|
image_dtype : dtype
|
|
Image dtype.
|
|
order : int, optional
|
|
The order of the spline interpolation. The order has to be in
|
|
the range 0-5. See `skimage.transform.warp` for detail.
|
|
|
|
Returns
|
|
-------
|
|
order : int
|
|
if input order is None, returns 0 if image_dtype is bool and 1
|
|
otherwise. Otherwise, image_dtype is checked and input order
|
|
is validated accordingly (order > 0 is not supported for bool
|
|
image dtype)
|
|
|
|
"""
|
|
|
|
if order is None:
|
|
return 0 if image_dtype == bool else 1
|
|
|
|
if order < 0 or order > 5:
|
|
raise ValueError(
|
|
"Spline interpolation order has to be in the " "range 0-5.")
|
|
|
|
if image_dtype == bool and order != 0:
|
|
raise ValueError(
|
|
"Input image dtype is bool. Interpolation is not defined "
|
|
"with bool data type. Please set order to 0 or explicitly "
|
|
"cast input image to another data type."
|
|
)
|
|
|
|
return order
|
|
|
|
|
|
def _preprocess_resize_output_shape(image, output_shape):
|
|
"""Validate resize output shape according to input image.
|
|
|
|
Parameters
|
|
----------
|
|
image: ndarray
|
|
Image to be resized.
|
|
output_shape: iterable
|
|
Size of the generated output image `(rows, cols[, ...][, dim])`. If
|
|
`dim` is not provided, the number of channels is preserved.
|
|
|
|
Returns
|
|
-------
|
|
image: ndarray
|
|
The input image, but with additional singleton dimensions appended in
|
|
the case where ``len(output_shape) > input.ndim``.
|
|
output_shape: tuple
|
|
The output image converted to tuple.
|
|
|
|
Raises
|
|
------
|
|
ValueError:
|
|
If output_shape length is smaller than the image number of
|
|
dimensions
|
|
|
|
Notes
|
|
-----
|
|
The input image is reshaped if its number of dimensions is not
|
|
equal to output_shape_length.
|
|
|
|
"""
|
|
output_shape = tuple(output_shape)
|
|
output_ndim = len(output_shape)
|
|
input_shape = image.shape
|
|
if output_ndim > image.ndim:
|
|
# append dimensions to input_shape
|
|
input_shape += (1,) * (output_ndim - image.ndim)
|
|
image = np.reshape(image, input_shape)
|
|
elif output_ndim == image.ndim - 1:
|
|
# multichannel case: append shape of last axis
|
|
output_shape = output_shape + (image.shape[-1],)
|
|
elif output_ndim < image.ndim:
|
|
raise ValueError(
|
|
"output_shape length cannot be smaller than the "
|
|
"image number of dimensions"
|
|
)
|
|
|
|
return image, output_shape
|
|
|
|
|
|
def _to_ndimage_mode(mode):
|
|
"""Convert from `numpy.pad` mode name to the corresponding ndimage mode."""
|
|
mode_translation_dict = dict(
|
|
constant='constant',
|
|
edge='nearest',
|
|
symmetric='reflect',
|
|
reflect='mirror',
|
|
wrap='wrap',
|
|
)
|
|
if mode not in mode_translation_dict:
|
|
raise ValueError(
|
|
f"Unknown mode: '{mode}', or cannot translate mode. The "
|
|
f"mode should be one of 'constant', 'edge', 'symmetric', "
|
|
f"'reflect', or 'wrap'. See the documentation of numpy.pad for "
|
|
f"more info."
|
|
)
|
|
return _fix_ndimage_mode(mode_translation_dict[mode])
|
|
|
|
|
|
def _fix_ndimage_mode(mode):
|
|
# SciPy 1.6.0 introduced grid variants of constant and wrap which
|
|
# have less surprising behavior for images. Use these when available
|
|
grid_modes = {'constant': 'grid-constant', 'wrap': 'grid-wrap'}
|
|
return grid_modes.get(mode, mode)
|
|
|
|
|
|
def resize(
|
|
image,
|
|
output_shape,
|
|
order=None,
|
|
mode='reflect',
|
|
cval=0,
|
|
clip=True,
|
|
preserve_range=False,
|
|
anti_aliasing=None,
|
|
anti_aliasing_sigma=None,
|
|
):
|
|
"""Resize image to match a certain size.
|
|
|
|
Performs interpolation to up-size or down-size N-dimensional images. Note
|
|
that anti-aliasing should be enabled when down-sizing images to avoid
|
|
aliasing artifacts. For downsampling with an integer factor also see
|
|
`skimage.transform.downscale_local_mean`.
|
|
|
|
Parameters
|
|
----------
|
|
image : ndarray
|
|
Input image.
|
|
output_shape : iterable
|
|
Size of the generated output image `(rows, cols[, ...][, dim])`. If
|
|
`dim` is not provided, the number of channels is preserved. In case the
|
|
number of input channels does not equal the number of output channels a
|
|
n-dimensional interpolation is applied.
|
|
|
|
Returns
|
|
-------
|
|
resized : ndarray
|
|
Resized version of the input.
|
|
|
|
Other parameters
|
|
----------------
|
|
order : int, optional
|
|
The order of the spline interpolation, default is 0 if
|
|
image.dtype is bool and 1 otherwise. The order has to be in
|
|
the range 0-5. See `skimage.transform.warp` for detail.
|
|
mode : {'constant', 'edge', 'symmetric', 'reflect', 'wrap'}, optional
|
|
Points outside the boundaries of the input are filled according
|
|
to the given mode. Modes match the behaviour of `numpy.pad`.
|
|
cval : float, optional
|
|
Used in conjunction with mode 'constant', the value outside
|
|
the image boundaries.
|
|
clip : bool, optional
|
|
Whether to clip the output to the range of values of the input image.
|
|
This is enabled by default, since higher order interpolation may
|
|
produce values outside the given input range.
|
|
preserve_range : bool, optional
|
|
Whether to keep the original range of values. Otherwise, the input
|
|
image is converted according to the conventions of `img_as_float`.
|
|
Also see https://scikit-image.org/docs/dev/user_guide/data_types.html
|
|
anti_aliasing : bool, optional
|
|
Whether to apply a Gaussian filter to smooth the image prior
|
|
to downsampling. It is crucial to filter when downsampling
|
|
the image to avoid aliasing artifacts. If not specified, it is set to
|
|
True when downsampling an image whose data type is not bool.
|
|
It is also set to False when using nearest neighbor interpolation
|
|
(``order`` == 0) with integer input data type.
|
|
anti_aliasing_sigma : {float, tuple of floats}, optional
|
|
Standard deviation for Gaussian filtering used when anti-aliasing.
|
|
By default, this value is chosen as (s - 1) / 2 where s is the
|
|
downsampling factor, where s > 1. For the up-size case, s < 1, no
|
|
anti-aliasing is performed prior to rescaling.
|
|
|
|
Notes
|
|
-----
|
|
Modes 'reflect' and 'symmetric' are similar, but differ in whether the edge
|
|
pixels are duplicated during the reflection. As an example, if an array
|
|
has values [0, 1, 2] and was padded to the right by four values using
|
|
symmetric, the result would be [0, 1, 2, 2, 1, 0, 0], while for reflect it
|
|
would be [0, 1, 2, 1, 0, 1, 2].
|
|
|
|
Examples
|
|
--------
|
|
>>> from skimage import data
|
|
>>> from skimage.transform import resize
|
|
>>> image = data.camera()
|
|
>>> resize(image, (100, 100)).shape
|
|
(100, 100)
|
|
|
|
"""
|
|
|
|
image, output_shape = _preprocess_resize_output_shape(image, output_shape)
|
|
input_shape = image.shape
|
|
input_type = image.dtype
|
|
|
|
if input_type == np.float16:
|
|
image = image.astype(np.float32)
|
|
|
|
if anti_aliasing is None:
|
|
anti_aliasing = (
|
|
not input_type == bool
|
|
and not (np.issubdtype(input_type, np.integer) and order == 0)
|
|
and any(x < y for x, y in zip(output_shape, input_shape))
|
|
)
|
|
|
|
if input_type == bool and anti_aliasing:
|
|
raise ValueError("anti_aliasing must be False for boolean images")
|
|
|
|
factors = np.divide(input_shape, output_shape)
|
|
order = _validate_interpolation_order(input_type, order)
|
|
if order > 0:
|
|
image = convert_to_float(image, preserve_range)
|
|
|
|
# Translate modes used by np.pad to those used by scipy.ndimage
|
|
ndi_mode = _to_ndimage_mode(mode)
|
|
if anti_aliasing:
|
|
if anti_aliasing_sigma is None:
|
|
anti_aliasing_sigma = np.maximum(0, (factors - 1) / 2)
|
|
else:
|
|
anti_aliasing_sigma = np.atleast_1d(anti_aliasing_sigma) * np.ones_like(
|
|
factors
|
|
)
|
|
if np.any(anti_aliasing_sigma < 0):
|
|
raise ValueError(
|
|
"Anti-aliasing standard deviation must be "
|
|
"greater than or equal to zero"
|
|
)
|
|
elif np.any((anti_aliasing_sigma > 0) & (factors <= 1)):
|
|
warnings.warn(
|
|
"Anti-aliasing standard deviation greater than zero but "
|
|
"not down-sampling along all axes",
|
|
stacklevel=2
|
|
)
|
|
filtered = ndi.gaussian_filter(
|
|
image, anti_aliasing_sigma, cval=cval, mode=ndi_mode
|
|
)
|
|
else:
|
|
filtered = image
|
|
|
|
zoom_factors = [1 / f for f in factors]
|
|
out = ndi.zoom(
|
|
filtered, zoom_factors, order=order, mode=ndi_mode, cval=cval, grid_mode=True
|
|
)
|
|
|
|
_clip_warp_output(image, out, mode, cval, clip)
|
|
|
|
return out
|