diff --git a/skimage/exposure/__init__.py b/skimage/exposure/__init__.py index aba05dae..b873c339 100644 --- a/skimage/exposure/__init__.py +++ b/skimage/exposure/__init__.py @@ -1,5 +1,7 @@ from .exposure import histogram, equalize, equalize_hist, \ - rescale_intensity, cumulative_distribution + rescale_intensity, cumulative_distribution, \ + adjust_gamma, adjust_sigmoid, adjust_log + from ._adapthist import equalize_adapthist __all__ = ['histogram', @@ -7,4 +9,7 @@ __all__ = ['histogram', 'equalize_hist', 'equalize_adapthist', 'rescale_intensity', - 'cumulative_distribution'] + 'cumulative_distribution', + 'adjust_gamma', + 'adjust_sigmoid', + 'adjust_log'] diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 84462931..a26e5630 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -2,14 +2,13 @@ import warnings import numpy as np from skimage import img_as_float -from skimage.util.dtype import dtype_range -import skimage.color as color -from skimage.util.dtype import convert +from skimage.util.dtype import dtype_range, dtype_limits from skimage._shared.utils import deprecated __all__ = ['histogram', 'cumulative_distribution', 'equalize', - 'rescale_intensity'] + 'rescale_intensity', 'adjust_gamma', + 'adjust_log', 'adjust_sigmoid'] def histogram(image, nbins=256): @@ -216,3 +215,139 @@ def rescale_intensity(image, in_range=None, out_range=None): image = (image - imin) / float(imax - imin) return dtype(image * (omax - omin) + omin) + + +def _assert_non_negative(image): + + if np.any(image < 0): + raise ValueError('Image Correction methods work correctly only on ' + 'images with non-negative values. Use ' + 'skimage.exposure.rescale_intensity.') + + +def adjust_gamma(image, gamma=1, gain=1): + """Performs Gamma Correction on the input image. + + Also known as Power Law Transform. + This function transforms the input image pixelwise according to the + equation ``O = I**gamma`` after scaling each pixel to the range 0 to 1. + + Parameters + ---------- + image : ndarray + Input image. + gamma : float + Non negative real number. Default value is 1. + gain : float + The constant multiplier. Default value is 1. + + Returns + ------- + out : ndarray + Gamma corrected output image. + + Notes + ----- + For gamma greater than 1, the histogram will shift towards left and + the output image will be darker than the input image. + + For gamma less than 1, the histogram will shift towards right and + the output image will be brighter than the input image. + + References + ---------- + .. [1] http://en.wikipedia.org/wiki/Gamma_correction + + """ + _assert_non_negative(image) + dtype = image.dtype.type + + if gamma < 0: + return "Gamma should be a non-negative real number" + + scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0]) + + out = ((image / scale) ** gamma) * scale * gain + return dtype(out) + + +def adjust_log(image, gain=1, inv=False): + """Performs Logarithmic correction on the input image. + + This function transforms the input image pixelwise according to the + equation ``O = gain*log(1 + I)`` after scaling each pixel to the range 0 to 1. + For inverse logarithmic correction, the equation is ``O = gain*(2**I - 1)``. + + Parameters + ---------- + image : ndarray + Input image. + gain : float + The constant multiplier. Default value is 1. + inv : float + If True, it performs inverse logarithmic correction, + else correction will be logarithmic. Defaults to False. + + Returns + ------- + out : ndarray + Logarithm corrected output image. + + References + ---------- + .. [1] http://www.ece.ucsb.edu/Faculty/Manjunath/courses/ece178W03/EnhancePart1.pdf + + """ + _assert_non_negative(image) + dtype = image.dtype.type + scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0]) + + if inv: + out = (2 ** (image / scale) - 1) * scale * gain + return dtype(out) + + out = np.log2(1 + image / scale) * scale * gain + return dtype(out) + + +def adjust_sigmoid(image, cutoff=0.5, gain=10, inv=False): + """Performs Sigmoid Correction on the input image. + + Also known as Contrast Adjustment. + This function transforms the input image pixelwise according to the + equation ``O = 1/(1 + exp*(gain*(cutoff - I)))`` after scaling each pixel + to the range 0 to 1. + + Parameters + ---------- + image : ndarray + Input image. + cutoff : float + Cutoff of the sigmoid function that shifts the characteristic curve + in horizontal direction. Default value is 0.5. + gain : float + The constant multiplier in exponential's power of sigmoid function. + Default value is 10. + inv : bool + If True, returns the negative sigmoid correction. Defaults to False. + + Returns + ------- + out : ndarray + Sigmoid corrected output image. + + References + ---------- + .. [1] http://bme.med.upatras.gr/improc/matalb_code_toc.htm#12. Adjust Contrast : + + """ + _assert_non_negative(image) + dtype = image.dtype.type + scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0]) + + if inv: + out = (1 - 1 / (1 + np.exp(gain * (cutoff - image/scale)))) * scale + return dtype(out) + + out = (1 / (1 + np.exp(gain * (cutoff - image/scale)))) * scale + return dtype(out) diff --git a/skimage/exposure/tests/test_exposure.py b/skimage/exposure/tests/test_exposure.py index a5f5aef9..2b696b32 100644 --- a/skimage/exposure/tests/test_exposure.py +++ b/skimage/exposure/tests/test_exposure.py @@ -2,6 +2,7 @@ import warnings import numpy as np from numpy.testing import assert_array_almost_equal as assert_close +from numpy.testing import assert_array_equal import skimage from skimage import data from skimage import exposure @@ -175,3 +176,163 @@ def norm_brightness_err(img1, img2): if __name__ == '__main__': from numpy import testing testing.run_module_suite() + + +# 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) + + +# 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)