def apply_along_axis(func1d, axis, arr, *args, **kwargs): """Apply a function to 1-D slices along the given axis. Args: func1d (function (M,) -> (Nj...)): This function should accept 1-D arrays. It is applied to 1-D slices of ``arr`` along the specified axis. It must return a 1-D ``cupy.ndarray``. axis (integer): Axis along which ``arr`` is sliced. arr (cupy.ndarray (Ni..., M, Nk...)): Input array. args: Additional arguments for ``func1d``. kwargs: Additional keyword arguments for ``func1d``. Returns: cupy.ndarray: The output array. The shape of ``out`` is identical to the shape of ``arr``, except along the ``axis`` dimension. This axis is removed, and replaced with new dimensions equal to the shape of the return value of ``func1d``. So if ``func1d`` returns a scalar ``out`` will have one fewer dimensions than ``arr``. .. seealso:: :func:`numpy.apply_along_axis` """ ndim = arr.ndim axis = internal._normalize_axis_index(axis, ndim) inarr_view = cupy.moveaxis(arr, axis, -1) # compute indices for the iteration axes, and append a trailing ellipsis to # prevent 0d arrays decaying to scalars inds = index_tricks.ndindex(inarr_view.shape[:-1]) inds = (ind + (Ellipsis,) for ind in inds) # invoke the function on the first item try: ind0 = next(inds) except StopIteration: raise ValueError( 'Cannot apply_along_axis when any iteration dimensions are 0' ) res = func1d(inarr_view[ind0], *args, **kwargs) if cupy.isscalar(res): # scalar outputs need to be transfered to a device ndarray res = cupy.asarray(res) # build a buffer for storing evaluations of func1d. # remove the requested axis, and add the new ones on the end. # laid out so that each write is contiguous. # for a tuple index inds, buff[inds] = func1d(inarr_view[inds]) buff = cupy.empty(inarr_view.shape[:-1] + res.shape, res.dtype) # save the first result, then compute and save all remaining results buff[ind0] = res for ind in inds: buff[ind] = func1d(inarr_view[ind], *args, **kwargs) # restore the inserted axes back to where they belong for i in range(res.ndim): buff = cupy.moveaxis(buff, -1, axis) return buff
def logneg(wave, n, partition): L, la, lb, lc1, lc2 = int(partition[0]), int(partition[1]), int( partition[2]), int(partition[3]), int(partition[4]) # region A ps = cp.reshape(wave, (2**lc1, 2**la, 2**lc2, 2**lb)) ps = cp.moveaxis(ps, 0, 1) ps = cp.reshape(ps, (2**la, 2**(L - la))) # entanglement entropy in region A en = ent(ps, n, L, la) # sa and sar stand for von-Neumann and Renyi entanglement entropies sa, sar = en[0], en[1] # region B ps = cp.reshape(wave, (2**(L - lb), 2**lb)) en = ent(ps, n, L, L - lb) sb, sbr = en[0], en[1] # region C # since C composed of c1 and c2, we need to re-arrange the index to combine c1 and c2 into # a connected region ps = cp.reshape(wave, (2**lc1, 2**la, 2**lc2, 2**lb)) ps = cp.moveaxis(ps, 1, 2) ps = cp.reshape(ps, (2**(lc1 + lc2), 2**(la + lb))) en = ent(ps, n, L, lc1 + lc2) sc, scr = en[0], en[1] # log(negativity) rab = cp.dot(ps.T, cp.conj(ps)) #reduced density matrix by tracing out C # reshape the reduced density matrix to have 4 indices to facilitate partial transpose rab = cp.reshape(rab, (2**la, 2**lb, 2**la, 2**lb)) # partial transpose on A pab = cp.moveaxis(rab, 0, 2) # rearrange indices to make pab into a matrix pab = pab.reshape(2**(la + lb), 2**(la + lb)) # SVD of partial transposed density matrix sp = cp.linalg.svd(pab, compute_uv=False) # definition of logarithmic negativity logn = cp.log2(cp.sum(sp)) tol = 1e-10 # returns logarithmic negativity and two mutual information result = np.array([logn, sa + sb - sc, sar + sbr - scr]) # chop small values to be zero result[abs(result) < tol] = 0.0 return result
def evo(steps, wave, prob, L, n, partition): von = cp.zeros(steps, dtype='float64') # von-Neumann entropy renyi = cp.zeros(steps, dtype='float64') # Renyi entropy neg = cp.zeros(steps, dtype='float64') # logarithmic negativity mut = cp.zeros( steps, dtype='float64') # mutual information using von-Neumann entropy mutr = cp.zeros( steps, dtype='float64') # mutual information in terms of Renyi entropy for t in range(steps): # evolve over odd links for i in range(L // 2): wave = unitary(wave, i, L) # measurement layer for i in range(L): wave = measure(wave, prob, i, L) # before evolve on even link, we need to rearrange indices first to accommodate the boundary condition PBC wave = cp.reshape(wave, (2, 2**(L - 2), 2)) # move the last site into the first one such that the unitaries can connect the 1st and the last site wave = cp.moveaxis(wave, -1, 0) wave = wave.flatten() # evolve over even links for i in range(L // 2): wave = unitary(wave, i, L) #shift the index back to the original order after evolution wave = cp.reshape(wave, (2, 2, 2**(L - 2))) wave = cp.moveaxis(wave, -1, 0) wave = cp.moveaxis(wave, -1, 0).flatten() #measurement layer for i in range(L): wave = measure(wave, prob, i, L) result = ent(wave, n, L, L // 2) von[t] = result[0] renyi[t] = result[1] result = logneg(wave, n, partition) neg[t] = result[0] mut[t] = result[1] mutr[t] = result[2] return von, renyi, neg, mut, mutr
def checkerboard(array, inverse=False): """In-place FFTshift for even sized grids only. If and only if the dimensions of `array` are even numbers, flipping the signs of input signal in an alternating pattern before an FFT is equivalent to shifting the zero-frequency component to the center of the spectrum before the FFT. """ def g(x): return 1 - 2 * (x % 2) for i in range(2): array = cp.moveaxis(array, i, -1) array *= g(cp.arange(array.shape[-1]) + 1) if inverse: array *= g(array.shape[-1] // 2) array = cp.moveaxis(array, -1, i) return array
def move_broadcast_axes_to_front(ioperands, subscripts): broadcasted_operands = [] broadcasted_subscripts = [] for operand, subscript in six.moves.zip(ioperands, subscripts): if '@' in subscript: ellipsis_pos = subscript.find('@') source_axes = list(six.moves.range(ellipsis_pos)) destination_axes = [i - ellipsis_pos for i in source_axes] operand = cupy.moveaxis(operand, source_axes, destination_axes) subscript = subscript[ellipsis_pos:] + subscript[:ellipsis_pos] broadcasted_operands.append(operand) broadcasted_subscripts.append(subscript) return broadcasted_operands, broadcasted_subscripts
def each_channel(image_filter, image, *args, **kwargs): """Return color image by applying `image_filter` on channels of `image`. Note that this function is intended for use with `adapt_rgb`. Parameters ---------- image_filter : function Function that filters a gray-scale image. image : array Input image. """ c_new = [ image_filter(c, *args, **kwargs) for c in cp.moveaxis(image, -1, 0) ] return cp.stack(c_new, axis=-1)
def agents_neighbour_flat_positions(self): """ For any agent we find 3x3x3 array of their neighbour cells indexes. Flatten position in env of an agent will be at the center of this array. Result array will be 4 dimensional. """ agents_positions = cp.asarray(self._agents_positions) neighbour_positions = cp.expand_dims(agents_positions, 0).repeat(27, axis=0) neighbour_positions = cp.moveaxis(neighbour_positions, (0, 1, 2), (2, 0, 1)) neighbour_positions = neighbour_positions.reshape( (-1, 3, 3, 3, 3)).swapaxes(1, 4) neighbour_positions = neighbour_positions + NEIGHBOUR_POSITIONS_DELTA neighbour_positions = neighbour_positions.reshape(-1, 3).T neighbour_flattened_poses = cp.ravel_multi_index( neighbour_positions, self.env.shape) return neighbour_flattened_poses.reshape((-1, 3, 3, 3))
def polyvander(x, deg): """Computes the Vandermonde matrix of given degree. Args: x (cupy.ndarray): array of points deg (int): degree of the resulting matrix. Returns: cupy.ndarray: The Vandermonde matrix .. seealso:: :func:`numpy.polynomial.polynomial.polyvander` """ deg = cupy.polynomial.polyutils._deprecate_as_int(deg, 'deg') if deg < 0: raise ValueError('degree must be non-negative') if x.ndim == 0: x = x.ravel() dtype = cupy.float64 if x.dtype.kind in 'biu' else x.dtype out = x**cupy.arange(deg + 1, dtype=dtype).reshape((-1, ) + (1, ) * x.ndim) return cupy.moveaxis(out, 0, -1)
def calc_transposed_view(ioperand, input_subscript, output_subscript): """Calculates 'ij->ji' by cupy.transpose if needed. Args: ioperand (cupy.ndarray): Array to be transpose. input_subscript (str): Specifies the subscripts for input arrays. output_subscript (str): Specifies the subscripts for output arrays. If input does not match output, ``operand`` is transposed so that it matches. """ assert len(set(output_subscript)) == len(output_subscript) assert set(input_subscript) == set(output_subscript) if input_subscript == output_subscript: return ioperand source_axes = [] destination_axes = [] ellipsis_pos_input = input_subscript.find('@') ellipsis_pos_output = output_subscript.find('@') for label_pos_output, label in enumerate(output_subscript): if label == '@': continue if ellipsis_pos_input == -1 or label_pos_output < ellipsis_pos_output: destination_axes.append(label_pos_output) else: destination_axes.append(label_pos_output - len(output_subscript)) label_pos_input = input_subscript.find(label) if ellipsis_pos_input == -1 or label_pos_input < ellipsis_pos_input: source_axes.append(label_pos_input) else: source_axes.append(label_pos_input - len(input_subscript)) return cupy.moveaxis(ioperand, source_axes, destination_axes)
def step(self, model, step_num): alpha = self.get_alpha(model, step_num) # multiplication det_num = model.detector_signal.shape[0] for i in range(self.n_slices): # get slice # ############## may be speeded up if all slices are calc. once out of for cycle i1 = int(i * cp.ceil(det_num / self.n_slices)) i2 = int(min((i + 1) * cp.ceil(det_num / self.n_slices), det_num)) w_slice = model.detector_geometry[i1:i2] wi_slice = self.wi[i1:i2] y_slice = model.detector_signal[i1:i2] # calculating correction p = signal.get_signal_gpu(model.solution, w_slice) dp = y_slice - p a = cp.divide(dp, wi_slice) a = cp.where(cp.isnan(a), 0, a) if self.iter_type == 1: # SMART y_slice = cp.where(y_slice < 1E-20, 0, y_slice) a = cp.divide(a, np.abs(y_slice)) a = cp.where(cp.isnan(a), 0, a) correction = alpha / (i2 - i1) * cp.sum(cp.multiply(cp.moveaxis(w_slice, 0, -1), a), axis=-1) model._solution = model.solution + correction
def warp( volume, d1, affine_idx_in=None, affine_idx_out=None, affine_disp=None, out_shape=None, *, order=1, mode="constant", coord_axis=-1, ): """ Deforms the input volume under the given transformation. The warped volume is computed using is given by: (1) warped[i] = volume[ C * d1[A*i] + B*i ] where: A = affine_idx_in B = affine_idx_out C = affine_disp """ A = affine_idx_in B = affine_idx_out C = affine_disp if out_shape is None: out_shape = volume.shape if A is not None: A = cupy.asarray(A) if B is not None: B = cupy.asarray(B) if C is not None: C = cupy.asarray(C) # TODO: reduce number of temporary arrays coord_dtype = cupy.promote_types(volume.dtype, np.float32) ndim = volume.ndim if d1.shape[coord_axis] != ndim: raise ValueError("expected a displacement field with shape " "{} along axis {}".format(ndim, coord_axis)) if A is None: xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=coord_dtype) for s in out_shape], indexing="ij", sparse=True, ) Z = cupy.ascontiguousarray(cupy.moveaxis(d1, -1, 0)) else: xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=coord_dtype) for s in out_shape], indexing="ij", sparse=True, ) # Y = mul0(A, xcoords, sh, cupy, lastcol=1) Y = _apply_affine_to_field( xcoords, A[:ndim, :], out=None, include_translations=True, coord_axis=0, ) # for CuPy with non-legacy linear interpolation, don't need to extend d1 Z = cupy.empty_like(Y) if coord_axis == -1: for n in range(ndim): Z[n, ...] = ndi.map_coordinates(d1[..., n], Y, order=1, mode=mode) else: for n in range(ndim): Z[n, ...] = ndi.map_coordinates(d1[n], Y, order=1, mode=mode) if C is not None: # Z = mul0(C, Z, sh, cupy, out=Z, lastcol=0) Z = _apply_affine_to_field( Z, C[:ndim, :ndim], out=None, include_translations=False, coord_axis=0, ) if B is not None: # Z += mul0(B, xcoords, sh, cupy, lastcol=1) Z += _apply_affine_to_field( xcoords, B[:ndim, :], out=None, include_translations=True, coord_axis=0, ) else: if A is None: for n in range(ndim): Z[n, ...] += xcoords[n] else: Z += Y return ndi.map_coordinates(volume, Z, order=order, mode=mode)
def simplify_warp_function( d, affine_idx_in, affine_idx_out, affine_disp, out_shape, *, mode="constant", coord_axis=-1, ): """ Simplifies a nonlinear warping function combined with an affine transform Modifies the given deformation field by incorporating into it an affine transformation and voxel-to-space transforms associated with the discretization of its domain and codomain. The resulting transformation may be regarded as operating on the image spaces given by the domain and codomain discretization. More precisely, the resulting transform is of the form: (1) T[i] = W * d[U * i] + V * i Where U = affine_idx_in, V = affine_idx_out, W = affine_disp. Parameters ---------- d : array, shape (S', R', C', 3) the non-linear part of the transformation (displacement field) affine_idx_in : array, shape (4, 4) the matrix U in eq. (1) above affine_idx_out : array, shape (4, 4) the matrix V in eq. (1) above affine_disp : array, shape (4, 4) the matrix W in eq. (1) above out_shape : array, shape (3,) the number of slices, rows and columns of the sampling grid Returns ------- out : array, shape = out_shape the deformation field `out` associated with `T` in eq. (1) such that: T[i] = i + out[i] Notes ----- Both the direct and inverse transforms of a DiffeomorphicMap can be written in this form: Direct: Let D be the voxel-to-space transform of the domain's discretization, P be the pre-align matrix, Rinv the space-to-voxel transform of the reference grid (the grid the displacement field is defined on) and Cinv be the space-to-voxel transform of the codomain's discretization. Then, for each i in the domain's grid, the direct transform is given by (2) T[i] = Cinv * d[Rinv * P * D * i] + Cinv * P * D * i and we identify U = Rinv * P * D, V = Cinv * P * D, W = Cinv Inverse: Let C be the voxel-to-space transform of the codomain's discretization, Pinv be the inverse of the pre-align matrix, Rinv the space-to-voxel transform of the reference grid (the grid the displacement field is defined on) and Dinv be the space-to-voxel transform of the domain's discretization. Then, for each j in the codomain's grid, the inverse transform is given by (3) Tinv[j] = Dinv * Pinv * d[Rinv * C * j] + Dinv * Pinv * C * j and we identify U = Rinv * C, V = Dinv * Pinv * C, W = Dinv * Pinv """ if coord_axis not in [0, -1]: raise ValueError("coord_axis must be 0 or -1") ndim = d.shape[coord_axis] U = affine_idx_in V = affine_idx_out W = affine_disp # TODO: reduce number of temporary arrays coord_dtype = cupy.promote_types(d.dtype, np.float32) if U is None: xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=coord_dtype) for s in d.shape[:-1]], indexing="ij", sparse=True, ) if coord_axis == 0: Z = d.copy() else: Z = cupy.ascontiguousarray(cupy.moveaxis(d, -1, 0)) else: xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=coord_dtype) for s in d.shape[:-1]], indexing="ij", sparse=True, ) # Y = mul0(A, xcoords, sh, cupy, lastcol=1) Y = _apply_affine_to_field( xcoords, U[:ndim, :], out=None, include_translations=True, coord_axis=0, ) # for CuPy with non-legacy linear interpolation, don't need to extend d Z = cupy.empty_like(Y) if coord_axis == 0: for n in range(ndim): Z[n, ...] = ndi.map_coordinates(d[n], Y, order=1, mode=mode) else: for n in range(ndim): Z[n, ...] = ndi.map_coordinates(d[..., n], Y, order=1, mode=mode) if W is not None: # Z = mul0(C, Z, sh, cupy, out=Z, lastcol=0) Z = _apply_affine_to_field( Z, W[:ndim, :ndim], out=None, include_translations=False, coord_axis=0, ) if V is not None: Z += _apply_affine_to_field( xcoords, V[:ndim, :], out=None, include_translations=True, coord_axis=0, ) for n in range(ndim): Z[n, ...] -= xcoords[ n] # TODO: just subtract one from last column of V instead? if coord_axis == -1: Z = cupy.moveaxis(Z, 0, -1) if not Z.flags.c_contiguous: Z = cupy.ascontiguousarray(Z) return Z
def invert_vector_field_fixed_point( d, d_world2grid, spacing, max_iter, tol, start=None, *, coord_axis=-1, print_stats=False, ): """Computes the inverse of a 3D displacement fields Computes the inverse of the given 3-D displacement field d using the fixed-point algorithm [1]. [1] Chen, M., Lu, W., Chen, Q., Ruchala, K. J., & Olivera, G. H. (2008). A simple fixed-point approach to invert a deformation field. Medical Physics, 35(1), 81. doi:10.1118/1.2816107 Parameters ---------- d : array, shape (S, R, C, 3) the 3-D displacement field to be inverted d_world2grid : array, shape (4, 4) the space-to-grid transformation associated to the displacement field d (transforming physical space coordinates to voxel coordinates of the displacement field grid) spacing :array, shape (3,) the spacing between voxels (voxel size along each axis) max_iter : int maximum number of iterations to be performed tol : float maximum tolerated inversion error start : array, shape (S, R, C) an approximation to the inverse displacement field (if no approximation is available, None can be provided and the start displacement field will be zero) Returns ------- p : array, shape (S, R, C, 3) the inverse displacement field Notes ----- We assume that the displacement field is an endomorphism so that the shape and voxel-to-space transformation of the inverse field's discretization is the same as those of the input displacement field. The 'inversion error' at iteration t is defined as the mean norm of the displacement vectors of the input displacement field composed with the inverse at iteration t. """ ndim = d.shape[coord_axis] if coord_axis != 0: d = cupy.moveaxis(d, coord_axis, 0) if start is not None: if coord_axis != 0: start = cupy.moveaxis(start, coord_axis, 0) if start.shape != d.shape: raise ValueError("start must have the same shape as d") p = cupy.ascontiguousarray(start) else: p = start.copy() else: p = cupy.zeros_like(d) q = cupy.empty_like(d) if spacing.dtype != q.dtype: spacing = spacing.astype(q.dtype) if d_world2grid is not None: d_world2grid = cupy.asarray(d_world2grid) if not d_world2grid.flags.c_contiguous: d_world2grid = cupy.ascontiguousarray(d_world2grid) # for efficiency, precompute xcoords, Y and Z here instead of repeatedly # doing so inside of compose_vector_fields xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=d.real.dtype) for s in d.shape[1:]], indexing="ij", sparse=True, ) Y = cupy.empty_like(d) Z = cupy.empty_like(d) iter_count = 0 difmag = 1 error = 1 + tol while (0.1 < difmag) and (iter_count < max_iter) and (tol < error): if iter_count == 0: epsilon = 0.75 else: epsilon = 0.5 q, _stats = compose_vector_fields( p, d, None, d_world2grid, 1.0, comp=q, coord_axis=0, omit_stats=True, xcoords=xcoords, Y=Y, Z=Z, ) difmag = 0 error = 0 # could make special case 2d/3d elementwise kernel for computing norms norms = q[0] / spacing[0] norms *= norms for n in range(1, ndim): tmp = q[n] / spacing[n] norms += tmp * tmp cupy.sqrt(norms, out=norms) error = float(norms.sum()) difmag = float(norms.max()) maxlen = difmag * epsilon _norm_thresh_kernel(q, norms[np.newaxis, ...], epsilon, maxlen, p) error /= norms.size iter_count += 1 if print_stats: stats = np.empty((2, ), dtype=float) stats[0] = error stats[1] = iter_count print(f"stats={stats}, diffmag={difmag}") if coord_axis != 0: p = cupy.moveaxis(p, 0, coord_axis) return p # , q, norms, tmp1, tmp2, epsilon, maxlen
def _apply_affine_to_field(field, affine, out=None, include_translations=False, coord_axis=-1): """Reorient a vector field. Parameters ---------- field : cupy.ndarray The vector displacement field. Should have ndim + 1 dimensions with shape (ndim) on the first axis. Alternatively it can be a list of length `ndim` where each array corresponds to one coordinate vector. This type of list inputs allows for field to be a coordinate array as returned by meshgrid (optionally using the `sparse=True` option to conserve memory). affine : cupy.ndarray affine should be a transformation matrix with shape (ndim, ndim). It can also be a (ndim + 1, ndim + 1) affine matrix, but in this case, only the upper left (ndim, ndim) portion of the matrix will be applied. out : cupy.ndarray or None The output array (same shape as `field`). Note that in-place reorientation is not supported (i.e. `out` cannot be the same array as `field`). Returns ------- reoriented : cupy.ndarray The reoriented displacement field. """ # TODO: remove support for dimensions as last axis instead of first? # that would simplify the function. first axis is more efficient for # C-contiguous arrays if coord_axis not in [0, -1]: raise ValueError("coord_axis must be 0 or -1.") if isinstance(field, cupy.ndarray): # field is a single, dense ndarray ndim = field.ndim - 1 if field.shape[coord_axis] != ndim: print( f"field.shape={field.shape}, ndim={ndim}, coord_axis={coord_axis}" ) raise ValueError("shape mismatch") if field.ndim != ndim + 1: raise ValueError("invalid field") if not field.dtype.kind == "f": raise ValueError("field must having floating point dtype") if coord_axis == 0: field = tuple([f for f in field]) else: field = tuple( [cupy.ascontiguousarray(field[..., n]) for n in range(ndim)]) else: ndim = len(field) field_dtype = field[0].dtype if include_translations: affine_shape = (ndim, ndim + 1) else: affine_shape = (ndim, ndim) affine = cupy.asarray(affine, dtype=field_dtype, order="C") if affine.shape == (ndim + 1, ndim + 1): affine = affine[:affine_shape[0], :affine_shape[1]] if not affine.flags.c_contiguous: affine = cupy.ascontiguousarray(affine) if affine.shape != affine_shape: raise ValueError( "expected anaffine array with shape {}".format(affine_shape)) out_shape = (ndim, ) + tuple([field[n].shape[n] for n in range(ndim)]) if out is None: out = cupy.empty(out_shape, dtype=field_dtype, order="C") else: if out.shape != field.shape or out.dtype != field_dtype: raise ValueError( "out and field must have matching shape and dtype") if not out.flags.c_contiguous: raise ValueError("out must be C contiguous") # Note: affine must be contiguous. # field and out do not have to be contiguous, but performance # will be better if they are. if ndim == 3: if include_translations: kernel = aff_kernel_3d else: kernel = reorient_kernel_3d # args = field + (affine, out[0], out[1], out[2]) kernel(*field, affine, out[0], out[1], out[2]) elif ndim == 2: if include_translations: kernel = aff_kernel_2d else: kernel = reorient_kernel_2d kernel(*field, affine, out[0], out[1]) if coord_axis == -1: out = cupy.moveaxis(out, 0, -1) return out
def compose_vector_fields( d1, d2, premult_index, premult_disp, time_scaling, comp=None, order=1, *, coord_axis=-1, omit_stats=False, xcoords=None, Y=None, Z=None, ): if comp is None: comp = cupy.empty_like(d1, order="C") # need vector elements on first axis, not last if coord_axis != 0: d1 = cupy.ascontiguousarray(cupy.moveaxis(d1, -1, 0)) d2 = cupy.ascontiguousarray(cupy.moveaxis(d2, -1, 0)) else: if not d1.flags.c_contiguous: d1 = cupy.ascontiguousarray(d1) if not d2.flags.c_contiguous: d2 = cupy.ascontiguousarray(d2) ndim = d1.shape[0] B = premult_disp A = premult_index t = time_scaling if xcoords is None: xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=d1.real.dtype) for s in d1.shape[1:]], indexing="ij", sparse=True, ) # TODO: reduce number of temporary arrays if ndim in [2, 3]: if Y is None: Y = cupy.empty_like(d1) if A is None: if B is None: if ndim == 3: composeNone_3d( d1[0], d1[1], d1[2], xcoords[0], xcoords[1], xcoords[2], Y[0], Y[1], Y[2], ) else: composeNone_2d(d1[0], d1[1], xcoords[0], xcoords[1], Y[0], Y[1]) else: B = cupy.asarray(B[:ndim, :ndim], dtype=d1.dtype, order="C") if ndim == 3: composeB_3d( d1[0], d1[1], d1[2], xcoords[0], xcoords[1], xcoords[2], B, Y[0], Y[1], Y[2], ) else: composeB_2d(d1[0], d1[1], xcoords[0], xcoords[1], B, Y[0], Y[1]) elif B is None: A = cupy.asarray(A[:ndim, :], dtype=d1.dtype, order="C") if ndim == 3: composeA_3d(xcoords[0], xcoords[1], xcoords[2], A, Y[0], Y[1], Y[2]) else: composeA_2d(xcoords[0], xcoords[1], A, Y[0], Y[1]) else: A = cupy.asarray(A[:ndim, :], dtype=d1.dtype, order="C") B = cupy.asarray(B[:ndim, :ndim], dtype=d1.dtype, order="C") if ndim == 3: composeAB_3d( d1[0], d1[1], d1[2], xcoords[0], xcoords[1], xcoords[2], B, A, Y[0], Y[1], Y[2], ) else: composeAB_2d(d1[0], d1[1], xcoords[0], xcoords[1], B, A, Y[0], Y[1]) else: if B is None: d1tmp = d1.copy() # have to copy to avoid modification of d1 else: d1tmp = _apply_affine_to_field(d1, B[:ndim, :ndim], include_translations=False, coord_axis=0) if A is None: Y = d1tmp for n in range(ndim): Y[n] += xcoords[n] else: # Y = mul0(A, xcoords, sh, cupy, lastcol=1) Y = _apply_affine_to_field(xcoords, A[:ndim, :], include_translations=True, coord_axis=0) Y += d1tmp if Z is None: Z = cupy.empty_like(Y) for n in range(ndim): Z[n, ...] = ndi.map_coordinates(d2[n], Y, order=1, mode="constant") if coord_axis == 0: res = comp else: res = cupy.empty_like(Z) if omit_stats and ndim in [2, 3]: _shape = cupy.asarray([d1.shape[1 + n] - 1 for n in range(ndim)], dtype=cupy.int32) if ndim == 3: _comp_apply_masked_time_scaling_3d( d1[0], d1[1], d1[2], Y[0], Y[1], Y[2], Z[0], Z[1], Z[2], t, _shape, res[0], res[1], res[2], ) else: _comp_apply_masked_time_scaling_2d(d1[0], d1[1], Y[0], Y[1], Z[0], Z[1], t, _shape, res[0], res[1]) else: # TODO: declare count as boolean? count = cupy.zeros(Z.shape[1:], dtype=np.int32) # We now compute: # res = d1 + t * Z # except that res = 0 where either coordinate in # interpolating Y was outside the displacement extent for n in range(ndim): _comp_apply_masked_time_scaling_nd(d1[n], Y[n], Z[n], t, d1.shape[1 + n] - 1, res[n], count) # nnz corresponds to the number of points in comp inside the domain count = count > 0 # remove after init count as boolean if not omit_stats: nnz = res.size // ndim - cupy.count_nonzero(count) res *= ~count[np.newaxis, ...] if omit_stats: stats = None else: # compute the stats stats = cupy.empty((3, ), dtype=float) nn = res[0] * res[0] for n in range(1, ndim): nn += res[n] * res[n] # TODO: do we want stats to be a GPU array or CPU array? stats[0] = cupy.sqrt(nn.max()) mean_norm = nn.sum() / nnz stats[1] = cupy.sqrt(mean_norm) nn *= nn stats[2] = cupy.sqrt(nn.sum() / nnz - mean_norm * mean_norm) if coord_axis != 0: res = cupy.moveaxis(res, 0, -1) comp[...] = res return comp, stats
def gen_data_npz(fimg, img, mask, config, ntiles=1000, save_dir='train'): """ Extract random patches from cupy arrays. Args: fimg (str): data filename img (cupy.array): cupy array with data mask (cupy.array): cupy array with mask save_dir (str): directory to save output Return: save dataset to save_dir. ---------- Example ---------- gen_data_npz('image.tif', arr, mask, config, 8000, 'output') """ # set dimensions of the input image array, and get desired tile size z_dim, x_dim, y_dim = img.shape tsz = config.TILE_SIZE # placeholders for final datasets img_cp = cp.empty((ntiles, tsz, tsz, z_dim), dtype=cp.float32) mask_np = np.empty((ntiles, tsz, tsz, config.N_CLASSES), dtype=np.float16) # generate n number of tiles for i in tqdm(range(ntiles)): # Generate random integers from image xc = random.randint(0, x_dim - tsz) yc = random.randint(0, y_dim - tsz) # verify data is not on nodata region while cp.any(img[:, xc:(xc + tsz), yc:(yc + tsz)] == config.NODATA_VAL): xc = random.randint(0, x_dim - tsz) yc = random.randint(0, y_dim - tsz) # change order to (h, w, c) tile_img = cp.moveaxis(img[:, xc:(xc + tsz), yc:(yc + tsz)], 0, -1) # TODO: replace with cuml One-hot encoder on future date when they fix # a bug on the output types. Using to_categorical in the meantime # Converts labels into one-hot encoding labels tile_mask = to_categorical(cp.asnumpy(mask[xc:(xc + tsz), yc:(yc + tsz)]), num_classes=config.N_CLASSES, dtype='float16') # maybe standardize here? depends on performance of single img vs batch img_cp[i, :, :, :] = tile_img mask_np[i, :, :, :] = tile_mask # normalize if config.NORMALIZE: img_cp = img_cp / config.normalization_factor # standardize if config.STANDARDIZE: img_cp = batch_normalize(img_cp, axis=(0, 1), c=1e-8) # save dataset into local disk, npz format with x and y labels cp.savez(f'{save_dir}/{fimg[:-4]}.npz', x=img_cp, y=cp.asarray(mask_np))
def _multi_svd_norm(x, row_axis, col_axis, op): y = cupy.moveaxis(x, (row_axis, col_axis), (-2, -1)) result = op(_decomposition.svd(y, compute_uv=False), axis=-1) return result
def gaussian_filter(input, sigma, order=0, output=None, mode="reflect", cval=0.0, truncate=4.0): """Multidimensional Gaussian filter. Based on SciPy implementation. Arguments: input: array_like The input array. sigma: scalar or sequence of scalars Standard deviation for Gaussian kernel. The standard deviations of the Gaussian filter are given for each axis as a sequence, or as a single number, in which case it is equal for all axes. order: int or sequence of ints, optional The order of the filter along each axis is given as a sequence of integers, or as a single number. An order of 0 corresponds to convolution with a Gaussian kernel. A positive order corresponds to convolution with that derivative of a Gaussian. output: cupy.ndarray 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``. truncate : float Truncate the filter at this many standard deviations. Default is 4.0. Returns: gaussian_filter: cupy.ndarray Returned array of same shape as `input`. Notes: The multidimensional filter is implemented as a sequence of one-dimensional convolution filters. The intermediate arrays are stored in the same data type as the output. Therefore, for output types with a limited precision, the results may be imprecise because intermediate results may be stored with insufficient precision. """ input = cupy.array(input) if output is None: output = cupy.zeros_like(input) # Code from Scipy function. orders = _ni_support._normalize_sequence(order, input.ndim) sigmas = _ni_support._normalize_sequence(sigma, input.ndim) modes = _ni_support._normalize_sequence(mode, input.ndim) axes = list(range(input.ndim)) axes = [(axes[ii], sigmas[ii], orders[ii], modes[ii]) for ii in range(len(axes)) if sigmas[ii] > 1e-15] if len(axes) > 0: # Create valiable for output. out = output for axis, sigma, order, mode in axes: # Flatten other axis. # This seems to improve performance at the cost of some memory. out_flat = cupy.moveaxis(out, axis, -1) out_shape = out_flat.shape out_flat = out_flat.reshape(-1, out_flat.shape[-1]) input_flat = cupy.moveaxis(input, axis, -1) input_flat = input_flat.reshape(-1, input_flat.shape[-1]) # Do 1D filtering. out = gaussian_filter1d(input_flat, sigma, -1, order, out_flat, mode, cval, truncate) out = cupy.moveaxis(out.reshape(out_shape), -1, axis) # Swap input and out. This was we only use two arrays. tmp = input input = out out = tmp # If there was an even number of iterations we need to copy values from input to output. if len(axes) % 2 == 0: output[:] = input else: output[...] = input[...] return output
def _ilk(reference_image, moving_image, flow0, radius, num_warp, gaussian, prefilter): """Iterative Lucas-Kanade (iLK) solver for optical flow estimation. Parameters ---------- reference_image : ndarray, shape (M, N[, P[, ...]]) The first gray scale image of the sequence. moving_image : ndarray, shape (M, N[, P[, ...]]) The second gray scale image of the sequence. flow0 : ndarray, shape (reference_image.ndim, M, N[, P[, ...]]) Initialization for the vector field. radius : int Radius of the window considered around each pixel. num_warp : int Number of times moving_image is warped. gaussian : bool if True, a gaussian kernel is used for the local integration. Otherwise, a uniform kernel is used. prefilter : bool Whether to prefilter the estimated optical flow before each image warp. This helps to remove potential outliers. Returns ------- flow : ndarray, shape ((reference_image.ndim, M, N[, P[, ...]]) The estimated optical flow components for each axis. """ dtype = reference_image.dtype ndim = reference_image.ndim size = 2 * radius + 1 if gaussian: sigma = ndim * (size / 4, ) filter_func = partial(ndi.gaussian_filter, sigma=sigma, mode="mirror") else: filter_func = partial(ndi.uniform_filter, size=ndim * (size, ), mode="mirror") flow = flow0 # For each pixel location (i, j), the optical flow X = flow[:, i, j] # is the solution of the ndim x ndim linear system # A[i, j] * X = b[i, j] A = cp.zeros(reference_image.shape + (ndim, ndim), dtype=dtype) b = cp.zeros(reference_image.shape + (ndim, ), dtype=dtype) grid = cp.meshgrid( *[cp.arange(n, dtype=dtype) for n in reference_image.shape], indexing="ij", sparse=True, ) for _ in range(num_warp): if prefilter: flow = ndi.filters.median_filter(flow, (1, ) + ndim * (3, )) moving_image_warp = warp(moving_image, get_warp_points(grid, flow), mode="nearest") grad = cp.stack(cp.gradient(moving_image_warp), axis=0) error_image = ((grad * flow).sum(axis=0) + reference_image - moving_image_warp) # Local linear systems creation for i, j in combinations_with_replacement(range(ndim), 2): A[..., i, j] = A[..., j, i] = filter_func(grad[i] * grad[j]) for i in range(ndim): b[..., i] = filter_func(grad[i] * error_image) # Don't consider badly conditioned linear systems idx = abs(cp.linalg.det(A)) < 1e-14 A[idx] = cp.eye(ndim, dtype=dtype) b[idx] = 0 # Solve the local linear systems flow = cp.moveaxis(cp.linalg.solve(A, b), ndim, 0) return flow
def sosfilt( sos, x, axis=-1, zi=None, ): """ Filter data along one dimension using cascaded second-order sections. Filter a data sequence, `x`, using a digital IIR filter defined by `sos`. Parameters ---------- sos : array_like Array of second-order filter coefficients, must have shape ``(n_sections, 6)``. Each row corresponds to a second-order section, with the first three columns providing the numerator coefficients and the last three providing the denominator coefficients. x : array_like An N-dimensional input array. axis : int, optional The axis of the input data array along which to apply the linear filter. The filter is applied to each subarray along this axis. Default is -1. zi : array_like, optional Initial conditions for the cascaded filter delays. It is a (at least 2D) vector of shape ``(n_sections, ..., 2, ...)``, where ``..., 2, ...`` denotes the shape of `x`, but with ``x.shape[axis]`` replaced by 2. If `zi` is None or is not given then initial rest (i.e. all zeros) is assumed. Note that these initial conditions are *not* the same as the initial conditions given by `lfiltic` or `lfilter_zi`. Returns ------- y : ndarray The output of the digital filter. zf : ndarray, optional If `zi` is None, this is not returned, otherwise, `zf` holds the final filter delay values. See Also -------- zpk2sos, sos2zpk, sosfilt_zi, sosfiltfilt, sosfreqz Notes ----- WARNING: This is an experimental API and is prone to change in future versions of cuSignal. The filter function is implemented as a series of second-order filters with direct-form II transposed structure. It is designed to minimize numerical precision errors for high-order filters. Limitations ----------- 1. The number of n_sections must be less than 513. 2. The number of samples must be greater than the number of sections Examples -------- sosfilt is a stable alternative to `lfilter` as using 2nd order sections reduces numerical error. We are working on building out sos filter output, so please submit GitHub feature requests as needed. You can also generate a filter on CPU with scipy.signal and then move that to GPU for actual filtering operations with `cp.asarray`. Plot a 13th-order filter's impulse response using both `sosfilt`: >>> from scipy import signal >>> import cusignal >>> import cupy as cp >>> # Generate filter on CPU with Scipy.Signal >>> sos = signal.ellip(13, 0.009, 80, 0.05, output='sos') >>> # Move data to GPU >>> sos = cp.asarray(sos) >>> x = cp.random.randn(100_000_000) >>> y = cusignal.sosfilt(sos, x) """ x = cp.asarray(x) if x.ndim == 0: raise ValueError("x must be at least 1D") sos, n_sections = _validate_sos(sos) sos = cp.asarray(sos) x_zi_shape = list(x.shape) x_zi_shape[axis] = 2 x_zi_shape = tuple([n_sections] + x_zi_shape) inputs = [sos, x] if zi is not None: inputs.append(np.asarray(zi)) dtype = cp.result_type(*inputs) if dtype.char not in "fdgFDGO": raise NotImplementedError("input type '%s' not supported" % dtype) if zi is not None: zi = cp.array(zi, dtype) # make a copy so that we can operate in place if zi.shape != x_zi_shape: raise ValueError("Invalid zi shape. With axis=%r, an input with " "shape %r, and an sos array with %d sections, zi " "must have shape %r, got %r." % (axis, x.shape, n_sections, x_zi_shape, zi.shape)) return_zi = True else: zi = cp.zeros(x_zi_shape, dtype=dtype) return_zi = False axis = axis % x.ndim # make positive x = cp.moveaxis(x, axis, -1) zi = cp.moveaxis(zi, [0, axis + 1], [-2, -1]) x_shape, zi_shape = x.shape, zi.shape x = cp.reshape(x, (-1, x.shape[-1])) x = cp.array(x, dtype, order="C") # make a copy, can modify in place zi = cp.ascontiguousarray(cp.reshape(zi, (-1, n_sections, 2))) sos = sos.astype(dtype, copy=False) max_smem = _get_max_smem() max_tpb = _get_max_tpb() # Determine how much shared memory is needed out_size = sos.shape[0] z_size = zi.shape[1] * zi.shape[2] sos_size = sos.shape[0] * sos.shape[1] shared_mem = (out_size + z_size + sos_size) * x.dtype.itemsize if shared_mem > max_smem: max_sections = (max_smem // (1 + zi.shape[2] + sos.shape[1]) // x.dtype.itemsize) raise ValueError("The number of sections ({}), requires too much " "shared memory ({}B) > ({}B). \n" "\n**Max sections possible ({})**".format( sos.shape[0], shared_mem, max_smem, max_sections)) if sos.shape[0] > max_tpb: raise ValueError("The number of sections ({}), must be less " "than max threads per block ({})".format( sos.shape[0], max_tpb)) if sos.shape[0] > x.shape[1]: raise ValueError("The number of samples ({}), must be greater " "than the number of sections ({})".format( x.shape[1], sos.shape[0])) _sosfilt(sos, x, zi) x.shape = x_shape x = cp.moveaxis(x, -1, axis) if return_zi: zi.shape = zi_shape zi = cp.moveaxis(zi, [-2, -1], [0, axis + 1]) out = (x, zi) else: out = x return out
def moments_coords_central(coords, center=None, order=3): """Calculate all central image moments up to a certain order. The following properties can be calculated from raw image moments: * Area as: ``M[0, 0]``. * Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}. Note that raw moments are neither translation, scale nor rotation invariant. Parameters ---------- coords : (N, D) floating point or uint8 array Array of N points that describe an image of D dimensionality in Cartesian space. A tuple of coordinates as returned by ``cp.nonzero`` is also accepted as input. center : tuple of float, optional Coordinates of the image centroid. This will be computed if it is not provided. order : int, optional Maximum order of moments. Default is 3. Returns ------- Mc : (``order + 1``, ``order + 1``, ...) array Central image moments. (D dimensions) References ---------- .. [1] Johannes Kilian. Simple Image Analysis By Moments. Durham University, version 0.2, Durham, 2001. Examples -------- >>> import cupy as cp >>> from cucim.skimage.measure import moments_coords_central >>> coords = cp.array([[row, col] ... for row in range(13, 17) ... for col in range(14, 18)]) >>> moments_coords_central(coords) array([[16., 0., 20., 0.], [ 0., 0., 0., 0.], [20., 0., 25., 0.], [ 0., 0., 0., 0.]]) As seen above, for symmetric objects, odd-order moments (columns 1 and 3, rows 1 and 3) are zero when centered on the centroid, or center of mass, of the object (the default). If we break the symmetry by adding a new point, this no longer holds: >>> coords2 = cp.concatenate((coords, [[17, 17]]), axis=0) >>> cp.round(moments_coords_central(coords2), ... decimals=2) # doctest: +NORMALIZE_WHITESPACE array([[17. , 0. , 22.12, -2.49], [ 0. , 3.53, 1.73, 7.4 ], [25.88, 6.02, 36.63, 8.83], [ 4.15, 19.17, 14.8 , 39.6 ]]) Image moments and central image moments are equivalent (by definition) when the center is (0, 0): >>> cp.allclose(moments_coords(coords), ... moments_coords_central(coords, (0, 0))) True """ if isinstance(coords, tuple): # This format corresponds to coordinate tuples as returned by # e.g. cp.nonzero: (row_coords, column_coords). # We represent them as an npoints x ndim array. coords = cp.stack(coords, axis=-1) float_dtype = coords.dtype if coords.dtype.kind == 'f' else cp.float64 check_nD(coords, 2) ndim = coords.shape[1] if center is None: center = cp.mean(coords, axis=0) else: center = cp.asarray(center, dtype=float_dtype) # center the coordinates coords = coords.astype(float_dtype, copy=False) coords -= center # CuPy backend: for efficiency, sum over the last axis # (which is memory contiguous) # generate all possible exponents for each axis in the given set of points # produces a matrix of shape (order + 1, D, N) coords = coords.T powers = cp.arange(order + 1, dtype=float_dtype)[:, np.newaxis, np.newaxis] coords = coords[cp.newaxis, ...]**powers # add extra dimensions for proper broadcasting coords = coords.reshape((1, ) * (ndim - 1) + coords.shape) calc = cp.moveaxis(coords[..., 0, :], -2, 0) for axis in range(1, ndim): # isolate each point's axis isolated_axis = coords[..., axis, :] # rotate orientation of matrix for proper broadcasting isolated_axis = cp.moveaxis(isolated_axis, -2, axis) # calculate the moments for each point, one axis at a time calc = calc * isolated_axis # sum all individual point moments to get our final answer Mc = cp.sum(calc, axis=-1) return Mc
def test_moveaxis_invalid5_2(self): a = testing.shaped_arange((2, 3, 4), cupy) with self.assertRaises(cupy.core._AxisError): return cupy.moveaxis(a, [0, 1], [-1, 2])
def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None): """Returns the cross product of two vectors. The cross product of ``a`` and ``b`` in :math:`R^3` is a vector perpendicular to both ``a`` and ``b``. If ``a`` and ``b`` are arrays of vectors, the vectors are defined by the last axis of ``a`` and ``b`` by default, and these axes can have dimensions 2 or 3. Where the dimension of either ``a`` or ``b`` is 2, the third component of the input vector is assumed to be zero and the cross product calculated accordingly. In cases where both input vectors have dimension 2, the z-component of the cross product is returned. Args: a (cupy.ndarray): Components of the first vector(s). b (cupy.ndarray): Components of the second vector(s). axisa (int, optional): Axis of ``a`` that defines the vector(s). By default, the last axis. axisb (int, optional): Axis of ``b`` that defines the vector(s). By default, the last axis. axisc (int, optional): Axis of ``c`` containing the cross product vector(s). Ignored if both input vectors have dimension 2, as the return is scalar. By default, the last axis. axis (int, optional): If defined, the axis of ``a``, ``b`` and ``c`` that defines the vector(s) and cross product(s). Overrides ``axisa``, ``axisb`` and ``axisc``. Returns: cupy.ndarray : Vector cross product(s). .. seealso:: :func:`numpy.cross` """ if axis is not None: axisa, axisb, axisc = (axis,) * 3 a = cupy.asarray(a) b = cupy.asarray(b) # Check axisa and axisb are within bounds axisa = internal._normalize_axis_index(axisa, a.ndim) axisb = internal._normalize_axis_index(axisb, b.ndim) # Move working axis to the end of the shape a = cupy.moveaxis(a, axisa, -1) b = cupy.moveaxis(b, axisb, -1) if a.shape[-1] not in (2, 3) or b.shape[-1] not in (2, 3): msg = ('incompatible dimensions for cross product\n' '(dimension must be 2 or 3)') raise ValueError(msg) # Create the output array shape = cupy.broadcast(a[..., 0], b[..., 0]).shape if a.shape[-1] == 3 or b.shape[-1] == 3: shape += (3,) # Check axisc is within bounds axisc = internal._normalize_axis_index(axisc, len(shape)) dtype = cupy.promote_types(a.dtype, b.dtype) cp = cupy.empty(shape, dtype) # create local aliases for readability a0 = a[..., 0] a1 = a[..., 1] if a.shape[-1] == 3: a2 = a[..., 2] b0 = b[..., 0] b1 = b[..., 1] if b.shape[-1] == 3: b2 = b[..., 2] if cp.ndim != 0 and cp.shape[-1] == 3: cp0 = cp[..., 0] cp1 = cp[..., 1] cp2 = cp[..., 2] if a.shape[-1] == 2: if b.shape[-1] == 2: # a0 * b1 - a1 * b0 cupy.multiply(a0, b1, out=cp) cp -= a1 * b0 return cp else: assert b.shape[-1] == 3 # cp0 = a1 * b2 - 0 (a2 = 0) # cp1 = 0 - a0 * b2 (a2 = 0) # cp2 = a0 * b1 - a1 * b0 cupy.multiply(a1, b2, out=cp0) cupy.multiply(a0, b2, out=cp1) cupy.negative(cp1, out=cp1) cupy.multiply(a0, b1, out=cp2) cp2 -= a1 * b0 else: assert a.shape[-1] == 3 if b.shape[-1] == 3: # cp0 = a1 * b2 - a2 * b1 # cp1 = a2 * b0 - a0 * b2 # cp2 = a0 * b1 - a1 * b0 cupy.multiply(a1, b2, out=cp0) tmp = a2 * b1 cp0 -= tmp cupy.multiply(a2, b0, out=cp1) cupy.multiply(a0, b2, out=tmp) cp1 -= tmp cupy.multiply(a0, b1, out=cp2) cupy.multiply(a1, b0, out=tmp) cp2 -= tmp else: assert b.shape[-1] == 2 # cp0 = 0 - a2 * b1 (b2 = 0) # cp1 = a2 * b0 - 0 (b2 = 0) # cp2 = a0 * b1 - a1 * b0 cupy.multiply(a2, b1, out=cp0) cupy.negative(cp0, out=cp0) cupy.multiply(a2, b0, out=cp1) cupy.multiply(a0, b1, out=cp2) cp2 -= a1 * b0 return cupy.moveaxis(cp, -1, axisc)
def pad(array, pad_width, mode='constant', **kwargs): """Pads an array with specified widths and values. Args: array(cupy.ndarray): The array to pad. pad_width(sequence, array_like or int): Number of values padded to the edges of each axis. ((before_1, after_1), ... (before_N, after_N)) unique pad widths for each axis. ((before, after),) yields same before and after pad for each axis. (pad,) or int is a shortcut for before = after = pad width for all axes. You cannot specify ``cupy.ndarray``. mode(str or function, optional): One of the following string values or a user supplied function 'constant' (default) Pads with a constant value. 'edge' Pads with the edge values of array. 'linear_ramp' Pads with the linear ramp between end_value and the array edge value. 'maximum' Pads with the maximum value of all or part of the vector along each axis. 'mean' Pads with the mean value of all or part of the vector along each axis. 'median' Pads with the median value of all or part of the vector along each axis. (Not Implemented) 'minimum' Pads with the minimum value of all or part of the vector along each axis. 'reflect' Pads with the reflection of the vector mirrored on the first and last values of the vector along each axis. 'symmetric' Pads with the reflection of the vector mirrored along the edge of the array. 'wrap' Pads with the wrap of the vector along the axis. The first values are used to pad the end and the end values are used to pad the beginning. 'empty' Pads with undefined values. <function> Padding function, see Notes. stat_length(sequence or int, optional): Used in 'maximum', 'mean', 'median', and 'minimum'. Number of values at edge of each axis used to calculate the statistic value. ((before_1, after_1), ... (before_N, after_N)) unique statistic lengths for each axis. ((before, after),) yields same before and after statistic lengths for each axis. (stat_length,) or int is a shortcut for before = after = statistic length for all axes. Default is ``None``, to use the entire axis. You cannot specify ``cupy.ndarray``. constant_values(sequence or scalar, optional): Used in 'constant'. The values to set the padded values for each axis. ((before_1, after_1), ... (before_N, after_N)) unique pad constants for each axis. ((before, after),) yields same before and after constants for each axis. (constant,) or constant is a shortcut for before = after = constant for all axes. Default is 0. You cannot specify ``cupy.ndarray``. end_values(sequence or scalar, optional): Used in 'linear_ramp'. The values used for the ending value of the linear_ramp and that will form the edge of the padded array. ((before_1, after_1), ... (before_N, after_N)) unique end values for each axis. ((before, after),) yields same before and after end values for each axis. (constant,) or constant is a shortcut for before = after = constant for all axes. Default is 0. You cannot specify ``cupy.ndarray``. reflect_type({'even', 'odd'}, optional): Used in 'reflect', and 'symmetric'. The 'even' style is the default with an unaltered reflection around the edge value. For the 'odd' style, the extended part of the array is created by subtracting the reflected values from two times the edge value. Returns: cupy.ndarray: Padded array with shape extended by ``pad_width``. .. note:: For an array with rank greater than 1, some of the padding of later axes is calculated from padding of previous axes. This is easiest to think about with a rank 2 array where the corners of the padded array are calculated by using padded values from the first axis. The padding function, if used, should modify a rank 1 array in-place. It has the following signature: ``padding_func(vector, iaxis_pad_width, iaxis, kwargs)`` where vector (cupy.ndarray) A rank 1 array already padded with zeros. Padded values are ``vector[:iaxis_pad_width[0]]`` and ``vector[-iaxis_pad_width[1]:]``. iaxis_pad_width (tuple) A 2-tuple of ints, ``iaxis_pad_width[0]`` represents the number of values padded at the beginning of vector where ``iaxis_pad_width[1]`` represents the number of values padded at the end of vector. iaxis (int) The axis currently being calculated. kwargs (dict) Any keyword arguments the function requires. Examples -------- >>> a = cupy.array([1, 2, 3, 4, 5]) >>> cupy.pad(a, (2, 3), 'constant', constant_values=(4, 6)) array([4, 4, 1, ..., 6, 6, 6]) >>> cupy.pad(a, (2, 3), 'edge') array([1, 1, 1, ..., 5, 5, 5]) >>> cupy.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4)) array([ 5, 3, 1, 2, 3, 4, 5, 2, -1, -4]) >>> cupy.pad(a, (2,), 'maximum') array([5, 5, 1, 2, 3, 4, 5, 5, 5]) >>> cupy.pad(a, (2,), 'mean') array([3, 3, 1, 2, 3, 4, 5, 3, 3]) >>> a = cupy.array([[1, 2], [3, 4]]) >>> cupy.pad(a, ((3, 2), (2, 3)), 'minimum') array([[1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [3, 3, 3, 4, 3, 3, 3], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1]]) >>> a = cupy.array([1, 2, 3, 4, 5]) >>> cupy.pad(a, (2, 3), 'reflect') array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2]) >>> cupy.pad(a, (2, 3), 'reflect', reflect_type='odd') array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]) >>> cupy.pad(a, (2, 3), 'symmetric') array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3]) >>> cupy.pad(a, (2, 3), 'symmetric', reflect_type='odd') array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7]) >>> cupy.pad(a, (2, 3), 'wrap') array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3]) >>> def pad_with(vector, pad_width, iaxis, kwargs): ... pad_value = kwargs.get('padder', 10) ... vector[:pad_width[0]] = pad_value ... vector[-pad_width[1]:] = pad_value >>> a = cupy.arange(6) >>> a = a.reshape((2, 3)) >>> cupy.pad(a, 2, pad_with) array([[10, 10, 10, 10, 10, 10, 10], [10, 10, 10, 10, 10, 10, 10], [10, 10, 0, 1, 2, 10, 10], [10, 10, 3, 4, 5, 10, 10], [10, 10, 10, 10, 10, 10, 10], [10, 10, 10, 10, 10, 10, 10]]) >>> cupy.pad(a, 2, pad_with, padder=100) array([[100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100], [100, 100, 0, 1, 2, 100, 100], [100, 100, 3, 4, 5, 100, 100], [100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100]]) """ pad_width = numpy.asarray(pad_width) if not pad_width.dtype.kind == 'i': raise TypeError('`pad_width` must be of integral type.') # Broadcast to shape (array.ndim, 2) pad_width = _as_pairs(pad_width, array.ndim, as_index=True) if callable(mode): # Old behavior: Use user-supplied function with numpy.apply_along_axis function = mode # Create a new zero padded array padded, _ = _pad_simple(array, pad_width, fill_value=0) # And apply along each axis for axis in range(padded.ndim): # Iterate using ndindex as in apply_along_axis, but assuming that # function operates inplace on the padded array. # view with the iteration axis at the end view = cupy.moveaxis(padded, axis, -1) # compute indices for the iteration axes, and append a trailing # ellipsis to prevent 0d arrays decaying to scalars (gh-8642) inds = numpy.ndindex(view.shape[:-1]) inds = (ind + (Ellipsis,) for ind in inds) for ind in inds: function(view[ind], pad_width[axis], axis, kwargs) return padded # Make sure that no unsupported keywords were passed for the current mode allowed_kwargs = { 'empty': [], 'edge': [], 'wrap': [], 'constant': ['constant_values'], 'linear_ramp': ['end_values'], 'maximum': ['stat_length'], 'mean': ['stat_length'], # 'median': ['stat_length'], 'minimum': ['stat_length'], 'reflect': ['reflect_type'], 'symmetric': ['reflect_type'], } try: unsupported_kwargs = set(kwargs) - set(allowed_kwargs[mode]) except KeyError: raise ValueError("mode '{}' is not supported".format(mode)) if unsupported_kwargs: raise ValueError( "unsupported keyword arguments for mode '{}': {}".format( mode, unsupported_kwargs ) ) stat_functions = { 'maximum': cupy.max, 'minimum': cupy.min, 'mean': cupy.mean, # 'median': cupy.median, } # Create array with final shape and original values # (padded area is undefined) padded, original_area_slice = _pad_simple(array, pad_width) # And prepare iteration over all dimensions # (zipping may be more readable than using enumerate) axes = range(padded.ndim) if mode == 'constant': values = kwargs.get('constant_values', 0) values = _as_pairs(values, padded.ndim) for axis, width_pair, value_pair in zip(axes, pad_width, values): roi = _view_roi(padded, original_area_slice, axis) _set_pad_area(roi, axis, width_pair, value_pair) elif mode == 'empty': pass # Do nothing as _pad_simple already returned the correct result elif array.size == 0: # Only modes 'constant' and 'empty' can extend empty axes, all other # modes depend on `array` not being empty # -> ensure every empty axis is only 'padded with 0' for axis, width_pair in zip(axes, pad_width): if array.shape[axis] == 0 and any(width_pair): raise ValueError( "can't extend empty axis {} using modes other than " "'constant' or 'empty'".format(axis) ) # passed, don't need to do anything more as _pad_simple already # returned the correct result elif mode == 'edge': for axis, width_pair in zip(axes, pad_width): roi = _view_roi(padded, original_area_slice, axis) edge_pair = _get_edges(roi, axis, width_pair) _set_pad_area(roi, axis, width_pair, edge_pair) elif mode == 'linear_ramp': end_values = kwargs.get('end_values', 0) end_values = _as_pairs(end_values, padded.ndim) for axis, width_pair, value_pair in zip(axes, pad_width, end_values): roi = _view_roi(padded, original_area_slice, axis) ramp_pair = _get_linear_ramps(roi, axis, width_pair, value_pair) _set_pad_area(roi, axis, width_pair, ramp_pair) elif mode in stat_functions: func = stat_functions[mode] length = kwargs.get('stat_length', None) length = _as_pairs(length, padded.ndim, as_index=True) for axis, width_pair, length_pair in zip(axes, pad_width, length): roi = _view_roi(padded, original_area_slice, axis) stat_pair = _get_stats(roi, axis, width_pair, length_pair, func) _set_pad_area(roi, axis, width_pair, stat_pair) elif mode in {'reflect', 'symmetric'}: method = kwargs.get('reflect_type', 'even') include_edge = True if mode == 'symmetric' else False for axis, (left_index, right_index) in zip(axes, pad_width): if array.shape[axis] == 1 and (left_index > 0 or right_index > 0): # Extending singleton dimension for 'reflect' is legacy # behavior; it really should raise an error. edge_pair = _get_edges(padded, axis, (left_index, right_index)) _set_pad_area( padded, axis, (left_index, right_index), edge_pair ) continue roi = _view_roi(padded, original_area_slice, axis) while left_index > 0 or right_index > 0: # Iteratively pad until dimension is filled with reflected # values. This is necessary if the pad area is larger than # the length of the original values in the current dimension. left_index, right_index = _set_reflect_both( roi, axis, (left_index, right_index), method, include_edge ) elif mode == 'wrap': for axis, (left_index, right_index) in zip(axes, pad_width): roi = _view_roi(padded, original_area_slice, axis) while left_index > 0 or right_index > 0: # Iteratively pad until dimension is filled with wrapped # values. This is necessary if the pad area is larger than # the length of the original values in the current dimension. left_index, right_index = _set_wrap_both( roi, axis, (left_index, right_index) ) return padded
def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): """Returns an array with evenly-spaced values within a given interval. Instead of specifying the step width like :func:`cupy.arange`, this function requires the total number of elements specified. Args: start (scalar or array_like): Starting value(s) of the sequence. stop (scalar or array_like): Ending value(s) of the sequence, unless ``endpoint`` is set to ``False``. In that case, the sequence consists of all but the last of ``num + 1`` evenly spaced samples, so that ``stop`` is excluded. Note that the step size changes when ``endpoint`` is ``False``. num: Number of elements. endpoint (bool): If ``True``, the stop value is included as the last element. Otherwise, the stop value is omitted. retstep (bool): If ``True``, this function returns (array, step). Otherwise, it returns only the array. dtype: Data type specifier. It is inferred from the start and stop arguments by default. axis (int): The axis in the result to store the samples. Relevant only if start or stop are array-like. By default ``0``, the samples will be along a new axis inserted at the beginning. Use ``-1`` to get an axis at the end. Returns: cupy.ndarray: The 1-D array of ranged values. .. seealso:: :func:`numpy.linspace` """ if num < 0: raise ValueError('linspace with num<0 is not supported') div = (num - 1) if endpoint else num scalar_start = cupy.isscalar(start) scalar_stop = cupy.isscalar(stop) if scalar_start and scalar_stop: return _linspace_scalar(start, stop, num, endpoint, retstep, dtype) if not scalar_start: if not (isinstance(start, cupy.ndarray) and start.dtype.kind == 'f'): start = cupy.asarray(start) * 1.0 if not scalar_stop: if not (isinstance(stop, cupy.ndarray) and stop.dtype.kind == 'f'): stop = cupy.asarray(stop) * 1.0 dt = cupy.result_type(start, stop, float(num)) if dtype is None: # In actual implementation, only float is used dtype = dt delta = stop - start # ret = cupy.arange(0, num, dtype=dt).reshape((-1,) + (1,) * delta.ndim) ret = cupy.empty((num, ), dtype=dt) _arange_ufunc(0.0, 1.0, ret, dtype=dt) ret = ret.reshape((-1, ) + (1, ) * delta.ndim) # In-place multiplication y *= delta/div is faster, but prevents the # multiplicant from overriding what class is produced, and thus prevents, # e.g. use of Quantities, see numpy#7142. Hence, we multiply in place only # for standard scalar types. if num > 1: step = delta / div if cupy.any(step == 0): # Special handling for denormal numbers, numpy#5437 ret /= div ret = ret * delta else: ret = ret * step else: # 0 and 1 item long sequences have an undefined step step = float('nan') # Multiply with delta to allow possible override of output class. ret = ret * delta ret += start if endpoint and num > 1: ret[-1] = stop if axis != 0: ret = cupy.moveaxis(ret, 0, axis) if cupy.issubdtype(dtype, cupy.integer): cupy.floor(ret, out=ret) ret = ret.astype(dtype, copy=False) if retstep: return ret, step else: return ret
def test_moveaxis_invalid5_3(self): a = testing.shaped_arange((2, 3, 4), cupy) with self.assertRaises(numpy.AxisError): return cupy.moveaxis(a, [0, 1], [1, 1])