Exemplo n.º 1
0
    def randint(self, low, high=None, size=None, dtype='l'):
        """Returns a scalar or an array of integer values over ``[low, high)``.

        .. seealso::
            :func:`cupy.random.randint` for full documentation,
            :meth:`numpy.random.RandomState.randint`
        """
        if high is None:
            lo = 0
            hi = low
        else:
            lo = low
            hi = high

        if lo >= hi:
            raise ValueError('low >= high')
        if lo < cupy.iinfo(dtype).min:
            raise ValueError('low is out of bounds for {}'.format(
                cupy.dtype(dtype).name))
        if hi > cupy.iinfo(dtype).max + 1:
            raise ValueError('high is out of bounds for {}'.format(
                cupy.dtype(dtype).name))

        diff = hi - lo - 1
        if diff > cupy.iinfo(cupy.int32).max - cupy.iinfo(cupy.int32).min + 1:
            raise NotImplementedError(
                'Sampling from a range whose extent is larger than int32 '
                'range is currently not supported')
        x = self._interval(diff, size).astype(dtype, copy=False)
        cupy.add(x, lo, out=x)
        return x
Exemplo n.º 2
0
    def randint(self, low, high=None, size=None, dtype='l'):
        """Returns a scalar or an array of integer values over ``[low, high)``.

        .. seealso::
            :func:`cupy.random.randint` for full documentation,
            :meth:`numpy.random.RandomState.randint
            <numpy.random.mtrand.RandomState.randint>`
        """
        if high is None:
            lo = 0
            hi1 = int(low) - 1
        else:
            lo = int(low)
            hi1 = int(high) - 1

        if lo > hi1:
            raise ValueError('low >= high')
        if lo < cupy.iinfo(dtype).min:
            raise ValueError('low is out of bounds for {}'.format(
                cupy.dtype(dtype).name))
        if hi1 > cupy.iinfo(dtype).max:
            raise ValueError('high is out of bounds for {}'.format(
                cupy.dtype(dtype).name))

        diff = hi1 - lo
        x = self._interval(diff, size).astype(dtype, copy=False)
        cupy.add(x, lo, out=x)
        return x
Exemplo n.º 3
0
    def from_4Dcamera_file(filename):
        with h5py.File(filename, 'r') as f0:
            frames = f0['electron_events/frames'][:]
            scan_dimensions = (
                f0['electron_events/scan_positions'].attrs['Ny'],
                f0['electron_events/scan_positions'].attrs['Nx'])
            frame_dimensions = np.array((576, 576))

        def unragged_frames_size(frames):
            mm = 0
            for ev in frames:
                if ev.shape[0] > mm:
                    mm = ev.shape[0]
            return mm

        def make_unragged_frames(frames, scan_dimensions):
            unragged_frame_size = unragged_frames_size(frames.ravel())
            fr_full = cp.zeros((frames.ravel().shape[0], unragged_frame_size),
                               dtype=cp.int32)
            fr_full[:] = cp.iinfo(fr_full.dtype).max
            for ii, ev in enumerate(frames.ravel()):
                fr_full[ii, :ev.shape[0]] = cp.array(ev)
            fr_full_4d = fr_full.reshape((*scan_dimensions, fr_full.shape[1]))
            fr_full_4d = fr_full_4d[:, :-1, :]
            return fr_full_4d

        d = Sparse4DData()
        d.indices = cp.ascontiguousarray(
            make_unragged_frames(frames.ravel(), scan_dimensions))
        d.scan_dimensions = np.array(d.indices.shape[:2])
        d.frame_dimensions = frame_dimensions
        d.counts = cp.zeros(d.indices.shape, dtype=cp.bool)
        d.counts[d.indices != cp.iinfo(d.indices.dtype).max] = 1

        return d
Exemplo n.º 4
0
    def fftshift_and_pad_to(sparse_data, pad_to_frame_dimensions):
        indices = sparse_data.indices
        scan_dimensions = sparse_data.scan_dimensions
        frame_dimensions = sparse_data.frame_dimensions
        center_frame = frame_dimensions / 2

        threadsperblock = (16, 16)
        blockspergrid = tuple(
            np.ceil(np.array(indices.shape[:2]) / threadsperblock).astype(
                np.int))

        no_count_indicator_old = np.iinfo(indices.dtype).max

        inds = np.prod(pad_to_frame_dimensions)
        if inds > 2**15:
            dtype = cp.int64
        elif inds > 2**15:
            dtype = cp.int32
        elif inds > 2**8:
            dtype = cp.int16
        else:
            dtype = cp.uint8

        no_count_indicator_new = cp.iinfo(dtype).max

        inds = cp.array(indices, dtype=dtype)
        fftshift_pad_kernel[blockspergrid,
                            threadsperblock](inds, center_frame,
                                             scan_dimensions,
                                             cp.array(pad_to_frame_dimensions),
                                             no_count_indicator_old,
                                             no_count_indicator_new)
        sparse_data.indices = inds.get()
        sparse_data.frame_dimensions = np.array(pad_to_frame_dimensions)
        return sparse_data
Exemplo n.º 5
0
    def tomaxint(self, size=None):
        """Draws integers between 0 and max integer inclusive.

        Return a sample of uniformly distributed random integers in the
        interval [0, ``np.iinfo(np.int_).max``]. The `np.int_` type translates
        to the C long integer type and its precision is platform dependent.

        Args:
            size (int or tuple of ints): Output shape.

        Returns:
            cupy.ndarray: Drawn samples.

        .. seealso::
            :meth:`numpy.random.RandomState.tomaxint`

        """
        if size is None:
            size = ()
        sample = cupy.empty(size, dtype=cupy.int_)
        # cupy.random only uses int32 random generator
        size_in_int = sample.dtype.itemsize // 4
        curand.generate(self._generator, sample.data.ptr,
                        sample.size * size_in_int)

        # Disable sign bit
        sample &= cupy.iinfo(cupy.int_).max
        return sample
Exemplo n.º 6
0
    def tomaxint(self, size=None):
        """Draws integers between 0 and max integer inclusive.

        Args:
            size (int or tuple of ints): Output shape.

        Returns:
            cupy.ndarray: Drawn samples.

        .. seealso::
            :meth:`numpy.random.RandomState.tomaxint
            <numpy.random.mtrand.RandomState.tomaxint>`

        """
        if size is None:
            size = ()
        sample = cupy.empty(size, dtype=cupy.int_)
        # cupy.random only uses int32 random generator
        size_in_int = sample.dtype.itemsize // 4
        curand.generate(self._generator, sample.data.ptr,
                        sample.size * size_in_int)

        # Disable sign bit
        sample &= cupy.iinfo(cupy.int_).max
        return sample
Exemplo n.º 7
0
def randint(low, high=None, size=None, dtype='l'):
    """Returns a scalar or an array of integer values over ``[low, high)``.

    Each element of returned values are independently sampled from
    uniform distribution over left-close and right-open interval
    ``[low, high)``.

    Args:
        low (int): If ``high`` is not ``None``,
            it is the lower bound of the interval.
            Otherwise, it is the **upper** bound of the interval
            and lower bound of the interval is set to ``0``.
        high (int): Upper bound of the interval.
        size (None or int or tuple of ints): The shape of returned value.
        dtype: Data type specifier.

    Returns:
        int or cupy.ndarray of ints: If size is ``None``,
        it is single integer sampled.
        If size is integer, it is the 1D-array of length ``size`` element.
        Otherwise, it is the array whose shape specified by ``size``.
    """
    if high is None:
        lo = 0
        hi = low
    else:
        lo = low
        hi = high

    if lo >= hi:
        raise ValueError('low >= high')
    if lo < cupy.iinfo(dtype).min:
        raise ValueError('low is out of bounds for {}'.format(
            cupy.dtype(dtype).name))
    if hi > cupy.iinfo(dtype).max + 1:
        raise ValueError('high is out of bounds for {}'.format(
            cupy.dtype(dtype).name))

    diff = hi - lo - 1
    if diff > cupy.iinfo(cupy.int32).max - cupy.iinfo(cupy.int32).min + 1:
        raise NotImplementedError(
            'Sampling from a range whose extent is larger than int32 range is '
            'currently not supported')
    rs = generator.get_random_state()
    x = rs.interval(diff, size).astype(dtype, copy=False)
    cupy.add(x, lo, out=x)
    return x
Exemplo n.º 8
0
def test_relabel_sequential_signed_overflow():
    imax = cp.iinfo(cp.int32).max
    labels = cp.asarray([0, 1, 99, 42, 42], dtype=cp.int32)
    output, fw, inv = relabel_sequential(labels, offset=imax)
    reference = np.array([0, imax, imax + 2, imax + 1, imax + 1],
                         dtype=cp.uint32)
    assert_array_equal(output, reference)
    assert output.dtype == reference.dtype
Exemplo n.º 9
0
def get_index_dtype(arrays=(), maxval=None, check_contents=False):
    """Based on input (integer) arrays ``a``, determines a suitable index data
    type that can hold the data in the arrays.

    Args:
        arrays (tuple of array_like):
            Input arrays whose types/contents to check
        maxval (float, optional):
            Maximum value needed
        check_contents (bool, optional):
            Whether to check the values in the arrays and not just their types.
            Default: False (check only the types)

    Returns:
        dtype: Suitable index data type (int32 or int64)
    """

    int32min = cupy.iinfo(cupy.int32).min
    int32max = cupy.iinfo(cupy.int32).max

    dtype = cupy.int32
    if maxval is not None:
        if maxval > int32max:
            dtype = cupy.int64

    if isinstance(arrays, cupy.ndarray):
        arrays = (arrays,)

    for arr in arrays:
        arr = cupy.asarray(arr)
        if not cupy.can_cast(arr.dtype, cupy.int32):
            if check_contents:
                if arr.size == 0:
                    # a bigger type not needed
                    continue
                elif cupy.issubdtype(arr.dtype, cupy.integer):
                    maxval = arr.max()
                    minval = arr.min()
                    if minval >= int32min and maxval <= int32max:
                        # a bigger type not needed
                        continue

            dtype = cupy.int64
            break

    return dtype
Exemplo n.º 10
0
def iinfo(type: Union[Dtype, Array], /) -> iinfo_object:
    """
    Array API compatible wrapper for :py:func:`np.iinfo <numpy.iinfo>`.

    See its docstring for more information.
    """
    ii = np.iinfo(type)  # type: ignore
    return iinfo_object(ii.bits, ii.max, ii.min)
Exemplo n.º 11
0
 def make_unragged_frames(frames, scan_dimensions):
     unragged_frame_size = unragged_frames_size(frames.ravel())
     fr_full = cp.zeros((frames.ravel().shape[0], unragged_frame_size), dtype=cp.int32)
     fr_full[:] = cp.iinfo(fr_full.dtype).max
     for ii, ev in enumerate(frames.ravel()):
         fr_full[ii, :ev.shape[0]] = cp.array(ev)
     fr_full_4d = fr_full.reshape((*scan_dimensions, fr_full.shape[1]))
     fr_full_4d = fr_full_4d[:, :-1, :]
     return fr_full_4d
Exemplo n.º 12
0
def kron(A, B, format=None):
    """Kronecker product of sparse matrices A and B.

    Args:
        A (cupyx.scipy.sparse.spmatrix): a sparse matrix.
        B (cupyx.scipy.sparse.spmatrix): a sparse matrix.
        format (str): the format of the returned sparse matrix.

    Returns:
        cupyx.scipy.sparse.spmatrix:
            Generated sparse matrix with the specified ``format``.

    .. seealso:: :func:`scipy.sparse.kron`

    """
    # TODO(leofang): support BSR format when it's added to CuPy
    # TODO(leofang): investigate if possible to optimize performance by
    #                starting with CSR instead of COO matrices

    A = _coo.coo_matrix(A)
    B = _coo.coo_matrix(B)
    out_shape = (A.shape[0] * B.shape[0], A.shape[1] * B.shape[1])

    if A.nnz == 0 or B.nnz == 0:
        # kronecker product is the zero matrix
        return _coo.coo_matrix(out_shape).asformat(format)

    if max(out_shape[0], out_shape[1]) > cupy.iinfo('int32').max:
        dtype = cupy.int64
    else:
        dtype = cupy.int32

    # expand entries of A into blocks
    row = A.row.astype(dtype, copy=True) * B.shape[0]
    row = row.repeat(B.nnz)
    col = A.col.astype(dtype, copy=True) * B.shape[1]
    col = col.repeat(B.nnz)
    data = A.data.repeat(B.nnz)  # data's dtype follows that of A in SciPy

    # increment block indices
    row, col = row.reshape(-1, B.nnz), col.reshape(-1, B.nnz)
    row += B.row
    col += B.col
    row, col = row.ravel(), col.ravel()

    # compute block entries
    data = data.reshape(-1, B.nnz) * B.data
    data = data.ravel()

    return _coo.coo_matrix(
        (data, (row, col)), shape=out_shape).asformat(format)
Exemplo n.º 13
0
    def from_dense(dense, make_float=False):
        res = Sparse4DData()
        res.frame_dimensions = np.array(dense.shape[-2:])
        res.scan_dimensions = np.array(dense.shape[:2])

        inds = np.prod(res.frame_dimensions)
        if inds > 2**31:
            dtype = cp.int64
        elif inds > 2**15:
            dtype = cp.int32
        elif inds > 2**8:
            dtype = cp.int16
        else:
            dtype = cp.uint8

        nonzeros = cp.sum((dense > 0), (2, 3))
        nonzeros = cp.max(nonzeros)

        bits_counts = np.log2(dense.max())
        if make_float:
            dtype_counts = cp.float32
        else:
            if bits_counts > np.log2(2**31 - 1):
                dtype_counts = cp.int64
            elif bits_counts > np.log2(2**15 - 1):
                dtype_counts = cp.int32
            elif bits_counts > 8:
                dtype_counts = cp.int16
            else:
                dtype_counts = cp.uint8

        threadsperblock = (16, 16)
        blockspergrid = tuple(
            np.ceil(res.scan_dimensions / threadsperblock).astype(np.int))
        dense = cp.array(dense)
        indices = cp.zeros((*dense.shape[:2], nonzeros), dtype=dtype)
        indices[:] = cp.iinfo(dtype).max
        counts = cp.zeros((*dense.shape[:2], nonzeros), dtype=dtype_counts)
        dense_to_sparse_kernel[blockspergrid,
                               threadsperblock](dense, indices, counts,
                                                cp.array(res.frame_dimensions))

        res.indices = indices.get()
        res.counts = counts.get()

        print(f'frame_dimensions: {res.frame_dimensions}')
        print(f'scan_dimensions : {res.scan_dimensions}')
        print(f'Using dtype: {dtype} for indices')
        print(f'Using dtype: {dtype_counts} for counts')
        return res
Exemplo n.º 14
0
 def crop_symmetric_center_(self, center, max_radius = None):
     if max_radius is None:
         y_min_radius = np.min([center[0], self.frame_dimensions[0] - center[0]])
         x_min_radius = np.min([center[1], self.frame_dimensions[1] - center[1]])
         max_radius = np.min([y_min_radius, x_min_radius])
     new_frames, new_frame_dimensions = crop_symmetric_around_center(self.indices,
                                                                     self.frame_dimensions,
                                                                     center, max_radius)
     print(f'old frames frame_dimensions: {self.frame_dimensions}')
     print(f'new frames frame_dimensions: {new_frame_dimensions}')
     self.indices = new_frames
     self.counts = cp.zeros(self.indices.shape, dtype=cp.bool)
     self.counts[self.indices != cp.iinfo(self.indices.dtype).max] = 1
     self.frame_dimensions = new_frame_dimensions
Exemplo n.º 15
0
def pcm2float(sig, output_dtype=cp.float64):

    # Make sure it's a NumPy array.
    sig = cp.asnumpy(cp.asarray(sig))

    # Check if it is an array of signed integers.
    assert sig.dtype.kind == 'i', "'sig' must be an array of signed integers!"
    # Set the array output format. Accepts string as input argument for the
    # desired output format (e.g. 'f').
    out_dtype = cp.dtype(output_dtype)

    # Note that 'min' has a greater (by 1) absolute value than 'max'!
    # Therefore, we use 'min' here to avoid clipping.
    return sig.astype(out_dtype) / out_dtype.type(-cp.iinfo(sig.dtype).min)
Exemplo n.º 16
0
 def crop_symmetric_center(self, center, max_radius = None):
     if max_radius is None:
         y_min_radius = np.min([center[0], self.frame_dimensions[0] - center[0]])
         x_min_radius = np.min([center[1], self.frame_dimensions[1] - center[1]])
         max_radius = np.min([y_min_radius, x_min_radius])
     new_frames, new_frame_dimensions = crop_symmetric_around_center(cp.array(self.indices),
                                                                     cp.array(self.frame_dimensions),
                                                                     center, max_radius)
     print(f'old frames frame_dimensions: {self.frame_dimensions}')
     print(f'new frames frame_dimensions: {new_frame_dimensions}')
     res = Sparse4DData()
     res.indices = new_frames
     res.counts = cp.zeros(self.indices.shape, dtype=cp.bool)
     res.counts[self.indices != cp.iinfo(self.indices.dtype).max] = 1
     res.frame_dimensions = new_frame_dimensions
     res.scan_dimensions = self.scan_dimensions.copy()
     return res
Exemplo n.º 17
0
def _find_boundaries_subpixel(label_img):
    """See ``find_boundaries(..., mode='subpixel')``.

    Notes
    -----
    This function puts in an empty row and column between each *actual*
    row and column of the image, for a corresponding shape of ``2s - 1``
    for every image dimension of size ``s``. These "interstitial" rows
    and columns are filled as ``True`` if they separate two labels in
    `label_img`, ``False`` otherwise.

    I used ``view_as_windows`` to get the neighborhood of each pixel.
    Then I check whether there are two labels or more in that
    neighborhood.
    """
    ndim = label_img.ndim
    max_label = cp.iinfo(label_img.dtype).max

    label_img_expanded = cp.zeros(
        [(2 * s - 1) for s in label_img.shape], label_img.dtype
    )
    pixels = (slice(None, None, 2),) * ndim
    label_img_expanded[pixels] = label_img

    edges = cp.ones(label_img_expanded.shape, dtype=bool)
    edges[pixels] = False
    label_img_expanded[edges] = max_label
    windows = view_as_windows(
        cp.pad(label_img_expanded, 1, mode="constant", constant_values=0),
        (3,) * ndim,
    )

    boundaries = cp.zeros_like(edges)
    for index in np.ndindex(label_img_expanded.shape):
        if edges[index]:
            values = cp.unique(windows[index].ravel())
            if len(values) > 2:  # single value and max_label
                boundaries[index] = True
    return boundaries
Exemplo n.º 18
0
def _find_boundaries_subpixel(label_img):
    """See ``find_boundaries(..., mode='subpixel')``.

    Notes
    -----
    This function puts in an empty row and column between each *actual*
    row and column of the image, for a corresponding shape of ``2s - 1``
    for every image dimension of size ``s``. These "interstitial" rows
    and columns are filled as ``True`` if they separate two labels in
    `label_img`, ``False`` otherwise.
    """
    ndim = label_img.ndim
    max_label = cp.iinfo(label_img.dtype).max

    label_img_expanded = cp.full([(2 * s - 1) for s in label_img.shape],
                                 max_label, label_img.dtype)
    pixels = (slice(None, None, 2),) * ndim
    label_img_expanded[pixels] = label_img

    # CuPy Backend: TODO: Refactor all rank filtering below into a single
    #                     ElementwiseKernel that counts # of unique values.

    # at most 2**ndim non max_label pixels in a 3**ndim shape neighborhood
    max_possible_unique = 2 ** ndim

    # Count the number of unique values aside from max_label or
    # the background.
    n_unique = cp.zeros(label_img_expanded.shape, dtype=cp.uint8)
    rank_prev = ndi.minimum_filter(label_img_expanded, size=3)
    for n in range(1, max_possible_unique + 1):
        rank = ndi.rank_filter(label_img_expanded, n, size=3)
        n_unique += (rank != rank_prev)
        rank_prev = rank

    # Boundaries occur where there is more than 1 unique value
    return n_unique > 1
Exemplo n.º 19
0
 def test_tomaxint_tuple(self):
     x = self.generate((2, 3))
     assert x.shape == (2, 3)
     assert (0 <= x).all()
     assert (x <= cupy.iinfo(cupy.int_).max).all()
Exemplo n.º 20
0
 def test_tomaxint_int(self):
     x = self.generate(3)
     assert x.shape == (3, )
     assert (0 <= x).all()
     assert (x <= cupy.iinfo(cupy.int_).max).all()
Exemplo n.º 21
0
 def test_tomaxint_int(self):
     x = self.rs.tomaxint(3)
     self.assertEqual(x.shape, (3,))
     self.assertTrue((0 <= x).all())
     self.assertTrue((x <= cupy.iinfo(cupy.int_).max).all())
Exemplo n.º 22
0
def peak_local_max(
    image,
    min_distance=1,
    threshold_abs=None,
    threshold_rel=None,
    exclude_border=True,
    indices=True,
    num_peaks=cp.inf,
    footprint=None,
    labels=None,
    num_peaks_per_label=cp.inf,
    p_norm=cp.inf,
):
    """Find peaks in an image as coordinate list or boolean mask.

    Peaks are the local maxima in a region of `2 * min_distance + 1`
    (i.e. peaks are separated by at least `min_distance`).

    If both `threshold_abs` and `threshold_rel` are provided, the maximum
    of the two is chosen as the minimum intensity threshold of peaks.

    .. versionchanged:: 0.18
        Prior to version 0.18, peaks of the same height within a radius of
        `min_distance` were all returned, but this could cause unexpected
        behaviour. From 0.18 onwards, an arbitrary peak within the region is
        returned. See issue gh-2592.

    Parameters
    ----------
    image : ndarray
        Input image.
    min_distance : int, optional
        The minimal allowed distance separating peaks. To find the
        maximum number of peaks, use `min_distance=1`.
    threshold_abs : float, optional
        Minimum intensity of peaks. By default, the absolute threshold is
        the minimum intensity of the image.
    threshold_rel : float, optional
        Minimum intensity of peaks, calculated as `max(image) * threshold_rel`.
    exclude_border : int, tuple of ints, or bool, optional
        If positive integer, `exclude_border` excludes peaks from within
        `exclude_border`-pixels of the border of the image.
        If tuple of non-negative ints, the length of the tuple must match the
        input array's dimensionality.  Each element of the tuple will exclude
        peaks from within `exclude_border`-pixels of the border of the image
        along that dimension.
        If True, takes the `min_distance` parameter as value.
        If zero or False, peaks are identified regardless of their distance
        from the border.
    indices : bool, optional
        If True, the output will be an array representing peak
        coordinates. The coordinates are sorted according to peaks
        values (Larger first). If False, the output will be a boolean
        array shaped as `image.shape` with peaks present at True
        elements. ``indices`` is deprecated and will be removed in
        version 0.20. Default behavior will be to always return peak
        coordinates. You can obtain a mask as shown in the example
        below.
    num_peaks : int, optional
        Maximum number of peaks. When the number of peaks exceeds `num_peaks`,
        return `num_peaks` peaks based on highest peak intensity.
    footprint : ndarray of bools, optional
        If provided, `footprint == 1` represents the local region within which
        to search for peaks at every point in `image`.
    labels : ndarray of ints, optional
        If provided, each unique region `labels == value` represents a unique
        region to search for peaks. Zero is reserved for background.
    num_peaks_per_label : int, optional
        Maximum number of peaks for each label.
    p_norm : float
        Which Minkowski p-norm to use. Should be in the range [1, inf].
        A finite large p may cause a ValueError if overflow can occur.
        ``inf`` corresponds to the Chebyshev distance and 2 to the
        Euclidean distance.

    Returns
    -------
    output : ndarray or ndarray of bools

        * If `indices = True`  : (row, column, ...) coordinates of peaks.
        * If `indices = False` : Boolean array shaped like `image`, with peaks
          represented by True values.

    Notes
    -----
    The peak local maximum function returns the coordinates of local peaks
    (maxima) in an image. Internally, a maximum filter is used for finding local
    maxima. This operation dilates the original image. After comparison of the
    dilated and original image, this function returns the coordinates or a mask
    of the peaks where the dilated image equals the original image.

    See also
    --------
    skimage.feature.corner_peaks

    Examples
    --------
    >>> import cupy as cp
    >>> img1 = cp.zeros((7, 7))
    >>> img1[3, 4] = 1
    >>> img1[3, 2] = 1.5
    >>> img1
    array([[0. , 0. , 0. , 0. , 0. , 0. , 0. ],
           [0. , 0. , 0. , 0. , 0. , 0. , 0. ],
           [0. , 0. , 0. , 0. , 0. , 0. , 0. ],
           [0. , 0. , 1.5, 0. , 1. , 0. , 0. ],
           [0. , 0. , 0. , 0. , 0. , 0. , 0. ],
           [0. , 0. , 0. , 0. , 0. , 0. , 0. ],
           [0. , 0. , 0. , 0. , 0. , 0. , 0. ]])

    >>> peak_local_max(img1, min_distance=1)
    array([[3, 2],
           [3, 4]])

    >>> peak_local_max(img1, min_distance=2)
    array([[3, 2]])

    >>> img2 = cp.zeros((20, 20, 20))
    >>> img2[10, 10, 10] = 1
    >>> img2[15, 15, 15] = 1
    >>> peak_idx = peak_local_max(img2, exclude_border=0)
    >>> peak_idx
    array([[10, 10, 10],
           [15, 15, 15]])

    >>> peak_mask = cp.zeros_like(img2, dtype=bool)
    >>> peak_mask[tuple(peak_idx.T)] = True
    >>> np.argwhere(peak_mask)
    array([[10, 10, 10],
           [15, 15, 15]])

    """
    if (footprint is None or footprint.size == 1) and min_distance < 1:
        warnings.warn(
            "When min_distance < 1, peak_local_max acts as finding "
            "image > max(threshold_abs, threshold_rel * max(image)).",
            RuntimeWarning,
            stacklevel=2,
        )

    border_width = _get_excluded_border_width(image, min_distance,
                                              exclude_border)

    threshold = _get_threshold(image, threshold_abs, threshold_rel)

    if footprint is None:
        size = 2 * min_distance + 1
        footprint = cp.ones((size, ) * image.ndim, dtype=bool)
    else:
        footprint = cp.asarray(footprint)

    if labels is None:
        # Non maximum filter
        mask = _get_peak_mask(image, footprint, threshold)

        mask = _exclude_border(mask, border_width)

        # Select highest intensities (num_peaks)
        coordinates = _get_high_intensity_peaks(image, mask, num_peaks,
                                                min_distance, p_norm)

    else:
        _labels = _exclude_border(labels.astype(int), border_width)

        if np.issubdtype(image.dtype, np.floating):
            bg_val = cp.finfo(image.dtype).min
        else:
            bg_val = cp.iinfo(image.dtype).min

        # For each label, extract a smaller image enclosing the object of
        # interest, identify num_peaks_per_label peaks
        labels_peak_coord = []

        # For each label, extract a smaller image enclosing the object of
        # interest, identify num_peaks_per_label peaks and mark them in
        # variable out.
        # TODO: use GPU version of find_objects
        try:
            objects = cupyx_ndi.find_objects(_labels)
        except AttributeError:
            objects = cpu_find_objects(cp.asnumpy(_labels))

        for label_idx, roi in enumerate(objects):
            if roi is None:
                continue

            # Get roi mask
            label_mask = labels[roi] == label_idx + 1
            # Extract image roi
            img_object = image[roi]
            # Ensure masked values don't affect roi's local peaks
            img_object[np.logical_not(label_mask)] = bg_val

            mask = _get_peak_mask(img_object, footprint, threshold, label_mask)

            coordinates = _get_high_intensity_peaks(img_object, mask,
                                                    num_peaks_per_label,
                                                    min_distance, p_norm)

            # transform coordinates in global image indices space
            for idx, s in enumerate(roi):
                coordinates[:, idx] += s.start

            labels_peak_coord.append(coordinates)

        if labels_peak_coord:
            coordinates = cp.vstack(labels_peak_coord)
        else:
            coordinates = cp.empty((0, 2), dtype=int)

        if len(coordinates) > num_peaks:
            out = cp.zeros_like(image, dtype=np.bool_)
            out[tuple(coordinates.T)] = True
            coordinates = _get_high_intensity_peaks(image, out, num_peaks,
                                                    min_distance, p_norm)

    if indices:
        return coordinates
    else:
        out = cp.zeros_like(image, dtype=np.bool_)
        out[tuple(coordinates.T)] = True
        return out
Exemplo n.º 23
0
 def test_tomaxint_tuple(self):
     x = self.generate((2, 3))
     self.assertEqual(x.shape, (2, 3))
     self.assertTrue((0 <= x).all())
     self.assertTrue((x <= cupy.iinfo(cupy.int_).max).all())
Exemplo n.º 24
0
# (some of them are platform specific). For the details, see:
# http://www.unix.org/whitepapers/64bit.html
_integer_types = (
    cp.byte,
    cp.ubyte,  # 8 bits
    cp.short,
    cp.ushort,  # 16 bits
    cp.intc,
    cp.uintc,  # 16 or 32 or 64 bits
    int,
    cp.int_,
    cp.uint,  # 32 or 64 bits
    cp.longlong,
    cp.ulonglong)  # 64 bits
_integer_ranges = {
    t: (cp.iinfo(t).min, cp.iinfo(t).max)
    for t in _integer_types
}
dtype_range = {
    bool: (False, True),
    cp.bool_: (False, True),
    cp.bool8: (False, True),
    float: (-1, 1),
    cp.float_: (-1, 1),
    cp.float16: (-1, 1),
    cp.float32: (-1, 1),
    cp.float64: (-1, 1)
}
dtype_range.update(_integer_ranges)

_supported_types = list(dtype_range.keys())
Exemplo n.º 25
0
def relabel_sequential(label_field, offset=1):
    """Relabel arbitrary labels to {`offset`, ... `offset` + number_of_labels}.

    This function also returns the forward map (mapping the original labels to
    the reduced labels) and the inverse map (mapping the reduced labels back
    to the original ones).

    Parameters
    ----------
    label_field : numpy array of int, arbitrary shape
        An array of labels, which must be non-negative integers.
    offset : int, optional
        The return labels will start at `offset`, which should be
        strictly positive.

    Returns
    -------
    relabeled : numpy array of int, same shape as `label_field`
        The input label field with labels mapped to
        {offset, ..., number_of_labels + offset - 1}.
        The data type will be the same as `label_field`, except when
        offset + number_of_labels causes overflow of the current data type.
    forward_map : ArrayMap
        The map from the original label space to the returned label
        space. Can be used to re-apply the same mapping. See examples
        for usage. The output data type will be the same as `relabeled`.
    inverse_map : ArrayMap
        The map from the new label space to the original space. This
        can be used to reconstruct the original label field from the
        relabeled one. The output data type will be the same as `label_field`.

    Notes
    -----
    The label 0 is assumed to denote the background and is never remapped.

    The forward map can be extremely big for some inputs, since its
    length is given by the maximum of the label field. However, in most
    situations, ``label_field.max()`` is much smaller than
    ``label_field.size``, and in these cases the forward map is
    guaranteed to be smaller than either the input or output images.

    Examples
    --------
    >>> import cupy as cp
    >>> from skimage.segmentation import relabel_sequential
    >>> label_field = cp.asarray([1, 1, 5, 5, 8, 99, 42])
    >>> relab, fw, inv = relabel_sequential(label_field)
    >>> relab
    array([1, 1, 2, 2, 3, 5, 4])
    >>> print(fw)
    ArrayMap:
      1 → 1
      5 → 2
      8 → 3
      42 → 4
      99 → 5
    >>> cp.asarray(fw)
    array([0, 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5])
    >>> cp.asarray(inv)
    array([ 0,  1,  5,  8, 42, 99])
    >>> (fw[label_field] == relab).all()
    True
    >>> (inv[relab] == label_field).all()
    True
    >>> relab, fw, inv = relabel_sequential(label_field, offset=5)
    >>> relab
    array([5, 5, 6, 6, 7, 9, 8])
    """
    if offset <= 0:
        raise ValueError("Offset must be strictly positive.")
    if label_field.min() < 0:
        raise ValueError("Cannot relabel array that contains negative values.")
    offset = int(offset)
    in_vals = cp.unique(label_field)
    if len(in_vals) > cp.iinfo(cp.int32).max:
        raise ValueError(
            "Too many unique values in label_field (current implementation "
            "uses 32-bit indexing).")

    out_val_dtype = cp.min_scalar_type(offset + len(in_vals))
    if int(in_vals[0]) == 0:
        # always map 0 to 0
        out_vals = cp.concatenate([
            cp.asarray([0], dtype=out_val_dtype),
            cp.arange(offset, offset + len(in_vals) - 1, dtype=out_val_dtype),
        ])
    else:
        out_vals = cp.arange(offset,
                             offset + len(in_vals),
                             dtype=out_val_dtype)
    input_type = label_field.dtype
    if input_type.kind not in "iu":
        raise TypeError("label_field must have an integer dtype")

    # Some logic to determine the output type:
    #  - we don't want to return a smaller output type than the input type,
    #  ie if we get uint32 as labels input, don't return a uint8 array.
    #  - but, in some cases, using the input type could result in overflow. The
    #  input type could be a signed integer (e.g. int32) but
    #  `np.min_scalar_type` will always return an unsigned type. We check for
    #  that by casting the largest output value to the input type. If it is
    #  unchanged, we use the input type, else we use the unsigned minimum
    #  required type
    out_max = int(out_vals[-1])
    required_type = cp.min_scalar_type(out_max)
    if input_type.itemsize < required_type.itemsize:
        output_type = required_type
    else:
        if out_max <= cp.iinfo(input_type).max:
            output_type = input_type
        else:
            output_type = required_type
    out_array = cp.empty(label_field.shape, dtype=output_type)
    out_vals = out_vals.astype(output_type, copy=False)
    map_array(label_field, in_vals, out_vals, out=out_array)
    fw_map = ArrayMap(in_vals, out_vals)
    inv_map = ArrayMap(out_vals, in_vals)
    return out_array, fw_map, inv_map
Exemplo n.º 26
0
def find_boundaries(label_img, connectivity=1, mode="thick", background=0):
    """Return bool array where boundaries between labeled regions are True.

    Parameters
    ----------
    label_img : array of int or bool
        An array in which different regions are labeled with either different
        integers or boolean values.
    connectivity : int in {1, ..., `label_img.ndim`}, optional
        A pixel is considered a boundary pixel if any of its neighbors
        has a different label. `connectivity` controls which pixels are
        considered neighbors. A connectivity of 1 (default) means
        pixels sharing an edge (in 2D) or a face (in 3D) will be
        considered neighbors. A connectivity of `label_img.ndim` means
        pixels sharing a corner will be considered neighbors.
    mode : string in {'thick', 'inner', 'outer', 'subpixel'}
        How to mark the boundaries:

        - thick: any pixel not completely surrounded by pixels of the
          same label (defined by `connectivity`) is marked as a boundary.
          This results in boundaries that are 2 pixels thick.
        - inner: outline the pixels *just inside* of objects, leaving
          background pixels untouched.
        - outer: outline pixels in the background around object
          boundaries. When two objects touch, their boundary is also
          marked.
        - subpixel: return a doubled image, with pixels *between* the
          original pixels marked as boundary where appropriate.
    background : int, optional
        For modes 'inner' and 'outer', a definition of a background
        label is required. See `mode` for descriptions of these two.

    Returns
    -------
    boundaries : array of bool, same shape as `label_img`
        A bool image where ``True`` represents a boundary pixel. For
        `mode` equal to 'subpixel', ``boundaries.shape[i]`` is equal
        to ``2 * label_img.shape[i] - 1`` for all ``i`` (a pixel is
        inserted in between all other pairs of pixels).

    Examples
    --------
    >>> labels = cp.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ...                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ...                    [0, 0, 0, 0, 0, 5, 5, 5, 0, 0],
    ...                    [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
    ...                    [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
    ...                    [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
    ...                    [0, 0, 0, 0, 0, 5, 5, 5, 0, 0],
    ...                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ...                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=cp.uint8)
    >>> find_boundaries(labels, mode='thick').astype(cp.uint8)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
           [0, 1, 1, 1, 1, 1, 0, 1, 1, 0],
           [0, 1, 1, 0, 1, 1, 0, 1, 1, 0],
           [0, 1, 1, 1, 1, 1, 0, 1, 1, 0],
           [0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
    >>> find_boundaries(labels, mode='inner').astype(cp.uint8)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 0, 1, 0, 0],
           [0, 0, 1, 0, 1, 1, 0, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 0, 1, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
    >>> find_boundaries(labels, mode='outer').astype(cp.uint8)
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
           [0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
           [0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
           [0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
           [0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
           [0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
    >>> labels_small = labels[::2, ::3]
    >>> labels_small
    array([[0, 0, 0, 0],
           [0, 0, 5, 0],
           [0, 1, 5, 0],
           [0, 0, 5, 0],
           [0, 0, 0, 0]], dtype=uint8)
    >>> find_boundaries(labels_small, mode='subpixel').astype(cp.uint8)
    array([[0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 1, 1, 0],
           [0, 0, 0, 1, 0, 1, 0],
           [0, 1, 1, 1, 0, 1, 0],
           [0, 1, 0, 1, 0, 1, 0],
           [0, 1, 1, 1, 0, 1, 0],
           [0, 0, 0, 1, 0, 1, 0],
           [0, 0, 0, 1, 1, 1, 0],
           [0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
    >>> bool_image = cp.array([[False, False, False, False, False],
    ...                        [False, False, False, False, False],
    ...                        [False, False,  True,  True,  True],
    ...                        [False, False,  True,  True,  True],
    ...                        [False, False,  True,  True,  True]],
    ...                       dtype=cp.bool)
    >>> find_boundaries(bool_image)
    array([[False, False, False, False, False],
           [False, False,  True,  True,  True],
           [False,  True,  True,  True,  True],
           [False,  True,  True, False, False],
           [False,  True,  True, False, False]])
    """
    if label_img.dtype == 'bool':
        label_img = label_img.astype(cp.uint8)
    ndim = label_img.ndim
    selem = ndi.generate_binary_structure(ndim, connectivity)
    if mode != 'subpixel':
        boundaries = dilation(label_img, selem) != erosion(label_img, selem)
        if mode == 'inner':
            foreground_image = label_img != background
            boundaries &= foreground_image
        elif mode == 'outer':
            max_label = cp.iinfo(label_img.dtype).max
            background_image = label_img == background
            selem = ndi.generate_binary_structure(ndim, ndim)
            inverted_background = cp.array(label_img, copy=True)
            inverted_background[background_image] = max_label
            adjacent_objects = ((dilation(label_img, selem) !=
                                 erosion(inverted_background, selem)) &
                                ~background_image)
            boundaries &= (background_image | adjacent_objects)
        return boundaries
    else:
        boundaries = _find_boundaries_subpixel(label_img)
        return boundaries
Exemplo n.º 27
0
def test_very_large_labels():
    imax = cp.iinfo(cp.int64).max
    labels = cp.asarray([0, 1, imax, 42, 42], dtype=cp.int64)
    output, fw, inv = relabel_sequential(labels, offset=imax)
    assert int(cp.max(output)) == imax + 2
Exemplo n.º 28
0
def ravel_multi_index(multi_index, dims, mode='wrap', order='C'):
    """
    Converts a tuple of index arrays into an array of flat indices, applying
    boundary modes to the multi-index.

    Args:
        multi_index (tuple of cupy.ndarray) : A tuple of integer arrays, one
            array for each dimension.
        dims (tuple of ints): The shape of array into which the indices from
            ``multi_index`` apply.
        mode ('raise', 'wrap' or 'clip'), optional: Specifies how out-of-bounds
            indices are handled.  Can specify either one mode or a tuple of
            modes, one mode per index:

            - *'raise'* -- raise an error
            - *'wrap'* -- wrap around (default)
            - *'clip'* -- clip to the range

            In 'clip' mode, a negative index which would normally wrap will
            clip to 0 instead.
        order ('C' or 'F'), optional: Determines whether the multi-index should
            be viewed as indexing in row-major (C-style) or column-major
            (Fortran-style) order.

    Returns:
        raveled_indices (cupy.ndarray): An array of indices into the flattened
            version of an array of dimensions ``dims``.

    .. warning::

        This function may synchronize the device when ``mode == 'raise'``.

    Notes
    -----
    Note that the default `mode` (``'wrap'``) is different than in NumPy. This
    is done to avoid potential device synchronization.

    Examples
    --------
    >>> cupy.ravel_multi_index(cupy.asarray([[3,6,6],[4,5,1]]), (7,6))
    array([22, 41, 37])
    >>> cupy.ravel_multi_index(cupy.asarray([[3,6,6],[4,5,1]]), (7,6),
    ...                        order='F')
    array([31, 41, 13])
    >>> cupy.ravel_multi_index(cupy.asarray([[3,6,6],[4,5,1]]), (4,6),
    ...                        mode='clip')
    array([22, 23, 19])
    >>> cupy.ravel_multi_index(cupy.asarray([[3,6,6],[4,5,1]]), (4,4),
    ...                        mode=('clip', 'wrap'))
    array([12, 13, 13])
    >>> cupy.ravel_multi_index(cupy.asarray((3,1,4,1)), (6,7,8,9))
    array(1621)

    .. seealso:: :func:`numpy.ravel_multi_index`, :func:`unravel_index`
    """

    ndim = len(dims)
    if len(multi_index) != ndim:
        raise ValueError(
            "parameter multi_index must be a sequence of "
            "length {}".format(ndim))

    for d in dims:
        if not isinstance(d, numbers.Integral):
            raise TypeError(
                "{} object cannot be interpreted as an integer".format(
                    type(d)))

    if isinstance(mode, str):
        mode = (mode, ) * ndim

    if functools.reduce(operator.mul, dims) > cupy.iinfo(cupy.int64).max:
        raise ValueError("invalid dims: array size defined by dims is larger "
                         "than the maximum possible size")

    s = 1
    ravel_strides = [1] * ndim
    if order is None:
        order = "C"
    if order == "C":
        for i in range(ndim - 2, -1, -1):
            s = s * dims[i + 1]
            ravel_strides[i] = s
    elif order == "F":
        for i in range(1, ndim):
            s = s * dims[i - 1]
            ravel_strides[i] = s
    else:
        raise TypeError("order not understood")

    multi_index = cupy.broadcast_arrays(*multi_index)
    raveled_indices = cupy.zeros(multi_index[0].shape, dtype=cupy.int64)
    for d, stride, idx, _mode in zip(dims, ravel_strides, multi_index, mode):

        if not isinstance(idx, cupy.ndarray):
            raise TypeError("elements of multi_index must be cupy arrays")
        if not cupy.can_cast(idx, cupy.int64, 'same_kind'):
            raise TypeError(
                'multi_index entries could not be cast from dtype(\'{}\') to '
                'dtype(\'{}\') according to the rule \'same_kind\''.format(
                    idx.dtype, cupy.int64().dtype))
        idx = idx.astype(cupy.int64, copy=False)

        if _mode == "raise":
            if cupy.any(cupy.logical_or(idx >= d, idx < 0)):
                raise ValueError("invalid entry in coordinates array")
        elif _mode == "clip":
            idx = cupy.clip(idx, 0, d - 1)
        elif _mode == 'wrap':
            idx = idx % d
        else:
            raise TypeError("Unrecognized mode: {}".format(_mode))
        raveled_indices += stride * idx
    return raveled_indices
Exemplo n.º 29
0
def _convert(image, dtype, force_copy=False, uniform=False):
    """
    Convert an image to the requested data-type.

    Warnings are issued in case of precision loss, or when negative values
    are clipped during conversion to unsigned integer types (sign loss).

    Floating point values are expected to be normalized and will be clipped
    to the range [0.0, 1.0] or [-1.0, 1.0] when converting to unsigned or
    signed integers respectively.

    Numbers are not shifted to the negative side when converting from
    unsigned to signed integer types. Negative values will be clipped when
    converting to unsigned integers.

    Parameters
    ----------
    image : ndarray
        Input image.
    dtype : dtype
        Target data-type.
    force_copy : bool, optional
        Force a copy of the data, irrespective of its current dtype.
    uniform : bool, optional
        Uniformly quantize the floating point range to the integer range.
        By default (uniform=False) floating point values are scaled and
        rounded to the nearest integers, which minimizes back and forth
        conversion errors.

    .. versionchanged :: 0.15
        ``_convert`` no longer warns about possible precision or sign
        information loss. See discussions on these warnings at:
        https://github.com/scikit-image/scikit-image/issues/2602
        https://github.com/scikit-image/scikit-image/issues/543#issuecomment-208202228
        https://github.com/scikit-image/scikit-image/pull/3575

    References
    ----------
    .. [1] DirectX data conversion rules.
           https://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx
    .. [2] Data Conversions. In "OpenGL ES 2.0 Specification v2.0.25",
           pp 7-8. Khronos Group, 2010.
    .. [3] Proper treatment of pixels as integers. A.W. Paeth.
           In "Graphics Gems I", pp 249-256. Morgan Kaufmann, 1990.
    .. [4] Dirty Pixels. J. Blinn. In "Jim Blinn's corner: Dirty Pixels",
           pp 47-57. Morgan Kaufmann, 1998.

    """
    dtypeobj_in = image.dtype
    if dtype is cp.floating:
        dtypeobj_out = cp.dtype("float64")
    else:
        dtypeobj_out = cp.dtype(dtype)
    dtype_in = dtypeobj_in.type
    dtype_out = dtypeobj_out.type
    kind_in = dtypeobj_in.kind
    kind_out = dtypeobj_out.kind
    itemsize_in = dtypeobj_in.itemsize
    itemsize_out = dtypeobj_out.itemsize

    # Below, we do an `issubdtype` check.  Its purpose is to find out
    # whether we can get away without doing any image conversion.  This happens
    # when:
    #
    # - the output and input dtypes are the same or
    # - when the output is specified as a type, and the input dtype
    #   is a subclass of that type (e.g. `cp.floating` will allow
    #   `float32` and `float64` arrays through)

    if cp.issubdtype(dtype_in, cp.obj2sctype(dtype)):
        if force_copy:
            image = image.copy()
        return image

    if not (dtype_in in _supported_types and dtype_out in _supported_types):
        raise ValueError("Can not convert from {} to {}.".format(
            dtypeobj_in, dtypeobj_out))

    if kind_in in 'ui':
        imin_in = cp.iinfo(dtype_in).min
        imax_in = cp.iinfo(dtype_in).max
    if kind_out in 'ui':
        imin_out = cp.iinfo(dtype_out).min
        imax_out = cp.iinfo(dtype_out).max

    # any -> binary
    if kind_out == 'b':
        return image > dtype_in(dtype_range[dtype_in][1] / 2)

    # binary -> any
    if kind_in == 'b':
        result = image.astype(dtype_out)
        if kind_out != 'f':
            result *= dtype_out(dtype_range[dtype_out][1])
        return result

    # float -> any
    if kind_in == 'f':
        if kind_out == 'f':
            # float -> float
            return image.astype(dtype_out)

        if cp.min(image) < -1.0 or cp.max(image) > 1.0:
            raise ValueError("Images of type float must be between -1 and 1.")
        # floating point -> integer
        # use float type that can represent output integer type
        computation_type = _dtype_itemsize(itemsize_out, dtype_in, cp.float32,
                                           cp.float64)

        if not uniform:
            if kind_out == 'u':
                image_out = cp.multiply(image,
                                        imax_out,
                                        dtype=computation_type)
            else:
                image_out = cp.multiply(image, (imax_out - imin_out) / 2,
                                        dtype=computation_type)
                image_out -= 1.0 / 2.0
            cp.rint(image_out, out=image_out)
            cp.clip(image_out, imin_out, imax_out, out=image_out)
        elif kind_out == 'u':
            image_out = cp.multiply(image,
                                    imax_out + 1,
                                    dtype=computation_type)
            cp.clip(image_out, 0, imax_out, out=image_out)
        else:
            image_out = cp.multiply(image, (imax_out - imin_out + 1.0) / 2.0,
                                    dtype=computation_type)
            cp.floor(image_out, out=image_out)
            cp.clip(image_out, imin_out, imax_out, out=image_out)
        return image_out.astype(dtype_out)

    # signed/unsigned int -> float
    if kind_out == 'f':
        # use float type that can exactly represent input integers
        computation_type = _dtype_itemsize(itemsize_in, dtype_out, cp.float32,
                                           cp.float64)

        if kind_in == 'u':
            # using cp.divide or cp.multiply doesn't copy the data
            # until the computation time
            image = cp.multiply(image, 1. / imax_in, dtype=computation_type)
            # DirectX uses this conversion also for signed ints
            # if imin_in:
            #     cp.maximum(image, -1.0, out=image)
        else:
            image = cp.add(image, 0.5, dtype=computation_type)
            image *= 2 / (imax_in - imin_in)

        return image.astype(dtype_out, copy=False)

    # unsigned int -> signed/unsigned int
    if kind_in == 'u':
        if kind_out == 'i':
            # unsigned int -> signed int
            image = _scale(image, 8 * itemsize_in, 8 * itemsize_out - 1)
            return image.view(dtype_out)
        else:
            # unsigned int -> unsigned int
            return _scale(image, 8 * itemsize_in, 8 * itemsize_out)

    # signed int -> unsigned int
    if kind_out == 'u':
        image = _scale(image, 8 * itemsize_in - 1, 8 * itemsize_out)
        result = cp.empty(image.shape, dtype_out)
        cp.maximum(image, 0, out=result, dtype=image.dtype, casting='unsafe')
        return result

    # signed int -> signed int
    if itemsize_in > itemsize_out:
        return _scale(image, 8 * itemsize_in - 1, 8 * itemsize_out - 1)

    image = image.astype(_dtype_bits('i', itemsize_out * 8))
    image -= imin_in
    image = _scale(image, 8 * itemsize_in, 8 * itemsize_out, copy=False)
    image += imin_out
    return image.astype(dtype_out)