1import numpy as np 2 3from ..color import rgb2gray, rgba2rgb 4from ..util.dtype import dtype_range, dtype_limits 5from .._shared import utils 6 7 8__all__ = ['histogram', 'cumulative_distribution', 'equalize_hist', 9 'rescale_intensity', 'adjust_gamma', 'adjust_log', 'adjust_sigmoid'] 10 11 12DTYPE_RANGE = dtype_range.copy() 13DTYPE_RANGE.update((d.__name__, limits) for d, limits in dtype_range.items()) 14DTYPE_RANGE.update({'uint10': (0, 2 ** 10 - 1), 15 'uint12': (0, 2 ** 12 - 1), 16 'uint14': (0, 2 ** 14 - 1), 17 'bool': dtype_range[bool], 18 'float': dtype_range[np.float64]}) 19 20 21def _offset_array(arr, low_boundary, high_boundary): 22 """Offset the array to get the lowest value at 0 if negative.""" 23 if low_boundary < 0: 24 offset = low_boundary 25 dyn_range = high_boundary - low_boundary 26 # get smallest dtype that can hold both minimum and offset maximum 27 offset_dtype = np.promote_types(np.min_scalar_type(dyn_range), 28 np.min_scalar_type(low_boundary)) 29 if arr.dtype != offset_dtype: 30 # prevent overflow errors when offsetting 31 arr = arr.astype(offset_dtype) 32 arr = arr - offset 33 else: 34 offset = 0 35 return arr, offset 36 37 38def _bincount_histogram_centers(image, source_range): 39 """Compute bin centers for bincount-based histogram.""" 40 if source_range not in ['image', 'dtype']: 41 raise ValueError( 42 f'Incorrect value for `source_range` argument: {source_range}' 43 ) 44 if source_range == 'image': 45 image_min = int(image.min().astype(np.int64)) 46 image_max = int(image.max().astype(np.int64)) 47 elif source_range == 'dtype': 48 image_min, image_max = dtype_limits(image, clip_negative=False) 49 bin_centers = np.arange(image_min, image_max + 1) 50 return bin_centers 51 52 53def _bincount_histogram(image, source_range, bin_centers=None): 54 """ 55 Efficient histogram calculation for an image of integers. 56 57 This function is significantly more efficient than np.histogram but 58 works only on images of integers. It is based on np.bincount. 59 60 Parameters 61 ---------- 62 image : array 63 Input image. 64 source_range : string 65 'image' determines the range from the input image. 66 'dtype' determines the range from the expected range of the images 67 of that data type. 68 69 Returns 70 ------- 71 hist : array 72 The values of the histogram. 73 bin_centers : array 74 The values at the center of the bins. 75 """ 76 if bin_centers is None: 77 bin_centers = _bincount_histogram_centers(image, source_range) 78 image_min, image_max = bin_centers[0], bin_centers[-1] 79 image, offset = _offset_array(image, image_min, image_max) 80 hist = np.bincount(image.ravel(), minlength=image_max - image_min + 1) 81 if source_range == 'image': 82 idx = max(image_min, 0) 83 hist = hist[idx:] 84 return hist, bin_centers 85 86 87def _get_outer_edges(image, hist_range): 88 """Determine the outer bin edges to use for `numpy.histogram`. 89 90 These are obtained from either the image or hist_range. 91 92 Parameters 93 ---------- 94 image : ndarray 95 Image for which the histogram is to be computed. 96 hist_range: 2-tuple of int or None 97 Range of values covered by the histogram bins. If None, the minimum 98 and maximum values of `image` are used. 99 100 Returns 101 ------- 102 first_edge, last_edge : int 103 The range spanned by the histogram bins. 104 105 Notes 106 ----- 107 This function is adapted from ``np.lib.histograms._get_outer_edges``. 108 """ 109 if hist_range is not None: 110 first_edge, last_edge = hist_range 111 if first_edge > last_edge: 112 raise ValueError( 113 "max must be larger than min in hist_range parameter." 114 ) 115 if not (np.isfinite(first_edge) and np.isfinite(last_edge)): 116 raise ValueError( 117 f'supplied hist_range of [{first_edge}, {last_edge}] is ' 118 f'not finite' 119 ) 120 elif image.size == 0: 121 # handle empty arrays. Can't determine hist_range, so use 0-1. 122 first_edge, last_edge = 0, 1 123 else: 124 first_edge, last_edge = image.min(), image.max() 125 if not (np.isfinite(first_edge) and np.isfinite(last_edge)): 126 raise ValueError( 127 f'autodetected hist_range of [{first_edge}, {last_edge}] is ' 128 f'not finite' 129 ) 130 131 # expand empty hist_range to avoid divide by zero 132 if first_edge == last_edge: 133 first_edge = first_edge - 0.5 134 last_edge = last_edge + 0.5 135 136 return first_edge, last_edge 137 138 139def _get_bin_edges(image, nbins, hist_range): 140 """Computes histogram bins for use with `numpy.histogram`. 141 142 Parameters 143 ---------- 144 image : ndarray 145 Image for which the histogram is to be computed. 146 nbins : int 147 The number of bins. 148 hist_range: 2-tuple of int 149 Range of values covered by the histogram bins. 150 151 Returns 152 ------- 153 bin_edges : ndarray 154 The histogram bin edges. 155 156 Notes 157 ----- 158 This function is a simplified version of 159 ``np.lib.histograms._get_bin_edges`` that only supports uniform bins. 160 """ 161 first_edge, last_edge = _get_outer_edges(image, hist_range) 162 # numpy/gh-10322 means that type resolution rules are dependent on array 163 # shapes. To avoid this causing problems, we pick a type now and stick 164 # with it throughout. 165 bin_type = np.result_type(first_edge, last_edge, image) 166 if np.issubdtype(bin_type, np.integer): 167 bin_type = np.result_type(bin_type, float) 168 169 # compute bin edges 170 bin_edges = np.linspace( 171 first_edge, last_edge, nbins + 1, endpoint=True, dtype=bin_type 172 ) 173 return bin_edges 174 175 176def _get_numpy_hist_range(image, source_range): 177 if source_range == 'image': 178 hist_range = None 179 elif source_range == 'dtype': 180 hist_range = dtype_limits(image, clip_negative=False) 181 else: 182 ValueError('Wrong value for the `source_range` argument') 183 return hist_range 184 185 186@utils.channel_as_last_axis(multichannel_output=False) 187def histogram(image, nbins=256, source_range='image', normalize=False, *, 188 channel_axis=None): 189 """Return histogram of image. 190 191 Unlike `numpy.histogram`, this function returns the centers of bins and 192 does not rebin integer arrays. For integer arrays, each integer value has 193 its own bin, which improves speed and intensity-resolution. 194 195 If `channel_axis` is not set, the histogram is computed on the flattened 196 image. For color or multichannel images, set ``channel_axis`` to use a 197 common binning for all channels. Alternatively, one may apply the function 198 separately on each channel to obtain a histogram for each color channel 199 with separate binning. 200 201 Parameters 202 ---------- 203 image : array 204 Input image. 205 nbins : int, optional 206 Number of bins used to calculate histogram. This value is ignored for 207 integer arrays. 208 source_range : string, optional 209 'image' (default) determines the range from the input image. 210 'dtype' determines the range from the expected range of the images 211 of that data type. 212 normalize : bool, optional 213 If True, normalize the histogram by the sum of its values. 214 channel_axis : int or None, optional 215 If None, the image is assumed to be a grayscale (single channel) image. 216 Otherwise, this parameter indicates which axis of the array corresponds 217 to channels. 218 219 Returns 220 ------- 221 hist : array 222 The values of the histogram. When ``channel_axis`` is not None, hist 223 will be a 2D array where the first axis corresponds to channels. 224 bin_centers : array 225 The values at the center of the bins. 226 227 See Also 228 -------- 229 cumulative_distribution 230 231 Examples 232 -------- 233 >>> from skimage import data, exposure, img_as_float 234 >>> image = img_as_float(data.camera()) 235 >>> np.histogram(image, bins=2) 236 (array([ 93585, 168559]), array([0. , 0.5, 1. ])) 237 >>> exposure.histogram(image, nbins=2) 238 (array([ 93585, 168559]), array([0.25, 0.75])) 239 """ 240 sh = image.shape 241 if len(sh) == 3 and sh[-1] < 4 and channel_axis is None: 242 utils.warn('This might be a color image. The histogram will be ' 243 'computed on the flattened image. You can instead ' 244 'apply this function to each color channel, or set ' 245 'channel_axis.') 246 247 if channel_axis is not None: 248 channels = sh[-1] 249 hist = [] 250 251 # compute bins based on the raveled array 252 if np.issubdtype(image.dtype, np.integer): 253 # here bins corresponds to the bin centers 254 bins = _bincount_histogram_centers(image, source_range) 255 else: 256 # determine the bin edges for np.histogram 257 hist_range = _get_numpy_hist_range(image, source_range) 258 bins = _get_bin_edges(image, nbins, hist_range) 259 260 for chan in range(channels): 261 h, bc = _histogram(image[..., chan], bins, source_range, normalize) 262 hist.append(h) 263 # Convert to numpy arrays 264 bin_centers = np.asarray(bc) 265 hist = np.stack(hist, axis=0) 266 else: 267 hist, bin_centers = _histogram(image, nbins, source_range, normalize) 268 269 return hist, bin_centers 270 271 272def _histogram(image, bins, source_range, normalize): 273 """ 274 275 Parameters 276 ---------- 277 image : ndarray 278 Image for which the histogram is to be computed. 279 bins : int or ndarray 280 The number of histogram bins. For images with integer dtype, an array 281 containing the bin centers can also be provided. For images with 282 floating point dtype, this can be an array of bin_edges for use by 283 ``np.histogram``. 284 source_range : string, optional 285 'image' (default) determines the range from the input image. 286 'dtype' determines the range from the expected range of the images 287 of that data type. 288 normalize : bool, optional 289 If True, normalize the histogram by the sum of its values. 290 """ 291 292 image = image.flatten() 293 # For integer types, histogramming with bincount is more efficient. 294 if np.issubdtype(image.dtype, np.integer): 295 bin_centers = bins if isinstance(bins, np.ndarray) else None 296 hist, bin_centers = _bincount_histogram( 297 image, source_range, bin_centers 298 ) 299 else: 300 hist_range = _get_numpy_hist_range(image, source_range) 301 hist, bin_edges = np.histogram(image, bins=bins, range=hist_range) 302 bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2. 303 304 if normalize: 305 hist = hist / np.sum(hist) 306 return hist, bin_centers 307 308 309def cumulative_distribution(image, nbins=256): 310 """Return cumulative distribution function (cdf) for the given image. 311 312 Parameters 313 ---------- 314 image : array 315 Image array. 316 nbins : int, optional 317 Number of bins for image histogram. 318 319 Returns 320 ------- 321 img_cdf : array 322 Values of cumulative distribution function. 323 bin_centers : array 324 Centers of bins. 325 326 See Also 327 -------- 328 histogram 329 330 References 331 ---------- 332 .. [1] https://en.wikipedia.org/wiki/Cumulative_distribution_function 333 334 Examples 335 -------- 336 >>> from skimage import data, exposure, img_as_float 337 >>> image = img_as_float(data.camera()) 338 >>> hi = exposure.histogram(image) 339 >>> cdf = exposure.cumulative_distribution(image) 340 >>> np.alltrue(cdf[0] == np.cumsum(hi[0])/float(image.size)) 341 True 342 """ 343 hist, bin_centers = histogram(image, nbins) 344 img_cdf = hist.cumsum() 345 img_cdf = img_cdf / float(img_cdf[-1]) 346 347 # cast img_cdf to single precision for float32 or float16 inputs 348 cdf_dtype = utils._supported_float_type(image.dtype) 349 img_cdf = img_cdf.astype(cdf_dtype, copy=False) 350 351 return img_cdf, bin_centers 352 353 354def equalize_hist(image, nbins=256, mask=None): 355 """Return image after histogram equalization. 356 357 Parameters 358 ---------- 359 image : array 360 Image array. 361 nbins : int, optional 362 Number of bins for image histogram. Note: this argument is 363 ignored for integer images, for which each integer is its own 364 bin. 365 mask : ndarray of bools or 0s and 1s, optional 366 Array of same shape as `image`. Only points at which mask == True 367 are used for the equalization, which is applied to the whole image. 368 369 Returns 370 ------- 371 out : float array 372 Image array after histogram equalization. 373 374 Notes 375 ----- 376 This function is adapted from [1]_ with the author's permission. 377 378 References 379 ---------- 380 .. [1] http://www.janeriksolem.net/histogram-equalization-with-python-and.html 381 .. [2] https://en.wikipedia.org/wiki/Histogram_equalization 382 383 """ 384 if mask is not None: 385 mask = np.array(mask, dtype=bool) 386 cdf, bin_centers = cumulative_distribution(image[mask], nbins) 387 else: 388 cdf, bin_centers = cumulative_distribution(image, nbins) 389 out = np.interp(image.flat, bin_centers, cdf) 390 out = out.reshape(image.shape) 391 # Unfortunately, np.interp currently always promotes to float64, so we 392 # have to cast back to single precision when float32 output is desired 393 return out.astype(utils._supported_float_type(image.dtype), copy=False) 394 395 396def intensity_range(image, range_values='image', clip_negative=False): 397 """Return image intensity range (min, max) based on desired value type. 398 399 Parameters 400 ---------- 401 image : array 402 Input image. 403 range_values : str or 2-tuple, optional 404 The image intensity range is configured by this parameter. 405 The possible values for this parameter are enumerated below. 406 407 'image' 408 Return image min/max as the range. 409 'dtype' 410 Return min/max of the image's dtype as the range. 411 dtype-name 412 Return intensity range based on desired `dtype`. Must be valid key 413 in `DTYPE_RANGE`. Note: `image` is ignored for this range type. 414 2-tuple 415 Return `range_values` as min/max intensities. Note that there's no 416 reason to use this function if you just want to specify the 417 intensity range explicitly. This option is included for functions 418 that use `intensity_range` to support all desired range types. 419 420 clip_negative : bool, optional 421 If True, clip the negative range (i.e. return 0 for min intensity) 422 even if the image dtype allows negative values. 423 """ 424 if range_values == 'dtype': 425 range_values = image.dtype.type 426 427 if range_values == 'image': 428 i_min = np.min(image) 429 i_max = np.max(image) 430 elif range_values in DTYPE_RANGE: 431 i_min, i_max = DTYPE_RANGE[range_values] 432 if clip_negative: 433 i_min = 0 434 else: 435 i_min, i_max = range_values 436 return i_min, i_max 437 438 439def _output_dtype(dtype_or_range, image_dtype): 440 """Determine the output dtype for rescale_intensity. 441 442 The dtype is determined according to the following rules: 443 - if ``dtype_or_range`` is a dtype, that is the output dtype. 444 - if ``dtype_or_range`` is a dtype string, that is the dtype used, unless 445 it is not a NumPy data type (e.g. 'uint12' for 12-bit unsigned integers), 446 in which case the data type that can contain it will be used 447 (e.g. uint16 in this case). 448 - if ``dtype_or_range`` is a pair of values, the output data type will be 449 ``_supported_float_type(image_dtype)``. This preserves float32 output for 450 float32 inputs. 451 452 Parameters 453 ---------- 454 dtype_or_range : type, string, or 2-tuple of int/float 455 The desired range for the output, expressed as either a NumPy dtype or 456 as a (min, max) pair of numbers. 457 image_dtype : np.dtype 458 The input image dtype. 459 460 Returns 461 ------- 462 out_dtype : type 463 The data type appropriate for the desired output. 464 """ 465 if type(dtype_or_range) in [list, tuple, np.ndarray]: 466 # pair of values: always return float. 467 return utils._supported_float_type(image_dtype) 468 if type(dtype_or_range) == type: 469 # already a type: return it 470 return dtype_or_range 471 if dtype_or_range in DTYPE_RANGE: 472 # string key in DTYPE_RANGE dictionary 473 try: 474 # if it's a canonical numpy dtype, convert 475 return np.dtype(dtype_or_range).type 476 except TypeError: # uint10, uint12, uint14 477 # otherwise, return uint16 478 return np.uint16 479 else: 480 raise ValueError( 481 'Incorrect value for out_range, should be a valid image data ' 482 f'type or a pair of values, got {dtype_or_range}.' 483 ) 484 485 486def rescale_intensity(image, in_range='image', out_range='dtype'): 487 """Return image after stretching or shrinking its intensity levels. 488 489 The desired intensity range of the input and output, `in_range` and 490 `out_range` respectively, are used to stretch or shrink the intensity range 491 of the input image. See examples below. 492 493 Parameters 494 ---------- 495 image : array 496 Image array. 497 in_range, out_range : str or 2-tuple, optional 498 Min and max intensity values of input and output image. 499 The possible values for this parameter are enumerated below. 500 501 'image' 502 Use image min/max as the intensity range. 503 'dtype' 504 Use min/max of the image's dtype as the intensity range. 505 dtype-name 506 Use intensity range based on desired `dtype`. Must be valid key 507 in `DTYPE_RANGE`. 508 2-tuple 509 Use `range_values` as explicit min/max intensities. 510 511 Returns 512 ------- 513 out : array 514 Image array after rescaling its intensity. This image is the same dtype 515 as the input image. 516 517 Notes 518 ----- 519 .. versionchanged:: 0.17 520 The dtype of the output array has changed to match the input dtype, or 521 float if the output range is specified by a pair of floats. 522 523 See Also 524 -------- 525 equalize_hist 526 527 Examples 528 -------- 529 By default, the min/max intensities of the input image are stretched to 530 the limits allowed by the image's dtype, since `in_range` defaults to 531 'image' and `out_range` defaults to 'dtype': 532 533 >>> image = np.array([51, 102, 153], dtype=np.uint8) 534 >>> rescale_intensity(image) 535 array([ 0, 127, 255], dtype=uint8) 536 537 It's easy to accidentally convert an image dtype from uint8 to float: 538 539 >>> 1.0 * image 540 array([ 51., 102., 153.]) 541 542 Use `rescale_intensity` to rescale to the proper range for float dtypes: 543 544 >>> image_float = 1.0 * image 545 >>> rescale_intensity(image_float) 546 array([0. , 0.5, 1. ]) 547 548 To maintain the low contrast of the original, use the `in_range` parameter: 549 550 >>> rescale_intensity(image_float, in_range=(0, 255)) 551 array([0.2, 0.4, 0.6]) 552 553 If the min/max value of `in_range` is more/less than the min/max image 554 intensity, then the intensity levels are clipped: 555 556 >>> rescale_intensity(image_float, in_range=(0, 102)) 557 array([0.5, 1. , 1. ]) 558 559 If you have an image with signed integers but want to rescale the image to 560 just the positive range, use the `out_range` parameter. In that case, the 561 output dtype will be float: 562 563 >>> image = np.array([-10, 0, 10], dtype=np.int8) 564 >>> rescale_intensity(image, out_range=(0, 127)) 565 array([ 0. , 63.5, 127. ]) 566 567 To get the desired range with a specific dtype, use ``.astype()``: 568 569 >>> rescale_intensity(image, out_range=(0, 127)).astype(np.int8) 570 array([ 0, 63, 127], dtype=int8) 571 572 If the input image is constant, the output will be clipped directly to the 573 output range: 574 >>> image = np.array([130, 130, 130], dtype=np.int32) 575 >>> rescale_intensity(image, out_range=(0, 127)).astype(np.int32) 576 array([127, 127, 127], dtype=int32) 577 """ 578 if out_range in ['dtype', 'image']: 579 out_dtype = _output_dtype(image.dtype.type, image.dtype) 580 else: 581 out_dtype = _output_dtype(out_range, image.dtype) 582 583 imin, imax = map(float, intensity_range(image, in_range)) 584 omin, omax = map(float, intensity_range(image, out_range, 585 clip_negative=(imin >= 0))) 586 587 if np.any(np.isnan([imin, imax, omin, omax])): 588 utils.warn( 589 "One or more intensity levels are NaN. Rescaling will broadcast " 590 "NaN to the full image. Provide intensity levels yourself to " 591 "avoid this. E.g. with np.nanmin(image), np.nanmax(image).", 592 stacklevel=2 593 ) 594 595 image = np.clip(image, imin, imax) 596 597 if imin != imax: 598 image = (image - imin) / (imax - imin) 599 return np.asarray(image * (omax - omin) + omin, dtype=out_dtype) 600 else: 601 return np.clip(image, omin, omax).astype(out_dtype) 602 603 604def _assert_non_negative(image): 605 606 if np.any(image < 0): 607 raise ValueError('Image Correction methods work correctly only on ' 608 'images with non-negative values. Use ' 609 'skimage.exposure.rescale_intensity.') 610 611 612def _adjust_gamma_u8(image, gamma, gain): 613 """LUT based implementation of gamma adjustment. 614 615 """ 616 lut = (255 * gain * (np.linspace(0, 1, 256) ** gamma)) 617 lut = np.minimum(lut, 255).astype('uint8') 618 return lut[image] 619 620 621def adjust_gamma(image, gamma=1, gain=1): 622 """Performs Gamma Correction on the input image. 623 624 Also known as Power Law Transform. 625 This function transforms the input image pixelwise according to the 626 equation ``O = I**gamma`` after scaling each pixel to the range 0 to 1. 627 628 Parameters 629 ---------- 630 image : ndarray 631 Input image. 632 gamma : float, optional 633 Non negative real number. Default value is 1. 634 gain : float, optional 635 The constant multiplier. Default value is 1. 636 637 Returns 638 ------- 639 out : ndarray 640 Gamma corrected output image. 641 642 See Also 643 -------- 644 adjust_log 645 646 Notes 647 ----- 648 For gamma greater than 1, the histogram will shift towards left and 649 the output image will be darker than the input image. 650 651 For gamma less than 1, the histogram will shift towards right and 652 the output image will be brighter than the input image. 653 654 References 655 ---------- 656 .. [1] https://en.wikipedia.org/wiki/Gamma_correction 657 658 Examples 659 -------- 660 >>> from skimage import data, exposure, img_as_float 661 >>> image = img_as_float(data.moon()) 662 >>> gamma_corrected = exposure.adjust_gamma(image, 2) 663 >>> # Output is darker for gamma > 1 664 >>> image.mean() > gamma_corrected.mean() 665 True 666 """ 667 if gamma < 0: 668 raise ValueError("Gamma should be a non-negative real number.") 669 670 dtype = image.dtype.type 671 672 if dtype is np.uint8: 673 out = _adjust_gamma_u8(image, gamma, gain) 674 else: 675 _assert_non_negative(image) 676 677 scale = float(dtype_limits(image, True)[1] 678 - dtype_limits(image, True)[0]) 679 680 out = (((image / scale) ** gamma) * scale * gain).astype(dtype) 681 682 return out 683 684 685def adjust_log(image, gain=1, inv=False): 686 """Performs Logarithmic correction on the input image. 687 688 This function transforms the input image pixelwise according to the 689 equation ``O = gain*log(1 + I)`` after scaling each pixel to the range 690 0 to 1. For inverse logarithmic correction, the equation is 691 ``O = gain*(2**I - 1)``. 692 693 Parameters 694 ---------- 695 image : ndarray 696 Input image. 697 gain : float, optional 698 The constant multiplier. Default value is 1. 699 inv : float, optional 700 If True, it performs inverse logarithmic correction, 701 else correction will be logarithmic. Defaults to False. 702 703 Returns 704 ------- 705 out : ndarray 706 Logarithm corrected output image. 707 708 See Also 709 -------- 710 adjust_gamma 711 712 References 713 ---------- 714 .. [1] http://www.ece.ucsb.edu/Faculty/Manjunath/courses/ece178W03/EnhancePart1.pdf 715 716 """ 717 _assert_non_negative(image) 718 dtype = image.dtype.type 719 scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0]) 720 721 if inv: 722 out = (2 ** (image / scale) - 1) * scale * gain 723 return dtype(out) 724 725 out = np.log2(1 + image / scale) * scale * gain 726 return out.astype(dtype) 727 728 729def adjust_sigmoid(image, cutoff=0.5, gain=10, inv=False): 730 """Performs Sigmoid Correction on the input image. 731 732 Also known as Contrast Adjustment. 733 This function transforms the input image pixelwise according to the 734 equation ``O = 1/(1 + exp*(gain*(cutoff - I)))`` after scaling each pixel 735 to the range 0 to 1. 736 737 Parameters 738 ---------- 739 image : ndarray 740 Input image. 741 cutoff : float, optional 742 Cutoff of the sigmoid function that shifts the characteristic curve 743 in horizontal direction. Default value is 0.5. 744 gain : float, optional 745 The constant multiplier in exponential's power of sigmoid function. 746 Default value is 10. 747 inv : bool, optional 748 If True, returns the negative sigmoid correction. Defaults to False. 749 750 Returns 751 ------- 752 out : ndarray 753 Sigmoid corrected output image. 754 755 See Also 756 -------- 757 adjust_gamma 758 759 References 760 ---------- 761 .. [1] Gustav J. Braun, "Image Lightness Rescaling Using Sigmoidal Contrast 762 Enhancement Functions", 763 http://markfairchild.org/PDFs/PAP07.pdf 764 765 """ 766 _assert_non_negative(image) 767 dtype = image.dtype.type 768 scale = float(dtype_limits(image, True)[1] - dtype_limits(image, True)[0]) 769 770 if inv: 771 out = (1 - 1 / (1 + np.exp(gain * (cutoff - image / scale)))) * scale 772 return dtype(out) 773 774 out = (1 / (1 + np.exp(gain * (cutoff - image / scale)))) * scale 775 return out.astype(dtype) 776 777 778def is_low_contrast(image, fraction_threshold=0.05, lower_percentile=1, 779 upper_percentile=99, method='linear'): 780 """Determine if an image is low contrast. 781 782 Parameters 783 ---------- 784 image : array-like 785 The image under test. 786 fraction_threshold : float, optional 787 The low contrast fraction threshold. An image is considered low- 788 contrast when its range of brightness spans less than this 789 fraction of its data type's full range. [1]_ 790 lower_percentile : float, optional 791 Disregard values below this percentile when computing image contrast. 792 upper_percentile : float, optional 793 Disregard values above this percentile when computing image contrast. 794 method : str, optional 795 The contrast determination method. Right now the only available 796 option is "linear". 797 798 Returns 799 ------- 800 out : bool 801 True when the image is determined to be low contrast. 802 803 Notes 804 ----- 805 For boolean images, this function returns False only if all values are 806 the same (the method, threshold, and percentile arguments are ignored). 807 808 References 809 ---------- 810 .. [1] https://scikit-image.org/docs/dev/user_guide/data_types.html 811 812 Examples 813 -------- 814 >>> image = np.linspace(0, 0.04, 100) 815 >>> is_low_contrast(image) 816 True 817 >>> image[-1] = 1 818 >>> is_low_contrast(image) 819 True 820 >>> is_low_contrast(image, upper_percentile=100) 821 False 822 """ 823 image = np.asanyarray(image) 824 825 if image.dtype == bool: 826 return not ((image.max() == 1) and (image.min() == 0)) 827 828 if image.ndim == 3: 829 if image.shape[2] == 4: 830 image = rgba2rgb(image) 831 if image.shape[2] == 3: 832 image = rgb2gray(image) 833 834 dlimits = dtype_limits(image, clip_negative=False) 835 limits = np.percentile(image, [lower_percentile, upper_percentile]) 836 ratio = (limits[1] - limits[0]) / (dlimits[1] - dlimits[0]) 837 838 return ratio < fraction_threshold 839