From 0e61374a899ab776e2652622759bfb741f4872b3 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 7 Mar 2015 09:29:04 -0600 Subject: [PATCH] Add a helper function to check for low contrast 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 --- skimage/exposure/__init__.py | 6 ++- skimage/exposure/exposure.py | 48 ++++++++++++++++++++++- skimage/exposure/tests/test_exposure.py | 16 ++++++++ skimage/io/_io.py | 5 +++ skimage/io/_plugins/matplotlib_plugin.py | 10 +++-- skimage/io/tests/test_mpl_imshow.py | 3 +- skimage/io/tests/test_pil.py | 3 +- skimage/measure/tests/test_regionprops.py | 2 +- skimage/util/dtype.py | 4 ++ 9 files changed, 87 insertions(+), 10 deletions(-) diff --git a/skimage/exposure/__init__.py b/skimage/exposure/__init__.py index d78cf927..1950f37a 100644 --- a/skimage/exposure/__init__.py +++ b/skimage/exposure/__init__.py @@ -1,6 +1,7 @@ from .exposure import histogram, equalize_hist, \ rescale_intensity, cumulative_distribution, \ - adjust_gamma, adjust_sigmoid, adjust_log + adjust_gamma, adjust_sigmoid, adjust_log, \ + is_low_contrast from ._adapthist import equalize_adapthist @@ -12,4 +13,5 @@ __all__ = ['histogram', 'cumulative_distribution', 'adjust_gamma', 'adjust_sigmoid', - 'adjust_log'] + 'adjust_log', + 'is_low_contrast'] diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 8774cf9b..dc2faebd 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -1,7 +1,8 @@ +from __future__ import division import warnings import numpy as np -from .. import img_as_float +from ..color import rgb2gray from ..util.dtype import dtype_range, dtype_limits @@ -463,3 +464,48 @@ def adjust_sigmoid(image, cutoff=0.5, gain=10, inv=False): out = (1 / (1 + np.exp(gain * (cutoff - image / scale)))) * scale return dtype(out) + + +def is_low_contrast(image, fraction_threshold=0.05, lower_percentile=1, + upper_percentile=99, method='linear'): + """Detemine if an image is low contrast. + + Parameters + ---------- + image : array-like + The image under test. + fraction_threshold : float, optional + The low contrast fraction threshold. + lower_bound : float, optional + Disregard values below this percentile when computing image contrast. + upper_bound : float, optional + Disregard values above this percentile when computing image contrast. + method : str, optional + The contrast determination method. Right now the only available + option is "linear". + + Returns + ------- + out : bool + True when the image is determined to be low contrast. + + Examples + -------- + >>> image = np.linspace(0, 0.04, 100) + >>> is_low_contrast(image) + True + >>> image[-1] = 1 + >>> is_low_contrast(image) + True + >>> is_low_contrast(image, upper_percentile=100) + False + """ + image = np.asanyarray(image) + if image.ndim == 3 and image.shape[2] in [3, 4]: + image = rgb2gray(image) + + dlimits = dtype_limits(image) + limits = np.percentile(image, [lower_percentile, upper_percentile]) + ratio = (limits[1] - limits[0]) / (dlimits[1] - dlimits[0]) + + return ratio < fraction_threshold diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py index 1316ea4e..265ce246 100644 --- a/skimage/exposure/tests/test_exposure.py +++ b/skimage/exposure/tests/test_exposure.py @@ -472,6 +472,22 @@ def test_negative(): 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() diff --git a/skimage/io/_io.py b/skimage/io/_io.py index f44bfd8b..e76c36f6 100644 --- a/skimage/io/_io.py +++ b/skimage/io/_io.py @@ -1,4 +1,5 @@ from io import BytesIO +import warnings import numpy as np import six @@ -6,6 +7,8 @@ import six from ..io.manage_plugins import call_plugin from ..color import rgb2grey from .util import file_or_url_context +from ..exposure import is_low_contrast +from .._shared._warnings import all_warnings __all__ = ['Image', 'imread', 'imread_collection', 'imsave', 'imshow', 'show'] @@ -152,6 +155,8 @@ def imsave(fname, arr, plugin=None, **plugin_args): Passed to the given plugin. """ + if is_low_contrast(arr): + warnings.warn('%s is a low contrast image' % fname) return call_plugin('imsave', fname, arr, plugin=plugin, **plugin_args) diff --git a/skimage/io/_plugins/matplotlib_plugin.py b/skimage/io/_plugins/matplotlib_plugin.py index 2de61ecf..32ebf2fd 100644 --- a/skimage/io/_plugins/matplotlib_plugin.py +++ b/skimage/io/_plugins/matplotlib_plugin.py @@ -2,7 +2,9 @@ from collections import namedtuple import numpy as np import warnings import matplotlib.pyplot as plt -from skimage.util import dtype as dtypes +from ...util import dtype as dtypes +from ...exposure import is_low_contrast +from ..._shared._warnings import all_warnings _default_colormap = 'gray' @@ -49,7 +51,7 @@ def _get_image_properties(image): out_of_range_float = (np.issubdtype(image.dtype, np.float) and (immin < lo or immax > hi)) low_dynamic_range = (immin != immax and - (float(immax - immin) / (hi - lo)) < (1. / 255)) + is_low_contrast(image)) unsupported_dtype = image.dtype not in dtypes._supported_types return ImageProperties(signed, out_of_range_float, @@ -72,8 +74,8 @@ def _raise_warnings(image_properties): warnings.warn("Low image dynamic range; displaying image with " "stretched contrast.") if ip.out_of_range_float: - warnings.warn("Float image out of standard range; displaying image " - "with stretched contrast.") + warnings.warn("Float image out of standard range; displaying " + "image with stretched contrast.") def _get_display_range(image): diff --git a/skimage/io/tests/test_mpl_imshow.py b/skimage/io/tests/test_mpl_imshow.py index aad51048..166c8cbc 100644 --- a/skimage/io/tests/test_mpl_imshow.py +++ b/skimage/io/tests/test_mpl_imshow.py @@ -87,7 +87,8 @@ def test_outside_standard_range(): def test_nonstandard_type(): plt.figure() - with expected_warnings(["Non-standard image type"]): + with expected_warnings(["Non-standard image type", + "Low image dynamic range"]): ax_im = io.imshow(im64) assert ax_im.get_clim() == (im64.min(), im64.max()) assert n_subplots(ax_im) == 2 diff --git a/skimage/io/tests/test_pil.py b/skimage/io/tests/test_pil.py index fc28ce89..e8909503 100644 --- a/skimage/io/tests/test_pil.py +++ b/skimage/io/tests/test_pil.py @@ -147,7 +147,8 @@ def test_imsave_filelike(): s = BytesIO() # save to file-like object - with expected_warnings(['precision loss|unclosed file']): + with expected_warnings(['precision loss|unclosed file', + 'is a low contrast image']): imsave(s, image) # read from file-like object diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 3ac735c7..487d782e 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -26,7 +26,7 @@ INTENSITY_SAMPLE[1, 9:11] = 2 def test_all_props(): region = regionprops(SAMPLE, INTENSITY_SAMPLE)[0] for prop in PROPS: - assert_equal(region[prop], getattr(region, PROPS[prop])) + assert_almost_equal(region[prop], getattr(region, PROPS[prop])) def test_dtype(): diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 0e7bc060..1c594534 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -11,6 +11,10 @@ dtype_range = {np.bool_: (False, True), np.uint16: (0, 65535), np.int8: (-128, 127), np.int16: (-32768, 32767), + np.int64: (-2**63, 2**63 - 1), + np.uint64: (0, 2**64 - 1), + np.int32: (-2**31, 2**31 - 1), + np.uint32: (0, 2**32 - 1), np.float32: (-1, 1), np.float64: (-1, 1)}