exposure.py 27 KB

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