def hstack(tup): """Stacks arrays horizontally. If an input array has one dimension, then the array is treated as a horizontal vector and stacked along the first axis. Otherwise, the array is stacked along the second axis. Args: tup (sequence of arrays): Arrays to be stacked. Returns: cupy.ndarray: Stacked array. .. seealso:: :func:`numpy.hstack` """ arrs = [cupy.atleast_1d(a) for a in tup] axis = 1 if arrs[0].ndim == 1: axis = 0 return concatenate(arrs, axis)
def hstack(tup): """Stacks arrays horizontally. If an input array has one dimension, then the array is treated as a horizontal vector and stacked along the first axis. Otherwise, the array is stacked along the second axis. Args: tup (sequence of arrays): Arrays to be stacked. Returns: cupy.ndarray: Stacked array. .. seealso:: :func:`numpy.hstack` """ arrs = [cupy.atleast_1d(a) for a in tup] axis = 1 if arrs[0].ndim == 1: axis = 0 return concatenate(tup, axis)
def gradient_norm(model, X, y, K, sw=None): if sw is None: sw = cp.ones(X.shape[0]) else: sw = cp.atleast_1d(cp.array(sw, dtype=np.float64)) X = cp.array(X, dtype=np.float64) y = cp.array(y, dtype=np.float64) K = cp.array(K, dtype=np.float64) betas = cp.array(as_type('cupy', model.dual_coef_), dtype=np.float64).reshape(y.shape) # initialise to NaN in case below loop has 0 iterations grads = cp.full_like(y, np.NAN) for i, (beta, target, current_alpha) in ( enumerate(zip(betas.T, y.T, model.alpha))): grads[:, i] = 0.0 grads[:, i] = -cp.dot(K * sw, target) grads[:, i] += cp.dot(cp.dot(K * sw, K), beta) grads[:, i] += cp.dot(K * current_alpha, beta) return linalg.norm(grads)
def __init__(self, arg1, shape=None, dtype=None, copy=False): if isinstance(arg1, tuple): data, offsets = arg1 if shape is None: raise ValueError('expected a shape argument') else: raise ValueError( 'unrecognized form for dia_matrix constructor') data = cupy.array(data, dtype=dtype, copy=copy) data = cupy.atleast_2d(data) offsets = cupy.array(offsets, dtype='i', copy=copy) offsets = cupy.atleast_1d(offsets) if offsets.ndim != 1: raise ValueError('offsets array must have rank 1') if data.ndim != 2: raise ValueError('data array must have rank 2') if data.shape[0] != len(offsets): raise ValueError( 'number of diagonals (%d) does not match the number of ' 'offsets (%d)' % (data.shape[0], len(offsets))) sorted_offsets = cupy.sort(offsets) if (sorted_offsets[:-1] == sorted_offsets[1:]).any(): raise ValueError('offset array contains duplicate values') self.data = data self.offsets = offsets if not util.isshape(shape): raise ValueError('invalid shape (must be a 2-tuple of int)') self._shape = int(shape[0]), int(shape[1])
def firwin( numtaps, cutoff, width=None, window="hamming", pass_zero=True, scale=True, nyq=1.0, fs=None, ): """ FIR filter design using the window method. This function computes the coefficients of a finite impulse response filter. The filter will have linear phase; it will be Type I if `numtaps` is odd and Type II if `numtaps` is even. Type II filters always have zero response at the Nyquist frequency, so a ValueError exception is raised if firwin is called with `numtaps` even and having a passband whose right end is at the Nyquist frequency. Parameters ---------- numtaps : int Length of the filter (number of coefficients, i.e. the filter order + 1). `numtaps` must be odd if a passband includes the Nyquist frequency. cutoff : float or 1D array_like Cutoff frequency of filter (expressed in the same units as `fs`) OR an array of cutoff frequencies (that is, band edges). In the latter case, the frequencies in `cutoff` should be positive and monotonically increasing between 0 and `fs/2`. The values 0 and `fs/2` must not be included in `cutoff`. width : float or None, optional If `width` is not None, then assume it is the approximate width of the transition region (expressed in the same units as `fs`) for use in Kaiser FIR filter design. In this case, the `window` argument is ignored. window : string or tuple of string and parameter values, optional Desired window to use. See `cusignal.get_window` for a list of windows and required parameters. pass_zero : {True, False, 'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional If True, the gain at the frequency 0 (i.e. the "DC gain") is 1. If False, the DC gain is 0. Can also be a string argument for the desired filter type (equivalent to ``btype`` in IIR design functions). .. versionadded:: 1.3.0 Support for string arguments. scale : bool, optional Set to True to scale the coefficients so that the frequency response is exactly unity at a certain frequency. That frequency is either: - 0 (DC) if the first passband starts at 0 (i.e. pass_zero is True) - `fs/2` (the Nyquist frequency) if the first passband ends at `fs/2` (i.e the filter is a single band highpass filter); center of first passband otherwise nyq : float, optional *Deprecated. Use `fs` instead.* This is the Nyquist frequency. Each frequency in `cutoff` must be between 0 and `nyq`. Default is 1. fs : float, optional The sampling frequency of the signal. Each frequency in `cutoff` must be between 0 and ``fs/2``. Default is 2. Returns ------- h : (numtaps,) ndarray Coefficients of length `numtaps` FIR filter. Raises ------ ValueError If any value in `cutoff` is less than or equal to 0 or greater than or equal to ``fs/2``, if the values in `cutoff` are not strictly monotonically increasing, or if `numtaps` is even but a passband includes the Nyquist frequency. See Also -------- firwin2 firls minimum_phase remez Examples -------- Low-pass from 0 to f: >>> import cusignal >>> numtaps = 3 >>> f = 0.1 >>> cusignal.firwin(numtaps, f) array([ 0.06799017, 0.86401967, 0.06799017]) Use a specific window function: >>> cusignal.firwin(numtaps, f, window='nuttall') array([ 3.56607041e-04, 9.99286786e-01, 3.56607041e-04]) High-pass ('stop' from 0 to f): >>> cusignal.firwin(numtaps, f, pass_zero=False) array([-0.00859313, 0.98281375, -0.00859313]) Band-pass: >>> f1, f2 = 0.1, 0.2 >>> cusignal.firwin(numtaps, [f1, f2], pass_zero=False) array([ 0.06301614, 0.88770441, 0.06301614]) Band-stop: >>> cusignal.firwin(numtaps, [f1, f2]) array([-0.00801395, 1.0160279 , -0.00801395]) Multi-band (passbands are [0, f1], [f2, f3] and [f4, 1]): >>> f3, f4 = 0.3, 0.4 >>> cusignal.firwin(numtaps, [f1, f2, f3, f4]) array([-0.01376344, 1.02752689, -0.01376344]) Multi-band (passbands are [f1, f2] and [f3,f4]): >>> cusignal.firwin(numtaps, [f1, f2, f3, f4], pass_zero=False) array([ 0.04890915, 0.91284326, 0.04890915]) """ cutoff = cp.atleast_1d(cutoff) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: raise ValueError( "The cutoff argument must be at most " "one-dimensional." ) if cutoff.size == 0: raise ValueError("At least one cutoff frequency must be given.") if cutoff.min() <= 0 or cutoff.max() >= 1: raise ValueError( "Invalid cutoff frequency: frequencies must be " "greater than 0 and less than nyq." ) if cp.any(cp.diff(cutoff) <= 0): raise ValueError( "Invalid cutoff frequencies: the frequencies " "must be strictly increasing." ) if width is not None: # A width was given. Find the beta parameter of the Kaiser window # and set `window`. This overrides the value of `window` passed in. atten = kaiser_atten(numtaps, float(width) / nyq) beta = kaiser_beta(atten) window = ("kaiser", beta) pass_nyquist = bool(cutoff.size & 1) ^ pass_zero if pass_nyquist and numtaps % 2 == 0: raise ValueError( "A filter with an even number of coefficients must " "have zero response at the Nyquist rate." ) # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff # is even, and each pair in cutoff corresponds to passband. cutoff = cp.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist)) # `bands` is a 2D array; each row gives the left and right edges of # a passband. bands = cutoff.reshape(-1, 2) # Build up the coefficients. alpha = 0.5 * (numtaps - 1) m = cp.arange(0, numtaps) - alpha h = 0 for left, right in bands: h += right * cp.sinc(right * m) h -= left * cp.sinc(left * m) # Get and apply the window function. win = get_window(window, numtaps, fftbins=False) h *= win # Now handle scaling if desired. if scale: # Get the first passband. left, right = bands[0] if left == 0: scale_frequency = 0.0 elif right == 1: scale_frequency = 1.0 else: scale_frequency = 0.5 * (left + right) c = cp.cos(cp.pi * m * scale_frequency) s = cp.sum(h * c) h /= s return h
def lstsq(a, b, rcond='warn'): """Return the least-squares solution to a linear matrix equation. Solves the equation `a x = b` by computing a vector `x` that minimizes the Euclidean 2-norm `|| b - a x ||^2`. The equation may be under-, well-, or over- determined (i.e., the number of linearly independent rows of `a` can be less than, equal to, or greater than its number of linearly independent columns). If `a` is square and of full rank, then `x` (but for round-off error) is the "exact" solution of the equation. Args: a (cupy.ndarray): "Coefficient" matrix with dimension ``(M, N)`` b (cupy.ndarray): "Dependent variable" values with dimension ``(M,)`` or ``(M, K)`` rcond (float): Cutoff parameter for small singular values. For stability it computes the largest singular value denoted by ``s``, and sets all singular values smaller than ``s`` to zero. Returns: tuple: A tuple of ``(x, residuals, rank, s)``. Note ``x`` is the least-squares solution with shape ``(N,)`` or ``(N, K)`` depending if ``b`` was two-dimensional. The sums of ``residuals`` is the squared Euclidean 2-norm for each column in b - a*x. The ``residuals`` is an empty array if the rank of a is < N or M <= N, but iff b is 1-dimensional, this is a (1,) shape array, Otherwise the shape is (K,). The ``rank`` of matrix ``a`` is an integer. The singular values of ``a`` are ``s``. .. warning:: This function calls one or more cuSOLVER routine(s) which may yield invalid results if input conditions are not met. To detect these invalid results, you can set the `linalg` configuration to a value that is not `ignore` in :func:`cupyx.errstate` or :func:`cupyx.seterr`. .. seealso:: :func:`numpy.linalg.lstsq` """ if rcond == 'warn': warnings.warn( '`rcond` parameter will change to the default of ' 'machine precision times ``max(M, N)`` where M and N ' 'are the input matrix dimensions.\n' 'To use the future default and silence this warning ' 'we advise to pass `rcond=None`, to keep using the old, ' 'explicitly pass `rcond=-1`.', FutureWarning) rcond = -1 _util._assert_cupy_array(a, b) _util._assert_2d(a) # TODO(kataoka): Fix 0-dim if b.ndim > 2: raise linalg.LinAlgError('{}-dimensional array given. Array must be at' ' most two-dimensional'.format(b.ndim)) m, n = a.shape[-2:] m2 = b.shape[0] if m != m2: raise linalg.LinAlgError('Incompatible dimensions') u, s, vh = cupy.linalg.svd(a, full_matrices=False) if rcond is None: rcond = numpy.finfo(s.dtype).eps * max(m, n) elif rcond <= 0 or rcond >= 1: # some doc of gelss/gelsd says "rcond < 0", but it's not true! rcond = numpy.finfo(s.dtype).eps # number of singular values and matrix rank s1 = 1 / s rank = cupy.array(s.size, numpy.int32) if s.size > 0: cutoff = rcond * s.max() sing_vals = s <= cutoff s1[sing_vals] = 0 rank -= sing_vals.sum(dtype=numpy.int32) # Solve the least-squares solution # x = vh.T.conj() @ diag(s1) @ u.T.conj() @ b z = (cupy.dot(b.T, u.conj()) * s1).T x = cupy.dot(vh.T.conj(), z) # Calculate squared Euclidean 2-norm for each column in b - a*x if m <= n or rank != n: resids = cupy.empty((0, ), dtype=s.dtype) else: e = b - a.dot(x) resids = cupy.atleast_1d(_nrm2_last_axis(e.T)) return x, resids, rank, s
def canny( image, sigma=1.0, low_threshold=None, high_threshold=None, mask=None, use_quantiles=False, ): """Edge filter an image using the Canny algorithm. Parameters ----------- image : 2D array Grayscale input image to detect edges on; can be of any dtype. sigma : float, optional Standard deviation of the Gaussian filter. low_threshold : float, optional Lower bound for hysteresis thresholding (linking edges). If None, low_threshold is set to 10% of dtype's max. high_threshold : float, optional Upper bound for hysteresis thresholding (linking edges). If None, high_threshold is set to 20% of dtype's max. mask : array, dtype=bool, optional Mask to limit the application of Canny to a certain area. use_quantiles : bool, optional If True then treat low_threshold and high_threshold as quantiles of the edge magnitude image, rather than absolute edge magnitude values. If True then the thresholds must be in the range [0, 1]. Returns ------- output : 2D array (image) The binary edge map. See also -------- skimage.sobel Notes ----- The steps of the algorithm are as follows: * Smooth the image using a Gaussian with ``sigma`` width. * Apply the horizontal and vertical Sobel operators to get the gradients within the image. The edge strength is the norm of the gradient. * Thin potential edges to 1-pixel wide curves. First, find the normal to the edge at each point. This is done by looking at the signs and the relative magnitude of the X-Sobel and Y-Sobel to sort the points into 4 categories: horizontal, vertical, diagonal and antidiagonal. Then look in the normal and reverse directions to see if the values in either of those directions are greater than the point in question. Use interpolation to get a mix of points instead of picking the one that's the closest to the normal. * Perform a hysteresis thresholding: first label all points above the high threshold as edges. Then recursively label any point above the low threshold that is 8-connected to a labeled point as an edge. References ----------- .. [1] Canny, J., A Computational Approach To Edge Detection, IEEE Trans. Pattern Analysis and Machine Intelligence, 8:679-714, 1986 :DOI:`10.1109/TPAMI.1986.4767851` .. [2] William Green's Canny tutorial https://en.wikipedia.org/wiki/Canny_edge_detector Examples -------- >>> import cupy as cp >>> from cupyimg.skimage import feature >>> # Generate noisy image of a square >>> im = cp.zeros((256, 256)) >>> im[64:-64, 64:-64] = 1 >>> im += 0.2 * cp.random.rand(*im.shape) >>> # First trial with the Canny filter, with the default smoothing >>> edges1 = feature.canny(im) >>> # Increase the smoothing for better results >>> edges2 = feature.canny(im, sigma=3) """ # # The steps involved: # # * Smooth using the Gaussian with sigma above. # # * Apply the horizontal and vertical Sobel operators to get the gradients # within the image. The edge strength is the sum of the magnitudes # of the gradients in each direction. # # * Find the normal to the edge at each point using the arctangent of the # ratio of the Y sobel over the X sobel - pragmatically, we can # look at the signs of X and Y and the relative magnitude of X vs Y # to sort the points into 4 categories: horizontal, vertical, # diagonal and antidiagonal. # # * Look in the normal and reverse directions to see if the values # in either of those directions are greater than the point in question. # Use interpolation to get a mix of points instead of picking the one # that's the closest to the normal. # # * Label all points above the high threshold as edges. # * Recursively label any point above the low threshold that is 8-connected # to a labeled point as an edge. # # Regarding masks, any point touching a masked point will have a gradient # that is "infected" by the masked point, so it's enough to erode the # mask by one and then mask the output. We also mask out the border points # because who knows what lies beyond the edge of the image? # check_nD(image, 2) dtype_max = dtype_limits(image, clip_negative=False)[1] if low_threshold is None: low_threshold = 0.1 elif use_quantiles: if not (0.0 <= low_threshold <= 1.0): raise ValueError("Quantile thresholds must be between 0 and 1.") else: low_threshold = low_threshold / dtype_max if high_threshold is None: high_threshold = 0.2 elif use_quantiles: if not (0.0 <= high_threshold <= 1.0): raise ValueError("Quantile thresholds must be between 0 and 1.") else: high_threshold = high_threshold / dtype_max if mask is None: mask = cp.ones(image.shape, dtype=bool) def fsmooth(x): return img_as_float(gaussian(x, sigma, mode="constant")) smoothed = smooth_with_function_and_mask(image, fsmooth, mask) jsobel = ndi.sobel(smoothed, axis=1) isobel = ndi.sobel(smoothed, axis=0) abs_isobel = cp.abs(isobel) abs_jsobel = cp.abs(jsobel) magnitude = cp.hypot(isobel, jsobel) # # Make the eroded mask. Setting the border value to zero will wipe # out the image edges for us. # s = generate_binary_structure(2, 2) eroded_mask = binary_erosion(mask, s, border_value=0) eroded_mask = eroded_mask & (magnitude > 0) # # --------- Find local maxima -------------- # # Assign each point to have a normal of 0-45 degrees, 45-90 degrees, # 90-135 degrees and 135-180 degrees. # local_maxima = cp.zeros(image.shape, bool) # ----- 0 to 45 degrees ------ pts_plus = (isobel >= 0) & (jsobel >= 0) & (abs_isobel >= abs_jsobel) pts_minus = (isobel <= 0) & (jsobel <= 0) & (abs_isobel >= abs_jsobel) pts = pts_plus | pts_minus pts = eroded_mask & pts # Get the magnitudes shifted left to make a matrix of the points to the # right of pts. Similarly, shift left and down to get the points to the # top right of pts. c1 = magnitude[1:, :][pts[:-1, :]] c2 = magnitude[1:, 1:][pts[:-1, :-1]] m = magnitude[pts] w = abs_jsobel[pts] / abs_isobel[pts] c_plus = c2 * w + c1 * (1 - w) <= m c1 = magnitude[:-1, :][pts[1:, :]] c2 = magnitude[:-1, :-1][pts[1:, 1:]] c_minus = c2 * w + c1 * (1 - w) <= m local_maxima[pts] = c_plus & c_minus # ----- 45 to 90 degrees ------ # Mix diagonal and vertical # pts_plus = (isobel >= 0) & (jsobel >= 0) & (abs_isobel <= abs_jsobel) pts_minus = (isobel <= 0) & (jsobel <= 0) & (abs_isobel <= abs_jsobel) pts = pts_plus | pts_minus pts = eroded_mask & pts c1 = magnitude[:, 1:][pts[:, :-1]] c2 = magnitude[1:, 1:][pts[:-1, :-1]] m = magnitude[pts] w = abs_isobel[pts] / abs_jsobel[pts] c_plus = c2 * w + c1 * (1 - w) <= m c1 = magnitude[:, :-1][pts[:, 1:]] c2 = magnitude[:-1, :-1][pts[1:, 1:]] c_minus = c2 * w + c1 * (1 - w) <= m local_maxima[pts] = c_plus & c_minus # ----- 90 to 135 degrees ------ # Mix anti-diagonal and vertical # pts_plus = (isobel <= 0) & (jsobel >= 0) & (abs_isobel <= abs_jsobel) pts_minus = (isobel >= 0) & (jsobel <= 0) & (abs_isobel <= abs_jsobel) pts = pts_plus | pts_minus pts = eroded_mask & pts c1a = magnitude[:, 1:][pts[:, :-1]] c2a = magnitude[:-1, 1:][pts[1:, :-1]] m = magnitude[pts] w = abs_isobel[pts] / abs_jsobel[pts] c_plus = c2a * w + c1a * (1.0 - w) <= m c1 = magnitude[:, :-1][pts[:, 1:]] c2 = magnitude[1:, :-1][pts[:-1, 1:]] c_minus = c2 * w + c1 * (1.0 - w) <= m local_maxima[pts] = c_plus & c_minus # ----- 135 to 180 degrees ------ # Mix anti-diagonal and anti-horizontal # pts_plus = (isobel <= 0) & (jsobel >= 0) & (abs_isobel >= abs_jsobel) pts_minus = (isobel >= 0) & (jsobel <= 0) & (abs_isobel >= abs_jsobel) pts = pts_plus | pts_minus pts = eroded_mask & pts c1 = magnitude[:-1, :][pts[1:, :]] c2 = magnitude[:-1, 1:][pts[1:, :-1]] m = magnitude[pts] w = abs_jsobel[pts] / abs_isobel[pts] c_plus = c2 * w + c1 * (1 - w) <= m c1 = magnitude[1:, :][pts[:-1, :]] c2 = magnitude[1:, :-1][pts[:-1, 1:]] c_minus = c2 * w + c1 * (1 - w) <= m local_maxima[pts] = c_plus & c_minus # # ---- If use_quantiles is set then calculate the thresholds to use # if use_quantiles: high_threshold = cp.percentile(magnitude, 100.0 * high_threshold) low_threshold = cp.percentile(magnitude, 100.0 * low_threshold) # # ---- Create two masks at the two thresholds. # high_mask = local_maxima & (magnitude >= high_threshold) low_mask = local_maxima & (magnitude >= low_threshold) # # Segment the low-mask, then only keep low-segments that have # some high_mask component in them # labels, count = ndi.label(low_mask, structure=cp.ones((3, 3), bool)) if count == 0: return low_mask sums = cp.asarray( ndi.sum(high_mask, labels, cp.arange(count, dtype=cp.int32) + 1), ) sums = cp.atleast_1d(sums) good_label = cp.zeros((count + 1,), bool) good_label[1:] = sums > 0 output_mask = good_label[labels] return output_mask
def _init_nd_shape_and_axes(x, shape, axes): """Handle shape and axes arguments for n-dimensional transforms. Returns the shape and axes in a standard form, taking into account negative values and checking for various potential errors. Parameters ---------- x : array_like The input array. shape : int or array_like of ints or None The shape of the result. If both `shape` and `axes` (see below) are None, `shape` is ``x.shape``; if `shape` is None but `axes` is not None, then `shape` is ``scipy.take(x.shape, axes, axis=0)``. If `shape` is -1, the size of the corresponding dimension of `x` is used. axes : int or array_like of ints or None Axes along which the calculation is computed. The default is over all axes. Negative indices are automatically converted to their positive counterpart. Returns ------- shape : array The shape of the result. It is a 1D integer array. axes : array The shape of the result. It is a 1D integer array. """ x = asarray(x) noshape = shape is None noaxes = axes is None if noaxes: axes = arange(x.ndim, dtype=intc) else: axes = atleast_1d(axes) if axes.size == 0: axes = axes.astype(intc) if not axes.ndim == 1: raise ValueError("when given, axes values must be a scalar or vector") if not issubdtype(axes.dtype, integer): raise ValueError("when given, axes values must be integers") axes = where(axes < 0, axes + x.ndim, axes) if axes.size != 0 and (axes.max() >= x.ndim or axes.min() < 0): raise ValueError("axes exceeds dimensionality of input") if axes.size != 0 and unique(axes).shape != axes.shape: raise ValueError("all axes must be unique") if not noshape: shape = atleast_1d(shape) elif isscalar(x): shape = array([], dtype=intc) elif noaxes: shape = array(x.shape, dtype=intc) else: shape = take(x.shape, axes) if shape.size == 0: shape = shape.astype(intc) if shape.ndim != 1: raise ValueError("when given, shape values must be a scalar or vector") if not issubdtype(shape.dtype, integer): raise ValueError("when given, shape values must be integers") if axes.shape != shape.shape: raise ValueError("when given, axes and shape arguments" " have to be of the same length") shape = where(shape == -1, array(x.shape)[axes], shape) if shape.size != 0 and (shape < 1).any(): raise ValueError( "invalid number of data points ({0}) specified".format(shape)) return shape, axes
def firfilter2(b, x, axis=-1, padtype='odd', padlen=None, method='pad', irlen=None): """ Apply a digital filter forward and backward to a signal. This function applies a linear digital filter twice, once forward and once backwards. The combined filter has zero phase and a filter order twice that of the original. The function provides options for handling the edges of the signal. The function `sosfiltfilt` (and filter design using ``output='sos'``) should be preferred over `filtfilt` for most filtering tasks, as second-order sections have fewer numerical problems. Parameters ---------- b : (N,) array_like The numerator coefficient vector of the filter. x : array_like The array of data to be filtered. axis : int, optional The axis of `x` to which the filter is applied. Default is -1. padtype : str or None, optional Must be 'odd', 'even', 'constant', or None. This determines the type of extension to use for the padded signal to which the filter is applied. If `padtype` is None, no padding is used. The default is 'odd'. padlen : int or None, optional The number of elements by which to extend `x` at both ends of `axis` before applying the filter. This value must be less than ``x.shape[axis] - 1``. ``padlen=0`` implies no padding. The default value is ``3 * max(len(a), len(b))``. method : str, optional Determines the method for handling the edges of the signal, either "pad" or "gust". When `method` is "pad", the signal is padded; the type of padding is determined by `padtype` and `padlen`, and `irlen` is ignored. When `method` is "gust", Gustafsson's method is used, and `padtype` and `padlen` are ignored. irlen : int or None, optional When `method` is "gust", `irlen` specifies the length of the impulse response of the filter. If `irlen` is None, no part of the impulse response is ignored. For a long signal, specifying `irlen` can significantly improve the performance of the filter. Returns ------- y : ndarray The filtered output with the same shape as `x`. Notes ----- When `method` is "pad", the function pads the data along the given axis in one of three ways: odd, even or constant. The odd and even extensions have the corresponding symmetry about the end point of the data. The constant extension extends the data with the values at the end points. On both the forward and backward passes, the initial condition of the filter is found by using `lfilter_zi` and scaling it by the end point of the extended data. When `method` is "gust", Gustafsson's method [1]_ is used. Initial conditions are chosen for the forward and backward passes so that the forward-backward filter gives the same result as the backward-forward filter. The option to use Gustaffson's method was added in scipy version 0.16.0. References ---------- .. [1] F. Gustaffson, "Determining the initial states in forward-backward filtering", Transactions on Signal Processing, Vol. 46, pp. 988-992, 1996. """ b = cp.atleast_1d(b) x = cp.asarray(x) if method not in ["pad", "gust"]: raise ValueError("method must be 'pad' or 'gust'.") if method == "gust": raise NotImplementedError("gust method not supported yet") # method == "pad" edge, ext = _validate_pad(padtype, padlen, x, axis, ntaps=len(b)) # Get the steady state of the filter's step response. zi = firfilter_zi(b) # Reshape zi and create x0 so that zi*x0 broadcasts # to the correct value for the 'zi' keyword argument # to lfilter. zi_shape = [1] * x.ndim zi_shape[axis] = zi.size zi = cp.reshape(zi, zi_shape) x0 = _axis_slice(ext, stop=1, axis=axis) # Forward filter. (y, zf) = firfilter(b, ext, axis=axis, zi=zi * x0) # Backward filter. # Create y0 so zi*y0 broadcasts appropriately. y0 = _axis_slice(y, start=-1, axis=axis) (y, zf) = firfilter(b, _axis_reverse(y, axis=axis), axis=axis, zi=zi * y0) # Reverse y. y = _axis_reverse(y, axis=axis) if edge > 0: # Slice the actual signal from the extended signal. y = _axis_slice(y, start=edge, stop=-edge, axis=axis) return cp.copy(y)
def unit_impulse(shape, idx=None, dtype=float): """ Unit impulse signal (discrete delta function) or unit basis vector. Parameters ---------- shape : int or tuple of int Number of samples in the output (1-D), or a tuple that represents the shape of the output (N-D). idx : None or int or tuple of int or 'mid', optional Index at which the value is 1. If None, defaults to the 0th element. If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in all dimensions. If an int, the impulse will be at `idx` in all dimensions. dtype : data-type, optional The desired data-type for the array, e.g., ``numpy.int8``. Default is ``numpy.float64``. Returns ------- y : ndarray Output array containing an impulse signal. Notes ----- The 1D case is also known as the Kronecker delta. Examples -------- An impulse at the 0th element (:math:`\\delta[n]`): >>> import cusignal >>> import cupy as cp >>> cusignal.unit_impulse(8) array([ 1., 0., 0., 0., 0., 0., 0., 0.]) Impulse offset by 2 samples (:math:`\\delta[n-2]`): >>> cusignal.unit_impulse(7, 2) array([ 0., 0., 1., 0., 0., 0., 0.]) 2-dimensional impulse, centered: >>> cusignal.unit_impulse((3, 3), 'mid') array([[ 0., 0., 0.], [ 0., 1., 0.], [ 0., 0., 0.]]) Impulse at (2, 2), using broadcasting: >>> cusignal.unit_impulse((4, 4), 2) array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 0.]]) """ out = zeros(shape, dtype) shape = cp.atleast_1d(shape) if idx is None: idx = (0, ) * len(shape) elif idx == 'mid': idx = tuple(shape // 2) elif not hasattr(idx, "__iter__"): idx = (idx, ) * len(shape) out[idx] = 1 return out
def unit_impulse(shape, idx=None, dtype=float): """ Unit impulse signal (discrete delta function) or unit basis vector. Parameters ---------- shape : int or tuple of int Number of samples in the output (1-D), or a tuple that represents the shape of the output (N-D). idx : None or int or tuple of int or 'mid', optional Index at which the value is 1. If None, defaults to the 0th element. If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in all dimensions. If an int, the impulse will be at `idx` in all dimensions. dtype : data-type, optional The desired data-type for the array, e.g., ``numpy.int8``. Default is ``numpy.float64``. Returns ------- y : ndarray Output array containing an impulse signal. Notes ----- The 1D case is also known as the Kronecker delta. .. versionadded:: 0.19.0 Examples -------- An impulse at the 0th element (:math:`\\delta[n]`): >>> from scipy import signal >>> signal.unit_impulse(8) array([ 1., 0., 0., 0., 0., 0., 0., 0.]) Impulse offset by 2 samples (:math:`\\delta[n-2]`): >>> signal.unit_impulse(7, 2) array([ 0., 0., 1., 0., 0., 0., 0.]) 2-dimensional impulse, centered: >>> signal.unit_impulse((3, 3), 'mid') array([[ 0., 0., 0.], [ 0., 1., 0.], [ 0., 0., 0.]]) Impulse at (2, 2), using broadcasting: >>> signal.unit_impulse((4, 4), 2) array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 0.]]) Plot the impulse response of a 4th-order Butterworth lowpass filter: >>> imp = signal.unit_impulse(100, 'mid') >>> b, a = signal.butter(4, 0.2) >>> response = signal.lfilter(b, a, imp) >>> import matplotlib.pyplot as plt >>> plt.plot(np.arange(-50, 50), imp) >>> plt.plot(np.arange(-50, 50), response) >>> plt.margins(0.1, 0.1) >>> plt.xlabel('Time [samples]') >>> plt.ylabel('Amplitude') >>> plt.grid(True) >>> plt.show() """ out = zeros(shape, dtype) shape = cp.atleast_1d(shape) if idx is None: idx = (0, ) * len(shape) elif idx == 'mid': idx = tuple(shape // 2) elif not hasattr(idx, "__iter__"): idx = (idx, ) * len(shape) out[idx] = 1 return out
def labeled_comprehension(input, labels, index, func, out_dtype, default, pass_positions=False): """ Roughly equivalent to [func(input[labels == i]) for i in index]. Sequentially applies an arbitrary function (that works on array_like input) to subsets of an N-D image array specified by `labels` and `index`. The option exists to provide the function with positional parameters as the second argument. Parameters ---------- input : array_like Data from which to select `labels` to process. labels : array_like or None Labels to objects in `input`. If not None, array must be same shape as `input`. If None, `func` is applied to raveled `input`. index : int, sequence of ints or None Subset of `labels` to which to apply `func`. If a scalar, a single value is returned. If None, `func` is applied to all non-zero values of `labels`. func : callable Python function to apply to `labels` from `input`. out_dtype : dtype Dtype to use for `result`. default : int, float or None Default return value when a element of `index` does not exist in `labels`. pass_positions : bool, optional If True, pass linear indices to `func` as a second argument. Default is False. Returns ------- result : ndarray Result of applying `func` to each of `labels` to `input` in `index`. Examples -------- >>> import cupy as cp >>> a = cp.array([[1, 2, 0, 0], ... [5, 3, 0, 4], ... [0, 0, 0, 7], ... [9, 3, 0, 0]]) >>> from cupyimg.scipy import ndimage >>> lbl, nlbl = ndimage.label(a) >>> lbls = cp.arange(1, nlbl+1) >>> ndimage.labeled_comprehension(a, lbl, lbls, cp.mean, float, 0) array([ 2.75, 5.5 , 6. ]) Falling back to `default`: >>> lbls = cp.arange(1, nlbl+2) >>> ndimage.labeled_comprehension(a, lbl, lbls, cp.mean, float, -1) array([ 2.75, 5.5 , 6. , -1. ]) Passing positions: >>> def fn(val, pos): ... print("fn says: %s : %s" % (val, pos)) ... return (val.sum()) if (pos.sum() % 2 == 0) else (-val.sum()) ... >>> ndimage.labeled_comprehension(a, lbl, lbls, fn, float, 0, True) fn says: [1 2 5 3] : [0 1 4 5] fn says: [4 7] : [ 7 11] fn says: [9 3] : [12 13] array([ 11., 11., -12., 0.]) """ as_scalar = cupy.isscalar(index) input = cupy.asarray(input) if pass_positions: positions = cupy.arange(input.size).reshape(input.shape) if labels is None: if index is not None: raise ValueError("index without defined labels") if not pass_positions: return func(input.ravel()) else: return func(input.ravel(), positions.ravel()) try: input, labels = cupy.broadcast_arrays(input, labels) except ValueError: raise ValueError("input and labels must have the same shape " "(excepting dimensions with width 1)") if index is None: if not pass_positions: return func(input[labels > 0]) else: return func(input[labels > 0], positions[labels > 0]) index = cupy.atleast_1d(index) if cupy.any(index.astype(labels.dtype).astype(index.dtype) != index): raise ValueError("Cannot convert index values from <%s> to <%s> " "(labels' type) without loss of precision" % (index.dtype, labels.dtype)) index = index.astype(labels.dtype) # optimization: find min/max in index, and select those parts of labels, input, and positions lo = index.min() hi = index.max() mask = (labels >= lo) & (labels <= hi) # this also ravels the arrays labels = labels[mask] input = input[mask] if pass_positions: positions = positions[mask] # sort everything by labels label_order = labels.argsort() labels = labels[label_order] input = input[label_order] if pass_positions: positions = positions[label_order] index_order = index.argsort() sorted_index = index[index_order] def do_map(inputs, output): """labels must be sorted""" nidx = sorted_index.size # Find boundaries for each stretch of constant labels # This could be faster, but we already paid N log N to sort labels. lo = cupy.searchsorted(labels, sorted_index, side="left") hi = cupy.searchsorted(labels, sorted_index, side="right") for i, l, h in zip(range(nidx), lo, hi): if l == h: continue output[i] = cupy.asnumpy(func(*[inp[l:h] for inp in inputs])) temp = numpy.empty(index.shape, out_dtype) temp[:] = default if not pass_positions: do_map([input], temp) else: do_map([input, positions], temp) output = numpy.zeros(index.shape, out_dtype) output[cupy.asnumpy(index_order)] = temp if as_scalar: output = output[0] return output
def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad', irlen=None): """ Apply a digital filter forward and backward to a signal. This function applies a linear digital filter twice, once forward and once backwards. The combined filter has zero phase and a filter order twice that of the original. The function provides options for handling the edges of the signal. The function `sosfiltfilt` (and filter design using ``output='sos'``) should be preferred over `filtfilt` for most filtering tasks, as second-order sections have fewer numerical problems. Parameters ---------- b : (N,) array_like The numerator coefficient vector of the filter. a : (N,) array_like The denominator coefficient vector of the filter. If ``a[0]`` is not 1, then both `a` and `b` are normalized by ``a[0]``. x : array_like The array of data to be filtered. axis : int, optional The axis of `x` to which the filter is applied. Default is -1. padtype : str or None, optional Must be 'odd', 'even', 'constant', or None. This determines the type of extension to use for the padded signal to which the filter is applied. If `padtype` is None, no padding is used. The default is 'odd'. padlen : int or None, optional The number of elements by which to extend `x` at both ends of `axis` before applying the filter. This value must be less than ``x.shape[axis] - 1``. ``padlen=0`` implies no padding. The default value is ``3 * max(len(a), len(b))``. method : str, optional Determines the method for handling the edges of the signal, either "pad" or "gust". When `method` is "pad", the signal is padded; the type of padding is determined by `padtype` and `padlen`, and `irlen` is ignored. When `method` is "gust", Gustafsson's method is used, and `padtype` and `padlen` are ignored. irlen : int or None, optional When `method` is "gust", `irlen` specifies the length of the impulse response of the filter. If `irlen` is None, no part of the impulse response is ignored. For a long signal, specifying `irlen` can significantly improve the performance of the filter. Returns ------- y : ndarray The filtered output with the same shape as `x`. Notes ----- When `method` is "pad", the function pads the data along the given axis in one of three ways: odd, even or constant. The odd and even extensions have the corresponding symmetry about the end point of the data. The constant extension extends the data with the values at the end points. On both the forward and backward passes, the initial condition of the filter is found by using `lfilter_zi` and scaling it by the end point of the extended data. When `method` is "gust", Gustafsson's method [1]_ is used. Initial conditions are chosen for the forward and backward passes so that the forward-backward filter gives the same result as the backward-forward filter. The option to use Gustaffson's method was added in scipy version 0.16.0. References ---------- .. [1] F. Gustaffson, "Determining the initial states in forward-backward filtering", Transactions on Signal Processing, Vol. 46, pp. 988-992, 1996. """ a = cp.atleast_1d(a) if len(a) == 1: return firfilter2(b, x, axis=axis, padtype=padtype, padlen=padlen, method=method, irlen=irlen) else: raise NotImplementedError("IIR support isn't supported yet")
def diags(diagonals, offsets=0, shape=None, format=None, dtype=None): """Construct a sparse matrix from diagonals. Args: diagonals (sequence of array_like): Sequence of arrays containing the matrix diagonals, corresponding to `offsets`. offsets (sequence of int or an int): Diagonals to set: - k = 0 the main diagonal (default) - k > 0 the k-th upper diagonal - k < 0 the k-th lower diagonal shape (tuple of int): Shape of the result. If omitted, a square matrix large enough to contain the diagonals is returned. format ({"dia", "csr", "csc", "lil", ...}): Matrix format of the result. By default (format=None) an appropriate sparse matrix format is returned. This choice is subject to change. dtype (dtype): Data type of the matrix. Returns: cupyx.scipy.sparse.spmatrix: Generated matrix. Notes: This function differs from `spdiags` in the way it handles off-diagonals. The result from `diags` is the sparse equivalent of:: cupy.diag(diagonals[0], offsets[0]) + ... + cupy.diag(diagonals[k], offsets[k]) Repeated diagonal offsets are disallowed. """ # if offsets is not a sequence, assume that there's only one diagonal if sputils.isscalarlike(offsets): # now check that there's actually only one diagonal if len(diagonals) == 0 or sputils.isscalarlike(diagonals[0]): diagonals = [cupy.atleast_1d(diagonals)] else: raise ValueError('Different number of diagonals and offsets.') else: diagonals = list(map(cupy.atleast_1d, diagonals)) if isinstance(offsets, cupy.ndarray): offsets = offsets.get() offsets = numpy.atleast_1d(offsets) # Basic check if len(diagonals) != len(offsets): raise ValueError('Different number of diagonals and offsets.') # Determine shape, if omitted if shape is None: m = len(diagonals[0]) + abs(int(offsets[0])) shape = (m, m) # Determine data type, if omitted if dtype is None: dtype = cupy.common_type(*diagonals) # Construct data array m, n = shape M = max([min(m + offset, n - offset) + max(0, offset) for offset in offsets]) M = max(0, M) data_arr = cupy.zeros((len(offsets), M), dtype=dtype) K = min(m, n) for j, diagonal in enumerate(diagonals): offset = offsets[j] k = max(0, offset) length = min(m + offset, n - offset, K) if length < 0: raise ValueError( 'Offset %d (index %d) out of bounds' % (offset, j)) try: data_arr[j, k:k+length] = diagonal[..., :length] except ValueError: if len(diagonal) != length and len(diagonal) != 1: raise ValueError( 'Diagonal length (index %d: %d at offset %d) does not ' 'agree with matrix size (%d, %d).' % ( j, len(diagonal), offset, m, n)) raise return dia.dia_matrix((data_arr, offsets), shape=(m, n)).asformat(format)
def __init__(self, up, down, h, x_dtype): self.up = up self.down = down self.h = cp.atleast_1d(h) self.x_dtype = x_dtype self.rng = cp.random.RandomState(17)
def labeled_comprehension( input, labels, index, func, out_dtype, default, pass_positions=False ): """Array resulting from applying ``func`` to each labeled region. Roughly equivalent to [func(input[labels == i]) for i in index]. Sequentially applies an arbitrary function (that works on array_like input) to subsets of an N-D image array specified by `labels` and `index`. The option exists to provide the function with positional parameters as the second argument. Args: input (cupy.ndarray): Data from which to select `labels` to process. labels (cupy.ndarray or None): Labels to objects in `input`. If not None, array must be same shape as `input`. If None, `func` is applied to raveled `input`. index (int, sequence of ints or None): Subset of `labels` to which to apply `func`. If a scalar, a single value is returned. If None, `func` is applied to all non-zero values of `labels`. func (callable): Python function to apply to `labels` from `input`. out_dtype (dtype): Dtype to use for `result`. default (int, float or None): Default return value when a element of `index` does not exist in `labels`. pass_positions (bool, optional): If True, pass linear indices to `func` as a second argument. Returns: cupy.ndarray: Result of applying `func` to each of `labels` to `input` in `index`. .. seealso:: :func:`scipy.ndimage.labeled_comprehension` """ as_scalar = cupy.isscalar(index) input = cupy.asarray(input) if pass_positions: positions = cupy.arange(input.size).reshape(input.shape) if labels is None: if index is not None: raise ValueError('index without defined labels') if not pass_positions: return func(input.ravel()) else: return func(input.ravel(), positions.ravel()) try: input, labels = cupy.broadcast_arrays(input, labels) except ValueError: raise ValueError( 'input and labels must have the same shape ' '(excepting dimensions with width 1)' ) if index is None: if not pass_positions: return func(input[labels > 0]) else: return func(input[labels > 0], positions[labels > 0]) index = cupy.atleast_1d(index) if cupy.any(index.astype(labels.dtype).astype(index.dtype) != index): raise ValueError( 'Cannot convert index values from <%s> to <%s> ' '(labels.dtype) without loss of precision' % (index.dtype, labels.dtype) ) index = index.astype(labels.dtype) # optimization: find min/max in index, and select those parts of labels, # input, and positions lo = index.min() hi = index.max() mask = (labels >= lo) & (labels <= hi) # this also ravels the arrays labels = labels[mask] input = input[mask] if pass_positions: positions = positions[mask] # sort everything by labels label_order = labels.argsort() labels = labels[label_order] input = input[label_order] if pass_positions: positions = positions[label_order] index_order = index.argsort() sorted_index = index[index_order] def do_map(inputs, output): """labels must be sorted""" nidx = sorted_index.size # Find boundaries for each stretch of constant labels # This could be faster, but we already paid N log N to sort labels. lo = cupy.searchsorted(labels, sorted_index, side='left') hi = cupy.searchsorted(labels, sorted_index, side='right') for i, low, high in zip(range(nidx), lo, hi): if low == high: continue output[i] = func(*[inp[low:high] for inp in inputs]) if out_dtype == object: temp = {i: default for i in range(index.size)} else: temp = cupy.empty(index.shape, out_dtype) if default is None and temp.dtype.kind in 'fc': default = numpy.nan # match NumPy floating-point None behavior temp[:] = default if not pass_positions: do_map([input], temp) else: do_map([input, positions], temp) if out_dtype == object: # use a list of arrays since object arrays are not supported index_order = cupy.asnumpy(index_order) output = [temp[i] for i in index_order.argsort()] else: output = cupy.zeros(index.shape, out_dtype) output[cupy.asnumpy(index_order)] = temp if as_scalar: output = output[0] return output
def lfilter_zi(b, a): """ Construct initial conditions for lfilter for step response steady-state. Compute an initial state `zi` for the `lfilter` function that corresponds to the steady state of the step response. A typical use of this function is to set the initial state so that the output of the filter starts at the same value as the first element of the signal to be filtered. Parameters ---------- b, a : array_like (1-D) The IIR filter coefficients. See `lfilter` for more information. Returns ------- zi : 1-D ndarray The initial state for the filter. See Also -------- lfilter, lfiltic, filtfilt Notes ----- A linear filter with order m has a state space representation (A, B, C, D), for which the output y of the filter can be expressed as:: z(n+1) = A*z(n) + B*x(n) y(n) = C*z(n) + D*x(n) where z(n) is a vector of length m, A has shape (m, m), B has shape (m, 1), C has shape (1, m) and D has shape (1, 1) (assuming x(n) is a scalar). lfilter_zi solves:: zi = A*zi + B In other words, it finds the initial condition for which the response to an input of all ones is a constant. Given the filter coefficients `a` and `b`, the state space matrices for the transposed direct form II implementation of the linear filter, which is the implementation used by scipy.signal.lfilter, are:: A = scipy.linalg.companion(a).T B = b[1:] - a[1:]*b[0] assuming `a[0]` is 1.0; if `a[0]` is not 1, `a` and `b` are first divided by a[0]. """ b = cp.atleast_1d(b) if b.ndim != 1: raise ValueError("Numerator b must be 1-D.") a = cp.atleast_1d(a) if a.ndim != 1: raise ValueError("Denominator a must be 1-D.") while len(a) > 1 and a[0] == 0.0: a = a[1:] if a.size < 1: raise ValueError("There must be at least one nonzero `a` coefficient.") if a[0] != 1.0: # Normalize the coefficients so a[0] == 1. b = b / a[0] a = a / a[0] n = max(len(a), len(b)) # Pad a or b with zeros so they are the same length. if len(a) < n: a = cp.r_[a, cp.zeros(n - len(a))] elif len(b) < n: b = cp.r_[b, cp.zeros(n - len(b))] IminusA = cp.eye(n - 1) - linalg.companion(a).T B = b[1:] - a[1:] * b[0] # Solve zi = A*zi + B zi = cp.linalg.solve(IminusA, B) return zi