| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 |
- import numpy as np
- from .._shared import utils
- def _match_cumulative_cdf(source, template):
- """
- Return modified source array so that the cumulative density function of
- its values matches the cumulative density function of the template.
- """
- if source.dtype.kind == 'u':
- src_lookup = source.reshape(-1)
- src_counts = np.bincount(src_lookup)
- tmpl_counts = np.bincount(template.reshape(-1))
- # omit values where the count was 0
- tmpl_values = np.nonzero(tmpl_counts)[0]
- tmpl_counts = tmpl_counts[tmpl_values]
- else:
- src_values, src_lookup, src_counts = np.unique(
- source.reshape(-1), return_inverse=True, return_counts=True
- )
- tmpl_values, tmpl_counts = np.unique(template.reshape(-1), return_counts=True)
- # calculate normalized quantiles for each array
- src_quantiles = np.cumsum(src_counts) / source.size
- tmpl_quantiles = np.cumsum(tmpl_counts) / template.size
- interp_a_values = np.interp(src_quantiles, tmpl_quantiles, tmpl_values)
- return interp_a_values[src_lookup].reshape(source.shape)
- @utils.channel_as_last_axis(channel_arg_positions=(0, 1))
- def match_histograms(image, reference, *, channel_axis=None):
- """Adjust an image so that its cumulative histogram matches that of another.
- The adjustment is applied separately for each channel.
- Parameters
- ----------
- image : ndarray
- Input image. Can be gray-scale or in color.
- reference : ndarray
- Image to match histogram of. Must have the same number of channels as
- image.
- channel_axis : int or None, optional
- If None, the image is assumed to be a grayscale (single channel) image.
- Otherwise, this parameter indicates which axis of the array corresponds
- to channels.
- Returns
- -------
- matched : ndarray
- Transformed input image.
- Raises
- ------
- ValueError
- Thrown when the number of channels in the input image and the reference
- differ.
- References
- ----------
- .. [1] http://paulbourke.net/miscellaneous/equalisation/
- """
- if image.ndim != reference.ndim:
- raise ValueError(
- 'Image and reference must have the same number ' 'of channels.'
- )
- if channel_axis is not None:
- if image.shape[-1] != reference.shape[-1]:
- raise ValueError(
- 'Number of channels in the input image and '
- 'reference image must match!'
- )
- matched = np.empty(image.shape, dtype=image.dtype)
- for channel in range(image.shape[-1]):
- matched_channel = _match_cumulative_cdf(
- image[..., channel], reference[..., channel]
- )
- matched[..., channel] = matched_channel
- else:
- # _match_cumulative_cdf will always return float64 due to np.interp
- matched = _match_cumulative_cdf(image, reference)
- if matched.dtype.kind == 'f':
- # output a float32 result when the input is float16 or float32
- out_dtype = utils._supported_float_type(image.dtype)
- matched = matched.astype(out_dtype, copy=False)
- return matched
|