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)}