| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705 |
- """
- Augmenters that apply changes to images based on segmentation methods.
- List of augmenters:
- * :class:`Superpixels`
- * :class:`Voronoi`
- * :class:`UniformVoronoi`
- * :class:`RegularGridVoronoi`
- * :class:`RelativeRegularGridVoronoi`
- """
- from __future__ import print_function, division, absolute_import
- from abc import ABCMeta, abstractmethod
- import numpy as np
- # use skimage.segmentation instead `from skimage import segmentation` here,
- # because otherwise unittest seems to mix up imgaug.augmenters.segmentation
- # with skimage.segmentation for whatever reason
- import skimage.segmentation
- import skimage.measure
- import six
- import six.moves as sm
- import imgaug as ia
- from . import meta
- from .. import random as iarandom
- from .. import parameters as iap
- from .. import dtypes as iadt
- # TODO merge this into imresize?
- def _ensure_image_max_size(image, max_size, interpolation):
- """Ensure that images do not exceed a required maximum sidelength.
- This downscales to `max_size` if any side violates that maximum.
- The other side is downscaled too so that the aspect ratio is maintained.
- **Supported dtypes**:
- See :func:`~imgaug.imgaug.imresize_single_image`.
- Parameters
- ----------
- image : ndarray
- Image to potentially downscale.
- max_size : int
- Maximum length of any side of the image.
- interpolation : string or int
- See :func:`~imgaug.imgaug.imresize_single_image`.
- """
- if max_size is not None:
- size = max(image.shape[0], image.shape[1])
- if size > max_size:
- resize_factor = max_size / size
- new_height = int(image.shape[0] * resize_factor)
- new_width = int(image.shape[1] * resize_factor)
- image = ia.imresize_single_image(
- image,
- (new_height, new_width),
- interpolation=interpolation)
- return image
- # TODO add compactness parameter
- class Superpixels(meta.Augmenter):
- """Transform images parially/completely to their superpixel representation.
- This implementation uses skimage's version of the SLIC algorithm.
- .. note::
- This augmenter is fairly slow. See :ref:`performance`.
- **Supported dtypes**:
- if (image size <= max_size):
- * ``uint8``: yes; fully tested
- * ``uint16``: yes; tested
- * ``uint32``: yes; tested
- * ``uint64``: limited (1)
- * ``int8``: yes; tested
- * ``int16``: yes; tested
- * ``int32``: yes; tested
- * ``int64``: limited (1)
- * ``float16``: no (2)
- * ``float32``: no (2)
- * ``float64``: no (3)
- * ``float128``: no (2)
- * ``bool``: yes; tested
- - (1) Superpixel mean intensity replacement requires computing
- these means as ``float64`` s. This can cause inaccuracies for
- large integer values.
- - (2) Error in scikit-image.
- - (3) Loss of resolution in scikit-image.
- if (image size > max_size):
- minimum of (
- ``imgaug.augmenters.segmentation.Superpixels(image size <= max_size)``,
- :func:`~imgaug.augmenters.segmentation._ensure_image_max_size`
- )
- Parameters
- ----------
- p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Defines for any segment the probability that the pixels within that
- segment are replaced by their average color (otherwise, the pixels
- are not changed).
- Examples:
- * A probability of ``0.0`` would mean, that the pixels in no
- segment are replaced by their average color (image is not
- changed at all).
- * A probability of ``0.5`` would mean, that around half of all
- segments are replaced by their average color.
- * A probability of ``1.0`` would mean, that all segments are
- replaced by their average color (resulting in a voronoi
- image).
- Behaviour based on chosen datatypes for this parameter:
- * If a ``number``, then that ``number`` will always be used.
- * If ``tuple`` ``(a, b)``, then a random probability will be
- sampled from the interval ``[a, b]`` per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, it is expected to return
- values between ``0.0`` and ``1.0`` and will be queried *for each
- individual segment* to determine whether it is supposed to
- be averaged (``>0.5``) or not (``<=0.5``).
- Recommended to be some form of ``Binomial(...)``.
- n_segments : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Rough target number of how many superpixels to generate (the algorithm
- may deviate from this number). Lower value will lead to coarser
- superpixels. Higher values are computationally more intensive and
- will hence lead to a slowdown.
- * If a single ``int``, then that value will always be used as the
- number of segments.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- max_size : int or None, optional
- Maximum image size at which the augmentation is performed.
- If the width or height of an image exceeds this value, it will be
- downscaled before the augmentation so that the longest side
- matches `max_size`.
- This is done to speed up the process. The final output image has the
- same size as the input image. Note that in case `p_replace` is below
- ``1.0``, the down-/upscaling will affect the not-replaced pixels too.
- Use ``None`` to apply no down-/upscaling.
- interpolation : int or str, optional
- Interpolation method to use during downscaling when `max_size` is
- exceeded. Valid methods are the same as in
- :func:`~imgaug.imgaug.imresize_single_image`.
- seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- name : None or str, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- Old name for parameter `seed`.
- Its usage will not yet cause a deprecation warning,
- but it is still recommended to use `seed` now.
- Outdated since 0.4.0.
- deterministic : bool, optional
- Deprecated since 0.4.0.
- See method ``to_deterministic()`` for an alternative and for
- details about what the "deterministic mode" actually does.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> aug = iaa.Superpixels(p_replace=1.0, n_segments=64)
- Generate around ``64`` superpixels per image and replace all of them with
- their average color (standard superpixel image).
- >>> aug = iaa.Superpixels(p_replace=0.5, n_segments=64)
- Generate around ``64`` superpixels per image and replace half of them
- with their average color, while the other half are left unchanged (i.e.
- they still show the input image's content).
- >>> aug = iaa.Superpixels(p_replace=(0.25, 1.0), n_segments=(16, 128))
- Generate between ``16`` and ``128`` superpixels per image and replace
- ``25`` to ``100`` percent of them with their average color.
- """
- def __init__(self, p_replace=(0.5, 1.0), n_segments=(50, 120),
- max_size=128, interpolation="linear",
- seed=None, name=None,
- random_state="deprecated", deterministic="deprecated"):
- super(Superpixels, self).__init__(
- seed=seed, name=name,
- random_state=random_state, deterministic=deterministic)
- self.p_replace = iap.handle_probability_param(
- p_replace, "p_replace", tuple_to_uniform=True, list_to_choice=True)
- self.n_segments = iap.handle_discrete_param(
- n_segments, "n_segments", value_range=(1, None),
- tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
- self.max_size = max_size
- self.interpolation = interpolation
- # Added in 0.4.0.
- def _augment_batch_(self, batch, random_state, parents, hooks):
- if batch.images is None:
- return batch
- images = batch.images
- iadt.gate_dtypes(images,
- allowed=["bool",
- "uint8", "uint16", "uint32", "uint64",
- "int8", "int16", "int32", "int64"],
- disallowed=["uint128", "uint256",
- "int128", "int256",
- "float16", "float32", "float64",
- "float96", "float128", "float256"],
- augmenter=self)
- nb_images = len(images)
- rss = random_state.duplicate(1+nb_images)
- n_segments_samples = self.n_segments.draw_samples(
- (nb_images,), random_state=rss[0])
- # We cant reduce images to 0 or less segments, hence we pick the
- # lowest possible value in these cases (i.e. 1). The alternative
- # would be to not perform superpixel detection in these cases
- # (akin to n_segments=#pixels).
- # TODO add test for this
- n_segments_samples = np.clip(n_segments_samples, 1, None)
- for i, (image, rs) in enumerate(zip(images, rss[1:])):
- if image.size == 0:
- # Image with 0-sized axis, nothing to change.
- # Placing this before the sampling step should be fine.
- continue
- replace_samples = self.p_replace.draw_samples(
- (n_segments_samples[i],), random_state=rs)
- if np.max(replace_samples) == 0:
- # not a single superpixel would be replaced by its average
- # color, i.e. the image would not be changed, so just keep it
- continue
- orig_shape = image.shape
- image = _ensure_image_max_size(image, self.max_size,
- self.interpolation)
- segments = skimage.segmentation.slic(
- image, n_segments=n_segments_samples[i], compactness=10)
- image_aug = self._replace_segments(image, segments, replace_samples)
- if orig_shape != image_aug.shape:
- image_aug = ia.imresize_single_image(
- image_aug,
- orig_shape[0:2],
- interpolation=self.interpolation)
- batch.images[i] = image_aug
- return batch
- @classmethod
- def _replace_segments(cls, image, segments, replace_samples):
- min_value, _center_value, max_value = \
- iadt.get_value_range_of_dtype(image.dtype)
- image_sp = np.copy(image)
- nb_channels = image.shape[2]
- for c in sm.xrange(nb_channels):
- # segments+1 here because otherwise regionprops always
- # misses the last label
- regions = skimage.measure.regionprops(
- segments+1, intensity_image=image[..., c])
- for ridx, region in enumerate(regions):
- # with mod here, because slic can sometimes create more
- # superpixel than requested. replace_samples then does not
- # have enough values, so we just start over with the first one
- # again.
- if replace_samples[ridx % len(replace_samples)] > 0.5:
- mean_intensity = region.mean_intensity
- image_sp_c = image_sp[..., c]
- if image_sp_c.dtype.kind in ["i", "u", "b"]:
- # After rounding the value can end up slightly outside
- # of the value_range. Hence, we need to clip. We do
- # clip via min(max(...)) instead of np.clip because
- # the latter one does not seem to keep dtypes for
- # dtypes with large itemsizes (e.g. uint64).
- value = int(np.round(mean_intensity))
- value = min(max(value, min_value), max_value)
- else:
- value = mean_intensity
- image_sp_c[segments == ridx] = value
- return image_sp
- def get_parameters(self):
- """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
- return [self.p_replace, self.n_segments, self.max_size,
- self.interpolation]
- # TODO don't average the alpha channel for RGBA?
- def segment_voronoi(image, cell_coordinates, replace_mask=None):
- """Average colors within voronoi cells of an image.
- Parameters
- ----------
- image : ndarray
- The image to convert to a voronoi image. May be ``HxW`` or
- ``HxWxC``. Note that for ``RGBA`` images the alpha channel
- will currently also by averaged.
- cell_coordinates : ndarray
- A ``Nx2`` float array containing the center coordinates of voronoi
- cells on the image. Values are expected to be in the interval
- ``[0.0, height-1.0]`` for the y-axis (x-axis analogous).
- If this array contains no coordinate, the image will not be
- changed.
- replace_mask : None or ndarray, optional
- Boolean mask of the same length as `cell_coordinates`, denoting
- for each cell whether its pixels are supposed to be replaced
- by the cell's average color (``True``) or left untouched (``False``).
- If this is set to ``None``, all cells will be replaced.
- Returns
- -------
- ndarray
- Voronoi image.
- """
- input_dims = image.ndim
- if input_dims == 2:
- image = image[..., np.newaxis]
- if len(cell_coordinates) <= 0:
- if input_dims == 2:
- return image[..., 0]
- return image
- height, width = image.shape[0:2]
- pixel_coords, ids_of_nearest_cells = \
- _match_pixels_with_voronoi_cells(height, width, cell_coordinates)
- cell_colors = _compute_avg_segment_colors(
- image, pixel_coords, ids_of_nearest_cells,
- len(cell_coordinates))
- image_aug = _render_segments(image, ids_of_nearest_cells, cell_colors,
- replace_mask)
- if input_dims == 2:
- return image_aug[..., 0]
- return image_aug
- def _match_pixels_with_voronoi_cells(height, width, cell_coordinates):
- # deferred import so that scipy is an optional dependency
- from scipy.spatial import cKDTree as KDTree # TODO add scipy for reqs
- tree = KDTree(cell_coordinates)
- pixel_coords = _generate_pixel_coords(height, width)
- pixel_coords_subpixel = pixel_coords.astype(np.float32) + 0.5
- ids_of_nearest_cells = tree.query(pixel_coords_subpixel)[1]
- return pixel_coords, ids_of_nearest_cells
- def _generate_pixel_coords(height, width):
- xx, yy = np.meshgrid(np.arange(width), np.arange(height))
- return np.c_[xx.ravel(), yy.ravel()]
- def _compute_avg_segment_colors(image, pixel_coords, ids_of_nearest_segments,
- nb_segments):
- nb_channels = image.shape[2]
- cell_colors = np.zeros((nb_segments, nb_channels), dtype=np.float64)
- cell_counters = np.zeros((nb_segments,), dtype=np.uint32)
- # TODO vectorize
- for pixel_coord, id_of_nearest_cell in zip(pixel_coords,
- ids_of_nearest_segments):
- # pixel_coord is (x,y), so we have to swap it to access the HxW image
- pixel_coord_yx = pixel_coord[::-1]
- cell_colors[id_of_nearest_cell] += image[tuple(pixel_coord_yx)]
- cell_counters[id_of_nearest_cell] += 1
- # cells without associated pixels can have a count of 0, we clip
- # here to 1 as the result for these cells doesn't matter
- cell_counters = np.clip(cell_counters, 1, None)
- cell_colors = cell_colors / cell_counters[:, np.newaxis]
- return cell_colors.astype(np.uint8)
- def _render_segments(image, ids_of_nearest_segments, avg_segment_colors,
- replace_mask):
- ids_of_nearest_segments = np.copy(ids_of_nearest_segments)
- height, width, nb_channels = image.shape
- # without replace_mask we could reduce this down to:
- # data = cell_colors[ids_of_nearest_cells, :].reshape(
- # (width, height, 3))
- # data = np.transpose(data, (1, 0, 2))
- keep_mask = (~replace_mask) if replace_mask is not None else None
- if keep_mask is None or not np.any(keep_mask):
- data = avg_segment_colors[ids_of_nearest_segments, :]
- else:
- ids_to_keep = np.nonzero(keep_mask)[0]
- indices_to_keep = np.where(
- np.isin(ids_of_nearest_segments, ids_to_keep))[0]
- data = avg_segment_colors[ids_of_nearest_segments, :]
- image_data = image.reshape((height*width, -1))
- data[indices_to_keep] = image_data[indices_to_keep, :]
- data = data.reshape((height, width, nb_channels))
- return data
- # TODO this can be reduced down to a similar problem as Superpixels:
- # generate an integer-based class id map of segments, then replace all
- # segments with the same class id by the average color within that
- # segment
- class Voronoi(meta.Augmenter):
- """Average colors of an image within Voronoi cells.
- This augmenter performs the following steps:
- 1. Query `points_sampler` to sample random coordinates of cell
- centers. On the image.
- 2. Estimate for each pixel to which voronoi cell (i.e. segment)
- it belongs. Each pixel belongs to the cell with the closest center
- coordinate (euclidean distance).
- 3. Compute for each cell the average color of the pixels within it.
- 4. Replace the pixels of `p_replace` percent of all cells by their
- average color. Do not change the pixels of ``(1 - p_replace)``
- percent of all cells. (The percentages are average values over
- many images. Some images may get more/less cells replaced by
- their average color.)
- This code is very loosely based on
- https://codegolf.stackexchange.com/questions/50299/draw-an-image-as-a-voronoi-map/50345#50345
- **Supported dtypes**:
- if (image size <= max_size):
- * ``uint8``: yes; fully tested
- * ``uint16``: no; not tested
- * ``uint32``: no; not tested
- * ``uint64``: no; not tested
- * ``int8``: no; not tested
- * ``int16``: no; not tested
- * ``int32``: no; not tested
- * ``int64``: no; not tested
- * ``float16``: no; not tested
- * ``float32``: no; not tested
- * ``float64``: no; not tested
- * ``float128``: no; not tested
- * ``bool``: no; not tested
- if (image size > max_size):
- minimum of (
- ``imgaug.augmenters.segmentation.Voronoi(image size <= max_size)``,
- :func:`~imgaug.augmenters.segmentation._ensure_image_max_size`
- )
- Parameters
- ----------
- points_sampler : IPointsSampler
- A points sampler which will be queried per image to generate the
- coordinates of the centers of voronoi cells.
- p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Defines for any segment the probability that the pixels within that
- segment are replaced by their average color (otherwise, the pixels
- are not changed).
- Examples:
- * A probability of ``0.0`` would mean, that the pixels in no
- segment are replaced by their average color (image is not
- changed at all).
- * A probability of ``0.5`` would mean, that around half of all
- segments are replaced by their average color.
- * A probability of ``1.0`` would mean, that all segments are
- replaced by their average color (resulting in a voronoi
- image).
- Behaviour based on chosen datatypes for this parameter:
- * If a ``number``, then that ``number`` will always be used.
- * If ``tuple`` ``(a, b)``, then a random probability will be
- sampled from the interval ``[a, b]`` per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, it is expected to return
- values between ``0.0`` and ``1.0`` and will be queried *for each
- individual segment* to determine whether it is supposed to
- be averaged (``>0.5``) or not (``<=0.5``).
- Recommended to be some form of ``Binomial(...)``.
- max_size : int or None, optional
- Maximum image size at which the augmentation is performed.
- If the width or height of an image exceeds this value, it will be
- downscaled before the augmentation so that the longest side
- matches `max_size`.
- This is done to speed up the process. The final output image has the
- same size as the input image. Note that in case `p_replace` is below
- ``1.0``, the down-/upscaling will affect the not-replaced pixels too.
- Use ``None`` to apply no down-/upscaling.
- interpolation : int or str, optional
- Interpolation method to use during downscaling when `max_size` is
- exceeded. Valid methods are the same as in
- :func:`~imgaug.imgaug.imresize_single_image`.
- seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- name : None or str, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- Old name for parameter `seed`.
- Its usage will not yet cause a deprecation warning,
- but it is still recommended to use `seed` now.
- Outdated since 0.4.0.
- deterministic : bool, optional
- Deprecated since 0.4.0.
- See method ``to_deterministic()`` for an alternative and for
- details about what the "deterministic mode" actually does.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> points_sampler = iaa.RegularGridPointsSampler(n_cols=20, n_rows=40)
- >>> aug = iaa.Voronoi(points_sampler)
- Create an augmenter that places a ``20x40`` (``HxW``) grid of cells on
- the image and replaces all pixels within each cell by the cell's average
- color. The process is performed at an image size not exceeding ``128`` px
- on any side (default). If necessary, the downscaling is performed using
- ``linear`` interpolation (default).
- >>> points_sampler = iaa.DropoutPointsSampler(
- >>> iaa.RelativeRegularGridPointsSampler(
- >>> n_cols_frac=(0.05, 0.2),
- >>> n_rows_frac=0.1),
- >>> 0.2)
- >>> aug = iaa.Voronoi(points_sampler, p_replace=0.9, max_size=None)
- Create a voronoi augmenter that generates a grid of cells dynamically
- adapted to the image size. Larger images get more cells. On the x-axis,
- the distance between two cells is ``w * W`` pixels, where ``W`` is the
- width of the image and ``w`` is always ``0.1``. On the y-axis,
- the distance between two cells is ``h * H`` pixels, where ``H`` is the
- height of the image and ``h`` is sampled uniformly from the interval
- ``[0.05, 0.2]``. To make the voronoi pattern less regular, about ``20``
- percent of the cell coordinates are randomly dropped (i.e. the remaining
- cells grow in size). In contrast to the first example, the image is not
- resized (if it was, the sampling would happen *after* the resizing,
- which would affect ``W`` and ``H``). Not all voronoi cells are replaced
- by their average color, only around ``90`` percent of them. The
- remaining ``10`` percent's pixels remain unchanged.
- """
- def __init__(self, points_sampler, p_replace=1.0, max_size=128,
- interpolation="linear",
- seed=None, name=None,
- random_state="deprecated", deterministic="deprecated"):
- super(Voronoi, self).__init__(
- seed=seed, name=name,
- random_state=random_state, deterministic=deterministic)
- assert isinstance(points_sampler, IPointsSampler), (
- "Expected 'points_sampler' to be an instance of IPointsSampler, "
- "got %s." % (type(points_sampler),))
- self.points_sampler = points_sampler
- self.p_replace = iap.handle_probability_param(
- p_replace, "p_replace", tuple_to_uniform=True, list_to_choice=True)
- self.max_size = max_size
- self.interpolation = interpolation
- # Added in 0.4.0.
- def _augment_batch_(self, batch, random_state, parents, hooks):
- if batch.images is None:
- return batch
- images = batch.images
- iadt.gate_dtypes(images,
- allowed=["uint8"],
- disallowed=["bool",
- "uint16", "uint32", "uint64", "uint128",
- "uint256",
- "int8", "int16", "int32", "int64",
- "int128", "int256",
- "float16", "float32", "float64",
- "float96", "float128", "float256"],
- augmenter=self)
- rss = random_state.duplicate(len(images))
- for i, (image, rs) in enumerate(zip(images, rss)):
- batch.images[i] = self._augment_single_image(image, rs)
- return batch
- def _augment_single_image(self, image, random_state):
- rss = random_state.duplicate(2)
- orig_shape = image.shape
- image = _ensure_image_max_size(image, self.max_size, self.interpolation)
- cell_coordinates = self.points_sampler.sample_points([image], rss[0])[0]
- p_replace = self.p_replace.draw_samples((len(cell_coordinates),),
- rss[1])
- replace_mask = (p_replace > 0.5)
- image_aug = segment_voronoi(image, cell_coordinates, replace_mask)
- if orig_shape != image_aug.shape:
- image_aug = ia.imresize_single_image(
- image_aug,
- orig_shape[0:2],
- interpolation=self.interpolation)
- return image_aug
- def get_parameters(self):
- """See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
- return [self.points_sampler, self.p_replace, self.max_size,
- self.interpolation]
- class UniformVoronoi(Voronoi):
- """Uniformly sample Voronoi cells on images and average colors within them.
- This augmenter is a shortcut for the combination of
- :class:`~imgaug.augmenters.segmentation.Voronoi` with
- :class:`~imgaug.augmenters.segmentation.UniformPointsSampler`. Hence, it
- generates a fixed amount of ``N`` random coordinates of voronoi cells on
- each image. The cell coordinates are sampled uniformly using the image
- height and width as maxima.
- **Supported dtypes**:
- See :class:`~imgaug.augmenters.segmentation.Voronoi`.
- Parameters
- ----------
- n_points : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of points to sample on each image.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Defines for any segment the probability that the pixels within that
- segment are replaced by their average color (otherwise, the pixels
- are not changed).
- Examples:
- * A probability of ``0.0`` would mean, that the pixels in no
- segment are replaced by their average color (image is not
- changed at all).
- * A probability of ``0.5`` would mean, that around half of all
- segments are replaced by their average color.
- * A probability of ``1.0`` would mean, that all segments are
- replaced by their average color (resulting in a voronoi
- image).
- Behaviour based on chosen datatypes for this parameter:
- * If a ``number``, then that ``number`` will always be used.
- * If ``tuple`` ``(a, b)``, then a random probability will be
- sampled from the interval ``[a, b]`` per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, it is expected to return
- values between ``0.0`` and ``1.0`` and will be queried *for each
- individual segment* to determine whether it is supposed to
- be averaged (``>0.5``) or not (``<=0.5``).
- Recommended to be some form of ``Binomial(...)``.
- max_size : int or None, optional
- Maximum image size at which the augmentation is performed.
- If the width or height of an image exceeds this value, it will be
- downscaled before the augmentation so that the longest side
- matches `max_size`.
- This is done to speed up the process. The final output image has the
- same size as the input image. Note that in case `p_replace` is below
- ``1.0``, the down-/upscaling will affect the not-replaced pixels too.
- Use ``None`` to apply no down-/upscaling.
- interpolation : int or str, optional
- Interpolation method to use during downscaling when `max_size` is
- exceeded. Valid methods are the same as in
- :func:`~imgaug.imgaug.imresize_single_image`.
- seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- name : None or str, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- Old name for parameter `seed`.
- Its usage will not yet cause a deprecation warning,
- but it is still recommended to use `seed` now.
- Outdated since 0.4.0.
- deterministic : bool, optional
- Deprecated since 0.4.0.
- See method ``to_deterministic()`` for an alternative and for
- details about what the "deterministic mode" actually does.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> aug = iaa.UniformVoronoi((100, 500))
- Sample for each image uniformly the number of voronoi cells ``N`` from the
- interval ``[100, 500]``. Then generate ``N`` coordinates by sampling
- uniformly the x-coordinates from ``[0, W]`` and the y-coordinates from
- ``[0, H]``, where ``H`` is the image height and ``W`` the image width.
- Then use these coordinates to group the image pixels into voronoi
- cells and average the colors within them. The process is performed at an
- image size not exceeding ``128`` px on any side (default). If necessary,
- the downscaling is performed using ``linear`` interpolation (default).
- >>> aug = iaa.UniformVoronoi(250, p_replace=0.9, max_size=None)
- Same as above, but always samples ``N=250`` cells, replaces only
- ``90`` percent of them with their average color (the pixels of the
- remaining ``10`` percent are not changed) and performs the transformation
- at the original image size (``max_size=None``).
- """
- def __init__(self, n_points=(50, 500), p_replace=(0.5, 1.0), max_size=128,
- interpolation="linear",
- seed=None, name=None,
- random_state="deprecated", deterministic="deprecated"):
- super(UniformVoronoi, self).__init__(
- points_sampler=UniformPointsSampler(n_points),
- p_replace=p_replace,
- max_size=max_size,
- interpolation=interpolation,
- seed=seed, name=name,
- random_state=random_state, deterministic=deterministic)
- class RegularGridVoronoi(Voronoi):
- """Sample Voronoi cells from regular grids and color-average them.
- This augmenter is a shortcut for the combination of
- :class:`~imgaug.augmenters.segmentation.Voronoi`,
- :class:`~imgaug.augmenters.segmentation.RegularGridPointsSampler` and
- :class:`~imgaug.augmenters.segmentation.DropoutPointsSampler`. Hence, it
- generates a regular grid with ``R`` rows and ``C`` columns of coordinates
- on each image. Then, it drops ``p`` percent of the ``R*C`` coordinates
- to randomize the grid. Each image pixel then belongs to the voronoi
- cell with the closest coordinate.
- **Supported dtypes**:
- See :class:`~imgaug.augmenters.segmentation.Voronoi`.
- Parameters
- ----------
- n_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of rows of coordinates to place on each image, i.e. the number
- of coordinates on the y-axis. Note that for each image, the sampled
- value is clipped to the interval ``[1..H]``, where ``H`` is the image
- height.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- n_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of columns of coordinates to place on each image, i.e. the
- number of coordinates on the x-axis. Note that for each image, the
- sampled value is clipped to the interval ``[1..W]``, where ``W`` is
- the image width.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- p_drop_points : number or tuple of number or imgaug.parameters.StochasticParameter, optional
- The probability that a coordinate will be removed from the list
- of all sampled coordinates. A value of ``1.0`` would mean that (on
- average) ``100`` percent of all coordinates will be dropped,
- while ``0.0`` denotes ``0`` percent. Note that this sampler will
- always ensure that at least one coordinate is left after the dropout
- operation, i.e. even ``1.0`` will only drop all *except one*
- coordinate.
- * If a ``float``, then that value will be used for all images.
- * If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
- from the interval ``[a, b]`` per image.
- * If a ``StochasticParameter``, then this parameter will be used to
- determine per coordinate whether it should be *kept* (sampled
- value of ``>0.5``) or shouldn't be kept (sampled value of
- ``<=0.5``). If you instead want to provide the probability as
- a stochastic parameter, you can usually do
- ``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
- a 0/1 representation.
- p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Defines for any segment the probability that the pixels within that
- segment are replaced by their average color (otherwise, the pixels
- are not changed).
- Examples:
- * A probability of ``0.0`` would mean, that the pixels in no
- segment are replaced by their average color (image is not
- changed at all).
- * A probability of ``0.5`` would mean, that around half of all
- segments are replaced by their average color.
- * A probability of ``1.0`` would mean, that all segments are
- replaced by their average color (resulting in a voronoi
- image).
- Behaviour based on chosen datatypes for this parameter:
- * If a ``number``, then that number will always be used.
- * If ``tuple`` ``(a, b)``, then a random probability will be
- sampled from the interval ``[a, b]`` per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, it is expected to return
- values between ``0.0`` and ``1.0`` and will be queried *for each
- individual segment* to determine whether it is supposed to
- be averaged (``>0.5``) or not (``<=0.5``).
- Recommended to be some form of ``Binomial(...)``.
- max_size : int or None, optional
- Maximum image size at which the augmentation is performed.
- If the width or height of an image exceeds this value, it will be
- downscaled before the augmentation so that the longest side
- matches `max_size`.
- This is done to speed up the process. The final output image has the
- same size as the input image. Note that in case `p_replace` is below
- ``1.0``, the down-/upscaling will affect the not-replaced pixels too.
- Use ``None`` to apply no down-/upscaling.
- interpolation : int or str, optional
- Interpolation method to use during downscaling when `max_size` is
- exceeded. Valid methods are the same as in
- :func:`~imgaug.imgaug.imresize_single_image`.
- seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- name : None or str, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- Old name for parameter `seed`.
- Its usage will not yet cause a deprecation warning,
- but it is still recommended to use `seed` now.
- Outdated since 0.4.0.
- deterministic : bool, optional
- Deprecated since 0.4.0.
- See method ``to_deterministic()`` for an alternative and for
- details about what the "deterministic mode" actually does.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> aug = iaa.RegularGridVoronoi(10, 20)
- Place a regular grid of ``10x20`` (``height x width``) coordinates on
- each image. Randomly drop on average ``20`` percent of these points
- to create a less regular pattern. Then use the remaining coordinates
- to group the image pixels into voronoi cells and average the colors
- within them. The process is performed at an image size not exceeding
- ``128`` px on any side (default). If necessary, the downscaling is
- performed using ``linear`` interpolation (default).
- >>> aug = iaa.RegularGridVoronoi(
- >>> (10, 30), 20, p_drop_points=0.0, p_replace=0.9, max_size=None)
- Same as above, generates a grid with randomly ``10`` to ``30`` rows,
- drops none of the generates points, replaces only ``90`` percent of
- the voronoi cells with their average color (the pixels of the remaining
- ``10`` percent are not changed) and performs the transformation
- at the original image size (``max_size=None``).
- """
- def __init__(self, n_rows=(10, 30), n_cols=(10, 30),
- p_drop_points=(0.0, 0.5), p_replace=(0.5, 1.0),
- max_size=128, interpolation="linear",
- seed=None, name=None,
- random_state="deprecated", deterministic="deprecated"):
- super(RegularGridVoronoi, self).__init__(
- points_sampler=DropoutPointsSampler(
- RegularGridPointsSampler(n_rows, n_cols),
- p_drop_points
- ),
- p_replace=p_replace,
- max_size=max_size,
- interpolation=interpolation,
- seed=seed, name=name,
- random_state=random_state, deterministic=deterministic)
- class RelativeRegularGridVoronoi(Voronoi):
- """Sample Voronoi cells from image-dependent grids and color-average them.
- This augmenter is a shortcut for the combination of
- :class:`~imgaug.augmenters.segmentation.Voronoi`,
- :class:`~imgaug.augmenters.segmentation.RegularGridPointsSampler` and
- :class:`~imgaug.augmenters.segmentation.DropoutPointsSampler`. Hence, it
- generates a regular grid with ``R`` rows and ``C`` columns of coordinates
- on each image. Then, it drops ``p`` percent of the ``R*C`` coordinates
- to randomize the grid. Each image pixel then belongs to the voronoi
- cell with the closest coordinate.
- .. note::
- In contrast to the other voronoi augmenters, this one uses
- ``None`` as the default value for `max_size`, i.e. the color averaging
- is always performed at full resolution. This enables the augmenter to
- make use of the additional points on larger images. It does
- however slow down the augmentation process.
- **Supported dtypes**:
- See :class:`~imgaug.augmenters.segmentation.Voronoi`.
- Parameters
- ----------
- n_rows_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Relative number of coordinates to place on the y-axis. For a value
- ``y`` and image height ``H`` the number of actually placed coordinates
- (i.e. computed rows) is given by ``int(round(y*H))``.
- Note that for each image, the number of coordinates is clipped to the
- interval ``[1,H]``, where ``H`` is the image height.
- * If a single ``number``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the interval
- ``[a, b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- n_cols_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Relative number of coordinates to place on the x-axis. For a value
- ``x`` and image height ``W`` the number of actually placed coordinates
- (i.e. computed columns) is given by ``int(round(x*W))``.
- Note that for each image, the number of coordinates is clipped to the
- interval ``[1,W]``, where ``W`` is the image width.
- * If a single ``number``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the interval
- ``[a, b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- p_drop_points : number or tuple of number or imgaug.parameters.StochasticParameter, optional
- The probability that a coordinate will be removed from the list
- of all sampled coordinates. A value of ``1.0`` would mean that (on
- average) ``100`` percent of all coordinates will be dropped,
- while ``0.0`` denotes ``0`` percent. Note that this sampler will
- always ensure that at least one coordinate is left after the dropout
- operation, i.e. even ``1.0`` will only drop all *except one*
- coordinate.
- * If a ``float``, then that value will be used for all images.
- * If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
- from the interval ``[a, b]`` per image.
- * If a ``StochasticParameter``, then this parameter will be used to
- determine per coordinate whether it should be *kept* (sampled
- value of ``>0.5``) or shouldn't be kept (sampled value of
- ``<=0.5``). If you instead want to provide the probability as
- a stochastic parameter, you can usually do
- ``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
- a 0/1 representation.
- p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Defines for any segment the probability that the pixels within that
- segment are replaced by their average color (otherwise, the pixels
- are not changed).
- Examples:
- * A probability of ``0.0`` would mean, that the pixels in no
- segment are replaced by their average color (image is not
- changed at all).
- * A probability of ``0.5`` would mean, that around half of all
- segments are replaced by their average color.
- * A probability of ``1.0`` would mean, that all segments are
- replaced by their average color (resulting in a voronoi
- image).
- Behaviour based on chosen datatypes for this parameter:
- * If a ``number``, then that ``number`` will always be used.
- * If ``tuple`` ``(a, b)``, then a random probability will be
- sampled from the interval ``[a, b]`` per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, it is expected to return
- values between ``0.0`` and ``1.0`` and will be queried *for each
- individual segment* to determine whether it is supposed to
- be averaged (``>0.5``) or not (``<=0.5``).
- Recommended to be some form of ``Binomial(...)``.
- max_size : int or None, optional
- Maximum image size at which the augmentation is performed.
- If the width or height of an image exceeds this value, it will be
- downscaled before the augmentation so that the longest side
- matches `max_size`.
- This is done to speed up the process. The final output image has the
- same size as the input image. Note that in case `p_replace` is below
- ``1.0``, the down-/upscaling will affect the not-replaced pixels too.
- Use ``None`` to apply no down-/upscaling.
- interpolation : int or str, optional
- Interpolation method to use during downscaling when `max_size` is
- exceeded. Valid methods are the same as in
- :func:`~imgaug.imgaug.imresize_single_image`.
- seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- name : None or str, optional
- See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
- Old name for parameter `seed`.
- Its usage will not yet cause a deprecation warning,
- but it is still recommended to use `seed` now.
- Outdated since 0.4.0.
- deterministic : bool, optional
- Deprecated since 0.4.0.
- See method ``to_deterministic()`` for an alternative and for
- details about what the "deterministic mode" actually does.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> aug = iaa.RelativeRegularGridVoronoi(0.1, 0.25)
- Place a regular grid of ``R x C`` coordinates on each image, where
- ``R`` is the number of rows and computed as ``R=0.1*H`` with ``H`` being
- the height of the input image. ``C`` is the number of columns and
- analogously estimated from the image width ``W`` as ``C=0.25*W``.
- Larger images will lead to larger ``R`` and ``C`` values.
- On average, ``20`` percent of these grid coordinates are randomly
- dropped to create a less regular pattern. Then, the remaining coordinates
- are used to group the image pixels into voronoi cells and the colors
- within them are averaged.
- >>> aug = iaa.RelativeRegularGridVoronoi(
- >>> (0.03, 0.1), 0.1, p_drop_points=0.0, p_replace=0.9, max_size=512)
- Same as above, generates a grid with randomly ``R=r*H`` rows, where
- ``r`` is sampled uniformly from the interval ``[0.03, 0.1]`` and
- ``C=0.1*W`` rows. No points are dropped. The augmenter replaces only
- ``90`` percent of the voronoi cells with their average color (the pixels
- of the remaining ``10`` percent are not changed). Images larger than
- ``512`` px are temporarily downscaled (*before* sampling the grid points)
- so that no side exceeds ``512`` px. This improves performance, but
- degrades the quality of the resulting image.
- """
- def __init__(self, n_rows_frac=(0.05, 0.15), n_cols_frac=(0.05, 0.15),
- p_drop_points=(0.0, 0.5), p_replace=(0.5, 1.0),
- max_size=None, interpolation="linear",
- seed=None, name=None,
- random_state="deprecated", deterministic="deprecated"):
- super(RelativeRegularGridVoronoi, self).__init__(
- points_sampler=DropoutPointsSampler(
- RelativeRegularGridPointsSampler(n_rows_frac, n_cols_frac),
- p_drop_points
- ),
- p_replace=p_replace,
- max_size=max_size,
- interpolation=interpolation,
- seed=seed, name=name,
- random_state=random_state, deterministic=deterministic)
- @six.add_metaclass(ABCMeta)
- class IPointsSampler(object):
- """Interface for all point samplers.
- Point samplers return coordinate arrays of shape ``Nx2``.
- These coordinates can be used in other augmenters, see e.g.
- :class:`~imgaug.augmenters.segmentation.Voronoi`.
- """
- @abstractmethod
- def sample_points(self, images, random_state):
- """Generate coordinates of points on images.
- Parameters
- ----------
- images : ndarray or list of ndarray
- One or more images for which to generate points.
- If this is a ``list`` of arrays, each one of them is expected to
- have three dimensions.
- If this is an array, it must be four-dimensional and the first
- axis is expected to denote the image index. For ``RGB`` images
- the array would hence have to be of shape ``(N, H, W, 3)``.
- random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
- A random state to use for any probabilistic function required
- during the point sampling.
- See :func:`~imgaug.random.RNG` for details.
- Returns
- -------
- ndarray
- An ``(N,2)`` ``float32`` array containing ``(x,y)`` subpixel
- coordinates, all of which being within the intervals
- ``[0.0, width]`` and ``[0.0, height]``.
- """
- def _verify_sample_points_images(images):
- assert len(images) > 0, "Expected at least one image, got zero."
- if isinstance(images, list):
- assert all([ia.is_np_array(image) for image in images]), (
- "Expected list of numpy arrays, got list of types %s." % (
- ", ".join([str(type(image)) for image in images]),))
- assert all([image.ndim == 3 for image in images]), (
- "Expected each image to have three dimensions, "
- "got dimensions %s." % (
- ", ".join([str(image.ndim) for image in images]),))
- else:
- assert ia.is_np_array(images), (
- "Expected either a list of numpy arrays or a single numpy "
- "array of shape NxHxWxC. Got type %s." % (type(images),))
- assert images.ndim == 4, (
- "Expected a four-dimensional array of shape NxHxWxC. "
- "Got shape %d dimensions (shape: %s)." % (
- images.ndim, images.shape))
- class RegularGridPointsSampler(IPointsSampler):
- """Sampler that generates a regular grid of coordinates on an image.
- 'Regular grid' here means that on each axis all coordinates have the
- same distance from each other. Note that the distance may change between
- axis.
- Parameters
- ----------
- n_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of rows of coordinates to place on each image, i.e. the number
- of coordinates on the y-axis. Note that for each image, the sampled
- value is clipped to the interval ``[1..H]``, where ``H`` is the image
- height.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- n_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of columns of coordinates to place on each image, i.e. the
- number of coordinates on the x-axis. Note that for each image, the
- sampled value is clipped to the interval ``[1..W]``, where ``W`` is
- the image width.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> sampler = iaa.RegularGridPointsSampler(
- >>> n_rows=(5, 20),
- >>> n_cols=50)
- Create a point sampler that generates regular grids of points. These grids
- contain ``r`` points on the y-axis, where ``r`` is sampled
- uniformly from the discrete interval ``[5..20]`` per image.
- On the x-axis, the grids always contain ``50`` points.
- """
- def __init__(self, n_rows, n_cols):
- self.n_rows = iap.handle_discrete_param(
- n_rows, "n_rows", value_range=(1, None),
- tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
- self.n_cols = iap.handle_discrete_param(
- n_cols, "n_cols", value_range=(1, None),
- tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
- def sample_points(self, images, random_state):
- random_state = iarandom.RNG(random_state)
- _verify_sample_points_images(images)
- n_rows_lst, n_cols_lst = self._draw_samples(images, random_state)
- return self._generate_point_grids(images, n_rows_lst, n_cols_lst)
- def _draw_samples(self, images, random_state):
- rss = random_state.duplicate(2)
- n_rows_lst = self.n_rows.draw_samples(len(images), random_state=rss[0])
- n_cols_lst = self.n_cols.draw_samples(len(images), random_state=rss[1])
- return self._clip_rows_and_cols(n_rows_lst, n_cols_lst, images)
- @classmethod
- def _clip_rows_and_cols(cls, n_rows_lst, n_cols_lst, images):
- heights = np.int32([image.shape[0] for image in images])
- widths = np.int32([image.shape[1] for image in images])
- # We clip intentionally not to H-1 or W-1 here. If e.g. an image has
- # a width of 1, we want to get a maximum of 1 column of coordinates.
- # Note that we use two clips here instead of e.g. clip(., 1, height),
- # because images can have height/width zero and in these cases numpy
- # prefers the smaller value in clip(). But currently we want to get
- # at least 1 point for such images.
- n_rows_lst = np.clip(n_rows_lst, None, heights)
- n_cols_lst = np.clip(n_cols_lst, None, widths)
- n_rows_lst = np.clip(n_rows_lst, 1, None)
- n_cols_lst = np.clip(n_cols_lst, 1, None)
- return n_rows_lst, n_cols_lst
- @classmethod
- def _generate_point_grids(cls, images, n_rows_lst, n_cols_lst):
- grids = []
- for image, n_rows_i, n_cols_i in zip(images, n_rows_lst, n_cols_lst):
- grids.append(cls._generate_point_grid(image, n_rows_i, n_cols_i))
- return grids
- @classmethod
- def _generate_point_grid(cls, image, n_rows, n_cols):
- height, width = image.shape[0:2]
- # We do not have to subtract 1 here from height/width as these are
- # subpixel coordinates. Technically, we could also place the cell
- # centers outside of the image plane.
- y_spacing = height / n_rows
- y_start = 0.0 + y_spacing/2
- y_end = height - y_spacing/2
- if y_start - 1e-4 <= y_end <= y_start + 1e-4:
- yy = np.float32([y_start])
- else:
- yy = np.linspace(y_start, y_end, num=n_rows)
- x_spacing = width / n_cols
- x_start = 0.0 + x_spacing/2
- x_end = width - x_spacing/2
- if x_start - 1e-4 <= x_end <= x_start + 1e-4:
- xx = np.float32([x_start])
- else:
- xx = np.linspace(x_start, x_end, num=n_cols)
- xx, yy = np.meshgrid(xx, yy)
- grid = np.vstack([xx.ravel(), yy.ravel()]).T
- return grid
- def __repr__(self):
- return "RegularGridPointsSampler(%s, %s)" % (self.n_rows, self.n_cols)
- def __str__(self):
- return self.__repr__()
- class RelativeRegularGridPointsSampler(IPointsSampler):
- """Regular grid coordinate sampler; places more points on larger images.
- This is similar to ``RegularGridPointsSampler``, but the number of rows
- and columns is given as fractions of each image's height and width.
- Hence, more coordinates are generated for larger images.
- Parameters
- ----------
- n_rows_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Relative number of coordinates to place on the y-axis. For a value
- ``y`` and image height ``H`` the number of actually placed coordinates
- (i.e. computed rows) is given by ``int(round(y*H))``.
- Note that for each image, the number of coordinates is clipped to the
- interval ``[1,H]``, where ``H`` is the image height.
- * If a single ``number``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the interval
- ``[a, b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- n_cols_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
- Relative number of coordinates to place on the x-axis. For a value
- ``x`` and image height ``W`` the number of actually placed coordinates
- (i.e. computed columns) is given by ``int(round(x*W))``.
- Note that for each image, the number of coordinates is clipped to the
- interval ``[1,W]``, where ``W`` is the image width.
- * If a single ``number``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the interval
- ``[a, b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> sampler = iaa.RelativeRegularGridPointsSampler(
- >>> n_rows_frac=(0.01, 0.1),
- >>> n_cols_frac=0.2)
- Create a point sampler that generates regular grids of points. These grids
- contain ``round(y*H)`` points on the y-axis, where ``y`` is sampled
- uniformly from the interval ``[0.01, 0.1]`` per image and ``H`` is the
- image height. On the x-axis, the grids always contain ``0.2*W`` points,
- where ``W`` is the image width.
- """
- def __init__(self, n_rows_frac, n_cols_frac):
- eps = 1e-4
- self.n_rows_frac = iap.handle_continuous_param(
- n_rows_frac, "n_rows_frac", value_range=(0.0+eps, 1.0),
- tuple_to_uniform=True, list_to_choice=True)
- self.n_cols_frac = iap.handle_continuous_param(
- n_cols_frac, "n_cols_frac", value_range=(0.0+eps, 1.0),
- tuple_to_uniform=True, list_to_choice=True)
- def sample_points(self, images, random_state):
- # pylint: disable=protected-access
- random_state = iarandom.RNG(random_state)
- _verify_sample_points_images(images)
- n_rows, n_cols = self._draw_samples(images, random_state)
- return RegularGridPointsSampler._generate_point_grids(images,
- n_rows, n_cols)
- def _draw_samples(self, images, random_state):
- # pylint: disable=protected-access
- n_augmentables = len(images)
- rss = random_state.duplicate(2)
- n_rows_frac = self.n_rows_frac.draw_samples(n_augmentables,
- random_state=rss[0])
- n_cols_frac = self.n_cols_frac.draw_samples(n_augmentables,
- random_state=rss[1])
- heights = np.int32([image.shape[0] for image in images])
- widths = np.int32([image.shape[1] for image in images])
- n_rows = np.round(n_rows_frac * heights)
- n_cols = np.round(n_cols_frac * widths)
- n_rows, n_cols = RegularGridPointsSampler._clip_rows_and_cols(
- n_rows, n_cols, images)
- return n_rows.astype(np.int32), n_cols.astype(np.int32)
- def __repr__(self):
- return "RelativeRegularGridPointsSampler(%s, %s)" % (
- self.n_rows_frac, self.n_cols_frac)
- def __str__(self):
- return self.__repr__()
- class DropoutPointsSampler(IPointsSampler):
- """Remove a defined fraction of sampled points.
- Parameters
- ----------
- other_points_sampler : IPointsSampler
- Another point sampler that is queried to generate a list of points.
- The dropout operation will be applied to that list.
- p_drop : number or tuple of number or imgaug.parameters.StochasticParameter
- The probability that a coordinate will be removed from the list
- of all sampled coordinates. A value of ``1.0`` would mean that (on
- average) ``100`` percent of all coordinates will be dropped,
- while ``0.0`` denotes ``0`` percent. Note that this sampler will
- always ensure that at least one coordinate is left after the dropout
- operation, i.e. even ``1.0`` will only drop all *except one*
- coordinate.
- * If a ``float``, then that value will be used for all images.
- * If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
- from the interval ``[a, b]`` per image.
- * If a ``StochasticParameter``, then this parameter will be used to
- determine per coordinate whether it should be *kept* (sampled
- value of ``>0.5``) or shouldn't be kept (sampled value of
- ``<=0.5``). If you instead want to provide the probability as
- a stochastic parameter, you can usually do
- ``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
- a 0/1 representation.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> sampler = iaa.DropoutPointsSampler(
- >>> iaa.RegularGridPointsSampler(10, 20),
- >>> 0.2)
- Create a point sampler that first generates points following a regular
- grid of ``10`` rows and ``20`` columns, then randomly drops ``20`` percent
- of these points.
- """
- def __init__(self, other_points_sampler, p_drop):
- assert isinstance(other_points_sampler, IPointsSampler), (
- "Expected to get an instance of IPointsSampler as argument "
- "'other_points_sampler', got type %s." % (
- type(other_points_sampler),))
- self.other_points_sampler = other_points_sampler
- self.p_drop = self._convert_p_drop_to_inverted_mask_param(p_drop)
- @classmethod
- def _convert_p_drop_to_inverted_mask_param(cls, p_drop):
- # TODO this is the same as in Dropout, make DRY
- # TODO add list as an option
- if ia.is_single_number(p_drop):
- p_drop = iap.Binomial(1 - p_drop)
- elif ia.is_iterable(p_drop):
- assert len(p_drop) == 2, (
- "Expected 'p_drop' given as an iterable to contain exactly "
- "2 values, got %d." % (len(p_drop),))
- assert p_drop[0] < p_drop[1], (
- "Expected 'p_drop' given as iterable to contain exactly 2 "
- "values (a, b) with a < b. Got %.4f and %.4f." % (
- p_drop[0], p_drop[1]))
- assert 0 <= p_drop[0] <= 1.0 and 0 <= p_drop[1] <= 1.0, (
- "Expected 'p_drop' given as iterable to only contain values "
- "in the interval [0.0, 1.0], got %.4f and %.4f." % (
- p_drop[0], p_drop[1]))
- p_drop = iap.Binomial(iap.Uniform(1 - p_drop[1], 1 - p_drop[0]))
- elif isinstance(p_drop, iap.StochasticParameter):
- pass
- else:
- raise Exception(
- "Expected p_drop to be float or int or StochasticParameter, "
- "got %s." % (type(p_drop),))
- return p_drop
- def sample_points(self, images, random_state):
- random_state = iarandom.RNG(random_state)
- _verify_sample_points_images(images)
- rss = random_state.duplicate(2)
- points_on_images = self.other_points_sampler.sample_points(images,
- rss[0])
- drop_masks = self._draw_samples(points_on_images, rss[1])
- return self._apply_dropout_masks(points_on_images, drop_masks)
- def _draw_samples(self, points_on_images, random_state):
- rss = random_state.duplicate(len(points_on_images))
- drop_masks = [self._draw_samples_for_image(points_on_image, rs)
- for points_on_image, rs
- in zip(points_on_images, rss)]
- return drop_masks
- def _draw_samples_for_image(self, points_on_image, random_state):
- drop_samples = self.p_drop.draw_samples((len(points_on_image),),
- random_state)
- keep_mask = (drop_samples > 0.5)
- return keep_mask
- @classmethod
- def _apply_dropout_masks(cls, points_on_images, keep_masks):
- points_on_images_dropped = []
- for points_on_image, keep_mask in zip(points_on_images, keep_masks):
- if len(points_on_image) == 0:
- # other sampler didn't provide any points
- poi_dropped = points_on_image
- else:
- if not np.any(keep_mask):
- # keep at least one point if all were supposed to be
- # dropped
- # TODO this could also be moved into its own point sampler,
- # like AtLeastOnePoint(...)
- idx = (len(points_on_image) - 1) // 2
- keep_mask = np.copy(keep_mask)
- keep_mask[idx] = True
- poi_dropped = points_on_image[keep_mask, :]
- points_on_images_dropped.append(poi_dropped)
- return points_on_images_dropped
- def __repr__(self):
- return "DropoutPointsSampler(%s, %s)" % (self.other_points_sampler,
- self.p_drop)
- def __str__(self):
- return self.__repr__()
- class UniformPointsSampler(IPointsSampler):
- """Sample points uniformly on images.
- This point sampler generates `n_points` points per image. The x- and
- y-coordinates are both sampled from uniform distributions matching the
- respective image width and height.
- Parameters
- ----------
- n_points : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
- Number of points to sample on each image.
- * If a single ``int``, then that value will always be used.
- * If a ``tuple`` ``(a, b)``, then a value from the discrete
- interval ``[a..b]`` will be sampled per image.
- * If a ``list``, then a random value will be sampled from that
- ``list`` per image.
- * If a ``StochasticParameter``, then that parameter will be
- queried to draw one value per image.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> sampler = iaa.UniformPointsSampler(500)
- Create a point sampler that generates an array of ``500`` random points for
- each input image. The x- and y-coordinates of each point are sampled
- from uniform distributions.
- """
- def __init__(self, n_points):
- self.n_points = iap.handle_discrete_param(
- n_points, "n_points", value_range=(1, None),
- tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
- def sample_points(self, images, random_state):
- random_state = iarandom.RNG(random_state)
- _verify_sample_points_images(images)
- rss = random_state.duplicate(2)
- n_points_imagewise = self._draw_samples(len(images), rss[0])
- n_points_total = np.sum(n_points_imagewise)
- n_components_total = 2 * n_points_total
- coords_relative = rss[1].uniform(0.0, 1.0, n_components_total)
- coords_relative_xy = coords_relative.reshape(n_points_total, 2)
- return self._convert_relative_coords_to_absolute(
- coords_relative_xy, n_points_imagewise, images)
- def _draw_samples(self, n_augmentables, random_state):
- n_points = self.n_points.draw_samples((n_augmentables,),
- random_state=random_state)
- n_points_clipped = np.clip(n_points, 1, None)
- return n_points_clipped
- @classmethod
- def _convert_relative_coords_to_absolute(cls, coords_rel_xy,
- n_points_imagewise, images):
- coords_absolute = []
- i = 0
- for image, n_points_image in zip(images, n_points_imagewise):
- height, width = image.shape[0:2]
- xx = coords_rel_xy[i:i+n_points_image, 0]
- yy = coords_rel_xy[i:i+n_points_image, 1]
- xx_int = np.clip(np.round(xx * width), 0, width)
- yy_int = np.clip(np.round(yy * height), 0, height)
- coords_absolute.append(np.stack([xx_int, yy_int], axis=-1))
- i += n_points_image
- return coords_absolute
- def __repr__(self):
- return "UniformPointsSampler(%s)" % (self.n_points,)
- def __str__(self):
- return self.__repr__()
- class SubsamplingPointsSampler(IPointsSampler):
- """Ensure that the number of sampled points is below a maximum.
- This point sampler will sample points from another sampler and
- then -- in case more points were generated than an allowed maximum --
- will randomly pick `n_points_max` of these.
- Parameters
- ----------
- other_points_sampler : IPointsSampler
- Another point sampler that is queried to generate a ``list`` of points.
- The dropout operation will be applied to that ``list``.
- n_points_max : int
- Maximum number of allowed points. If `other_points_sampler` generates
- more points than this maximum, a random subset of size `n_points_max`
- will be selected.
- Examples
- --------
- >>> import imgaug.augmenters as iaa
- >>> sampler = iaa.SubsamplingPointsSampler(
- >>> iaa.RelativeRegularGridPointsSampler(0.1, 0.2),
- >>> 50
- >>> )
- Create a points sampler that places ``y*H`` points on the y-axis (with
- ``y`` being ``0.1`` and ``H`` being an image's height) and ``x*W`` on
- the x-axis (analogous). Then, if that number of placed points exceeds
- ``50`` (can easily happen for larger images), a random subset of ``50``
- points will be picked and returned.
- """
- def __init__(self, other_points_sampler, n_points_max):
- assert isinstance(other_points_sampler, IPointsSampler), (
- "Expected to get an instance of IPointsSampler as argument "
- "'other_points_sampler', got type %s." % (
- type(other_points_sampler),))
- self.other_points_sampler = other_points_sampler
- self.n_points_max = np.clip(n_points_max, -1, None)
- if self.n_points_max == 0:
- ia.warn("Got n_points_max=0 in SubsamplingPointsSampler. "
- "This will result in no points ever getting "
- "returned.")
- def sample_points(self, images, random_state):
- random_state = iarandom.RNG(random_state)
- _verify_sample_points_images(images)
- rss = random_state.duplicate(len(images) + 1)
- points_on_images = self.other_points_sampler.sample_points(
- images, rss[-1])
- return [self._subsample(points_on_image, self.n_points_max, rs)
- for points_on_image, rs
- in zip(points_on_images, rss[:-1])]
- @classmethod
- def _subsample(cls, points_on_image, n_points_max, random_state):
- if len(points_on_image) <= n_points_max:
- return points_on_image
- indices = np.arange(len(points_on_image))
- indices_to_keep = random_state.permutation(indices)[0:n_points_max]
- return points_on_image[indices_to_keep]
- def __repr__(self):
- return "SubsamplingPointsSampler(%s, %d)" % (self.other_points_sampler,
- self.n_points_max)
- def __str__(self):
- return self.__repr__()
- # TODO Add points subsampler that drops points close to each other first
- # TODO Add poisson points sampler
- # TODO Add jitter points sampler that moves points around
- # for both see https://codegolf.stackexchange.com/questions/50299/draw-an-image-as-a-voronoi-map/50345#50345
|