Skip to content

Commit

Permalink
Misc updates for consistency with scikit-image 0.20 (#424)
Browse files Browse the repository at this point in the history
related to #419

The larger feature updates related to skimage 0.20 were opened in separate PRs. This one contains any other minor bug fixes and documentation tweaks.

Authors:
  - Gregory Lee (https://github.com/grlee77)

Approvers:
  - Gigon Bae (https://github.com/gigony)

URL: #424
  • Loading branch information
grlee77 authored Nov 17, 2022
1 parent 0cc44fa commit 706b6c0
Show file tree
Hide file tree
Showing 50 changed files with 980 additions and 639 deletions.
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

0 comments on commit 706b6c0

Please sign in to comment.