Skip to content

Commit

Permalink
introduce temporary numpy 2.0 compatibility fixes
Browse files Browse the repository at this point in the history
should be able to revert these changes after CuPy v13.3 is released
  • Loading branch information
grlee77 committed Aug 14, 2024
1 parent 79228c6 commit 9f9c049
Show file tree
Hide file tree
Showing 30 changed files with 100 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions python/cucim/src/cucim/skimage/_shared/compat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Compatibility helpers for dependencies."""

import cupy as cp
import numpy as np
from packaging.version import parse

Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion python/cucim/src/cucim/skimage/_shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 3 additions & 4 deletions python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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(
Expand Down
6 changes: 5 additions & 1 deletion python/cucim/src/cucim/skimage/color/colorconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion python/cucim/src/cucim/skimage/feature/tests/test_canny.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions python/cucim/src/cucim/skimage/feature/tests/test_peak.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion python/cucim/src/cucim/skimage/filters/tests/test_gabor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))


Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)))


Expand Down
3 changes: 2 additions & 1 deletion python/cucim/src/cucim/skimage/measure/_moments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion python/cucim/src/cucim/skimage/metrics/_contingency_table.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import cupy as cp
import cupyx.scipy.sparse as sparse

from .._shared.compat import _full

__all__ = ["contingency_table"]


Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions python/cucim/src/cucim/skimage/morphology/_skeletonize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion python/cucim/src/cucim/skimage/morphology/grayreconstruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion python/cucim/src/cucim/skimage/registration/_optical_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion python/cucim/src/cucim/skimage/restoration/deconvolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 9f9c049

Please sign in to comment.