def movepixels_3d(self, Iin, Tx): ''' This function will translate the pixels of an image according to Tx translation images. Inputs; Tx: The transformation image, dscribing the (backwards) translation of every pixel in the x direction. Outputs, Iout : The transformed image ''' nx, ny, nz = Iin.shape tmpx = cp.linspace(0, nx - 1, num=nx, dtype=np.float32) tmpy = cp.linspace(0, ny - 1, num=ny, dtype=np.float32) tmpz = cp.linspace(0, nz - 1, num=nz, dtype=np.float32) x, y, z = cp.meshgrid(tmpx, tmpy, tmpz, indexing='ij') Tlocalx = cp.expand_dims(x + Tx, axis=0) y = cp.expand_dims(y, axis=0) z = cp.expand_dims(z, axis=0) coordinates = cp.vstack((Tlocalx, y, z)) return map_coordinates(Iin, coordinates, order=1, cval=0)
def _transform_points(self, points): assert points.dtype == DTYPE # Empty list for the interpolated B-spline grid's components. displacement = [] # Reshape points for the cupy map_coordinates function to # receive coordinates in the expected shape points_gpu = points.reshape(self.ndim, -1) # Points is in the [0, 1)^ndim domain. Here it is scaled to the # B-spline grid's size. scaled_points = points_gpu * ( np.array(self.parameters.shape[1:], dtype=DTYPE) - 1)[:, None] # Every component (e.g. Tx, Ty, Tz in 3D) of the B-spline grid is # interpolated at the scaled point's positions. for bspline_component in self.parameters: displacement.append( cp.asnumpy( nd.map_coordinates(input=cp.array(bspline_component), coordinates=cp.array(scaled_points), order=self.bspline_order, mode=self.mode, cval=self.cval))) result = points + np.array(displacement).reshape(points.shape) assert result.dtype == DTYPE return result
def transform_affine(volume, ref_shape, affine, order=1): ndim = volume.ndim affine = cupy.asarray(affine) if True: out = ndi.affine_transform( volume, matrix=affine, order=order, mode="constant", output_shape=tuple(ref_shape), ) else: # use map_coordinates instead of affine_transform xcoords = cupy.meshgrid( *[cupy.arange(s, dtype=volume.dtype) for s in ref_shape], indexing="ij", sparse=True, ) coords = _apply_affine_to_field( xcoords, affine[:ndim, :], include_translations=True, coord_axis=0, ) out = ndi.map_coordinates(volume, coords, order=1) return out
def sample(self, points, mode=None, order=None, cval=None): """ Samples the image at given points. Args: points (np.array): An N x ndims array of points. order (int): The order of the B-spline. Default is 3. Use 0 for binary images. Use 1 for normal linear interpolation. mode (str): How edges of image domain should be treated when transformed of 'constant', 'nearest', 'mirror', 'reflect', 'wrap'. Default is 'constant'. See https://docs.scipy.org/doc/ scipy-0.14.0/reference/generated/ scipy.ndimage.interpolation.map_coordinates.html for more information about modes. cval (numeric): Constant value for mode='constant' Returns: np.array: N-shaped array of intensities at the points. """ new_mode = mode if mode else self.default_mode new_order = order if order else self.default_order new_cval = cval if cval else self.default_cval # Reshape points for the cupy map_coordinates function to # receive coordinates in the expected shape points_gpu = points.reshape(self.image.ndim, -1) sample_gpu = nd.map_coordinates(input=cp.array(self.image), coordinates=cp.array(points_gpu), mode=new_mode, order=new_order, cval=new_cval) # Convert back to CPU array and reshape to original shape sample_cpu = cp.asnumpy(sample_gpu) sample = sample_cpu.transpose().reshape(points.shape[1:]) return np.array(sample.astype(DTYPE))
def imtransform(proj, T): """ transform image @param proj: input image @type proj: numpy array @param T: transformation matrix (?) @type T: @return: transformed image @rtype: numpy array """ from scipy import mgrid import numpy cx = proj.shape[0] // 2 cy = proj.shape[1] // 2 grid = np.mgrid[-float(cx):proj.shape[0] - cx, -float(cy):proj.shape[1] - cy] temp = grid.reshape((2, grid.size // 2)) x, y = temp # temp2 = np.array([y, x, np.ones(x.shape)]) temp2 = np.array([x, y, np.ones(x.shape)]) temp3 = np.dot(temp2.transpose(), np.array(numpy.array(T))) # yy, xx, zz = temp3.transpose() xx, yy, zz = temp3.transpose() grid = np.reshape(np.array([xx, yy]), grid.shape) grid[0] += cx grid[1] += cy del temp, temp2, temp3, x, y #from scipy.ndimage import map_coordinates projt = map_coordinates(np.array(proj), grid.reshape(len(grid), -1), cval=np.mean(proj, dtype='double')).reshape( grid.shape[1:]) return np.array(projt)
def rotate3d(data, phi=0, psi=0, the=0, center=None, order=1, output=None): """Rotate a 3D data using ZXZ convention (phi: z1, the: x, psi: z2). @param data: data to be rotated. @param phi: 1st rotate around Z axis, in degree. @param psi: 3rd rotate around Z axis, in degree. @param the: 2nd rotate around X axis, in degree. @param center: rotation center. @return: the data after rotation. """ from cupyx.scipy.ndimage import map_coordinates import cupy as cp # Figure out the rotation center if center is None: cx = data.shape[0] / 2 cy = data.shape[1] / 2 cz = data.shape[2] / 2 else: assert len(center) == 3 (cx, cy, cz) = center # Transfer the angle to Euclidean phi = -float(phi) * cp.pi / 180.0 the = -float(the) * cp.pi / 180.0 psi = -float(psi) * cp.pi / 180.0 sin_alpha = cp.sin(phi) cos_alpha = cp.cos(phi) sin_beta = cp.sin(the) cos_beta = cp.cos(the) sin_gamma = cp.sin(psi) cos_gamma = cp.cos(psi) # Calculate inverse rotation matrix Inv_R = cp.zeros((3, 3), dtype=cp.float32) Inv_R[0][0] = cos_alpha * cos_gamma - cos_beta * sin_alpha * sin_gamma Inv_R[0][1] = -cos_alpha * sin_gamma - cos_beta * sin_alpha * cos_gamma Inv_R[0][2] = sin_beta * sin_alpha Inv_R[1][0] = sin_alpha * cos_gamma + cos_beta * cos_alpha * sin_gamma Inv_R[1][1] = -sin_alpha * sin_gamma + cos_beta * cos_alpha * cos_gamma Inv_R[1][2] = -sin_beta * cos_alpha Inv_R[2][0] = sin_beta * sin_gamma Inv_R[2][1] = sin_beta * cos_gamma Inv_R[2][2] = cos_beta grid = cp.mgrid[-cx:data.shape[0] - cx, -cy:data.shape[1] - cy, -cz:data.shape[2] - cz] temp = grid.reshape((3, grid.size // 3)) temp = cp.dot(Inv_R, temp) grid = cp.reshape(temp, grid.shape) grid[0] += cx grid[1] += cy grid[2] += cz dataout = cp.zeros_like(data) # Interpolation print(data.shape, grid.shape, dataout.shape) dataout = map_coordinates(data, grid.reshape(len(grid), -1), order=order).reshape(grid.shape[1:]) return dataout
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 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 warp(image, inverse_map, map_args={}, output_shape=None, order=None, mode='constant', cval=0., clip=True, preserve_range=False): """Warp an image according to a given coordinate transformation. Parameters ---------- image : ndarray Input image. inverse_map : transformation object, callable ``cr = f(cr, **kwargs)``, or ndarray Inverse coordinate map, which transforms coordinates in the output images into their corresponding coordinates in the input image. There are a number of different options to define this map, depending on the dimensionality of the input image. A 2-D image can have 2 dimensions for gray-scale images, or 3 dimensions with color information. - For 2-D images, you can directly pass a transformation object, e.g. `skimage.transform.SimilarityTransform`, or its inverse. - For 2-D images, you can pass a ``(3, 3)`` homogeneous transformation matrix, e.g. `skimage.transform.SimilarityTransform.params`. - For 2-D images, a function that transforms a ``(M, 2)`` array of ``(col, row)`` coordinates in the output image to their corresponding coordinates in the input image. Extra parameters to the function can be specified through `map_args`. - For N-D images, you can directly pass an array of coordinates. The first dimension specifies the coordinates in the input image, while the subsequent dimensions determine the position in the output image. E.g. in case of 2-D images, you need to pass an array of shape ``(2, rows, cols)``, where `rows` and `cols` determine the shape of the output image, and the first dimension contains the ``(row, col)`` coordinate in the input image. See `scipy.ndimage.map_coordinates` for further documentation. Note, that a ``(3, 3)`` matrix is interpreted as a homogeneous transformation matrix, so you cannot interpolate values from a 3-D input, if the output is of shape ``(3,)``. See example section for usage. map_args : dict, optional Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input image is preserved. Note that, even for multi-band images, only rows and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: - 0: Nearest-neighbor - 1: Bi-linear (default) - 2: Bi-quadratic - 3: Bi-cubic - 4: Bi-quartic - 5: Bi-quintic Default is 0 if image.dtype is bool and 1 otherwise. mode : {'constant', 'edge', 'symmetric', 'reflect', 'wrap'}, optional Points outside the boundaries of the input are filled according to the given mode. Modes match the behaviour of `numpy.pad`. cval : float, optional Used in conjunction with mode 'constant', the value outside the image boundaries. clip : bool, optional Whether to clip the output to the range of values of the input image. This is enabled by default, since higher order interpolation may produce values outside the given input range. preserve_range : bool, optional Whether to keep the original range of values. Otherwise, the input image is converted according to the conventions of `img_as_float`. Also see https://scikit-image.org/docs/dev/user_guide/data_types.html Returns ------- warped : double ndarray The warped input image. Notes ----- - The input image is converted to a `double` image. - In case of a `SimilarityTransform`, `AffineTransform` and `ProjectiveTransform` and `order` in [0, 3] this function uses the underlying transformation matrix to warp the image with a much faster routine. Examples -------- >>> from cucim.skimage.transform import warp >>> from skimage import data >>> image = cp.array(data.camera()) The following image warps are all equal but differ substantially in execution time. The image is shifted to the bottom. Use a geometric transform to warp an image (fast): >>> from cucim.skimage.transform import SimilarityTransform >>> tform = SimilarityTransform(translation=(0, -10)) >>> warped = warp(image, tform) Use a callable (slow): >>> def shift_down(xy): ... xy[:, 1] -= 10 ... return xy >>> warped = warp(image, shift_down) Use a transformation matrix to warp an image (fast): >>> import cupy as cp >>> matrix = cp.asarray([[1, 0, 0], [0, 1, -10], [0, 0, 1]]) >>> warped = warp(image, matrix) >>> from cucim.skimage.transform import ProjectiveTransform, warp >>> warped = warp(image, ProjectiveTransform(matrix=matrix)) You can also use the inverse of a geometric transformation (fast): >>> warped = warp(image, tform.inverse) For N-D images you can pass a coordinate array, that specifies the coordinates in the input image for every element in the output image. E.g. if you want to rescale a 3-D cube, you can do: >>> cube_shape = (30, 30, 30) >>> cube = cp.random.rand(*cube_shape) Setup the coordinate array, that defines the scaling: >>> scale = 0.1 >>> output_shape = tuple(int(scale * s) for s in cube_shape) >>> coords0, coords1, coords2 = cp.mgrid[:output_shape[0], ... :output_shape[1], :output_shape[2]] >>> coords = cp.asarray([coords0, coords1, coords2]) Assume that the cube contains spatial data, where the first array element center is at coordinate (0.5, 0.5, 0.5) in real space, i.e. we have to account for this extra offset when scaling the image: >>> coords = (coords + 0.5) / scale - 0.5 >>> warped = warp(cube, coords) """ # noqa if image.size == 0: raise ValueError("Cannot warp empty image with dimensions", image.shape) order = _validate_interpolation_order(image.dtype, order) if image.dtype.kind == "c": if not preserve_range: raise NotImplementedError("TODO") else: image = convert_to_float(image, preserve_range) input_shape = np.array(image.shape) if output_shape is None: output_shape = input_shape else: output_shape = safe_as_int(output_shape) if isinstance(inverse_map, cp.ndarray) and inverse_map.shape == ( 3, 3, ): # inverse_map is a transformation matrix as numpy array, # this is only used for order >= 4. inverse_map = ProjectiveTransform(matrix=inverse_map) if isinstance(inverse_map, cp.ndarray): # inverse_map is directly given as coordinates coords = inverse_map else: # inverse_map is given as function, that transforms (N, 2) # destination coordinates to their corresponding source # coordinates. This is only supported for 2(+1)-D images. if image.ndim < 2 or image.ndim > 3: raise ValueError("Only 2-D images (grayscale or color) are " "supported, when providing a callable " "`inverse_map`.") def coord_map(*args): return inverse_map(*args, **map_args) if len(input_shape) == 3 and len(output_shape) == 2: # Input image is 2D and has color channel, but output_shape is # given for 2-D images. Automatically add the color channel # dimensionality. output_shape = (output_shape[0], output_shape[1], input_shape[2]) coords = warp_coords(coord_map, output_shape) # Pre-filtering not necessary for order 0, 1 interpolation prefilter = order > 1 ndi_mode = _to_ndimage_mode(mode) warped = ndi.map_coordinates(image, coords, prefilter=prefilter, mode=ndi_mode, order=order, cval=cval) _clip_warp_output(image, warped, order, mode, cval, clip) return warped
def test_warp_coords_example(): image = cp.array(astronaut().astype(np.float32)) assert 3 == image.shape[2] tform = SimilarityTransform(translation=(0, -10), xp=cp) coords = warp_coords(tform, (30, 30, 3)) map_coordinates(image[:, :, 0], coords[:2])
def profile_line(image, src, dst, linewidth=1, order=None, mode=None, cval=0.0, *, reduce_func=cp.mean): """Return the intensity profile of an image measured along a scan line. Parameters ---------- image : ndarray, shape (M, N[, C]) The image, either grayscale (2D array) or multichannel (3D array, where the final axis contains the channel information). src : array_like, shape (2, ) The coordinates of the start point of the scan line. dst : array_like, shape (2, ) The coordinates of the end point of the scan line. The destination point is *included* in the profile, in contrast to standard numpy indexing. linewidth : int, optional Width of the scan, perpendicular to the line order : int in {0, 1, 2, 3, 4, 5}, optional The order of the spline interpolation, default is 0 if image.dtype is bool and 1 otherwise. The order has to be in the range 0-5. See `skimage.transform.warp` for detail. mode : {'constant', 'nearest', 'reflect', 'mirror', 'wrap'}, optional How to compute any values falling outside of the image. cval : float, optional If `mode` is 'constant', what constant value to use outside the image. reduce_func : callable, optional Function used to calculate the aggregation of pixel values perpendicular to the profile_line direction when `linewidth` > 1. If set to None the unreduced array will be returned. Returns ------- return_value : array The intensity profile along the scan line. The length of the profile is the ceil of the computed length of the scan line. Examples -------- >>> import cupy as cp >>> x = cp.asarray([[1, 1, 1, 2, 2, 2]]) >>> img = cp.vstack([cp.zeros_like(x), x, x, x, cp.zeros_like(x)]) >>> img array([[0, 0, 0, 0, 0, 0], [1, 1, 1, 2, 2, 2], [1, 1, 1, 2, 2, 2], [1, 1, 1, 2, 2, 2], [0, 0, 0, 0, 0, 0]]) >>> profile_line(img, (2, 1), (2, 4)) array([1., 1., 2., 2.]) >>> profile_line(img, (1, 0), (1, 6), cval=4) array([1., 1., 1., 2., 2., 2., 4.]) The destination point is included in the profile, in contrast to standard numpy indexing. For example: >>> profile_line(img, (1, 0), (1, 6)) # The final point is out of bounds array([1., 1., 1., 2., 2., 2., 0.]) >>> profile_line(img, (1, 0), (1, 5)) # This accesses the full first row array([1., 1., 1., 2., 2., 2.]) For different reduce_func inputs: >>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=cp.mean) array([0.66666667, 0.66666667, 0.66666667, 1.33333333]) >>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=cp.max) array([1, 1, 1, 2]) >>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=cp.sum) array([2, 2, 2, 4]) The unreduced array will be returned when `reduce_func` is None or when `reduce_func` acts on each pixel value individually. >>> profile_line(img, (1, 2), (4, 2), linewidth=3, order=0, ... reduce_func=None) array([[1, 1, 2], [1, 1, 2], [1, 1, 2], [0, 0, 0]]) >>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=cp.sqrt) array([[1. , 1. , 0. ], [1. , 1. , 0. ], [1. , 1. , 0. ], [1.41421356, 1.41421356, 0. ]]) """ order = _validate_interpolation_order(image.dtype, order) if mode is None: warn( "Default out of bounds interpolation mode 'constant' is " "deprecated. In version 0.19 it will be set to 'reflect'. " "To avoid this warning, set `mode=` explicitly.", FutureWarning, stacklevel=2) mode = 'constant' perp_lines = _line_profile_coordinates(src, dst, linewidth=linewidth) if image.ndim == 3: pixels = [ ndi.map_coordinates(image[..., i], perp_lines, prefilter=order > 1, order=order, mode=mode, cval=cval) for i in range(image.shape[2]) ] pixels = cp.transpose(cp.stack(pixels, axis=0), (1, 2, 0)) else: pixels = ndi.map_coordinates(image, perp_lines, prefilter=order > 1, order=order, mode=mode, cval=cval) # The outputted array with reduce_func=None gives an array where the # row values (axis=1) are flipped. Here, we make this consistent. pixels = np.flip(pixels, axis=1) if reduce_func is None: intensities = pixels else: try: intensities = reduce_func(pixels, axis=1) except TypeError: # function doesn't allow axis kwarg intensities = cp.apply_along_axis(reduce_func, arr=pixels, axis=1) return intensities