def _clahe(image, kernel_size, clip_limit, nbins): """Contrast Limited Adaptive Histogram Equalization. Parameters ---------- image : (N1,...,NN) ndarray Input image. kernel_size: int or N-tuple of int Defines the shape of contextual regions used in the algorithm. clip_limit : float Normalized clipping limit between 0 and 1 (higher values give more contrast). nbins : int Number of gray bins for histogram ("data range"). Returns ------- out : (N1,...,NN) ndarray Equalized image. The number of "effective" graylevels in the output image is set by `nbins`; selecting a small value (e.g. 128) speeds up processing and still produces an output image of good quality. A clip limit of 0 or larger than or equal to 1 results in standard (non-contrast limited) AHE. """ ndim = image.ndim dtype = image.dtype # pad the image such that the shape in each dimension # - is a multiple of the kernel_size and # - is preceded by half a kernel size pad_start_per_dim = [k // 2 for k in kernel_size] pad_end_per_dim = [ (k - s % k) % k + math.ceil(k / 2.0) for k, s in zip(kernel_size, image.shape) ] image = cp.pad( image, [(p_i, p_f) for p_i, p_f in zip(pad_start_per_dim, pad_end_per_dim)], mode="reflect", ) bin_size = 1 + NR_OF_GRAY // nbins if True: lut = cp.arange(NR_OF_GRAY) lut //= bin_size image = lut[image] else: lut = np.arange(NR_OF_GRAY) lut //= bin_size image = cp.asarray(lut[image.get()]) # calculate graylevel mappings for each contextual region # rearrange image into flattened contextual regions ns_hist = [int(s / k) - 1 for s, k in zip(image.shape, kernel_size)] hist_blocks_shape = functools.reduce( operator.add, [(s, k) for s, k in zip(ns_hist, kernel_size)] ) hist_blocks_axis_order = tuple(range(0, ndim * 2, 2)) + tuple( range(1, ndim * 2, 2) ) hist_slices = [ slice(k // 2, k // 2 + n * k) for k, n in zip(kernel_size, ns_hist) ] hist_blocks = image[tuple(hist_slices)].reshape(hist_blocks_shape) hist_blocks = hist_blocks.transpose(hist_blocks_axis_order) hist_block_assembled_shape = hist_blocks.shape hist_blocks = hist_blocks.reshape((_prod(ns_hist), -1)) # Calculate actual clip limit if clip_limit > 0.0: clim = int(max(clip_limit * _prod(kernel_size), 1)) else: # largest possible value, i.e., do not clip (AHE) clim = np.product(kernel_size) if True: # faster to loop over the arrays on the host hist_blocks = cp.asnumpy(hist_blocks) hist = np.apply_along_axis( np.bincount, -1, hist_blocks, minlength=nbins ) hist = np.apply_along_axis( clip_histogram, -1, hist, clip_limit=clim, xp=np ) hist = cp.asarray(hist) else: hist = cnp.apply_along_axis( cp.bincount, -1, hist_blocks, minlength=nbins ) hist = cnp.apply_along_axis(clip_histogram, -1, hist, clip_limit=clim) hist = map_histogram(hist, 0, NR_OF_GRAY - 1, _prod(kernel_size)) hist = hist.reshape(hist_block_assembled_shape[:ndim] + (-1,)) # duplicate leading mappings in each dim map_array = cp.pad( hist, [(1, 1) for _ in range(ndim)] + [(0, 0)], mode="edge" ) # Perform multilinear interpolation of graylevel mappings # using the convention described here: # https://en.wikipedia.org/w/index.php?title=Adaptive_histogram_ # equalization&oldid=936814673#Efficient_computation_by_interpolation # rearrange image into blocks for vectorized processing ns_proc = [int(s / k) for s, k in zip(image.shape, kernel_size)] blocks_shape = functools.reduce( operator.add, [(s, k) for s, k in zip(ns_proc, kernel_size)] ) blocks_axis_order = hist_blocks_axis_order blocks = image.reshape(blocks_shape) blocks = blocks.transpose(blocks_axis_order) blocks_flattened_shape = blocks.shape blocks = blocks.reshape((_prod(ns_proc), _prod(blocks.shape[ndim:]))) # calculate interpolation coefficients coeffs = cp.meshgrid( *tuple([cp.arange(k) / k for k in kernel_size[::-1]]), indexing="ij" ) coeffs = [cp.transpose(c).flatten() for c in coeffs] inv_coeffs = [1 - c for c in coeffs] # sum over contributions of neighboring contextual # regions in each direction result = cp.zeros(blocks.shape, dtype=cp.float32) for iedge, edge in enumerate(itertools.product(*((range(2),) * ndim))): edge_maps = map_array[ tuple([slice(e, e + n) for e, n in zip(edge, ns_proc)]) ] edge_maps = edge_maps.reshape((_prod(ns_proc), -1)) # apply map # edge_mapped = cp.asarray(np.take_along_axis(edge_maps.get(), blocks.get(), axis=-1)) edge_mapped = cp.take_along_axis(edge_maps, blocks, axis=-1) # interpolate edge_coeffs = functools.reduce( operator.mul, [[inv_coeffs, coeffs][e][d] for d, e in enumerate(edge[::-1])], ) result += (edge_mapped * edge_coeffs).astype(result.dtype) result = result.astype(dtype) # rebuild result image from blocks result = result.reshape(blocks_flattened_shape) blocks_axis_rebuild_order = functools.reduce( operator.add, [(s, k) for s, k in zip(range(0, ndim), range(ndim, ndim * 2))], ) result = result.transpose(blocks_axis_rebuild_order) result = result.reshape(image.shape) # undo padding unpad_slices = tuple( [ slice(p_i, s - p_f) for p_i, p_f, s in zip( pad_start_per_dim, pad_end_per_dim, image.shape ) ] ) result = result[unpad_slices] return result
def match_template(image, template, pad_input=False, mode="constant", constant_values=0): """Match a template to a 2-D or 3-D image using normalized correlation. The output is an array with values between -1.0 and 1.0. The value at a given position corresponds to the correlation coefficient between the image and the template. For `pad_input=True` matches correspond to the center and otherwise to the top-left corner of the template. To find the best match you must search for peaks in the response (output) image. Parameters ---------- image : (M, N[, D]) array 2-D or 3-D input image. template : (m, n[, d]) array Template to locate. It must be `(m <= M, n <= N[, d <= D])`. pad_input : bool If True, pad `image` so that output is the same size as the image, and output values correspond to the template center. Otherwise, the output is an array with shape `(M - m + 1, N - n + 1)` for an `(M, N)` image and an `(m, n)` template, and matches correspond to origin (top-left corner) of the template. mode : see `numpy.pad`, optional Padding mode. constant_values : see `numpy.pad`, optional Constant values used in conjunction with ``mode='constant'``. Returns ------- output : array Response image with correlation coefficients. Notes ----- Details on the cross-correlation are presented in [1]_. This implementation uses FFT convolutions of the image and the template. Reference [2]_ presents similar derivations but the approximation presented in this reference is not used in our implementation. This CuPy implementation does not force the image to float64 internally, but will use float32 for single-precision inputs. References ---------- .. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and Magic. .. [2] Briechle and Hanebeck, "Template Matching using Fast Normalized Cross Correlation", Proceedings of the SPIE (2001). :DOI:`10.1117/12.421129` Examples -------- >>> import cupy as cp >>> template = cp.zeros((3, 3)) >>> template[1, 1] = 1 >>> template array([[ 0., 0., 0.], [ 0., 1., 0.], [ 0., 0., 0.]]) >>> image = cp.zeros((6, 6)) >>> image[1, 1] = 1 >>> image[4, 4] = -1 >>> image array([[ 0., 0., 0., 0., 0., 0.], [ 0., 1., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., 0., 0.], [ 0., 0., 0., 0., -1., 0.], [ 0., 0., 0., 0., 0., 0.]]) >>> result = match_template(image, template) >>> cp.round(result, 3) array([[ 1. , -0.125, 0. , 0. ], [-0.125, -0.125, 0. , 0. ], [ 0. , 0. , 0.125, 0.125], [ 0. , 0. , 0.125, -1. ]]) >>> result = match_template(image, template, pad_input=True) >>> cp.round(result, 3) array([[-0.125, -0.125, -0.125, 0. , 0. , 0. ], [-0.125, 1. , -0.125, 0. , 0. , 0. ], [-0.125, -0.125, -0.125, 0. , 0. , 0. ], [ 0. , 0. , 0. , 0.125, 0.125, 0.125], [ 0. , 0. , 0. , 0.125, -1. , 0.125], [ 0. , 0. , 0. , 0.125, 0.125, 0.125]]) """ check_nD(image, (2, 3)) if image.ndim < template.ndim: raise ValueError("Dimensionality of template must be less than or " "equal to the dimensionality of image.") if any(si < st for si, st in zip(image.shape, template.shape)): raise ValueError("Image must be larger than template.") image_shape = image.shape float_dtype = cp.promote_types(image.dtype, cp.float32) image = cp.asarray(image, dtype=float_dtype) template = cp.asarray(template, dtype=float_dtype) pad_width = tuple((width, width) for width in template.shape) if mode == "constant": image = cp.pad( image, pad_width=pad_width, mode=mode, constant_values=constant_values, ) else: image = cp.pad(image, pad_width=pad_width, mode=mode) # Use special case for 2-D images for much better performance in # computation of integral images if image.ndim == 2: image_window_sum = _window_sum_2d(image, template.shape) image_window_sum2 = _window_sum_2d(image * image, template.shape) elif image.ndim == 3: image_window_sum = _window_sum_3d(image, template.shape) image_window_sum2 = _window_sum_3d(image * image, template.shape) template_mean = template.mean() template_volume = _prod(template.shape) template_ssd = template - template_mean template_ssd *= template_ssd template_ssd = cp.sum(template_ssd) if image.ndim == 2: xcorr = fftconvolve(image, template[::-1, ::-1], mode="valid")[1:-1, 1:-1] elif image.ndim == 3: xcorr = fftconvolve(image, template[::-1, ::-1, ::-1], mode="valid")[1:-1, 1:-1, 1:-1] numerator = xcorr - image_window_sum * template_mean denominator = image_window_sum2 cp.multiply(image_window_sum, image_window_sum, out=image_window_sum) cp.divide(image_window_sum, template_volume, out=image_window_sum) denominator -= image_window_sum denominator *= template_ssd cp.maximum(denominator, 0, out=denominator) # sqrt of negative number not allowed cp.sqrt(denominator, out=denominator) response = cp.zeros_like(xcorr, dtype=np.float64) # avoid zero-division mask = denominator > cp.finfo(np.float64).eps response[mask] = numerator[mask] / denominator[mask] slices = [] for i in range(template.ndim): if pad_input: d0 = (template.shape[i] - 1) // 2 d1 = d0 + image_shape[i] else: d0 = template.shape[i] - 1 d1 = d0 + image_shape[i] - template.shape[i] + 1 slices.append(slice(d0, d1)) return response[tuple(slices)]
def zoom( input, zoom, output=None, order=3, mode="constant", cval=0.0, prefilter=True, *, grid_mode=False, allow_float32=True, ): """Zoom an array. The array is zoomed using spline interpolation of the requested order. Args: input (cupy.ndarray): The input array. zoom (float or sequence): The zoom factor along the axes. If a float, ``zoom`` is the same for each axis. If a sequence, ``zoom`` should contain one value for each axis. output (cupy.ndarray or ~cupy.dtype): The array in which to place the output, or the dtype of the returned array. order (int): The order of the spline interpolation. If it is not given, order 1 is used. It is different from :mod:`scipy.ndimage` and can change in the future. The order has to be in the range 0-5. mode (str): Points outside the boundaries of the input are filled according to the given mode (``'constant'``, ``'nearest'``, ``'mirror'`` or ``'opencv'``). Default is ``'constant'``. cval (scalar): Value used for points outside the boundaries of the input if ``mode='constant'`` or ``mode='opencv'``. Default is 0.0 prefilter (bool): It is not used yet. It just exists for compatibility with :mod:`scipy.ndimage`. grid_mode (bool, optional): If False, the distance from the pixel centers is zoomed. Otherwise, the distance including the full pixel extent is used. For example, a 1d signal of length 5 is considered to have length 4 when `grid_mode` is False, but length 5 when `grid_mode` is True. See the following visual illustration: .. code-block:: text | pixel 1 | pixel 2 | pixel 3 | pixel 4 | pixel 5 | |<-------------------------------------->| vs. |<----------------------------------------------->| The starting point of the arrow in the diagram above corresponds to coordinate location 0 in each mode. This option is unused if ``mode='opencv'``. Returns: cupy.ndarray or None: The zoomed input. Notes ----- This implementation handles boundary modes 'wrap' and 'reflect' correctly, while SciPy prior to release 1.6.0 does not. So, if comparing to older SciPy, some disagreement near the borders may occur. For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary conditions are implemented correctly only for modes 'mirror', 'reflect' and 'grid-wrap'. .. seealso:: :func:`scipy.ndimage.zoom` """ _check_parameter("zoom", order, mode) if not hasattr(zoom, "__iter__") and type(zoom) is not cupy.ndarray: zoom = [zoom] * input.ndim output_shape = [] for s, z in zip(input.shape, zoom): output_shape.append(int(round(s * z))) output_shape = tuple(output_shape) if mode == "opencv": zoom = [] offset = [] for in_size, out_size in zip(input.shape, output_shape): if out_size > 1: zoom.append(float(in_size) / out_size) offset.append((zoom[-1] - 1) / 2.0) else: zoom.append(0) offset.append(0) mode = "nearest" output = affine_transform( input, cupy.asarray(zoom), offset, output_shape, output, order, mode, cval, prefilter, ) else: if order is None: order = 1 if grid_mode: # warn about modes that may have surprising behavior suggest_mode = None if mode == "constant": suggest_mode = "grid-constant" elif mode == "wrap": suggest_mode = "grid-wrap" if suggest_mode is not None: warnings.warn( ("It is recommended to use mode = {} instead of {} when " "grid_mode is True.").format(suggest_mode, mode)) zoom = [] for in_size, out_size in zip(input.shape, output_shape): if out_size > 1: if grid_mode: zoom.append(in_size / out_size) else: zoom.append((in_size - 1) / (out_size - 1)) else: zoom.append(1) output = _get_output(output, input, shape=output_shape) if input.dtype.kind in "iu": input = input.astype(cupy.float32) if prefilter and order > 1: padded, npad = _prepad_for_spline_filter(input, mode, cval) filtered = spline_filter( padded, order, output=input.dtype, mode=mode, allow_float32=allow_float32, ) else: npad = 0 filtered = input # kernel assumes C-contiguous arrays if not filtered.flags.c_contiguous: filtered = cupy.ascontiguousarray(filtered) integer_output = output.dtype.kind in "iu" large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) > 1 << 31) kern = _get_zoom_kernel( input.ndim, large_int, output_shape, mode, order=order, integer_output=integer_output, nprepad=npad, grid_mode=grid_mode, ) zoom = cupy.asarray(zoom, dtype=float, order="C") if zoom.ndim != 1: raise ValueError("zoom must be 1d") if zoom.size != filtered.ndim: raise ValueError("len(zoom) must equal input.ndim") kern(filtered, zoom, output) return output
def shift( input, shift, output=None, order=3, mode="constant", cval=0.0, prefilter=True, *, allow_float32=True, ): """Shift an array. The array is shifted using spline interpolation of the requested order. Points outside the boundaries of the input are filled according to the given mode. Args: input (cupy.ndarray): The input array. shift (float or sequence): The shift along the axes. If a float, ``shift`` is the same for each axis. If a sequence, ``shift`` should contain one value for each axis. output (cupy.ndarray or ~cupy.dtype): The array in which to place the output, or the dtype of the returned array. order (int): The order of the spline interpolation. If it is not given, order 1 is used. It is different from :mod:`scipy.ndimage` and can change in the future. The order has to be in the range 0-5. mode (str): Points outside the boundaries of the input are filled according to the given mode (``'constant'``, ``'nearest'``, ``'mirror'`` or ``'opencv'``). Default is ``'constant'``. cval (scalar): Value used for points outside the boundaries of the input if ``mode='constant'`` or ``mode='opencv'``. Default is 0.0 prefilter (bool): It is not used yet. It just exists for compatibility with :mod:`scipy.ndimage`. Returns: cupy.ndarray or None: The shifted input. Notes ----- This implementation handles boundary modes 'wrap' and 'reflect' correctly, while SciPy prior to release 1.6.0 does not. So, if comparing to older SciPy, some disagreement near the borders may occur. For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary conditions are implemented correctly only for modes 'mirror', 'reflect' and 'grid-wrap'. .. seealso:: :func:`scipy.ndimage.shift` """ _check_parameter("shift", order, mode) if not hasattr(shift, "__iter__") and type(shift) is not cupy.ndarray: shift = [shift] * input.ndim if mode == "opencv": mode = "_opencv_edge" output = affine_transform( input, cupy.ones(input.ndim, input.dtype), cupy.negative(cupy.asarray(shift)), None, output, order, mode, cval, prefilter, ) else: if order is None: order = 1 output = _get_output(output, input) if input.dtype.kind in "iu": input = input.astype(cupy.float32) if prefilter and order > 1: padded, npad = _prepad_for_spline_filter(input, mode, cval) filtered = spline_filter( padded, order, output=input.dtype, mode=mode, allow_float32=allow_float32, ) else: npad = 0 filtered = input # kernel assumes C-contiguous arrays if not filtered.flags.c_contiguous: filtered = cupy.ascontiguousarray(filtered) integer_output = output.dtype.kind in "iu" large_int = _misc._prod(input.shape) > 1 << 31 kern = _get_shift_kernel( input.ndim, large_int, input.shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=npad, ) shift = cupy.asarray(shift, dtype=float, order="C") if shift.ndim != 1: raise ValueError("shift must be 1d") if shift.size != filtered.ndim: raise ValueError("len(shift) must equal input.ndim") kern(filtered, shift, output) return output
def affine_transform( input, matrix, offset=0.0, output_shape=None, output=None, order=3, mode="constant", cval=0.0, prefilter=True, *, allow_float32=True, ): """Apply an affine transformation. Given an output image pixel index vector ``o``, the pixel value is determined from the input image at position ``cupy.dot(matrix, o) + offset``. Args: input (cupy.ndarray): The input array. matrix (cupy.ndarray): The inverse coordinate transformation matrix, mapping output coordinates to input coordinates. If ``ndim`` is the number of dimensions of ``input``, the given matrix must have one of the following shapes: - ``(ndim, ndim)``: the linear transformation matrix for each output coordinate. - ``(ndim,)``: assume that the 2D transformation matrix is diagonal, with the diagonal specified by the given value. - ``(ndim + 1, ndim + 1)``: assume that the transformation is specified using homogeneous coordinates. In this case, any value passed to ``offset`` is ignored. - ``(ndim, ndim + 1)``: as above, but the bottom row of a homogeneous transformation matrix is always ``[0, 0, ..., 1]``, and may be omitted. offset (float or sequence): The offset into the array where the transform is applied. If a float, ``offset`` is the same for each axis. If a sequence, ``offset`` should contain one value for each axis. output_shape (tuple of ints): Shape tuple. output (cupy.ndarray or ~cupy.dtype): The array in which to place the output, or the dtype of the returned array. order (int): The order of the spline interpolation. If it is not given, order 1 is used. It is different from :mod:`scipy.ndimage` and can change in the future. The order has to be in the range 0-5. mode (str): Points outside the boundaries of the input are filled according to the given mode (``'constant'``, ``'nearest'``, ``'mirror'`` or ``'opencv'``). Default is ``'constant'``. cval (scalar): Value used for points outside the boundaries of the input if ``mode='constant'`` or ``mode='opencv'``. Default is 0.0 prefilter (bool): It is not used yet. It just exists for compatibility with :mod:`scipy.ndimage`. Returns: cupy.ndarray or None: The transformed input. If ``output`` is given as a parameter, ``None`` is returned. Notes ----- This implementation handles boundary modes 'wrap' and 'reflect' correctly, while SciPy prior to release 1.6.0 does not. So, if comparing to older SciPy, some disagreement near the borders may occur. For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary conditions are implemented correctly only for modes 'mirror', 'reflect' and 'grid-wrap'. .. seealso:: :func:`scipy.ndimage.affine_transform` """ _check_parameter("affine_transform", order, mode) if not hasattr(offset, "__iter__") and type(offset) is not cupy.ndarray: offset = [offset] * input.ndim matrix = cupy.asarray(matrix, order="C", dtype=float) if matrix.ndim not in [1, 2]: raise RuntimeError("no proper affine matrix provided") if matrix.ndim == 2: if matrix.shape[0] == matrix.shape[1] - 1: offset = matrix[:, -1] matrix = matrix[:, :-1] elif matrix.shape[0] == input.ndim + 1: offset = matrix[:-1, -1] matrix = matrix[:-1, :-1] if mode == "opencv": m = cupy.zeros((input.ndim + 1, input.ndim + 1), dtype=float) m[:-1, :-1] = matrix m[:-1, -1] = offset m[-1, -1] = 1 m = cupy.linalg.inv(m) m[:2] = cupy.roll(m[:2], 1, axis=0) m[:2, :2] = cupy.roll(m[:2, :2], 1, axis=1) matrix = m[:-1, :-1] offset = m[:-1, -1] if output_shape is None: output_shape = input.shape matrix = matrix.astype(float, copy=False) if order is None: order = 1 ndim = input.ndim output = _get_output(output, input, shape=output_shape) if input.dtype.kind in "iu": input = input.astype(cupy.float32) if prefilter and order > 1: padded, npad = _prepad_for_spline_filter(input, mode, cval) filtered = spline_filter( padded, order, output=input.dtype, mode=mode, allow_float32=allow_float32, ) else: npad = 0 filtered = input # kernel assumes C-contiguous arrays if not filtered.flags.c_contiguous: filtered = cupy.ascontiguousarray(filtered) if not matrix.flags.c_contiguous: matrix = cupy.ascontiguousarray(matrix) integer_output = output.dtype.kind in "iu" large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) > 1 << 31) if matrix.ndim == 1: offset = cupy.asarray(offset, dtype=float, order="C") offset = -offset / matrix kern = _get_zoom_shift_kernel( ndim, large_int, output_shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=npad, ) kern(filtered, offset, matrix, output) else: kern = _get_affine_kernel( ndim, large_int, output_shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=npad, ) m = cupy.zeros((ndim, ndim + 1), dtype=float) m[:, :-1] = matrix m[:, -1] = cupy.asarray(offset, dtype=float) kern(filtered, m, output) return output
def map_coordinates( input, coordinates, output=None, order=3, mode="constant", cval=0.0, prefilter=True, *, allow_float32=True, ): """Map the input array to new coordinates by interpolation. The array of coordinates is used to find, for each point in the output, the corresponding coordinates in the input. The value of the input at those coordinates is determined by spline interpolation of the requested order. The shape of the output is derived from that of the coordinate array by dropping the first axis. The values of the array along the first axis are the coordinates in the input array at which the output value is found. Args: input (cupy.ndarray): The input array. coordinates (array_like): The coordinates at which ``input`` is evaluated. output (cupy.ndarray or ~cupy.dtype): The array in which to place the output, or the dtype of the returned array. order (int): The order of the spline interpolation. If it is not given, order 1 is used. It is different from :mod:`scipy.ndimage` and can change in the future. The order has to be in the range 0-5. mode (str): Points outside the boundaries of the input are filled according to the given mode (``'constant'``, ``'nearest'``, ``'mirror'`` or ``'opencv'``). Default is ``'constant'``. cval (scalar): Value used for points outside the boundaries of the input if ``mode='constant'`` or ``mode='opencv'``. Default is 0.0 prefilter (bool): It is not used yet. It just exists for compatibility with :mod:`scipy.ndimage`. Returns: cupy.ndarray: The result of transforming the input. The shape of the output is derived from that of ``coordinates`` by dropping the first axis. Notes ----- This implementation handles boundary modes 'wrap' and 'reflect' correctly, while SciPy prior to release 1.6.0 does not. So, if comparing to older SciPy, some disagreement near the borders may occur. For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary conditions are implemented correctly only for modes 'mirror', 'reflect' and 'grid-wrap'. .. seealso:: :func:`scipy.ndimage.map_coordinates` """ _check_parameter("map_coordinates", order, mode) if mode == "opencv" or mode == "_opencv_edge": input = cupy.pad(input, [(1, 1)] * input.ndim, "constant", constant_values=cval) coordinates = cupy.add(coordinates, 1) mode = "constant" ret = _get_output(output, input, coordinates.shape[1:]) integer_output = ret.dtype.kind in "iu" if input.dtype.kind in "iu": input = input.astype(cupy.float32) if coordinates.dtype.kind in "iu": if order > 1: # order > 1 (spline) kernels require floating-point coordinates if allow_float32: coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float32) else: coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float64) coordinates = coordinates.astype(coord_dtype) elif coordinates.dtype.kind != "f": raise ValueError("coordinates should have floating point dtype") else: if allow_float32: coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float32) else: coord_dtype = cupy.promote_types(coordinates.dtype, cupy.float64) coordinates = coordinates.astype(coord_dtype, copy=False) if prefilter and order > 1: padded, npad = _prepad_for_spline_filter(input, mode, cval) filtered = spline_filter( padded, order, output=input.dtype, mode=mode, allow_float32=allow_float32, ) else: npad = 0 filtered = input large_int = max(_misc._prod(input.shape), coordinates.shape[0]) > 1 << 31 kern = _get_map_kernel( filtered.ndim, large_int, yshape=coordinates.shape, mode=mode, cval=cval, order=order, integer_output=integer_output, nprepad=npad, ) # kernel assumes C-contiguous arrays if not filtered.flags.c_contiguous: filtered = cupy.ascontiguousarray(filtered) if not coordinates.flags.c_contiguous: coordinates = cupy.ascontiguousarray(coordinates) kern(filtered, coordinates, ret) return ret
def zoom( input, zoom, output=None, order=3, mode="constant", cval=0.0, prefilter=True, *, allow_float32=True, ): """Zoom an array. The array is zoomed using spline interpolation of the requested order. Args: input (cupy.ndarray): The input array. zoom (float or sequence): The zoom factor along the axes. If a float, ``zoom`` is the same for each axis. If a sequence, ``zoom`` should contain one value for each axis. output (cupy.ndarray or ~cupy.dtype): The array in which to place the output, or the dtype of the returned array. order (int): The order of the spline interpolation. Must be between 0 and 5. mode (str): Points outside the boundaries of the input are filled according to the given mode (``'constant'``, ``'nearest'``, ``'mirror'`` or ``'opencv'``). Default is ``'constant'``. cval (scalar): Value used for points outside the boundaries of the input if ``mode='constant'`` or ``mode='opencv'``. Default is 0.0 prefilter (bool): It is not used yet. It just exists for compatibility with :mod:`scipy.ndimage`. Returns: cupy.ndarray or None: The zoomed input. Notes ----- This implementation handles boundary modes 'wrap' and 'reflect' correctly, while SciPy does not (at least as of release 1.4.0). So, if comparing to SciPy, some disagreement near the borders may occur unless ``mode == 'mirror'``. For ``order > 1`` with ``prefilter == True``, the spline prefilter boundary conditions are implemented correctly only for modes 'mirror', 'reflect' and 'wrap'. For the other modes ('constant' and 'nearest'), there is some innacuracy near the boundary of the array. .. seealso:: :func:`scipy.ndimage.zoom` """ _check_parameter("zoom", order, mode) if not hasattr(zoom, "__iter__") and type(zoom) is not cupy.ndarray: zoom = [zoom] * input.ndim output_shape = [] for s, z in zip(input.shape, zoom): output_shape.append(int(round(s * z))) output_shape = tuple(output_shape) if mode == "opencv": zoom = [] offset = [] for in_size, out_size in zip(input.shape, output_shape): if out_size > 1: zoom.append(float(in_size) / out_size) offset.append((zoom[-1] - 1) / 2.0) else: zoom.append(0) offset.append(0) mode = "nearest" output = affine_transform( input, cupy.asarray(zoom), offset, output_shape, output, order, mode, cval, prefilter, ) else: if order is None: order = 1 zoom = [] for in_size, out_size in zip(input.shape, output_shape): if out_size > 1: zoom.append(float(in_size - 1) / (out_size - 1)) else: zoom.append(1) output = _get_output(output, input, shape=output_shape) if input.dtype.kind in "iu": input = input.astype(cupy.float32) if prefilter and order > 1: padded, npad = _prepad_for_spline_filter(input, mode, cval) filtered = spline_filter( padded, order, output=input.dtype, mode=mode, allow_float32=allow_float32, ) else: npad = 0 filtered = input # kernel assumes C-contiguous arrays if not filtered.flags.c_contiguous: filtered = cupy.ascontiguousarray(filtered) integer_output = output.dtype.kind in "iu" large_int = (max(_misc._prod(input.shape), _misc._prod(output_shape)) > 1 << 31) kern = _get_zoom_kernel( input.ndim, large_int, output_shape, mode, order=order, integer_output=integer_output, nprepad=npad, ) zoom = cupy.asarray(zoom, dtype=float, order="C") if zoom.ndim != 1: raise ValueError("zoom must be 1d") if zoom.size != filtered.ndim: raise ValueError("len(zoom) must equal input.ndim") kern(filtered, zoom, output) return output