From 9f9c049c40071a3ded4b793ea3fed2420a21e024 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 14 Aug 2024 10:38:59 -0400 Subject: [PATCH] introduce temporary numpy 2.0 compatibility fixes should be able to revert these changes after CuPy v13.3 is released --- .../tests/test_distance_transform.py | 3 ++- .../cucim/src/cucim/skimage/_shared/compat.py | 10 ++++++++ .../cucim/src/cucim/skimage/_shared/utils.py | 2 +- .../skimage/_vendored/_ndimage_filters.py | 7 +++--- .../src/cucim/skimage/color/colorconv.py | 6 ++++- .../skimage/color/tests/test_colorlabel.py | 5 +++- .../cucim/skimage/feature/tests/test_canny.py | 2 +- .../cucim/skimage/feature/tests/test_peak.py | 5 ++-- .../skimage/feature/tests/test_template.py | 3 ++- .../cucim/skimage/filters/tests/test_gabor.py | 3 ++- .../filters/tests/test_thresholding.py | 7 +++--- .../src/cucim/skimage/measure/_moments.py | 3 ++- .../skimage/metrics/_contingency_table.py | 4 +++- .../tests/test_structural_similarity.py | 6 ++--- .../cucim/skimage/morphology/_skeletonize.py | 3 +++ .../skimage/morphology/grayreconstruct.py | 4 +++- .../morphology/tests/test_skeletonize.py | 3 ++- .../skimage/registration/_optical_flow.py | 3 ++- .../skimage/registration/tests/test_tvl1.py | 2 +- .../skimage/restoration/deconvolution.py | 3 ++- .../skimage/restoration/tests/test_denoise.py | 4 ++-- .../cucim/skimage/segmentation/boundaries.py | 3 ++- .../random_walker_segmentation.py | 2 +- .../segmentation/tests/test_chan_vese.py | 3 ++- .../segmentation/tests/test_clear_border.py | 9 +++++--- .../segmentation/tests/test_random_walker.py | 5 ++-- .../src/cucim/skimage/transform/_geometric.py | 21 +++++++++-------- .../skimage/transform/tests/test_integral.py | 2 +- .../skimage/transform/tests/test_warps.py | 2 +- .../tests/unit/core/test_stain_normalizer.py | 23 ++++++++++--------- 30 files changed, 100 insertions(+), 58 deletions(-) diff --git a/python/cucim/src/cucim/core/operations/morphology/tests/test_distance_transform.py b/python/cucim/src/cucim/core/operations/morphology/tests/test_distance_transform.py index 50b6335db..bf4a54400 100644 --- a/python/cucim/src/cucim/core/operations/morphology/tests/test_distance_transform.py +++ b/python/cucim/src/cucim/core/operations/morphology/tests/test_distance_transform.py @@ -6,6 +6,7 @@ import scipy.ndimage as ndi_cpu from cucim.core.operations.morphology import distance_transform_edt +from cucim.skimage._shared.compat import _full def binary_image(shape, pct_true=50): @@ -137,7 +138,7 @@ def test_distance_transform_edt_block_params_invalid(block_params): @pytest.mark.parametrize("ndim", [2, 3]) def test_distance_transform_edt_uniform_valued(value, ndim): """ensure default block_params is robust to anisotropic shape.""" - img = cp.full((48,) * ndim, value, dtype=cp.uint8) + img = _full((48,) * ndim, value, dtype=cp.uint8) # ensure there is at least 1 pixel at background intensity img[(slice(24, 25),) * ndim] = 0 out = distance_transform_edt(img) diff --git a/python/cucim/src/cucim/skimage/_shared/compat.py b/python/cucim/src/cucim/skimage/_shared/compat.py index eb17c6202..6ce9ab11d 100644 --- a/python/cucim/src/cucim/skimage/_shared/compat.py +++ b/python/cucim/src/cucim/skimage/_shared/compat.py @@ -1,5 +1,6 @@ """Compatibility helpers for dependencies.""" +import cupy as cp import numpy as np from packaging.version import parse @@ -28,3 +29,12 @@ # deprecated in favor of `rtol`. # As of CuPy 13.0, it is still always using 'tol'' SCIPY_CG_TOL_PARAM_NAME = "tol" # if CUPY_LT_14 else "rtol" + + +def _full(shape, fill_value, dtype=None, order="C"): + if NUMPY_LT_2_0_0: + return cp.full(shape, fill_value, dtype, order) + else: + out = cp.empty(shape, dtype=dtype, order=order) + out[:] = fill_value + return out diff --git a/python/cucim/src/cucim/skimage/_shared/utils.py b/python/cucim/src/cucim/skimage/_shared/utils.py index d5da200b6..64c5fa8e1 100644 --- a/python/cucim/src/cucim/skimage/_shared/utils.py +++ b/python/cucim/src/cucim/skimage/_shared/utils.py @@ -743,7 +743,7 @@ def check_random_state(seed): if seed is None or seed is cp.random: return cp.random.mtrand._rand if isinstance(seed, (numbers.Integral, cp.integer)): - return cp.random.RandomState(seed) + return cp.random.RandomState(cp.uint32(seed)) if isinstance(seed, cp.random.RandomState): return seed raise ValueError( diff --git a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py index b7e2ab4a3..1467ddc1b 100644 --- a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py +++ b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py @@ -5,6 +5,7 @@ import cupy import numpy +from cucim.skimage._shared.compat import _full from cucim.skimage._vendored import ( _internal as internal, _ndimage_filters_core as _filters_core, @@ -361,7 +362,7 @@ def uniform_filter1d( from SciPy due to floating-point rounding of intermediate results. """ weights_dtype = cupy.promote_types(input.dtype, cupy.float32) - weights = cupy.full(size, 1 / size, dtype=weights_dtype) + weights = _full(size, 1 / size, dtype=weights_dtype) return correlate1d( input, weights, axis, output, mode, cval, origin, algorithm=algorithm ) @@ -410,9 +411,7 @@ def uniform_filter( def get(size): return ( - None - if size <= 1 - else cupy.full(size, 1 / size, dtype=weights_dtype) + None if size <= 1 else _full(size, 1 / size, dtype=weights_dtype) ) # noqa return _run_1d_correlates( diff --git a/python/cucim/src/cucim/skimage/color/colorconv.py b/python/cucim/src/cucim/skimage/color/colorconv.py index b7d33fc10..45bd5e189 100644 --- a/python/cucim/src/cucim/skimage/color/colorconv.py +++ b/python/cucim/src/cucim/skimage/color/colorconv.py @@ -55,6 +55,7 @@ import numpy as np from scipy import linalg +from .._shared.compat import _full from .._shared.utils import ( _supported_float_type, channel_as_last_axis, @@ -71,6 +72,9 @@ from numpy.exceptions import AxisError +using_numpy2 = np.__version__.split(".")[0] == 2 + + def convert_colorspace(arr, fromspace, tospace, *, channel_axis=-1): """Convert an image array to a new color space. @@ -1131,7 +1135,7 @@ def gray2rgba(image, alpha=None, *, channel_axis=-1, check_alpha=True): f'{image.dtype.name}', stacklevel=2 ) - alpha_arr = cp.full(image.shape, alpha, dtype=image.dtype) + alpha_arr = _full(image.shape, alpha, dtype=image.dtype) else: alpha_arr = cp.asarray(alpha).astype(image.dtype, copy=False) if check_alpha and not cp.array_equal(alpha, alpha_arr): diff --git a/python/cucim/src/cucim/skimage/color/tests/test_colorlabel.py b/python/cucim/src/cucim/skimage/color/tests/test_colorlabel.py index 281c1b1e6..21a187674 100644 --- a/python/cucim/src/cucim/skimage/color/tests/test_colorlabel.py +++ b/python/cucim/src/cucim/skimage/color/tests/test_colorlabel.py @@ -6,9 +6,12 @@ from cupy.testing import assert_array_almost_equal, assert_array_equal from numpy.testing import assert_no_warnings +from cucim.skimage._shared.compat import _full from cucim.skimage._shared.testing import expected_warnings from cucim.skimage.color.colorlabel import hsv2rgb, label2rgb, rgb2hsv +using_numpy2 = np.__version__.split(".")[0] == 2 + def test_shape_mismatch(): image = cp.ones((3, 3)) @@ -213,7 +216,7 @@ def test_avg(channel_axis): def test_negative_intensity(): labels = cp.arange(100).reshape(10, 10) - image = cp.full((10, 10), -1, dtype="float64") + image = _full((10, 10), -1, dtype="float64") with pytest.warns(UserWarning): label2rgb(labels, image, bg_label=-1) diff --git a/python/cucim/src/cucim/skimage/feature/tests/test_canny.py b/python/cucim/src/cucim/skimage/feature/tests/test_canny.py index 80beb4fc8..286410007 100644 --- a/python/cucim/src/cucim/skimage/feature/tests/test_canny.py +++ b/python/cucim/src/cucim/skimage/feature/tests/test_canny.py @@ -51,7 +51,7 @@ def test_01_01_circle(self): def test_01_02_circle_with_noise(self): """Test that the Canny filter finds the circle outlines in a noisy image""" - cp.random.seed(0) + cp.random.seed(cp.uint32(0)) i, j = cp.mgrid[-200:200, -200:200].astype(float) / 200 c = cp.abs(cp.sqrt(i * i + j * j) - 0.5) < 0.02 cf = c.astype(float) * 0.5 + cp.random.uniform(size=c.shape) * 0.5 diff --git a/python/cucim/src/cucim/skimage/feature/tests/test_peak.py b/python/cucim/src/cucim/skimage/feature/tests/test_peak.py index 58b5e490d..dfa14ab3e 100644 --- a/python/cucim/src/cucim/skimage/feature/tests/test_peak.py +++ b/python/cucim/src/cucim/skimage/feature/tests/test_peak.py @@ -8,6 +8,7 @@ from scipy import ndimage as ndimage_cpu from cucim.skimage._shared._warnings import expected_warnings +from cucim.skimage._shared.compat import _full from cucim.skimage.feature import peak np.random.seed(21) @@ -51,7 +52,7 @@ def test_absolute_threshold(self): assert_array_almost_equal(peaks, [(3, 3)]) def test_constant_image(self): - image = cp.full((20, 20), 128, dtype=cp.uint8) + image = _full((20, 20), 128, dtype=cp.uint8) peaks = peak.peak_local_max(image, min_distance=1) assert len(peaks) == 0 @@ -491,7 +492,7 @@ def test_threshold_rel_default(self): ) def test_peak_at_border(self): - image = cp.full((10, 10), -2) + image = _full((10, 10), -2) image[2, 4] = -1 image[3, 0] = -1 diff --git a/python/cucim/src/cucim/skimage/feature/tests/test_template.py b/python/cucim/src/cucim/skimage/feature/tests/test_template.py index 186b829e3..4253c6307 100644 --- a/python/cucim/src/cucim/skimage/feature/tests/test_template.py +++ b/python/cucim/src/cucim/skimage/feature/tests/test_template.py @@ -6,6 +6,7 @@ from skimage import data from cucim.skimage import img_as_float +from cucim.skimage._shared.compat import _full from cucim.skimage.feature import match_template, peak_local_max from cucim.skimage.morphology import diamond @@ -55,7 +56,7 @@ def test_normalization(): N = 20 ipos, jpos = (2, 3) ineg, jneg = (12, 11) - image = cp.full((N, N), 0.5) + image = _full((N, N), 0.5) image[ipos : ipos + n, jpos : jpos + n] = 1 image[ineg : ineg + n, jneg : jneg + n] = 0 diff --git a/python/cucim/src/cucim/skimage/filters/tests/test_gabor.py b/python/cucim/src/cucim/skimage/filters/tests/test_gabor.py index 7f3a8c4f2..72d050646 100644 --- a/python/cucim/src/cucim/skimage/filters/tests/test_gabor.py +++ b/python/cucim/src/cucim/skimage/filters/tests/test_gabor.py @@ -4,6 +4,7 @@ from cupy.testing import assert_array_almost_equal from numpy.testing import assert_almost_equal +from cucim.skimage._shared.compat import _full from cucim.skimage._shared.utils import _supported_float_type from cucim.skimage.filters._gabor import _sigma_prefactor, gabor, gabor_kernel @@ -117,6 +118,6 @@ def test_gabor_float_dtype(dtype): @pytest.mark.parametrize("dtype", [cp.uint8, cp.int32, cp.intp]) def test_gabor_int_dtype(dtype): - image = cp.full((16, 16), 128, dtype=dtype) + image = _full((16, 16), 128, dtype=dtype) y = gabor(image, 0.3) assert all(arr.dtype == dtype for arr in y) diff --git a/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py b/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py index 3825dcb7e..609782907 100644 --- a/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py +++ b/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py @@ -17,6 +17,7 @@ from cucim.skimage import util from cucim.skimage._shared._dependency_checks import has_mpl from cucim.skimage._shared._warnings import expected_warnings +from cucim.skimage._shared.compat import _full from cucim.skimage._shared.utils import _supported_float_type from cucim.skimage.color import rgb2gray from cucim.skimage.exposure import histogram @@ -388,7 +389,7 @@ def test_li_astro_image(): def test_li_nan_image(): - image = cp.full((5, 5), cp.nan) + image = _full((5, 5), cp.nan) assert cp.isnan(threshold_li(image)) @@ -639,7 +640,7 @@ def test_mean(): def test_triangle_uniform_images(dtype, kwargs): assert threshold_triangle(cp.zeros((10, 10), dtype=dtype), **kwargs) == 0 assert threshold_triangle(cp.ones((10, 10), dtype=dtype), **kwargs) == 1 - assert threshold_triangle(cp.full((10, 10), 2, dtype=dtype), **kwargs) == 2 + assert threshold_triangle(_full((10, 10), 2, dtype=dtype), **kwargs) == 2 # also run cases with nbins > 100000 to also test CuPy-based code path. @@ -775,7 +776,7 @@ def test_niblack_sauvola_pathological_image(): # resulted in NaNs. Here we check that these are safely caught. # see https://github.com/scikit-image/scikit-image/issues/3007 value = 0.03082192 + 2.19178082e-09 - src_img = cp.full((4, 4), value).astype(cp.float64) + src_img = _full((4, 4), value).astype(cp.float64) assert not cp.any(cp.isnan(threshold_niblack(src_img))) diff --git a/python/cucim/src/cucim/skimage/measure/_moments.py b/python/cucim/src/cucim/skimage/measure/_moments.py index 2c52b227f..2ea147ec2 100644 --- a/python/cucim/src/cucim/skimage/measure/_moments.py +++ b/python/cucim/src/cucim/skimage/measure/_moments.py @@ -3,6 +3,7 @@ import cupy as cp import numpy as np +from .._shared.compat import _full from .._shared.utils import _supported_float_type, check_nD from ._moments_analytical import moments_raw_to_central @@ -420,7 +421,7 @@ def moments_normalized(mu, order=3, spacing=None): # compute using in a single kernel for the 2D or 3D cases unit_scale = scale == 1.0 kernel = _get_normalize_kernel(mu.ndim, order, unit_scale) - nu = cp.full(mu.shape, cp.nan, dtype=mu.dtype) + nu = _full(mu.shape, cp.nan, dtype=mu.dtype) kernel(mu, order, scale, nu) return nu diff --git a/python/cucim/src/cucim/skimage/metrics/_contingency_table.py b/python/cucim/src/cucim/skimage/metrics/_contingency_table.py index 45923b478..e446bc176 100644 --- a/python/cucim/src/cucim/skimage/metrics/_contingency_table.py +++ b/python/cucim/src/cucim/skimage/metrics/_contingency_table.py @@ -1,6 +1,8 @@ import cupy as cp import cupyx.scipy.sparse as sparse +from .._shared.compat import _full + __all__ = ["contingency_table"] @@ -36,7 +38,7 @@ def contingency_table(im_true, im_test, *, ignore_labels=None, normalize=False): data /= cp.count_nonzero(data) else: if normalize: - data = cp.full((im_test_r.size,), 1 / im_test_r.size, dtype=float) + data = _full((im_test_r.size,), 1 / im_test_r.size, dtype=float) else: data = cp.ones((im_test_r.size,), dtype=float) cont = sparse.coo_matrix((data, (im_true_r, im_test_r))).tocsr() diff --git a/python/cucim/src/cucim/skimage/metrics/tests/test_structural_similarity.py b/python/cucim/src/cucim/skimage/metrics/tests/test_structural_similarity.py index 6a64a3b7e..778d46e2f 100644 --- a/python/cucim/src/cucim/skimage/metrics/tests/test_structural_similarity.py +++ b/python/cucim/src/cucim/skimage/metrics/tests/test_structural_similarity.py @@ -16,7 +16,7 @@ cam_noisy = cam_noisy.astype(cam.dtype) -cp.random.seed(1234) +cp.random.seed(cp.uint32(1234)) assert_equal = cp.testing.assert_array_equal assert_almost_equal = cp.testing.assert_array_almost_equal @@ -25,7 +25,7 @@ def test_structural_similarity_patch_range(): N = 51 - rstate = cp.random.RandomState(1234) + rstate = cp.random.RandomState(cp.uint32(1234)) X = (rstate.rand(N, N) * 255).astype(cp.uint8) Y = (rstate.rand(N, N) * 255).astype(cp.uint8) @@ -35,7 +35,7 @@ def test_structural_similarity_patch_range(): def test_structural_similarity_image(): N = 100 - rstate = cp.random.RandomState(1234) + rstate = cp.random.RandomState(cp.uint32(1234)) X = (rstate.rand(N, N) * 255).astype(cp.uint8) Y = (rstate.rand(N, N) * 255).astype(cp.uint8) diff --git a/python/cucim/src/cucim/skimage/morphology/_skeletonize.py b/python/cucim/src/cucim/skimage/morphology/_skeletonize.py index 4abe6c1c7..350ffe52f 100644 --- a/python/cucim/src/cucim/skimage/morphology/_skeletonize.py +++ b/python/cucim/src/cucim/skimage/morphology/_skeletonize.py @@ -159,6 +159,9 @@ def thin(image, max_num_iter=None): def _get_tiebreaker(n, seed): # CuPy generator doesn't currently have the permutation method, so # fall back to cp.random.permutation instead. + if np.isscalar(seed): + # TODO: remove this NumPy 2.0 compat. fix once CUPy 13.3 is released + seed = cp.uint32(seed) cp.random.seed(seed) if n < 2 << 31: dtype = np.int32 diff --git a/python/cucim/src/cucim/skimage/morphology/grayreconstruct.py b/python/cucim/src/cucim/skimage/morphology/grayreconstruct.py index e94dbcbec..266ce29b7 100644 --- a/python/cucim/src/cucim/skimage/morphology/grayreconstruct.py +++ b/python/cucim/src/cucim/skimage/morphology/grayreconstruct.py @@ -14,6 +14,8 @@ import skimage from packaging.version import Version +from .._shared.compat import _full + old_reconstruction_pyx = Version(skimage.__version__) < Version("0.20.0") @@ -190,7 +192,7 @@ def reconstruction(seed, mask, method="dilation", footprint=None, offset=None): # CuPy Backend: modified to allow images_dtype based on input dtype # instead of float64 images_dtype = np.promote_types(seed.dtype, mask.dtype) - images = cp.full(dims, pad_value, dtype=images_dtype) + images = _full(dims, pad_value, dtype=images_dtype) images[(0, *inside_slices)] = seed images[(1, *inside_slices)] = mask isize = images.size diff --git a/python/cucim/src/cucim/skimage/morphology/tests/test_skeletonize.py b/python/cucim/src/cucim/skimage/morphology/tests/test_skeletonize.py index 03c012e60..8a345c7bf 100644 --- a/python/cucim/src/cucim/skimage/morphology/tests/test_skeletonize.py +++ b/python/cucim/src/cucim/skimage/morphology/tests/test_skeletonize.py @@ -5,6 +5,7 @@ from skimage import data from skimage.morphology import thin as thin_cpu +from cucim.skimage._shared.compat import _full from cucim.skimage.morphology import medial_axis, thin @@ -99,7 +100,7 @@ def _test_vertical_line(self, dtype, **kwargs): img[:, 3] = 2 img[:, 4] = 3 - expected = cp.full(img.shape, False) + expected = _full(img.shape, False) expected[:, 3] = True result = medial_axis(img, **kwargs) diff --git a/python/cucim/src/cucim/skimage/registration/_optical_flow.py b/python/cucim/src/cucim/skimage/registration/_optical_flow.py index 6d115ac20..7a661582c 100644 --- a/python/cucim/src/cucim/skimage/registration/_optical_flow.py +++ b/python/cucim/src/cucim/skimage/registration/_optical_flow.py @@ -10,6 +10,7 @@ from .._shared._gradient import gradient from .._shared.utils import _supported_float_type +from .._vendored.ndimage import uniform_filter # has numpy 2.0 compat fix from ..transform import warp from ._optical_flow_utils import _coarse_to_fine, _get_warp_points @@ -303,7 +304,7 @@ def _ilk( filter_func = partial(gaussian_filter, sigma=sigma, mode="mirror") else: filter_func = partial( - ndi.uniform_filter, size=ndim * (size,), mode="mirror" + uniform_filter, size=ndim * (size,), mode="mirror" ) flow = flow0 diff --git a/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py b/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py index 20a70b039..4a6985698 100644 --- a/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py +++ b/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py @@ -41,7 +41,7 @@ def _sin_flow_gen(image0, max_motion=4.5, npics=5): @pytest.mark.parametrize("dtype", [cp.float16, cp.float32, cp.float64]) def test_2d_motion(dtype): # Generate synthetic data - rnd = cp.random.RandomState(0) + rnd = cp.random.RandomState(cp.uint32(0)) image0 = cp.array(rnd.normal(size=(256, 256)).astype(dtype)) gt_flow, image1 = _sin_flow_gen(image0) image1 = image1.astype(dtype, copy=False) diff --git a/python/cucim/src/cucim/skimage/restoration/deconvolution.py b/python/cucim/src/cucim/skimage/restoration/deconvolution.py index 41572359b..cbf21bdb5 100644 --- a/python/cucim/src/cucim/skimage/restoration/deconvolution.py +++ b/python/cucim/src/cucim/skimage/restoration/deconvolution.py @@ -4,6 +4,7 @@ import cupy as cp import numpy as np +from .._shared.compat import _full from .._shared.utils import ( DEPRECATED, _supported_float_type, @@ -456,7 +457,7 @@ def richardson_lucy(image, psf, num_iter=50, clip=True, filter_epsilon=None): float_type = _supported_float_type(image.dtype) image = image.astype(float_type, copy=False) psf = psf.astype(float_type, copy=False) - im_deconv = cp.full(image.shape, 0.5, dtype=float_type) + im_deconv = _full(image.shape, 0.5, dtype=float_type) psf_mirror = cp.ascontiguousarray(psf[::-1, ::-1]) # Small regularization parameter used to avoid 0 divisions diff --git a/python/cucim/src/cucim/skimage/restoration/tests/test_denoise.py b/python/cucim/src/cucim/skimage/restoration/tests/test_denoise.py index ae281a634..b907b0cbf 100644 --- a/python/cucim/src/cucim/skimage/restoration/tests/test_denoise.py +++ b/python/cucim/src/cucim/skimage/restoration/tests/test_denoise.py @@ -10,7 +10,7 @@ from cucim.skimage._shared.utils import _supported_float_type, slice_at_axis from cucim.skimage.metrics import structural_similarity -cp.random.seed(1234) +cp.random.seed(cp.uint32(1234)) astro = img_as_float(data.astronaut()[:128, :128]) @@ -140,7 +140,7 @@ def test_denoise_tv_chambolle_4d(): def test_denoise_tv_chambolle_weighting(): # make sure a specified weight gives consistent results regardless of # the number of input image dimensions - rstate = cp.random.RandomState(1234) + rstate = cp.random.RandomState(cp.uint32(1234)) img2d = astro_gray.copy() img2d += 0.15 * rstate.standard_normal(img2d.shape) img2d = cp.clip(img2d, 0, 1) diff --git a/python/cucim/src/cucim/skimage/segmentation/boundaries.py b/python/cucim/src/cucim/skimage/segmentation/boundaries.py index 7bdfb24ad..90c5ac8cb 100644 --- a/python/cucim/src/cucim/skimage/segmentation/boundaries.py +++ b/python/cucim/src/cucim/skimage/segmentation/boundaries.py @@ -2,6 +2,7 @@ import cucim.skimage._vendored.ndimage as ndi +from .._shared.compat import _full from .._shared.utils import _supported_float_type from ..color import gray2rgb from ..morphology import dilation, erosion, square @@ -26,7 +27,7 @@ def _find_boundaries_subpixel(label_img): ndim = label_img.ndim max_label = cp.iinfo(label_img.dtype).max - label_img_expanded = cp.full( + label_img_expanded = _full( [(2 * s - 1) for s in label_img.shape], max_label, label_img.dtype ) pixels = (slice(None, None, 2),) * ndim diff --git a/python/cucim/src/cucim/skimage/segmentation/random_walker_segmentation.py b/python/cucim/src/cucim/skimage/segmentation/random_walker_segmentation.py index cc3ff3e65..c2dea2b50 100644 --- a/python/cucim/src/cucim/skimage/segmentation/random_walker_segmentation.py +++ b/python/cucim/src/cucim/skimage/segmentation/random_walker_segmentation.py @@ -434,7 +434,7 @@ def random_walker( Examples -------- >>> import cupy as cp - >>> cp.random.seed(0) + >>> cp.random.seed(cp.uint32(0)) >>> a = cp.zeros((10, 10)) + 0.2 * cp.random.rand(10, 10) >>> a[5:8, 5:8] += 1 >>> b = cp.zeros_like(a, dtype=cp.int32) diff --git a/python/cucim/src/cucim/skimage/segmentation/tests/test_chan_vese.py b/python/cucim/src/cucim/skimage/segmentation/tests/test_chan_vese.py index ec1f61f06..2b2ec0913 100644 --- a/python/cucim/src/cucim/skimage/segmentation/tests/test_chan_vese.py +++ b/python/cucim/src/cucim/skimage/segmentation/tests/test_chan_vese.py @@ -2,6 +2,7 @@ import pytest from cupy.testing import assert_array_equal +from cucim.skimage._shared.compat import _full from cucim.skimage._shared.utils import _supported_float_type from cucim.skimage.segmentation import chan_vese @@ -16,7 +17,7 @@ def test_chan_vese_flat_level_set(dtype): # infinite time, the segmentation will still converge. img = cp.zeros((10, 10), dtype=dtype) img[3:6, 3:6] = 1 - ls = cp.full((10, 10), 1000, dtype=dtype) + ls = _full((10, 10), 1000, dtype=dtype) result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=ls) assert_array_equal(result.astype(float), cp.ones((10, 10))) result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=-ls) diff --git a/python/cucim/src/cucim/skimage/segmentation/tests/test_clear_border.py b/python/cucim/src/cucim/skimage/segmentation/tests/test_clear_border.py index aa70b185e..4aaf3fc4d 100644 --- a/python/cucim/src/cucim/skimage/segmentation/tests/test_clear_border.py +++ b/python/cucim/src/cucim/skimage/segmentation/tests/test_clear_border.py @@ -1,6 +1,7 @@ import cupy as cp from cupy.testing import assert_array_equal +from cucim.skimage._shared.compat import _full from cucim.skimage.segmentation import clear_border @@ -29,7 +30,7 @@ def test_clear_border(): # test background value result = clear_border(image.copy(), buffer_size=1, bgval=2) - assert_array_equal(result, cp.full_like(image, 2)) + assert_array_equal(result, _full(image.shape, 2, dtype=image.dtype)) # test mask mask = cp.array( @@ -77,12 +78,14 @@ def test_clear_border_3d(): # test background value result = clear_border(image.copy(), buffer_size=1, bgval=2) - assert_array_equal(result, cp.full_like(image, 2)) + assert_array_equal(result, _full(image.shape, 2, dtype=image.dtype)) # test floating-point background value image_f32 = image.astype(cp.float32) result = clear_border(image_f32, buffer_size=1, bgval=2.5) - assert_array_equal(result, cp.full_like(image_f32, 2.5)) + assert_array_equal( + result, _full(image_f32.shape, 2.5, dtype=image_f32.dtype) + ) def test_clear_border_non_binary(): diff --git a/python/cucim/src/cucim/skimage/segmentation/tests/test_random_walker.py b/python/cucim/src/cucim/skimage/segmentation/tests/test_random_walker.py index efcae02c5..b07494fa4 100644 --- a/python/cucim/src/cucim/skimage/segmentation/tests/test_random_walker.py +++ b/python/cucim/src/cucim/skimage/segmentation/tests/test_random_walker.py @@ -6,6 +6,7 @@ from cucim.skimage._shared import testing from cucim.skimage._shared._warnings import expected_warnings +from cucim.skimage._shared.compat import _full from cucim.skimage.segmentation import random_walker from cucim.skimage.transform import resize @@ -364,7 +365,7 @@ def test_trivial_cases(): cp.testing.assert_array_equal(test, expected) # Unlabeled voxels not connected to seed, so nothing can be done - img = cp.full((10, 10), False) + img = _full((10, 10), False) object_A = np.array([(6, 7), (6, 8), (7, 7), (7, 8)]) object_B = np.array( [(3, 1), (4, 1), (2, 2), (3, 2), (4, 2), (2, 3), (3, 3)] @@ -389,7 +390,7 @@ def test_trivial_cases(): def test_length2_spacing(): # If this passes without raising an exception (warnings OK), the new # spacing code is working properly. - cp.random.seed(42) + cp.random.seed(cp.uint32(42)) img = cp.ones((10, 10)) + 0.2 * cp.random.normal(size=(10, 10)) labels = cp.zeros((10, 10), dtype=cp.uint8) labels[2, 4] = 1 diff --git a/python/cucim/src/cucim/skimage/transform/_geometric.py b/python/cucim/src/cucim/skimage/transform/_geometric.py index 25347db26..83019d4e9 100644 --- a/python/cucim/src/cucim/skimage/transform/_geometric.py +++ b/python/cucim/src/cucim/skimage/transform/_geometric.py @@ -7,7 +7,7 @@ import numpy as np from scipy import spatial -from .._shared.compat import NP_COPY_IF_NEEDED +from .._shared.compat import NP_COPY_IF_NEEDED, _full from .._shared.utils import get_bound_method_class, safe_as_int _sin, _cos = math.sin, math.cos @@ -80,9 +80,10 @@ def _center_and_normalize_points(points): # small value; ie, we don't need to worry about numerical stability here, # only actual 0. if rms == 0: + full_func = xp.full if xp == np else _full return ( - xp.full((d + 1, d + 1), xp.nan), - xp.full_like(points, xp.nan), + full_func((d + 1, d + 1), xp.nan), + full_func(points.shape, xp.nan, dtype=points.dtype), True, ) @@ -411,17 +412,18 @@ def _setup_constraint_matrix(self, src, dst): raise ValueError("src.shape[0] must be equal or larger than 8.") xp = cp.get_array_module(src) + full_func = xp.full if xp == np else _full # Center and normalize image points for better numerical stability. try: src_matrix, src, has_nan1 = _center_and_normalize_points(src) dst_matrix, dst, has_nan2 = _center_and_normalize_points(dst) except ZeroDivisionError: - self.params = xp.full((3, 3), xp.nan) - return 3 * [xp.full((3, 3), xp.nan)] + self.params = full_func((3, 3), xp.nan) + return 3 * [full_func((3, 3), xp.nan)] if has_nan1 or has_nan2: - self.params = xp.full((3, 3), xp.nan) - return 3 * [xp.full((3, 3), xp.nan)] + self.params = full_func((3, 3), xp.nan) + return 3 * [full_func((3, 3), xp.nan)] # Setup homogeneous linear equation as dst' * F * src = 0. A = xp.ones((src.shape[0], 9)) @@ -848,8 +850,9 @@ def estimate(self, src, dst, weights=None): n, d = src.shape src_matrix, src, has_nan1 = _center_and_normalize_points(src) dst_matrix, dst, has_nan2 = _center_and_normalize_points(dst) + full_func = xp.full if xp == np else _full if has_nan1 or has_nan2: - self.params = xp.full((d + 1, d + 1), xp.nan) + self.params = full_func((d + 1, d + 1), xp.nan) return False # params: a0, a1, a2, b0, b1, b2, c0, c1 A = xp.zeros((n * d, (d + 1) ** 2)) @@ -878,7 +881,7 @@ def estimate(self, src, dst, weights=None): # because it is a rank-defective transform, which would map points # to a line rather than a plane. if xp.isclose(V[-1, -1], 0): - self.params = xp.full((d + 1, d + 1), xp.nan) + self.params = full_func((d + 1, d + 1), xp.nan) return False H = np.zeros( diff --git a/python/cucim/src/cucim/skimage/transform/tests/test_integral.py b/python/cucim/src/cucim/skimage/transform/tests/test_integral.py index 0010b2b03..5c6ab55ea 100644 --- a/python/cucim/src/cucim/skimage/transform/tests/test_integral.py +++ b/python/cucim/src/cucim/skimage/transform/tests/test_integral.py @@ -5,7 +5,7 @@ from cucim.skimage.transform import integral_image, integrate -cp.random.seed(0) +cp.random.seed(cp.uint32(0)) x = (cp.random.rand(50, 50) * 255).astype(np.uint8) s = integral_image(x) diff --git a/python/cucim/src/cucim/skimage/transform/tests/test_warps.py b/python/cucim/src/cucim/skimage/transform/tests/test_warps.py index 643663a5c..da0950832 100644 --- a/python/cucim/src/cucim/skimage/transform/tests/test_warps.py +++ b/python/cucim/src/cucim/skimage/transform/tests/test_warps.py @@ -41,7 +41,7 @@ # from skimage._shared.testing import test_parallel -cp.random.seed(0) +cp.random.seed(cp.uint32(0)) def test_stackcopy(): diff --git a/python/cucim/tests/unit/core/test_stain_normalizer.py b/python/cucim/tests/unit/core/test_stain_normalizer.py index 34a44384c..086784496 100644 --- a/python/cucim/tests/unit/core/test_stain_normalizer.py +++ b/python/cucim/tests/unit/core/test_stain_normalizer.py @@ -20,17 +20,18 @@ normalize_colors_pca, stain_extraction_pca, ) +from cucim.skimage._shared.compat import _full class TestStainExtractorMacenko: @pytest.mark.parametrize( "image, ErrorClass", [ - (cp.full((3, 2, 4), -1), ValueError), # negative value - (cp.full((3, 2, 4), 256), ValueError), # out of range value + (_full((3, 2, 4), -1), ValueError), # negative value + (_full((3, 2, 4), 256), ValueError), # out of range value (None, TypeError), ( - cp.full((3, 2, 4), 240), + _full((3, 2, 4), 240), ValueError, ), # uniformly below the beta threshold # noqa ], @@ -50,8 +51,8 @@ def test_transparent_image(self, image, ErrorClass): "image", [ None, - cp.full((3, 2, 4), 100), # uniform, above beta absorbance thresh. - cp.full((3, 2, 4), 150), # uniform, above beta absorbance thresh. + _full((3, 2, 4), 100), # uniform, above beta absorbance thresh. + _full((3, 2, 4), 150), # uniform, above beta absorbance thresh. ], ) def test_identical_result_vectors(self, image): @@ -144,10 +145,10 @@ class TestStainNormalizerMacenko: @pytest.mark.parametrize( "image", [ - cp.full((3, 2, 4), -1), # negative value case - cp.full((3, 2, 4), 256), # out of range value + _full((3, 2, 4), -1), # negative value case + _full((3, 2, 4), 256), # out of range value None, - cp.full((3, 2, 5), 240), # uniformly below the beta threshold + _full((3, 2, 5), 240), # uniformly below the beta threshold ], ) def test_transparent_image(self, image): @@ -185,9 +186,9 @@ def test_transparent_image(self, image): # and finally converting to uint8, we get that the stain # normalized image should be 12 everywhere. [ - {"ref_stain_coeff": cp.full((3, 2), 1)}, + {"ref_stain_coeff": _full((3, 2), 1)}, cp.zeros((3, 2, 4)), - cp.full((3, 2, 4), 12), + _full((3, 2, 4), 12), ], # 3.) input uniformly zero, and target stain matrix provided. # - As in test case 2, the normalized concentration matrix should @@ -223,7 +224,7 @@ def test_transparent_image(self, image): # the absorbance image, and finally converting to uint8, we get # the expected result listed here. [ - {"ref_stain_coeff": cp.full((3, 2), 1)}, + {"ref_stain_coeff": _full((3, 2), 1)}, cp.array( [ [[100, 0, 0], [0, 0, 0]],