Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc updates for consistency with scikit-image 0.20 #424

Merged
merged 24 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b0dfcf4
misc updates to skimage.color module
grlee77 Oct 30, 2022
9bbfe1e
misc updates to skimage.exposure module
grlee77 Oct 30, 2022
65b56ff
misc updates to skimage.feature
grlee77 Oct 30, 2022
6180836
support nD in farid filter
grlee77 Oct 30, 2022
8f26914
enhancements to filters.butterworth
grlee77 Oct 30, 2022
67ad3b7
misc updates to skimage.filters
grlee77 Oct 30, 2022
2525a4f
misc updates to skimage.measure
grlee77 Oct 30, 2022
dc309d4
fix math directive in normalized_mutual_information docstring
grlee77 Oct 30, 2022
7e0967c
use f-string in morphology/misc.py
grlee77 Oct 30, 2022
35bd045
apply codespell to skimage.registration
grlee77 Oct 30, 2022
a343b92
n-dimensional wiener filter
grlee77 Oct 30, 2022
f5f1945
misc changes to skimage.segmentation
grlee77 Oct 30, 2022
1a9965a
misc changes to skimage._shared
grlee77 Oct 30, 2022
38df053
Fix 3D case for EuclideanTransform, SimilarityTransform
grlee77 Oct 30, 2022
85de15f
update anti-aliasing handling in transforms
grlee77 Oct 30, 2022
6bccde7
misc updates to skimage.util
grlee77 Oct 30, 2022
b32c779
flake8 fixes
grlee77 Nov 2, 2022
d61d9ad
fix dtypes passed to Cython code in morphological reconstruction
grlee77 Nov 2, 2022
90506c5
use a fused kernel in structural_similarity
grlee77 Nov 2, 2022
28d5bb9
improve kernel fusion for the gradient=True case in structural_simila…
grlee77 Nov 2, 2022
8c6f4a4
revert unintended change to run-nv-bench-metrics.sh
grlee77 Nov 2, 2022
b89abba
switch from fused kernel to ElementwiseKernel
grlee77 Nov 6, 2022
118040f
fix typo, consistently use ndarray
grlee77 Nov 16, 2022
73db590
Merge remote-tracking branch 'upstream/branch-22.12' into skimage020-…
grlee77 Nov 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .version_requirements import is_installed

has_mpl = is_installed("matplotlib", ">=3.0.3")
has_mpl = is_installed("matplotlib", ">=3.3")
if has_mpl:
try:
# will fail with
Expand Down
6 changes: 3 additions & 3 deletions python/cucim/src/cucim/skimage/_shared/_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def all_warnings():
import inspect

# Whenever a warning is triggered, Python adds a __warningregistry__
# member to the *calling* module. The exercize here is to find
# member to the *calling* module. The exercise here is to find
# and eradicate all those breadcrumbs that were left lying around.
#
# We proceed by first searching all parent calling frames and explicitly
Expand Down Expand Up @@ -140,7 +140,7 @@ def expected_warnings(matching):
if match in remaining:
remaining.remove(match)
if strict_warnings and not found:
raise ValueError('Unexpected warning: %s' % str(warn.message))
raise ValueError(f'Unexpected warning: {str(warn.message)}')
if strict_warnings and (len(remaining) > 0):
msg = 'No warning raised matching:\n%s' % '\n'.join(remaining)
msg = f"No warning raised matching:\n{{'\n'.join(remaining)}}"
raise ValueError(msg)
20 changes: 9 additions & 11 deletions python/cucim/src/cucim/skimage/_shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def fixed_func(*args, **kwargs):
channel_axis = (channel_axis,)
if len(channel_axis) > 1:
raise ValueError(
"only a single channel axis is currently suported")
"only a single channel axis is currently supported")

if channel_axis == (-1,) or channel_axis == -1:
return func(*args, **kwargs)
Expand Down Expand Up @@ -402,14 +402,12 @@ def __call__(self, func):

alt_msg = ''
if self.alt_func is not None:
alt_msg = ' Use ``%s`` instead.' % self.alt_func
alt_msg = f' Use ``{self.alt_func}`` instead.'
rmv_msg = ''
if self.removed_version is not None:
rmv_msg = (' and will be removed in version %s' %
self.removed_version)
rmv_msg = f' and will be removed in version {self.removed_version}'

msg = ('Function ``%s`` is deprecated' % func.__name__
+ rmv_msg + '.' + alt_msg)
msg = f'Function ``{func.__name__}`` is deprecated{rmv_msg}.{alt_msg}'

@functools.wraps(func)
def wrapped(*args, **kwargs):
Expand Down Expand Up @@ -695,7 +693,7 @@ def _validate_interpolation_order(image_dtype, order):
if image_dtype == bool and order != 0:
raise ValueError(
"Input image dtype is bool. Interpolation is not defined "
"with bool data type. Please set order to 0 or explicitely "
"with bool data type. Please set order to 0 or explicitly "
"cast input image to another data type.")

return order
Expand All @@ -717,10 +715,10 @@ def _to_ndimage_mode(mode):
wrap='wrap')
if mode not in mode_translation_dict:
raise ValueError(
(f"Unknown mode: '{mode}', or cannot translate mode. The "
f"mode should be one of 'constant', 'edge', 'symmetric', "
f"'reflect', or 'wrap'. See the documentation of numpy.pad for "
f"more info."))
f"Unknown mode: '{mode}', or cannot translate mode. The "
f"mode should be one of 'constant', 'edge', 'symmetric', "
f"'reflect', or 'wrap'. See the documentation of numpy.pad for "
f"more info.")
return _fix_ndimage_mode(mode_translation_dict[mode])


Expand Down
25 changes: 0 additions & 25 deletions python/cucim/src/cucim/skimage/_shared/version_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,6 @@
from packaging import version as _version


def ensure_python_version(min_version):
if not isinstance(min_version, tuple):
min_version = (min_version, )
if sys.version_info < min_version:
# since ensure_python_version is in the critical import path,
# we lazy import it.
from platform import python_version

raise ImportError("""

You are running cucim on an unsupported version of Python.

Unfortunately, cucim no longer supports your installed version of Python (%s).
You therefore have two options: either upgrade to
Python %s, or install an older version of cucim.

Please also consider updating `pip` and `setuptools`:

$ pip install pip setuptools --upgrade

Newer versions of these tools avoid installing packages incompatible
with your version of Python.
""" % (python_version(), '.'.join([str(v) for v in min_version])))


def _check_version(actver, version, cmp_op):
"""
Check version string of an active module against a required version.
Expand Down
2 changes: 1 addition & 1 deletion python/cucim/src/cucim/skimage/color/colorconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,7 @@ def lab2xyz(lab, illuminant="D65", observer="2", *, channel_axis=-1):

nwarn = int(cp.count_nonzero(warnings))
if nwarn > 0: # synchronize!
warn('Color data out of range: Z < 0 in %s pixels' % nwarn,
warn(f'Color data out of range: Z < 0 in {nwarn} pixels',
stacklevel=2)
return xyz

Expand Down
8 changes: 4 additions & 4 deletions python/cucim/src/cucim/skimage/color/delta_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,16 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1, *, channel_axis=-1):
L2, a2, b2 = cp.moveaxis(lab2, source=channel_axis, destination=0)[:3]

# distort `a` based on average chroma
# then convert to lch coordines from distorted `a`
# all subsequence calculations are in the new coordiantes
# then convert to lch coordinates from distorted `a`
# all subsequence calculations are in the new coordinates
# (often denoted "prime" in the literature)
Cbar = 0.5 * (cp.hypot(a1, b1) + cp.hypot(a2, b2))
c7 = Cbar ** 7
G = 0.5 * (1 - cp.sqrt(c7 / (c7 + 25 ** 7)))
scale = 1 + G
C1, h1 = _cart2polar_2pi(a1 * scale, b1)
C2, h2 = _cart2polar_2pi(a2 * scale, b2)
# recall that c, h are polar coordiantes. c==r, h==theta
# recall that c, h are polar coordinates. c==r, h==theta

# cide2000 has four terms to delta_e:
# 1) Luminance term
Expand All @@ -235,7 +235,7 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1, *, channel_axis=-1):
L_term = (L2 - L1) / (kL * SL)

# chroma term
Cbar = 0.5 * (C1 + C2) # new coordiantes
Cbar = 0.5 * (C1 + C2) # new coordinates
SC = 1 + 0.045 * Cbar
C_term = (C2 - C1) / (kC * SC)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def test_label2rgb_nd(image_type):
labels[2:-2, 1:3] = 1
labels[3:-3, 6:9] = 2

# label in the 2D case (correct 2D output is tested in other funcitons)
# label in the 2D case (correct 2D output is tested in other functions)
labeled_2d = label2rgb(labels, image=img, bg_label=0)

# labeling a single line gives an equivalent result
Expand Down
7 changes: 4 additions & 3 deletions python/cucim/src/cucim/skimage/exposure/_adapthist.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ def _clahe(image, kernel_size, clip_limit, nbins):
hist_blocks = hist_blocks.reshape((_misc.prod(ns_hist), -1))

# Calculate actual clip limit
kernel_elements = _misc.prod(kernel_size)
if clip_limit > 0.0:
clim = int(max(clip_limit * _misc.prod(kernel_size), 1))
clim = int(max(clip_limit * kernel_elements, 1))
else:
# largest possible value, i.e., do not clip (AHE)
clim = np.product(kernel_size)
clim = kernel_elements

# Note: for 4096, 4096 input and default args, shapes are:
# hist_blocks.shape = (64, 262144)
Expand All @@ -189,7 +190,7 @@ def _clahe(image, kernel_size, clip_limit, nbins):
))
else:
hist = cp.apply_along_axis(clip_histogram, -1, hist, clip_limit=clim)
hist = map_histogram(hist, 0, NR_OF_GRAY - 1, _misc.prod(kernel_size))
hist = map_histogram(hist, 0, NR_OF_GRAY - 1, kernel_elements)
hist = hist.reshape(hist_block_assembled_shape[:ndim] + (-1,))

# duplicate leading mappings in each dim
Expand Down
6 changes: 3 additions & 3 deletions python/cucim/src/cucim/skimage/exposure/exposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def rescale_intensity(image, in_range="image", out_range="dtype"):
-----
.. versionchanged:: 0.17
The dtype of the output array has changed to match the output dtype, or
float if the output range is specified by a pair of floats.
float if the output range is specified by a pair of values.

See Also
--------
Expand Down Expand Up @@ -615,8 +615,8 @@ def _assert_non_negative(image):

def _adjust_gamma_u8(image, gamma, gain):
"""LUT based implmentation of gamma adjustement."""
lut = (255 * gain * (np.linspace(0, 1, 256) ** gamma))
lut = np.minimum(lut, 255).astype('uint8')
lut = 255 * gain * (np.linspace(0, 1, 256) ** gamma)
lut = np.minimum(np.rint(lut), 255).astype('uint8')
lut = cp.asarray(lut)
return lut[image]

Expand Down
20 changes: 15 additions & 5 deletions python/cucim/src/cucim/skimage/exposure/histogram_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@ def _match_cumulative_cdf(source, template):
Return modified source array so that the cumulative density function of
its values matches the cumulative density function of the template.
"""
src_values, src_unique_indices, src_counts = cp.unique(source.ravel(),
return_inverse=True,
return_counts=True)
tmpl_values, tmpl_counts = cp.unique(template.ravel(), return_counts=True)
if source.dtype.kind == 'u':
src_lookup = source.reshape(-1)
src_counts = cp.bincount(src_lookup)
tmpl_counts = cp.bincount(template.reshape(-1))

# omit values where the count was 0
tmpl_values = cp.nonzero(tmpl_counts)[0]
tmpl_counts = tmpl_counts[tmpl_values]
else:
src_values, src_lookup, src_counts = cp.unique(source.reshape(-1),
return_inverse=True,
return_counts=True)
tmpl_values, tmpl_counts = cp.unique(template.reshape(-1),
return_counts=True)

# calculate normalized quantiles for each array
src_quantiles = cp.cumsum(src_counts) / source.size
tmpl_quantiles = cp.cumsum(tmpl_counts) / template.size

interp_a_values = cp.interp(src_quantiles, tmpl_quantiles, tmpl_values)
return interp_a_values[src_unique_indices].reshape(source.shape)
return interp_a_values[src_lookup].reshape(source.shape)


@utils.channel_as_last_axis(channel_arg_positions=(0, 1))
Expand Down
84 changes: 60 additions & 24 deletions python/cucim/src/cucim/skimage/exposure/tests/test_exposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,6 @@ def test_adapthist_color():
assert_almost_equal(float(peak_snr(full_scale, adapted)), 109.393, 1)
assert_almost_equal(
float(norm_brightness_err(full_scale, adapted)), 0.02, 2)
return data, adapted


def test_adapthist_alpha():
Expand Down Expand Up @@ -597,7 +596,7 @@ def test_adjust_gamma_1x1_shape():

def test_adjust_gamma_one():
"""Same image should be returned for gamma equal to one"""
image = cp.random.uniform(0, 255, (8, 8))
image = cp.arange(0, 256, dtype=np.uint8).reshape((16, 16))
result = exposure.adjust_gamma(image, 1)
assert_array_almost_equal(result, image)

Expand All @@ -615,37 +614,74 @@ def test_adjust_gamma_zero(dtype):
def test_adjust_gamma_less_one():
"""Verifying the output with expected results for gamma
correction with gamma equal to half"""
image = cp.arange(0, 255, 4, cp.uint8).reshape((8, 8))
image = cp.arange(0, 256, dtype=np.uint8).reshape((16, 16))

# fmt: off
expected = cp.array([
[ 0, 31, 45, 55, 63, 71, 78, 84], # noqa
[ 90, 95, 100, 105, 110, 115, 119, 123], # noqa
[127, 131, 135, 139, 142, 146, 149, 153],
[156, 159, 162, 165, 168, 171, 174, 177],
[180, 183, 186, 188, 191, 194, 196, 199],
[201, 204, 206, 209, 211, 214, 216, 218],
[221, 223, 225, 228, 230, 232, 234, 236],
[238, 241, 243, 245, 247, 249, 251, 253]], dtype=cp.uint8)
expected = cp.array([0, 16, 23, 28, 32, 36, 39, 42, 45, 48, 50,
53, 55, 58, 60, 62, 64, 66, 68, 70, 71, 73,
75, 77, 78, 80, 81, 83, 84, 86, 87, 89, 90,
92, 93, 94, 96, 97, 98, 100, 101, 102, 103,
105, 106, 107, 108, 109, 111, 112, 113, 114,
115, 116, 117, 118, 119, 121, 122, 123, 124,
125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 135, 136, 137, 138, 139, 140, 141,
142, 143, 144, 145, 145, 146, 147, 148, 149,
150, 151, 151, 152, 153, 154, 155, 156, 156,
157, 158, 159, 160, 160, 161, 162, 163, 164,
164, 165, 166, 167, 167, 168, 169, 170, 170,
171, 172, 173, 173, 174, 175, 176, 176, 177,
178, 179, 179, 180, 181, 181, 182, 183, 183,
184, 185, 186, 186, 187, 188, 188, 189, 190,
190, 191, 192, 192, 193, 194, 194, 195, 196,
196, 197, 198, 198, 199, 199, 200, 201, 201,
202, 203, 203, 204, 204, 205, 206, 206, 207,
208, 208, 209, 209, 210, 211, 211, 212, 212,
213, 214, 214, 215, 215, 216, 217, 217, 218,
218, 219, 220, 220, 221, 221, 222, 222, 223,
224, 224, 225, 225, 226, 226, 227, 228, 228,
229, 229, 230, 230, 231, 231, 232, 233, 233,
234, 234, 235, 235, 236, 236, 237, 237, 238,
238, 239, 240, 240, 241, 241, 242, 242, 243,
243, 244, 244, 245, 245, 246, 246, 247, 247,
248, 248, 249, 249, 250, 250, 251, 251, 252,
252, 253, 253, 254, 254, 255],
dtype=cp.uint8).reshape((16, 16))
# fmt: on

result = exposure.adjust_gamma(image, 0.5)
assert_array_equal(result, expected)


def test_adjust_gamma_greater_one():
"""Verifying the output with expected results for gamma
correction with gamma equal to two"""
image = cp.arange(0, 255, 4, cp.uint8).reshape((8, 8))
image = np.arange(0, 256, dtype=np.uint8).reshape((16, 16))

# fmt: off
expected = cp.array([
[ 0, 0, 0, 0, 1, 1, 2, 3], # noqa
[ 4, 5, 6, 7, 9, 10, 12, 14], # noqa
[ 16, 18, 20, 22, 25, 27, 30, 33], # noqa
[ 36, 39, 42, 45, 49, 52, 56, 60], # noqa
[ 64, 68, 72, 76, 81, 85, 90, 95], # noqa
[100, 105, 110, 116, 121, 127, 132, 138],
[144, 150, 156, 163, 169, 176, 182, 189],
[196, 203, 211, 218, 225, 233, 241, 249]], dtype=cp.uint8)
expected = cp.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12,
13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18,
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24,
24, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31,
32, 32, 33, 34, 35, 35, 36, 37, 38, 38, 39,
40, 41, 42, 42, 43, 44, 45, 46, 47, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
70, 71, 73, 74, 75, 76, 77, 78, 79, 80, 81,
82, 84, 85, 86, 87, 88, 89, 91, 92, 93, 94,
95, 97, 98, 99, 100, 102, 103, 104, 105,
107, 108, 109, 111, 112, 113, 115, 116, 117,
119, 120, 121, 123, 124, 126, 127, 128, 130,
131, 133, 134, 136, 137, 139, 140, 142, 143,
145, 146, 148, 149, 151, 152, 154, 155, 157,
158, 160, 162, 163, 165, 166, 168, 170, 171,
173, 175, 176, 178, 180, 181, 183, 185, 186,
188, 190, 192, 193, 195, 197, 199, 200, 202,
204, 206, 207, 209, 211, 213, 215, 217, 218,
220, 222, 224, 226, 228, 230, 232, 233, 235,
237, 239, 241, 243, 245, 247, 249, 251, 253,
255] , dtype=cp.uint8).reshape((16, 16))
# fmt: on

result = exposure.adjust_gamma(image, 2)
Expand Down Expand Up @@ -838,7 +874,7 @@ def test_is_low_contrast_boolean():
exposure.adjust_log,
exposure.adjust_sigmoid])
def test_negative_input(exposure_func):
image = cp.arange(-10, 245, 4).reshape((8, 8)).astype(cp.double)
image = cp.arange(-10, 245, 4).reshape((8, 8)).astype(cp.float64)
with pytest.raises(ValueError):
exposure_func(image)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,15 @@ def _calculate_image_empirical_pdf(cls, image):
channels_pdf.append((channel_values, channel_quantiles))

return np.asarray(channels_pdf, dtype=object)

def test_match_histograms_consistency(self):
"""ensure equivalent results for float and integer-based code paths"""
image_u8 = self.image_rgb
reference_u8 = self.template_rgb
image_f64 = self.image_rgb.astype(np.float64)
reference_f64 = self.template_rgb.astype(np.float64, copy=False)
matched_u8 = exposure.match_histograms(image_u8, reference_u8)
matched_f64 = exposure.match_histograms(image_f64, reference_f64)
assert_array_almost_equal(
matched_u8.astype(np.float64), matched_f64, decimal=5
)
8 changes: 4 additions & 4 deletions python/cucim/src/cucim/skimage/feature/_basic_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def _mutiscale_basic_features_singlechannel(
at different scales are added to the feature set.
sigma_min : float, optional
Smallest value of the Gaussian kernel used to average local
neighbourhoods before extracting features.
neighborhoods before extracting features.
sigma_max : float, optional
Largest value of the Gaussian kernel used to average local
neighbourhoods before extracting features.
neighborhoods before extracting features.
num_sigma : int, optional
Number of values of the Gaussian kernel between sigma_min and sigma_max.
If None, sigma_min multiplied by powers of 2 are used.
Expand Down Expand Up @@ -136,10 +136,10 @@ def multiscale_basic_features(
at different scales are added to the feature set.
sigma_min : float, optional
Smallest value of the Gaussian kernel used to average local
neighbourhoods before extracting features.
neighborhoods before extracting features.
sigma_max : float, optional
Largest value of the Gaussian kernel used to average local
neighbourhoods before extracting features.
neighborhoods before extracting features.
num_sigma : int, optional
Number of values of the Gaussian kernel between sigma_min and sigma_max.
If None, sigma_min multiplied by powers of 2 are used.
Expand Down
Loading