kwimage.im_core module

Not sure how to best classify these functions

kwimage.im_core.num_channels(img)[source]

Returns the number of color channels in an image.

Assumes images are 2D and the the channels are the trailing dimension. Returns 1 in the case with no trailing channel dimension, otherwise simply returns img.shape[2].

Parameters:

img (ndarray) – an image with 2 or 3 dimensions.

Returns:

the number of color channels (1, 3, or 4)

Return type:

int

Example

>>> H = W = 3
>>> assert num_channels(np.empty((W, H))) == 1
>>> assert num_channels(np.empty((W, H, 1))) == 1
>>> assert num_channels(np.empty((W, H, 3))) == 3
>>> assert num_channels(np.empty((W, H, 4))) == 4
>>> assert num_channels(np.empty((W, H, 2))) == 2
kwimage.im_core.ensure_float01(img, dtype=<class 'numpy.float32'>, copy=True)[source]

Ensure that an image is encoded using a float32 properly

Parameters:
  • img (ndarray) – an image in uint255 or float01 format. Other formats will raise errors.

  • dtype (type) – a numpy floating type defaults to np.float32

  • copy (bool) – Always copy if True, else copy if needed. Defaults to True.

Returns:

an array of floats in the range 0-1

Return type:

ndarray

Raises:

ValueError – if the image type is integer and not in [0-255]

Example

>>> ensure_float01(np.array([[0, .5, 1.0]]))
array([[0. , 0.5, 1. ]], dtype=float32)
>>> ensure_float01(np.array([[0, 1, 200]]))
array([[0..., 0.0039..., 0.784...]], dtype=float32)
kwimage.im_core.ensure_uint255(img, copy=True)[source]

Ensure that an image is encoded using a uint8 properly. Either

Parameters:
  • img (ndarray) – an image in uint255 or float01 format. Other formats will raise errors.

  • copy (bool) – always copy if True, else copy if needed. Defaults to True.

Returns:

an array of bytes in the range 0-255

Return type:

ndarray

Raises:
  • ValueError – if the image type is float and not in [0-1]

  • ValueError – if the image type is integer and not in [0-255]

Example

>>> ensure_uint255(np.array([[0, .5, 1.0]]))
array([[  0, 127, 255]], dtype=uint8)
>>> ensure_uint255(np.array([[0, 1, 200]]))
array([[  0,   1, 200]], dtype=uint8)
kwimage.im_core.make_channels_comparable(img1, img2, atleast3d=False)[source]

Broadcasts image arrays so they can have elementwise operations applied

Parameters:
  • img1 (ndarray) – first image

  • img2 (ndarray) – second image

  • atleast3d (bool) – if true we ensure that the channel dimension exists (only relevant for 1-channel images). Defaults to False.

Example

>>> import itertools as it
>>> wh_basis = [(5, 5), (3, 5), (5, 3), (1, 1), (1, 3), (3, 1)]
>>> for w, h in wh_basis:
>>>     shape_basis = [(w, h), (w, h, 1), (w, h, 3)]
>>>     # Test all permutations of shap inputs
>>>     for shape1, shape2 in it.product(shape_basis, shape_basis):
>>>         print('*    input shapes: %r, %r' % (shape1, shape2))
>>>         img1 = np.empty(shape1)
>>>         img2 = np.empty(shape2)
>>>         img1, img2 = make_channels_comparable(img1, img2)
>>>         print('... output shapes: %r, %r' % (img1.shape, img2.shape))
>>>         elem = (img1 + img2)
>>>         print('... elem(+) shape: %r' % (elem.shape,))
>>>         assert elem.size == img1.size, 'outputs should have same size'
>>>         assert img1.size == img2.size, 'new imgs should have same size'
>>>         print('--------')
kwimage.im_core._alpha_fill_for(img)[source]

helper for make_channels_comparable

kwimage.im_core.atleast_3channels(arr, copy=True)[source]

Ensures that there are 3 channels in the image

Parameters:
  • arr (ndarray) – an image with 2 or 3 dims.

  • copy (bool) – Always copies if True, if False, then copies only when the size of the array must change. Defaults to True.

Returns:

with shape (N, M, C), where C in {3, 4}

Return type:

ndarray

SeeAlso:

Doctest

>>> assert atleast_3channels(np.zeros((10, 10))).shape[-1] == 3
>>> assert atleast_3channels(np.zeros((10, 10, 1))).shape[-1] == 3
>>> assert atleast_3channels(np.zeros((10, 10, 3))).shape[-1] == 3
>>> assert atleast_3channels(np.zeros((10, 10, 4))).shape[-1] == 4
kwimage.im_core.exactly_1channel(image, ndim=2)[source]

Returns a 1-channel image as either a 2D or 3D array.

Parameters:
  • image (ndarray) – an image with shape (H, W, 1) or (H, W).

  • ndim (int) – number of dimensions in the output array. Can be either 2 or 3.

Returns:

if ndim is 2, returns a (H, W) image. if ndim is 3, returns a (H, W, 1) image.

Return type:

ndarray

Raises:

ValueError – if assumptions are not met.

SeeAlso:

Example

>>> import kwimage
>>> assert kwimage.exactly_1channel(np.empty((3, 3)), ndim=2).shape == (3, 3)
>>> assert kwimage.exactly_1channel(np.empty((3, 3)), ndim=3).shape == (3, 3, 1)
>>> assert kwimage.exactly_1channel(np.empty((3, 3, 1)), ndim=2).shape == (3, 3)
>>> assert kwimage.exactly_1channel(np.empty((3, 3, 1)), ndim=3).shape == (3, 3, 1)
>>> import pytest
>>> with pytest.raises(ValueError):
>>>     kwimage.exactly_1channel(np.empty((3, 3, 2)), ndim=2)
>>> with pytest.raises(ValueError):
>>>     kwimage.exactly_1channel(np.empty((3)), ndim=3)
kwimage.im_core.padded_slice(data, in_slice, pad=None, padkw=None, return_info=False)[source]

Allows slices with out-of-bound coordinates. Any out of bounds coordinate will be sampled via padding.

DEPRECATED FOR THE VERSION IN KWARRAY (slices are more array-ish than image-ish)

Note

Negative slices have a different meaning here then they usually do. Normally, they indicate a wrap-around or a reversed stride, but here they index into out-of-bounds space (which depends on the pad mode). For example a slice of -2:1 literally samples two pixels to the left of the data and one pixel from the data, so you get two padded values and one data value.

Parameters:
  • data (Sliceable) – data to slice into. Any channels must be the last dimension.

  • in_slice (slice | Tuple[slice, …]) – slice for each dimensions

  • ndim (int) – number of spatial dimensions

  • pad (List[int|Tuple]) – additional padding of the slice

  • padkw (Dict) – if unspecified defaults to {'mode': 'constant'}

  • return_info (bool) – if True, return extra information about the transform. Defaults to False.

SeeAlso:

_padded_slice_embed - finds the embedded slice and padding _padded_slice_apply - applies padding to sliced data

Returns:

data_sliced: subregion of the input data (possibly with padding,

depending on if the original slice went out of bounds)

Tuple[Sliceable, Dict] :

data_sliced : as above

transform : information on how to return to the original coordinates

Currently a dict containing:
st_dims: a list indicating the low and high space-time

coordinate values of the returned data slice.

The structure of this dictionary mach change in the future

Return type:

Sliceable

Example

>>> data = np.arange(5)
>>> in_slice = [slice(-2, 7)]
>>> data_sliced = padded_slice(data, in_slice)
>>> print(ub.urepr(data_sliced, with_dtype=False))
np.array([0, 0, 0, 1, 2, 3, 4, 0, 0])
>>> data_sliced = padded_slice(data, in_slice, pad=(3, 3))
>>> print(ub.urepr(data_sliced, with_dtype=False))
np.array([0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0])
>>> data_sliced = padded_slice(data, slice(3, 4), pad=[(1, 0)])
>>> print(ub.urepr(data_sliced, with_dtype=False))
np.array([2, 3])
kwimage.im_core._padded_slice_apply(data_clipped, data_slice, extra_padding, padkw=None)[source]

Applies requested padding to an extracted data slice.

kwimage.im_core._padded_slice_embed(in_slice, data_dims, pad=None)[source]

Embeds a “padded-slice” inside known data dimension.

Returns the valid data portion of the slice with extra padding for regions outside of the available dimension.

Given a slices for each dimension, image dimensions, and a padding get the corresponding slice from the image and any extra padding needed to achieve the requested window size.

Parameters:
  • in_slice (Tuple[slice]) – a tuple of slices for to apply to data data dimension.

  • data_dims (Tuple[int]) – n-dimension data sizes (e.g. 2d height, width)

  • pad (tuple) – (List[int|Tuple]): extra pad applied to (left and right) / (both) sides of each slice dim

Returns:

data_slice - Tuple[slice] a slice that can be applied to an array

with with shape data_dims. This slice will not correspond to the full window size if the requested slice is out of bounds.

extra_padding - extra padding needed after slicing to achieve

the requested window size.

Return type:

Tuple

Example

>>> # Case where slice is inside the data dims on left edge
>>> from kwimage.im_core import *  # NOQA
>>> in_slice = (slice(0, 10), slice(0, 10))
>>> data_dims  = [300, 300]
>>> pad        = [10, 5]
>>> a, b = _padded_slice_embed(in_slice, data_dims, pad)
>>> print('data_slice = {!r}'.format(a))
>>> print('extra_padding = {!r}'.format(b))
data_slice = (slice(0, 20, None), slice(0, 15, None))
extra_padding = [(10, 0), (5, 0)]

Example

>>> # Case where slice is bigger than the image
>>> in_slice = (slice(-10, 400), slice(-10, 400))
>>> data_dims  = [300, 300]
>>> pad        = [10, 5]
>>> a, b = _padded_slice_embed(in_slice, data_dims, pad)
>>> print('data_slice = {!r}'.format(a))
>>> print('extra_padding = {!r}'.format(b))
data_slice = (slice(0, 300, None), slice(0, 300, None))
extra_padding = [(20, 110), (15, 105)]

Example

>>> # Case where slice is inside than the image
>>> in_slice = (slice(10, 40), slice(10, 40))
>>> data_dims  = [300, 300]
>>> pad        = None
>>> a, b = _padded_slice_embed(in_slice, data_dims, pad)
>>> print('data_slice = {!r}'.format(a))
>>> print('extra_padding = {!r}'.format(b))
data_slice = (slice(10, 40, None), slice(10, 40, None))
extra_padding = [(0, 0), (0, 0)]
kwimage.im_core.normalize(arr, mode='linear', alpha=None, beta=None, out=None)[source]

Rebalance pixel intensities via contrast stretching.

By default linearly stretches pixel intensities to minimum and maximum values.

Note

DEPRECATED: this function has been MOVED to kwarray.normalize

kwimage.im_core.find_robust_normalizers(data, params='auto')[source]

Finds robust normalization statistics for a single observation

DEPRECATED IN FAVOR of kwarray.find_robust_normalizers

Parameters:
  • data (ndarray) – a 1D numpy array where invalid data has already been removed

  • params (str | dict) – normalization params

Returns:

normalization parameters

Return type:

Dict[str, str | float]

Todo

  • [ ] No Magic Numbers! Use first principles to deterimine defaults.

  • [ ] Probably a lot of literature on the subject.

  • [ ] Is this a kwarray function in general?

Example

>>> from kwimage.im_core import *  # NOQA
>>> data = np.random.rand(100)
>>> norm_params1 = find_robust_normalizers(data, params='auto')
>>> norm_params2 = find_robust_normalizers(data, params={'low': 0, 'high': 1.0})
>>> norm_params3 = find_robust_normalizers(np.empty(0), params='auto')
>>> print('norm_params1 = {}'.format(ub.urepr(norm_params1, nl=1)))
>>> print('norm_params2 = {}'.format(ub.urepr(norm_params2, nl=1)))
>>> print('norm_params3 = {}'.format(ub.urepr(norm_params3, nl=1)))
kwimage.im_core.normalize_intensity(imdata, return_info=False, nodata=None, axis=None, dtype=<class 'numpy.float32'>, params='auto', mask=None)[source]

Normalize data intensities using heuristics to help put sensor data with extremely high or low contrast into a visible range.

This function is designed with an emphasis on getting something that is reasonable for visualization.

Todo

  • [x] Move to kwarray and renamed to robust_normalize?

  • [ ] Support for M-estimators?

Parameters:
  • imdata (ndarray) – raw intensity data

  • return_info (bool) – if True, return information about the chosen normalization heuristic.

  • params (str | dict) – can contain keys, low, high, or center e.g. {‘low’: 0.1, ‘center’: 0.8, ‘high’: 0.9}

  • axis (None | int) – The axis to normalize over, if unspecified, normalize jointly

  • nodata (None | int) – A value representing nodata to leave unchanged during normalization, for example 0

  • dtype (type) – can be float32 or float64

  • mask (ndarray | None) – A mask indicating what pixels are valid and what pixels should be considered nodata. Mutually exclusive with nodata argument. A mask value of 1 indicates a VALID pixel. A mask value of 0 indicates an INVALID pixel.

Returns:

a floating point array with values between 0 and 1.

Return type:

ndarray

Example

>>> from kwimage.im_core import *  # NOQA
>>> import ubelt as ub
>>> import kwimage
>>> import kwarray
>>> s = 512
>>> bit_depth = 11
>>> dtype = np.uint16
>>> max_val = int(2 ** bit_depth)
>>> min_val = int(0)
>>> rng = kwarray.ensure_rng(0)
>>> background = np.random.randint(min_val, max_val, size=(s, s), dtype=dtype)
>>> poly1 = kwimage.Polygon.random(rng=rng).scale(s / 2)
>>> poly2 = kwimage.Polygon.random(rng=rng).scale(s / 2).translate(s / 2)
>>> forground = np.zeros_like(background, dtype=np.uint8)
>>> forground = poly1.fill(forground, value=255)
>>> forground = poly2.fill(forground, value=122)
>>> forground = (kwimage.ensure_float01(forground) * max_val).astype(dtype)
>>> imdata = background + forground
>>> normed, info = normalize_intensity(imdata, return_info=True)
>>> print('info = {}'.format(ub.urepr(info, nl=1)))
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> kwplot.imshow(imdata, pnum=(1, 2, 1), fnum=1)
>>> kwplot.imshow(normed, pnum=(1, 2, 2), fnum=1)
_images/fig_kwimage_im_core_normalize_intensity_002.jpeg

Example

>>> from kwimage.im_core import *  # NOQA
>>> import ubelt as ub
>>> import kwimage
>>> # Test on an image that is already normalized to test how it
>>> # degrades
>>> imdata = kwimage.grab_test_image() / 255
>>> quantile_basis = {
>>>     'mode': ['linear', 'sigmoid'],
>>>     'high': [0.8, 0.9, 1.0],
>>> }
>>> quantile_grid = list(ub.named_product(quantile_basis))
>>> quantile_grid += ['auto']
>>> rows = []
>>> rows.append({'key': 'orig', 'result': imdata})
>>> for params in quantile_grid:
>>>     key = ub.urepr(params, compact=1)
>>>     result, info = normalize_intensity(imdata, return_info=True, params=params)
>>>     print('key = {}'.format(key))
>>>     print('info = {}'.format(ub.urepr(info, nl=1)))
>>>     rows.append({'key': key, 'info': info, 'result': result})
>>> # xdoctest: +REQUIRES(--show)
>>> import kwplot
>>> kwplot.autompl()
>>> pnum_ = kwplot.PlotNums(nSubplots=len(rows))
>>> for row in rows:
>>>     _, ax = kwplot.imshow(row['result'], fnum=1, pnum=pnum_())
>>>     ax.set_title(row['key'])
_images/fig_kwimage_im_core_normalize_intensity_003.jpeg