def _binary_erosion(input, structure, iterations, mask, output, border_value, origin, invert, brute_force=True): try: iterations = operator.index(iterations) except TypeError: raise TypeError('iterations parameter should be an integer') if input.dtype.kind == 'c': raise TypeError('Complex type not supported') if structure is None: structure = generate_binary_structure(input.ndim, 1) all_weights_nonzero = input.ndim == 1 center_is_true = True default_structure = True else: structure = structure.astype(dtype=bool, copy=False) # transfer to CPU for use in determining if it is fully dense # structure_cpu = cupy.asnumpy(structure) default_structure = False if structure.ndim != input.ndim: raise RuntimeError('structure and input must have same dimensionality') if not structure.flags.c_contiguous: structure = cupy.ascontiguousarray(structure) if structure.size < 1: raise RuntimeError('structure must not be empty') if mask is not None: if mask.shape != input.shape: raise RuntimeError('mask and input must have equal sizes') if not mask.flags.c_contiguous: mask = cupy.ascontiguousarray(mask) masked = True else: masked = False origin = _util._fix_sequence_arg(origin, input.ndim, 'origin', int) if isinstance(output, cupy.ndarray): if output.dtype.kind == 'c': raise TypeError('Complex output type not supported') else: output = bool output = _util._get_output(output, input) temp_needed = cupy.shares_memory(output, input, 'MAY_SHARE_BOUNDS') if temp_needed: # input and output arrays cannot share memory temp = output output = _util._get_output(output.dtype, input) if structure.ndim == 0: # kernel doesn't handle ndim=0, so special case it here if float(structure): output[...] = cupy.asarray(input, dtype=bool) else: output[...] = ~cupy.asarray(input, dtype=bool) return output origin = tuple(origin) int_type = _util._get_inttype(input) offsets = _filters_core._origins_to_offsets(origin, structure.shape) if not default_structure: # synchronize required to determine if all weights are non-zero nnz = int(cupy.count_nonzero(structure)) all_weights_nonzero = nnz == structure.size if all_weights_nonzero: center_is_true = True else: center_is_true = _center_is_true(structure, origin) erode_kernel = _get_binary_erosion_kernel( structure.shape, int_type, offsets, center_is_true, border_value, invert, masked, all_weights_nonzero, ) if iterations == 1: if masked: output = erode_kernel(input, structure, mask, output) else: output = erode_kernel(input, structure, output) elif center_is_true and not brute_force: raise NotImplementedError( 'only brute_force iteration has been implemented') else: if cupy.shares_memory(output, input, 'MAY_SHARE_BOUNDS'): raise ValueError('output and input may not overlap in memory') tmp_in = cupy.empty_like(input, dtype=output.dtype) tmp_out = output if iterations >= 1 and not iterations & 1: tmp_in, tmp_out = tmp_out, tmp_in if masked: tmp_out = erode_kernel(input, structure, mask, tmp_out) else: tmp_out = erode_kernel(input, structure, tmp_out) # TODO: kernel doesn't return the changed status, so determine it here changed = not (input == tmp_out).all() # synchronize! ii = 1 while ii < iterations or ((iterations < 1) and changed): tmp_in, tmp_out = tmp_out, tmp_in if masked: tmp_out = erode_kernel(tmp_in, structure, mask, tmp_out) else: tmp_out = erode_kernel(tmp_in, structure, tmp_out) changed = not (tmp_in == tmp_out).all() ii += 1 if not changed and (not ii & 1): # synchronize! # can exit early if nothing changed # (only do this after even number of tmp_in/out swaps) break output = tmp_out if temp_needed: temp[...] = output output = temp return output
def binary_hit_or_miss(input, structure1=None, structure2=None, output=None, origin1=0, origin2=None): """ Multidimensional binary hit-or-miss transform. The hit-or-miss transform finds the locations of a given pattern inside the input image. Args: input (cupy.ndarray): Binary image where a pattern is to be detected. structure1 (cupy.ndarray, optional): Part of the structuring element to be fitted to the foreground (non-zero elements) of ``input``. If no value is provided, a structure of square connectivity 1 is chosen. structure2 (cupy.ndarray, optional): Second part of the structuring element that has to miss completely the foreground. If no value is provided, the complementary of ``structure1`` is taken. output (cupy.ndarray, dtype or None, optional): Array of the same shape as input, into which the output is placed. By default, a new array is created. origin1 (int or tuple of ints, optional): Placement of the first part of the structuring element ``structure1``, by default 0 for a centered structure. origin2 (int or tuple of ints or None, optional): Placement of the second part of the structuring element ``structure2``, by default 0 for a centered structure. If a value is provided for ``origin1`` and not for ``origin2``, then ``origin2`` is set to ``origin1``. Returns: cupy.ndarray: Hit-or-miss transform of ``input`` with the given structuring element (``structure1``, ``structure2``). .. warning:: This function may synchronize the device. .. seealso:: :func:`scipy.ndimage.binary_hit_or_miss` """ if structure1 is None: structure1 = generate_binary_structure(input.ndim, 1) if structure2 is None: structure2 = cupy.logical_not(structure1) origin1 = _util._fix_sequence_arg(origin1, input.ndim, 'origin1', int) if origin2 is None: origin2 = origin1 else: origin2 = _util._fix_sequence_arg(origin2, input.ndim, 'origin2', int) tmp1 = _binary_erosion(input, structure1, 1, None, None, 0, origin1, 0, False) inplace = isinstance(output, cupy.ndarray) result = _binary_erosion(input, structure2, 1, None, output, 0, origin2, 1, False) if inplace: cupy.logical_not(output, output) cupy.logical_and(tmp1, output, output) else: cupy.logical_not(result, result) return cupy.logical_and(tmp1, result)
def grey_dilation(input, size=None, footprint=None, structure=None, output=None, mode='reflect', cval=0.0, origin=0): """Calculates a greyscale dilation. Args: input (cupy.ndarray): The input array. size (tuple of ints): Shape of a flat and full structuring element used for the greyscale dilation. Optional if ``footprint`` or ``structure`` is provided. footprint (array of ints): Positions of non-infinite elements of a flat structuring element used for greyscale dilation. Non-zero values give the set of neighbors of the center over which maximum is chosen. structure (array of ints): Structuring element used for the greyscale dilation. ``structure`` may be a non-flat structuring element. output (cupy.ndarray, dtype or None): The array in which to place the output. mode (str): The array borders are handled according to the given mode (``'reflect'``, ``'constant'``, ``'nearest'``, ``'mirror'``, ``'wrap'``). Default is ``'reflect'``. cval (scalar): Value to fill past edges of input if mode is ``constant``. Default is ``0.0``. origin (scalar or tuple of scalar): The origin parameter controls the placement of the filter, relative to the center of the current element of the input. Default of 0 is equivalent to ``(0,)*input.ndim``. Returns: cupy.ndarray: The result of greyscale dilation. .. seealso:: :func:`scipy.ndimage.grey_dilation` """ if size is None and footprint is None and structure is None: raise ValueError('size, footprint or structure must be specified') if structure is not None: structure = cupy.array(structure) structure = structure[tuple([slice(None, None, -1)] * structure.ndim)] if footprint is not None: footprint = cupy.array(footprint) footprint = footprint[tuple([slice(None, None, -1)] * footprint.ndim)] origin = _util._fix_sequence_arg(origin, input.ndim, 'origin', int) for i in range(len(origin)): origin[i] = -origin[i] if footprint is not None: sz = footprint.shape[i] elif structure is not None: sz = structure.shape[i] elif numpy.isscalar(size): sz = size else: sz = size[i] if sz % 2 == 0: origin[i] -= 1 return filters._min_or_max_filter(input, size, footprint, structure, output, mode, cval, origin, 'max')
def fourier_ellipsoid(input, size, n=-1, axis=-1, output=None): """Multidimensional ellipsoid Fourier filter. The array is multiplied with the fourier transform of a ellipsoid of given sizes. Args: input (cupy.ndarray): The input array. size (float or sequence of float): The size of the box used for filtering. If a float, `size` is the same for all axes. If a sequence, `size` has to contain one value for each axis. n (int, optional): If `n` is negative (default), then the input is assumed to be the result of a complex fft. If `n` is larger than or equal to zero, the input is assumed to be the result of a real fft, and `n` gives the length of the array before transformation along the real transform direction. axis (int, optional): The axis of the real transform (only used when ``n > -1``). output (cupy.ndarray, optional): If given, the result of shifting the input is placed in this array. Returns: output (cupy.ndarray): The filtered output. """ ndim = input.ndim if ndim == 1: return fourier_uniform(input, size, n, axis, output) if ndim > 3: # Note: SciPy currently does not do any filtering on >=4d inputs, but # does not warn about this! raise NotImplementedError('Only 1d, 2d and 3d inputs are supported') output = _get_output_fourier(output, input) axis = internal._normalize_axis_index(axis, ndim) sizes = _util._fix_sequence_arg(size, ndim, 'size') _core.elementwise_copy(input, output) # compute the distance from the origin for all samples in Fourier space distance = 0 for ax, (size, ax_size) in enumerate(zip(sizes, output.shape)): # compute the frequency grid in Hz if ax == axis and n > 0: arr = cupy.arange(ax_size, dtype=output.real.dtype) arr *= numpy.pi * size / n else: arr = cupy.fft.fftfreq(ax_size) arr *= numpy.pi * size arr = arr.astype(output.real.dtype, copy=False) arr *= arr arr = _reshape_nd(arr, ndim=ndim, axis=ax) distance = distance + arr cupy.sqrt(distance, out=distance) if ndim == 2: special.j1(distance, out=output) output *= 2 output /= distance elif ndim == 3: cupy.sin(distance, out=output) output -= distance * cupy.cos(distance) output *= 3 output /= distance**3 output[(0, ) * ndim] = 1.0 # avoid NaN in corner at frequency=0 location output *= input return output
def zoom(input, zoom, output=None, order=3, mode='constant', cval=0.0, prefilter=True, *, grid_mode=False): """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, default is 3. Must 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'``, ``'reflect'``, ``'wrap'``, ``'grid-mirror'``, ``'grid-wrap'``, ``'grid-constant'`` or ``'opencv'``). 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. Returns: cupy.ndarray or None: The zoomed input. .. seealso:: :func:`scipy.ndimage.zoom` """ _check_parameter('zoom', order, mode) zoom = _util._fix_sequence_arg(zoom, input.ndim, 'zoom', float) 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 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( f'It is recommended to use mode = {suggest_mode} instead ' f'of {mode} when grid_mode is True.') zoom = [] for in_size, out_size in zip(input.shape, output_shape): if grid_mode and out_size > 0: zoom.append(in_size / out_size) elif out_size > 1: zoom.append((in_size - 1) / (out_size - 1)) else: zoom.append(0) output = _util._get_output(output, input, shape=output_shape) if input.dtype.kind in 'iu': input = input.astype(cupy.float32) filtered, nprepad = _filter_input(input, prefilter, mode, cval, order) integer_output = output.dtype.kind in 'iu' _util._check_cval(mode, cval, integer_output) large_int = max(_prod(input.shape), _prod(output_shape)) > 1 << 31 kern = _interp_kernels._get_zoom_kernel(input.ndim, large_int, output_shape, mode, order=order, integer_output=integer_output, grid_mode=grid_mode, nprepad=nprepad) zoom = cupy.asarray(zoom, dtype=cupy.float64) kern(filtered, zoom, output) return output
def shift(input, shift, output=None, order=3, mode='constant', cval=0.0, prefilter=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, default is 3. Must 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'``, ``'reflect'``, ``'wrap'``, ``'grid-mirror'``, ``'grid-wrap'``, ``'grid-constant'`` or ``'opencv'``). 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. .. seealso:: :func:`scipy.ndimage.shift` """ _check_parameter('shift', order, mode) shift = _util._fix_sequence_arg(shift, input.ndim, 'shift', float) 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: output = _util._get_output(output, input) if input.dtype.kind in 'iu': input = input.astype(cupy.float32) filtered, nprepad = _filter_input(input, prefilter, mode, cval, order) integer_output = output.dtype.kind in 'iu' _util._check_cval(mode, cval, integer_output) large_int = _prod(input.shape) > 1 << 31 kern = _interp_kernels._get_shift_kernel(input.ndim, large_int, input.shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=nprepad) shift = cupy.asarray(shift, dtype=cupy.float64, 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, *, texture_memory=False): """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, default is 3. Must 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'``, ``'reflect'``, ``'wrap'``, ``'grid-mirror'``, ``'grid-wrap'``, ``'grid-constant'`` or ``'opencv'``). 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`. texture_memory (bool): If True, uses GPU texture memory. Supports only: - 2D and 3D float32 arrays as input - ``(ndim + 1, ndim + 1)`` homogeneous float32 transformation matrix - ``mode='constant'`` and ``mode='nearest'`` - ``order=0`` (nearest neighbor) and ``order=1`` (linear interpolation) - NVIDIA CUDA GPUs Returns: cupy.ndarray or None: The transformed input. If ``output`` is given as a parameter, ``None`` is returned. .. seealso:: :func:`scipy.ndimage.affine_transform` """ if texture_memory: if runtime.is_hip: raise RuntimeError( 'HIP currently does not support texture acceleration') tm_interp = 'linear' if order > 0 else 'nearest' return _texture.affine_transformation(data=input, transformation_matrix=matrix, output_shape=output_shape, output=output, interpolation=tm_interp, mode=mode, border_value=cval) _check_parameter('affine_transform', order, mode) offset = _util._fix_sequence_arg(offset, input.ndim, 'offset', float) if matrix.ndim not in [1, 2] or matrix.shape[0] < 1: 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 matrix.shape != (input.ndim, input.ndim): raise RuntimeError('improper affine shape') if mode == 'opencv': m = cupy.zeros((input.ndim + 1, input.ndim + 1)) 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 if mode == 'opencv' or mode == '_opencv_edge': if matrix.ndim == 1: matrix = cupy.diag(matrix) coordinates = cupy.indices(output_shape, dtype=cupy.float64) coordinates = cupy.dot(matrix, coordinates.reshape((input.ndim, -1))) coordinates += cupy.expand_dims(cupy.asarray(offset), -1) ret = _util._get_output(output, input, shape=output_shape) ret[:] = map_coordinates(input, coordinates, ret.dtype, order, mode, cval, prefilter).reshape(output_shape) return ret matrix = matrix.astype(cupy.float64, copy=False) ndim = input.ndim output = _util._get_output(output, input, shape=output_shape) if input.dtype.kind in 'iu': input = input.astype(cupy.float32) filtered, nprepad = _filter_input(input, prefilter, mode, cval, order) integer_output = output.dtype.kind in 'iu' _util._check_cval(mode, cval, integer_output) large_int = max(_prod(input.shape), _prod(output_shape)) > 1 << 31 if matrix.ndim == 1: offset = cupy.asarray(offset, dtype=cupy.float64) offset = -offset / matrix kern = _interp_kernels._get_zoom_shift_kernel( ndim, large_int, output_shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=nprepad) kern(filtered, offset, matrix, output) else: kern = _interp_kernels._get_affine_kernel( ndim, large_int, output_shape, mode, cval=cval, order=order, integer_output=integer_output, nprepad=nprepad) m = cupy.zeros((ndim, ndim + 1), dtype=cupy.float64) m[:, :-1] = matrix m[:, -1] = cupy.asarray(offset, dtype=cupy.float64) kern(filtered, m, output) return output