mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-02 16:31:29 +08:00
0e61374a89
Add a helper function to check for low contrast Add a check for low contrast when using imsave Use the low contrast helper in imshow and make sure warnings are always shown Clean up parameter names and add doctests Remove unnecessary warning context Remove unnecessary warning context Add dtype ranges for 64bit types Update tests with new warnings Fix doctest logic Fix doctest logic Add a low contrast test with multiple dtypes Fix check for color images Fix color check again Add support for int32 types Relax assertion for 32bit builds Add a low contrast test with multiple dtypes Add a low contrast test with multiple dtypes Fix check for color images Fix color check again Add support for int32 types
494 lines
17 KiB
Python
494 lines
17 KiB
Python
import warnings
|
|
|
|
import numpy as np
|
|
from numpy.testing import assert_array_almost_equal as assert_close
|
|
from numpy.testing import (assert_array_equal, assert_raises,
|
|
assert_almost_equal)
|
|
|
|
import skimage
|
|
from skimage import data
|
|
from skimage import exposure
|
|
from skimage.exposure.exposure import intensity_range
|
|
from skimage.color import rgb2gray
|
|
from skimage.util.dtype import dtype_range
|
|
from skimage._shared._warnings import expected_warnings
|
|
|
|
|
|
# Test integer histograms
|
|
# =======================
|
|
|
|
def test_negative_overflow():
|
|
im = np.array([-1, 127], dtype=np.int8)
|
|
frequencies, bin_centers = exposure.histogram(im)
|
|
assert_array_equal(bin_centers, np.arange(-1, 128))
|
|
assert frequencies[0] == 1
|
|
assert frequencies[-1] == 1
|
|
assert_array_equal(frequencies[1:-1], 0)
|
|
|
|
|
|
def test_all_negative_image():
|
|
im = np.array([-128, -1], dtype=np.int8)
|
|
frequencies, bin_centers = exposure.histogram(im)
|
|
assert_array_equal(bin_centers, np.arange(-128, 0))
|
|
assert frequencies[0] == 1
|
|
assert frequencies[-1] == 1
|
|
assert_array_equal(frequencies[1:-1], 0)
|
|
|
|
|
|
# Test histogram equalization
|
|
# ===========================
|
|
|
|
np.random.seed(0)
|
|
|
|
test_img_int = data.camera()
|
|
# squeeze image intensities to lower image contrast
|
|
test_img = skimage.img_as_float(test_img_int)
|
|
test_img = exposure.rescale_intensity(test_img / 5. + 100)
|
|
|
|
def test_equalize_uint8_approx():
|
|
"""Check integer bins used for uint8 images."""
|
|
img_eq0 = exposure.equalize_hist(test_img_int)
|
|
img_eq1 = exposure.equalize_hist(test_img_int, nbins=3)
|
|
np.testing.assert_allclose(img_eq0, img_eq1)
|
|
|
|
|
|
def test_equalize_ubyte():
|
|
with expected_warnings(['precision loss']):
|
|
img = skimage.img_as_ubyte(test_img)
|
|
img_eq = exposure.equalize_hist(img)
|
|
|
|
cdf, bin_edges = exposure.cumulative_distribution(img_eq)
|
|
check_cdf_slope(cdf)
|
|
|
|
|
|
def test_equalize_float():
|
|
img = skimage.img_as_float(test_img)
|
|
img_eq = exposure.equalize_hist(img)
|
|
|
|
cdf, bin_edges = exposure.cumulative_distribution(img_eq)
|
|
check_cdf_slope(cdf)
|
|
|
|
|
|
def test_equalize_masked():
|
|
img = skimage.img_as_float(test_img)
|
|
mask = np.zeros(test_img.shape)
|
|
mask[50:150, 50:250] = 1
|
|
img_mask_eq = exposure.equalize_hist(img, mask=mask)
|
|
img_eq = exposure.equalize_hist(img)
|
|
|
|
cdf, bin_edges = exposure.cumulative_distribution(img_mask_eq)
|
|
check_cdf_slope(cdf)
|
|
|
|
assert not (img_eq == img_mask_eq).all()
|
|
|
|
|
|
def check_cdf_slope(cdf):
|
|
"""Slope of cdf which should equal 1 for an equalized histogram."""
|
|
norm_intensity = np.linspace(0, 1, len(cdf))
|
|
slope, intercept = np.polyfit(norm_intensity, cdf, 1)
|
|
assert 0.9 < slope < 1.1
|
|
|
|
|
|
# Test intensity range
|
|
# ====================
|
|
|
|
|
|
def test_intensity_range_uint8():
|
|
image = np.array([0, 1], dtype=np.uint8)
|
|
input_and_expected = [('image', [0, 1]),
|
|
('dtype', [0, 255]),
|
|
((10, 20), [10, 20])]
|
|
for range_values, expected_values in input_and_expected:
|
|
out = intensity_range(image, range_values=range_values)
|
|
yield assert_array_equal, out, expected_values
|
|
|
|
|
|
def test_intensity_range_float():
|
|
image = np.array([0.1, 0.2], dtype=np.float64)
|
|
input_and_expected = [('image', [0.1, 0.2]),
|
|
('dtype', [-1, 1]),
|
|
((0.3, 0.4), [0.3, 0.4])]
|
|
for range_values, expected_values in input_and_expected:
|
|
out = intensity_range(image, range_values=range_values)
|
|
yield assert_array_equal, out, expected_values
|
|
|
|
|
|
def test_intensity_range_clipped_float():
|
|
image = np.array([0.1, 0.2], dtype=np.float64)
|
|
out = intensity_range(image, range_values='dtype', clip_negative=True)
|
|
assert_array_equal(out, (0, 1))
|
|
|
|
|
|
# Test rescale intensity
|
|
# ======================
|
|
|
|
|
|
uint10_max = 2**10 - 1
|
|
uint12_max = 2**12 - 1
|
|
uint14_max = 2**14 - 1
|
|
uint16_max = 2**16 - 1
|
|
|
|
|
|
def test_rescale_stretch():
|
|
image = np.array([51, 102, 153], dtype=np.uint8)
|
|
out = exposure.rescale_intensity(image)
|
|
assert out.dtype == np.uint8
|
|
assert_close(out, [0, 127, 255])
|
|
|
|
|
|
def test_rescale_shrink():
|
|
image = np.array([51., 102., 153.])
|
|
out = exposure.rescale_intensity(image)
|
|
assert_close(out, [0, 0.5, 1])
|
|
|
|
|
|
def test_rescale_in_range():
|
|
image = np.array([51., 102., 153.])
|
|
out = exposure.rescale_intensity(image, in_range=(0, 255))
|
|
assert_close(out, [0.2, 0.4, 0.6])
|
|
|
|
|
|
def test_rescale_in_range_clip():
|
|
image = np.array([51., 102., 153.])
|
|
out = exposure.rescale_intensity(image, in_range=(0, 102))
|
|
assert_close(out, [0.5, 1, 1])
|
|
|
|
|
|
def test_rescale_out_range():
|
|
image = np.array([-10, 0, 10], dtype=np.int8)
|
|
out = exposure.rescale_intensity(image, out_range=(0, 127))
|
|
assert out.dtype == np.int8
|
|
assert_close(out, [0, 63, 127])
|
|
|
|
|
|
def test_rescale_named_in_range():
|
|
image = np.array([0, uint10_max, uint10_max + 100], dtype=np.uint16)
|
|
out = exposure.rescale_intensity(image, in_range='uint10')
|
|
assert_close(out, [0, uint16_max, uint16_max])
|
|
|
|
|
|
def test_rescale_named_out_range():
|
|
image = np.array([0, uint16_max], dtype=np.uint16)
|
|
out = exposure.rescale_intensity(image, out_range='uint10')
|
|
assert_close(out, [0, uint10_max])
|
|
|
|
|
|
def test_rescale_uint12_limits():
|
|
image = np.array([0, uint16_max], dtype=np.uint16)
|
|
out = exposure.rescale_intensity(image, out_range='uint12')
|
|
assert_close(out, [0, uint12_max])
|
|
|
|
|
|
def test_rescale_uint14_limits():
|
|
image = np.array([0, uint16_max], dtype=np.uint16)
|
|
out = exposure.rescale_intensity(image, out_range='uint14')
|
|
assert_close(out, [0, uint14_max])
|
|
|
|
|
|
# Test adaptive histogram equalization
|
|
# ====================================
|
|
|
|
def test_adapthist_scalar():
|
|
"""Test a scalar uint8 image
|
|
"""
|
|
img = skimage.img_as_ubyte(data.moon())
|
|
adapted = exposure.equalize_adapthist(img, clip_limit=0.02)
|
|
assert adapted.min() == 0.0
|
|
assert adapted.max() == 1.0
|
|
assert img.shape == adapted.shape
|
|
full_scale = skimage.exposure.rescale_intensity(skimage.img_as_float(img))
|
|
|
|
assert_almost_equal = np.testing.assert_almost_equal
|
|
assert_almost_equal(peak_snr(full_scale, adapted), 101.2295, 3)
|
|
assert_almost_equal(norm_brightness_err(full_scale, adapted),
|
|
0.041, 3)
|
|
return img, adapted
|
|
|
|
|
|
def test_adapthist_grayscale():
|
|
"""Test a grayscale float image
|
|
"""
|
|
img = skimage.img_as_float(data.astronaut())
|
|
img = rgb2gray(img)
|
|
img = np.dstack((img, img, img))
|
|
with expected_warnings(['precision loss|non-contiguous input']):
|
|
adapted = exposure.equalize_adapthist(img, 10, 9, clip_limit=0.01,
|
|
nbins=128)
|
|
assert_almost_equal = np.testing.assert_almost_equal
|
|
assert img.shape == adapted.shape
|
|
assert_almost_equal(peak_snr(img, adapted), 97.6876, 3)
|
|
assert_almost_equal(norm_brightness_err(img, adapted), 0.0591, 3)
|
|
return data, adapted
|
|
|
|
|
|
def test_adapthist_color():
|
|
"""Test an RGB color uint16 image
|
|
"""
|
|
img = skimage.img_as_uint(data.astronaut())
|
|
with warnings.catch_warnings(record=True) as w:
|
|
warnings.simplefilter('always')
|
|
hist, bin_centers = exposure.histogram(img)
|
|
assert len(w) > 0
|
|
with expected_warnings(['precision loss']):
|
|
adapted = exposure.equalize_adapthist(img, clip_limit=0.01)
|
|
|
|
assert_almost_equal = np.testing.assert_almost_equal
|
|
assert adapted.min() == 0
|
|
assert adapted.max() == 1.0
|
|
assert img.shape == adapted.shape
|
|
full_scale = skimage.exposure.rescale_intensity(img)
|
|
assert_almost_equal(peak_snr(full_scale, adapted), 109.6, 1)
|
|
assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.02, 2)
|
|
return data, adapted
|
|
|
|
|
|
def test_adapthist_alpha():
|
|
"""Test an RGBA color image
|
|
"""
|
|
img = skimage.img_as_float(data.astronaut())
|
|
alpha = np.ones((img.shape[0], img.shape[1]), dtype=float)
|
|
img = np.dstack((img, alpha))
|
|
with expected_warnings(['precision loss']):
|
|
adapted = exposure.equalize_adapthist(img)
|
|
assert adapted.shape != img.shape
|
|
img = img[:, :, :3]
|
|
full_scale = skimage.exposure.rescale_intensity(img)
|
|
assert img.shape == adapted.shape
|
|
assert_almost_equal = np.testing.assert_almost_equal
|
|
assert_almost_equal(peak_snr(full_scale, adapted), 109.60, 2)
|
|
assert_almost_equal(norm_brightness_err(full_scale, adapted), 0.0235, 3)
|
|
|
|
|
|
def peak_snr(img1, img2):
|
|
"""Peak signal to noise ratio of two images
|
|
|
|
Parameters
|
|
----------
|
|
img1 : array-like
|
|
img2 : array-like
|
|
|
|
Returns
|
|
-------
|
|
peak_snr : float
|
|
Peak signal to noise ratio
|
|
"""
|
|
if img1.ndim == 3:
|
|
img1, img2 = rgb2gray(img1.copy()), rgb2gray(img2.copy())
|
|
img1 = skimage.img_as_float(img1)
|
|
img2 = skimage.img_as_float(img2)
|
|
mse = 1. / img1.size * np.square(img1 - img2).sum()
|
|
_, max_ = dtype_range[img1.dtype.type]
|
|
return 20 * np.log(max_ / mse)
|
|
|
|
|
|
def norm_brightness_err(img1, img2):
|
|
"""Normalized Absolute Mean Brightness Error between two images
|
|
|
|
Parameters
|
|
----------
|
|
img1 : array-like
|
|
img2 : array-like
|
|
|
|
Returns
|
|
-------
|
|
norm_brightness_error : float
|
|
Normalized absolute mean brightness error
|
|
"""
|
|
if img1.ndim == 3:
|
|
img1, img2 = rgb2gray(img1), rgb2gray(img2)
|
|
ambe = np.abs(img1.mean() - img2.mean())
|
|
nbe = ambe / dtype_range[img1.dtype.type][1]
|
|
return nbe
|
|
|
|
|
|
|
|
# Test Gamma Correction
|
|
# =====================
|
|
|
|
def test_adjust_gamma_one():
|
|
"""Same image should be returned for gamma equal to one"""
|
|
image = np.random.uniform(0, 255, (8, 8))
|
|
result = exposure.adjust_gamma(image, 1)
|
|
assert_array_equal(result, image)
|
|
|
|
|
|
def test_adjust_gamma_zero():
|
|
"""White image should be returned for gamma equal to zero"""
|
|
image = np.random.uniform(0, 255, (8, 8))
|
|
result = exposure.adjust_gamma(image, 0)
|
|
dtype = image.dtype.type
|
|
assert_array_equal(result, dtype_range[dtype][1])
|
|
|
|
|
|
def test_adjust_gamma_less_one():
|
|
"""Verifying the output with expected results for gamma
|
|
correction with gamma equal to half"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 0, 31, 45, 55, 63, 71, 78, 84],
|
|
[ 90, 95, 100, 105, 110, 115, 119, 123],
|
|
[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=np.uint8)
|
|
|
|
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 = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 0, 0, 0, 0, 1, 1, 2, 3],
|
|
[ 4, 5, 6, 7, 9, 10, 12, 14],
|
|
[ 16, 18, 20, 22, 25, 27, 30, 33],
|
|
[ 36, 39, 42, 45, 49, 52, 56, 60],
|
|
[ 64, 68, 72, 76, 81, 85, 90, 95],
|
|
[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=np.uint8)
|
|
|
|
result = exposure.adjust_gamma(image, 2)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_adjust_gamma_neggative():
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
assert_raises(ValueError, exposure.adjust_gamma, image, -1)
|
|
|
|
|
|
# Test Logarithmic Correction
|
|
# ===========================
|
|
|
|
def test_adjust_log():
|
|
"""Verifying the output with expected results for logarithmic
|
|
correction with multiplier constant multiplier equal to unity"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 0, 5, 11, 16, 22, 27, 33, 38],
|
|
[ 43, 48, 53, 58, 63, 68, 73, 77],
|
|
[ 82, 86, 91, 95, 100, 104, 109, 113],
|
|
[117, 121, 125, 129, 133, 137, 141, 145],
|
|
[149, 153, 157, 160, 164, 168, 172, 175],
|
|
[179, 182, 186, 189, 193, 196, 199, 203],
|
|
[206, 209, 213, 216, 219, 222, 225, 228],
|
|
[231, 234, 238, 241, 244, 246, 249, 252]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_log(image, 1)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_adjust_inv_log():
|
|
"""Verifying the output with expected results for inverse logarithmic
|
|
correction with multiplier constant multiplier equal to unity"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 0, 2, 5, 8, 11, 14, 17, 20],
|
|
[ 23, 26, 29, 32, 35, 38, 41, 45],
|
|
[ 48, 51, 55, 58, 61, 65, 68, 72],
|
|
[ 76, 79, 83, 87, 90, 94, 98, 102],
|
|
[106, 110, 114, 118, 122, 126, 130, 134],
|
|
[138, 143, 147, 151, 156, 160, 165, 170],
|
|
[174, 179, 184, 188, 193, 198, 203, 208],
|
|
[213, 218, 224, 229, 234, 239, 245, 250]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_log(image, 1, True)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
# Test Sigmoid Correction
|
|
# =======================
|
|
|
|
def test_adjust_sigmoid_cutoff_one():
|
|
"""Verifying the output with expected results for sigmoid correction
|
|
with cutoff equal to one and gain of 5"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 1, 1, 1, 2, 2, 2, 2, 2],
|
|
[ 3, 3, 3, 4, 4, 4, 5, 5],
|
|
[ 5, 6, 6, 7, 7, 8, 9, 10],
|
|
[ 10, 11, 12, 13, 14, 15, 16, 18],
|
|
[ 19, 20, 22, 24, 25, 27, 29, 32],
|
|
[ 34, 36, 39, 41, 44, 47, 50, 54],
|
|
[ 57, 61, 64, 68, 72, 76, 80, 85],
|
|
[ 89, 94, 99, 104, 108, 113, 118, 123]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_sigmoid(image, 1, 5)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_adjust_sigmoid_cutoff_zero():
|
|
"""Verifying the output with expected results for sigmoid correction
|
|
with cutoff equal to zero and gain of 10"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[127, 137, 147, 156, 166, 175, 183, 191],
|
|
[198, 205, 211, 216, 221, 225, 229, 232],
|
|
[235, 238, 240, 242, 244, 245, 247, 248],
|
|
[249, 250, 250, 251, 251, 252, 252, 253],
|
|
[253, 253, 253, 253, 254, 254, 254, 254],
|
|
[254, 254, 254, 254, 254, 254, 254, 254],
|
|
[254, 254, 254, 254, 254, 254, 254, 254],
|
|
[254, 254, 254, 254, 254, 254, 254, 254]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_sigmoid(image, 0, 10)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_adjust_sigmoid_cutoff_half():
|
|
"""Verifying the output with expected results for sigmoid correction
|
|
with cutoff equal to half and gain of 10"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[ 1, 1, 2, 2, 3, 3, 4, 5],
|
|
[ 5, 6, 7, 9, 10, 12, 14, 16],
|
|
[ 19, 22, 25, 29, 34, 39, 44, 50],
|
|
[ 57, 64, 72, 80, 89, 99, 108, 118],
|
|
[128, 138, 148, 158, 167, 176, 184, 192],
|
|
[199, 205, 211, 217, 221, 226, 229, 233],
|
|
[236, 238, 240, 242, 244, 246, 247, 248],
|
|
[249, 250, 250, 251, 251, 252, 252, 253]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_sigmoid(image, 0.5, 10)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_adjust_inv_sigmoid_cutoff_half():
|
|
"""Verifying the output with expected results for inverse sigmoid
|
|
correction with cutoff equal to half and gain of 10"""
|
|
image = np.arange(0, 255, 4, np.uint8).reshape(8,8)
|
|
expected = np.array([[253, 253, 252, 252, 251, 251, 250, 249],
|
|
[249, 248, 247, 245, 244, 242, 240, 238],
|
|
[235, 232, 229, 225, 220, 215, 210, 204],
|
|
[197, 190, 182, 174, 165, 155, 146, 136],
|
|
[126, 116, 106, 96, 87, 78, 70, 62],
|
|
[ 55, 49, 43, 37, 33, 28, 25, 21],
|
|
[ 18, 16, 14, 12, 10, 8, 7, 6],
|
|
[ 5, 4, 4, 3, 3, 2, 2, 1]], dtype=np.uint8)
|
|
|
|
result = exposure.adjust_sigmoid(image, 0.5, 10, True)
|
|
assert_array_equal(result, expected)
|
|
|
|
|
|
def test_negative():
|
|
image = np.arange(-10, 245, 4).reshape(8, 8).astype(np.double)
|
|
assert_raises(ValueError, exposure.adjust_gamma, image)
|
|
|
|
|
|
def test_is_low_contrast():
|
|
image = np.linspace(0, 0.04, 100)
|
|
assert exposure.is_low_contrast(image)
|
|
image[-1] = 1
|
|
assert exposure.is_low_contrast(image)
|
|
assert not exposure.is_low_contrast(image, upper_percentile=100)
|
|
|
|
image = (image * 255).astype(np.uint8)
|
|
assert exposure.is_low_contrast(image)
|
|
assert not exposure.is_low_contrast(image, upper_percentile=100)
|
|
|
|
image = (image.astype(np.uint16)) * 2**8
|
|
assert exposure.is_low_contrast(image)
|
|
assert not exposure.is_low_contrast(image, upper_percentile=100)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from numpy import testing
|
|
testing.run_module_suite()
|