From 20ae0a1ca283e5fcfc28260702ed511bc2130731 Mon Sep 17 00:00:00 2001 From: "Josh Warner (Mac)" Date: Sat, 1 Jun 2013 22:12:05 -0500 Subject: [PATCH] FEAT: Generalized n-dimensional array padding This PR exposes NumPy 1.8+ padding functionality to all users of scikit-image, regardless of their personal NumPy version. The improved (much better scaling with dimensionality) version introduced in NumPy 1.8 is used. --- CONTRIBUTORS.txt | 3 +- skimage/util/__init__.py | 12 +- skimage/util/arraypad.py | 1465 +++++++++++++++++++++++++++ skimage/util/tests/test_arraypad.py | 522 ++++++++++ 4 files changed, 2000 insertions(+), 2 deletions(-) create mode 100644 skimage/util/arraypad.py create mode 100644 skimage/util/tests/test_arraypad.py diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index c144e235..b60e9b0c 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -113,7 +113,8 @@ Fixes and tests for Histograms of Oriented Gradients. - Joshua Warner - Multichannel random walker segmentation. + Multichannel random walker segmentation, unified peak finder backend, + n-dimensional array padding, bug and doc fixes. - Petter Strandmark Perimeter calculation in regionprops. diff --git a/skimage/util/__init__.py b/skimage/util/__init__.py index 7e41627b..5433a14b 100644 --- a/skimage/util/__init__.py +++ b/skimage/util/__init__.py @@ -2,6 +2,15 @@ from .dtype import (img_as_float, img_as_int, img_as_uint, img_as_ubyte, img_as_bool, dtype_limits) from .shape import view_as_blocks, view_as_windows +import numpy +ver = numpy.__version__.split('.') +chk = int(ver[0] + ver[1]) +if chk < 18: # Use internal version for numpy versions < 1.8.x + from .arraypad import pad +else: + from numpy import pad +del numpy, ver, chk + __all__ = ['img_as_float', 'img_as_int', @@ -10,4 +19,5 @@ __all__ = ['img_as_float', 'img_as_bool', 'dtype_limits', 'view_as_blocks', - 'view_as_windows'] + 'view_as_windows', + 'pad'] diff --git a/skimage/util/arraypad.py b/skimage/util/arraypad.py new file mode 100644 index 00000000..52a2e3e5 --- /dev/null +++ b/skimage/util/arraypad.py @@ -0,0 +1,1465 @@ +""" +The arraypad module contains a group of functions to pad values onto the edges +of an n-dimensional array. + +""" +from __future__ import division, absolute_import, print_function + +import numpy as np +try: + # Available on 2.x at base, Py3 requires this compatibility import. + # Later versions of NumPy have this for 2.x as well. + from numpy.compat import long +except: + pass + +__all__ = ['pad'] + + +############################################################################### +# Private utility functions. + + +def _arange_ndarray(arr, shape, axis, reverse=False): + """ + Create an ndarray of `shape` with increments along specified `axis` + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + shape : tuple of ints + Shape of desired array. Should be equivalent to `arr.shape` except + `shape[axis]` which may have any positive value. + axis : int + Axis to increment along. + reverse : bool + If False, increment in a positive fashion from 1 to `shape[axis]`, + inclusive. If True, the bounds are the same but the order reversed. + + Returns + ------- + padarr : ndarray + Output array sized to pad `arr` along `axis`, with linear range from + 1 to `shape[axis]` along specified `axis`. + + Notes + ----- + The range is deliberately 1-indexed for this specific use case. Think of + this algorithm as broadcasting `np.arange` to a single `axis` of an + arbitrarily shaped ndarray. + + """ + initshape = tuple(1 if i != axis else shape[axis] + for (i, x) in enumerate(arr.shape)) + if not reverse: + padarr = np.arange(1, shape[axis] + 1) + else: + padarr = np.arange(shape[axis], 0, -1) + padarr = padarr.reshape(initshape) + for i, dim in enumerate(shape): + if padarr.shape[i] != dim: + padarr = padarr.repeat(dim, axis=i) + return padarr + + +def _round_ifneeded(arr, dtype): + """ + Rounds arr inplace if destination dtype is integer. + + Parameters + ---------- + arr : ndarray + Input array. + dtype : dtype + The dtype of the destination array. + + """ + if np.issubdtype(dtype, np.integer): + arr.round(out=arr) + + +def _prepend_const(arr, pad_amt, val, axis=-1): + """ + Prepend constant `val` along `axis` of `arr`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + val : scalar + Constant value to use. For best results should be of type `arr.dtype`; + if not `arr.dtype` will be cast to `arr.dtype`. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` constant `val` prepended along `axis`. + + """ + if pad_amt == 0: + return arr + padshape = tuple(x if i != axis else pad_amt + for (i, x) in enumerate(arr.shape)) + if val == 0: + return np.concatenate((np.zeros(padshape, dtype=arr.dtype), arr), + axis=axis) + else: + return np.concatenate(((np.zeros(padshape) + val).astype(arr.dtype), + arr), axis=axis) + + +def _append_const(arr, pad_amt, val, axis=-1): + """ + Append constant `val` along `axis` of `arr`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + val : scalar + Constant value to use. For best results should be of type `arr.dtype`; + if not `arr.dtype` will be cast to `arr.dtype`. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` constant `val` appended along `axis`. + + """ + if pad_amt == 0: + return arr + padshape = tuple(x if i != axis else pad_amt + for (i, x) in enumerate(arr.shape)) + if val == 0: + return np.concatenate((arr, np.zeros(padshape, dtype=arr.dtype)), + axis=axis) + else: + return np.concatenate( + (arr, (np.zeros(padshape) + val).astype(arr.dtype)), axis=axis) + + +def _prepend_edge(arr, pad_amt, axis=-1): + """ + Prepend `pad_amt` to `arr` along `axis` by extending edge values. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, extended by `pad_amt` edge values appended along `axis`. + + """ + if pad_amt == 0: + return arr + + edge_slice = tuple(slice(None) if i != axis else 0 + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + edge_arr = arr[edge_slice].reshape(pad_singleton) + return np.concatenate((edge_arr.repeat(pad_amt, axis=axis), arr), + axis=axis) + + +def _append_edge(arr, pad_amt, axis=-1): + """ + Append `pad_amt` to `arr` along `axis` by extending edge values. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, extended by `pad_amt` edge values prepended along + `axis`. + + """ + if pad_amt == 0: + return arr + + edge_slice = tuple(slice(None) if i != axis else arr.shape[axis] - 1 + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + edge_arr = arr[edge_slice].reshape(pad_singleton) + return np.concatenate((arr, edge_arr.repeat(pad_amt, axis=axis)), + axis=axis) + + +def _prepend_ramp(arr, pad_amt, end, axis=-1): + """ + Prepend linear ramp along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + end : scalar + Constal value to use. For best results should be of type `arr.dtype`; + if not `arr.dtype` will be cast to `arr.dtype`. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values prepended along `axis`. The + prepended region ramps linearly from the edge value to `end`. + + """ + if pad_amt == 0: + return arr + + # Generate shape for final concatenated array + padshape = tuple(x if i != axis else pad_amt + for (i, x) in enumerate(arr.shape)) + + # Generate an n-dimensional array incrementing along `axis` + ramp_arr = _arange_ndarray(arr, padshape, axis, + reverse=True).astype(np.float64) + + # Appropriate slicing to extract n-dimensional edge along `axis` + edge_slice = tuple(slice(None) if i != axis else 0 + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract edge, reshape to original rank, and extend along `axis` + edge_pad = arr[edge_slice].reshape(pad_singleton).repeat(pad_amt, axis) + + # Linear ramp + slope = (end - edge_pad) / float(pad_amt) + ramp_arr = ramp_arr * slope + ramp_arr += edge_pad + _round_ifneeded(ramp_arr, arr.dtype) + + # Ramp values will most likely be float, cast them to the same type as arr + return np.concatenate((ramp_arr.astype(arr.dtype), arr), axis=axis) + + +def _append_ramp(arr, pad_amt, end, axis=-1): + """ + Append linear ramp along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + end : scalar + Constal value to use. For best results should be of type `arr.dtype`; + if not `arr.dtype` will be cast to `arr.dtype`. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + appended region ramps linearly from the edge value to `end`. + + """ + if pad_amt == 0: + return arr + + # Generate shape for final concatenated array + padshape = tuple(x if i != axis else pad_amt + for (i, x) in enumerate(arr.shape)) + + # Generate an n-dimensional array incrementing along `axis` + ramp_arr = _arange_ndarray(arr, padshape, axis, + reverse=False).astype(np.float64) + + # Slice a chunk from the edge to calculate stats on + edge_slice = tuple(slice(None) if i != axis else -1 + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract edge, reshape to original rank, and extend along `axis` + edge_pad = arr[edge_slice].reshape(pad_singleton).repeat(pad_amt, axis) + + # Linear ramp + slope = (end - edge_pad) / float(pad_amt) + ramp_arr = ramp_arr * slope + ramp_arr += edge_pad + _round_ifneeded(ramp_arr, arr.dtype) + + # Ramp values will most likely be float, cast them to the same type as arr + return np.concatenate((arr, ramp_arr.astype(arr.dtype)), axis=axis) + + +def _prepend_max(arr, pad_amt, num, axis=-1): + """ + Prepend `pad_amt` maximum values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + num : int + Depth into `arr` along `axis` to calculate maximum. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + prepended region is the maximum of the first `num` values along + `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _prepend_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + max_slice = tuple(slice(None) if i != axis else slice(num) + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate max, reshape to add singleton dimension back + max_chunk = arr[max_slice].max(axis=axis).reshape(pad_singleton) + + # Concatenate `arr` with `max_chunk`, extended along `axis` by `pad_amt` + return np.concatenate((max_chunk.repeat(pad_amt, axis=axis), arr), + axis=axis) + + +def _append_max(arr, pad_amt, num, axis=-1): + """ + Pad one `axis` of `arr` with the maximum of the last `num` elements. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + num : int + Depth into `arr` along `axis` to calculate maximum. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + appended region is the maximum of the final `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _append_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + end = arr.shape[axis] - 1 + if num is not None: + max_slice = tuple( + slice(None) if i != axis else slice(end, end - num, -1) + for (i, x) in enumerate(arr.shape)) + else: + max_slice = tuple(slice(None) for x in arr.shape) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate max, reshape to add singleton dimension back + max_chunk = arr[max_slice].max(axis=axis).reshape(pad_singleton) + + # Concatenate `arr` with `max_chunk`, extended along `axis` by `pad_amt` + return np.concatenate((arr, max_chunk.repeat(pad_amt, axis=axis)), + axis=axis) + + +def _prepend_mean(arr, pad_amt, num, axis=-1): + """ + Prepend `pad_amt` mean values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + num : int + Depth into `arr` along `axis` to calculate mean. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values prepended along `axis`. The + prepended region is the mean of the first `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _prepend_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + mean_slice = tuple(slice(None) if i != axis else slice(num) + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate mean, reshape to add singleton dimension back + mean_chunk = arr[mean_slice].mean(axis).reshape(pad_singleton) + _round_ifneeded(mean_chunk, arr.dtype) + + # Concatenate `arr` with `mean_chunk`, extended along `axis` by `pad_amt` + return np.concatenate((mean_chunk.repeat(pad_amt, axis).astype(arr.dtype), + arr), axis=axis) + + +def _append_mean(arr, pad_amt, num, axis=-1): + """ + Append `pad_amt` mean values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + num : int + Depth into `arr` along `axis` to calculate mean. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + appended region is the maximum of the final `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _append_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + end = arr.shape[axis] - 1 + if num is not None: + mean_slice = tuple( + slice(None) if i != axis else slice(end, end - num, -1) + for (i, x) in enumerate(arr.shape)) + else: + mean_slice = tuple(slice(None) for x in arr.shape) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate mean, reshape to add singleton dimension back + mean_chunk = arr[mean_slice].mean(axis=axis).reshape(pad_singleton) + _round_ifneeded(mean_chunk, arr.dtype) + + # Concatenate `arr` with `mean_chunk`, extended along `axis` by `pad_amt` + return np.concatenate( + (arr, mean_chunk.repeat(pad_amt, axis).astype(arr.dtype)), axis=axis) + + +def _prepend_med(arr, pad_amt, num, axis=-1): + """ + Prepend `pad_amt` median values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + num : int + Depth into `arr` along `axis` to calculate median. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values prepended along `axis`. The + prepended region is the median of the first `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _prepend_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + med_slice = tuple(slice(None) if i != axis else slice(num) + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate median, reshape to add singleton dimension back + med_chunk = np.median(arr[med_slice], axis=axis).reshape(pad_singleton) + _round_ifneeded(med_chunk, arr.dtype) + + # Concatenate `arr` with `med_chunk`, extended along `axis` by `pad_amt` + return np.concatenate( + (med_chunk.repeat(pad_amt, axis).astype(arr.dtype), arr), axis=axis) + + +def _append_med(arr, pad_amt, num, axis=-1): + """ + Append `pad_amt` median values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + num : int + Depth into `arr` along `axis` to calculate median. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + appended region is the median of the final `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _append_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + end = arr.shape[axis] - 1 + if num is not None: + med_slice = tuple( + slice(None) if i != axis else slice(end, end - num, -1) + for (i, x) in enumerate(arr.shape)) + else: + med_slice = tuple(slice(None) for x in arr.shape) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate median, reshape to add singleton dimension back + med_chunk = np.median(arr[med_slice], axis=axis).reshape(pad_singleton) + _round_ifneeded(med_chunk, arr.dtype) + + # Concatenate `arr` with `med_chunk`, extended along `axis` by `pad_amt` + return np.concatenate( + (arr, med_chunk.repeat(pad_amt, axis).astype(arr.dtype)), axis=axis) + + +def _prepend_min(arr, pad_amt, num, axis=-1): + """ + Prepend `pad_amt` minimum values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to prepend. + num : int + Depth into `arr` along `axis` to calculate minimum. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values prepended along `axis`. The + prepended region is the minimum of the first `num` values along + `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _prepend_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + min_slice = tuple(slice(None) if i != axis else slice(num) + for (i, x) in enumerate(arr.shape)) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate min, reshape to add singleton dimension back + min_chunk = arr[min_slice].min(axis=axis).reshape(pad_singleton) + + # Concatenate `arr` with `min_chunk`, extended along `axis` by `pad_amt` + return np.concatenate((min_chunk.repeat(pad_amt, axis=axis), arr), + axis=axis) + + +def _append_min(arr, pad_amt, num, axis=-1): + """ + Append `pad_amt` median values along `axis`. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : int + Amount of padding to append. + num : int + Depth into `arr` along `axis` to calculate minimum. + Range: [1, `arr.shape[axis]`] or None (entire axis) + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt` values appended along `axis`. The + appended region is the minimum of the final `num` values along `axis`. + + """ + if pad_amt == 0: + return arr + + # Equivalent to edge padding for single value, so do that instead + if num == 1: + return _append_edge(arr, pad_amt, axis) + + # Use entire array if `num` is too large + if num is not None: + if num >= arr.shape[axis]: + num = None + + # Slice a chunk from the edge to calculate stats on + end = arr.shape[axis] - 1 + if num is not None: + min_slice = tuple( + slice(None) if i != axis else slice(end, end - num, -1) + for (i, x) in enumerate(arr.shape)) + else: + min_slice = tuple(slice(None) for x in arr.shape) + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + + # Extract slice, calculate min, reshape to add singleton dimension back + min_chunk = arr[min_slice].min(axis=axis).reshape(pad_singleton) + + # Concatenate `arr` with `min_chunk`, extended along `axis` by `pad_amt` + return np.concatenate((arr, min_chunk.repeat(pad_amt, axis=axis)), + axis=axis) + + +def _pad_ref(arr, pad_amt, method, axis=-1): + """ + Pad `axis` of `arr` by reflection. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : tuple of ints, length 2 + Padding to (prepend, append) along `axis`. + method : str + Controls method of reflection; options are 'even' or 'odd'. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt[0]` values prepended and `pad_amt[1]` + values appended along `axis`. Both regions are padded with reflected + values from the original array. + + Notes + ----- + This algorithm does not pad with repetition, i.e. the edges are not + repeated in the reflection. For that behavior, use `method='symmetric'`. + + The modes 'reflect', 'symmetric', and 'wrap' must be padded with a + single function, lest the indexing tricks in non-integer multiples of the + original shape would violate repetition in the final iteration. + + """ + # Implicit booleanness to test for zero (or None) in any scalar type + if pad_amt[0] == 0 and pad_amt[1] == 0: + return arr + + ########################################################################## + # Prepended region + + # Slice off a reverse indexed chunk from near edge to pad `arr` before + ref_slice = tuple(slice(None) if i != axis else slice(pad_amt[0], 0, -1) + for (i, x) in enumerate(arr.shape)) + + ref_chunk1 = arr[ref_slice] + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + if pad_amt[0] == 1: + ref_chunk1 = ref_chunk1.reshape(pad_singleton) + + # Memory/computationally more expensive, only do this if `method='odd'` + if 'odd' in method and pad_amt[0] > 0: + edge_slice1 = tuple(slice(None) if i != axis else 0 + for (i, x) in enumerate(arr.shape)) + edge_chunk = arr[edge_slice1].reshape(pad_singleton) + ref_chunk1 = 2 * edge_chunk - ref_chunk1 + del edge_chunk + + ########################################################################## + # Appended region + + # Slice off a reverse indexed chunk from far edge to pad `arr` after + start = arr.shape[axis] - pad_amt[1] - 1 + end = arr.shape[axis] - 1 + ref_slice = tuple(slice(None) if i != axis else slice(start, end) + for (i, x) in enumerate(arr.shape)) + rev_idx = tuple(slice(None) if i != axis else slice(None, None, -1) + for (i, x) in enumerate(arr.shape)) + ref_chunk2 = arr[ref_slice][rev_idx] + + if pad_amt[1] == 1: + ref_chunk2 = ref_chunk2.reshape(pad_singleton) + + if 'odd' in method: + edge_slice2 = tuple(slice(None) if i != axis else -1 + for (i, x) in enumerate(arr.shape)) + edge_chunk = arr[edge_slice2].reshape(pad_singleton) + ref_chunk2 = 2 * edge_chunk - ref_chunk2 + del edge_chunk + + # Concatenate `arr` with both chunks, extending along `axis` + return np.concatenate((ref_chunk1, arr, ref_chunk2), axis=axis) + + +def _pad_sym(arr, pad_amt, method, axis=-1): + """ + Pad `axis` of `arr` by symmetry. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : tuple of ints, length 2 + Padding to (prepend, append) along `axis`. + method : str + Controls method of symmetry; options are 'even' or 'odd'. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt[0]` values prepended and `pad_amt[1]` + values appended along `axis`. Both regions are padded with symmetric + values from the original array. + + Notes + ----- + This algorithm DOES pad with repetition, i.e. the edges are repeated. + For a method that does not repeat edges, use `method='reflect'`. + + The modes 'reflect', 'symmetric', and 'wrap' must be padded with a + single function, lest the indexing tricks in non-integer multiples of the + original shape would violate repetition in the final iteration. + + """ + # Implicit booleanness to test for zero (or None) in any scalar type + if pad_amt[0] == 0 and pad_amt[1] == 0: + return arr + + ########################################################################## + # Prepended region + + # Slice off a reverse indexed chunk from near edge to pad `arr` before + sym_slice = tuple(slice(None) if i != axis else slice(0, pad_amt[0]) + for (i, x) in enumerate(arr.shape)) + rev_idx = tuple(slice(None) if i != axis else slice(None, None, -1) + for (i, x) in enumerate(arr.shape)) + sym_chunk1 = arr[sym_slice][rev_idx] + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + if pad_amt[0] == 1: + sym_chunk1 = sym_chunk1.reshape(pad_singleton) + + # Memory/computationally more expensive, only do this if `method='odd'` + if 'odd' in method and pad_amt[0] > 0: + edge_slice1 = tuple(slice(None) if i != axis else 0 + for (i, x) in enumerate(arr.shape)) + edge_chunk = arr[edge_slice1].reshape(pad_singleton) + sym_chunk1 = 2 * edge_chunk - sym_chunk1 + del edge_chunk + + ########################################################################## + # Appended region + + # Slice off a reverse indexed chunk from far edge to pad `arr` after + start = arr.shape[axis] - pad_amt[1] + end = arr.shape[axis] + sym_slice = tuple(slice(None) if i != axis else slice(start, end) + for (i, x) in enumerate(arr.shape)) + sym_chunk2 = arr[sym_slice][rev_idx] + + if pad_amt[1] == 1: + sym_chunk2 = sym_chunk2.reshape(pad_singleton) + + if 'odd' in method: + edge_slice2 = tuple(slice(None) if i != axis else -1 + for (i, x) in enumerate(arr.shape)) + edge_chunk = arr[edge_slice2].reshape(pad_singleton) + sym_chunk2 = 2 * edge_chunk - sym_chunk2 + del edge_chunk + + # Concatenate `arr` with both chunks, extending along `axis` + return np.concatenate((sym_chunk1, arr, sym_chunk2), axis=axis) + + +def _pad_wrap(arr, pad_amt, axis=-1): + """ + Pad `axis` of `arr` via wrapping. + + Parameters + ---------- + arr : ndarray + Input array of arbitrary shape. + pad_amt : tuple of ints, length 2 + Padding to (prepend, append) along `axis`. + axis : int + Axis along which to pad `arr`. + + Returns + ------- + padarr : ndarray + Output array, with `pad_amt[0]` values prepended and `pad_amt[1]` + values appended along `axis`. Both regions are padded wrapped values + from the opposite end of `axis`. + + Notes + ----- + This method of padding is also known as 'tile' or 'tiling'. + + The modes 'reflect', 'symmetric', and 'wrap' must be padded with a + single function, lest the indexing tricks in non-integer multiples of the + original shape would violate repetition in the final iteration. + + """ + # Implicit booleanness to test for zero (or None) in any scalar type + if pad_amt[0] == 0 and pad_amt[1] == 0: + return arr + + ########################################################################## + # Prepended region + + # Slice off a reverse indexed chunk from near edge to pad `arr` before + start = arr.shape[axis] - pad_amt[0] + end = arr.shape[axis] + wrap_slice = tuple(slice(None) if i != axis else slice(start, end) + for (i, x) in enumerate(arr.shape)) + wrap_chunk1 = arr[wrap_slice] + + # Shape to restore singleton dimension after slicing + pad_singleton = tuple(x if i != axis else 1 + for (i, x) in enumerate(arr.shape)) + if pad_amt[0] == 1: + wrap_chunk1 = wrap_chunk1.reshape(pad_singleton) + + ########################################################################## + # Appended region + + # Slice off a reverse indexed chunk from far edge to pad `arr` after + wrap_slice = tuple(slice(None) if i != axis else slice(0, pad_amt[1]) + for (i, x) in enumerate(arr.shape)) + wrap_chunk2 = arr[wrap_slice] + + if pad_amt[1] == 1: + wrap_chunk2 = wrap_chunk2.reshape(pad_singleton) + + # Concatenate `arr` with both chunks, extending along `axis` + return np.concatenate((wrap_chunk1, arr, wrap_chunk2), axis=axis) + + +def _normalize_shape(narray, shape): + """ + Private function which does some checks and normalizes the possibly + much simpler representations of 'pad_width', 'stat_length', + 'constant_values', 'end_values'. + + Parameters + ---------- + narray : ndarray + Input ndarray + shape : {sequence, int}, optional + The width of padding (pad_width) or the number of elements on the + edge of the narray used for statistics (stat_length). + ((before_1, after_1), ... (before_N, after_N)) unique number of + elements for each axis where `N` is rank of `narray`. + ((before, after),) yields same before and after constants for each + axis. + (constant,) or int is a shortcut for before = after = constant for + all axes. + + Returns + ------- + _normalize_shape : tuple of tuples + int => ((int, int), (int, int), ...) + [[int1, int2], [int3, int4], ...] => ((int1, int2), (int3, int4), ...) + ((int1, int2), (int3, int4), ...) => no change + [[int1, int2], ] => ((int1, int2), (int1, int2), ...) + ((int1, int2), ) => ((int1, int2), (int1, int2), ...) + [[int , ], ] => ((int, int), (int, int), ...) + ((int , ), ) => ((int, int), (int, int), ...) + + """ + normshp = None + shapelen = len(np.shape(narray)) + if (isinstance(shape, int)) or shape is None: + normshp = ((shape, shape), ) * shapelen + elif (isinstance(shape, (tuple, list)) + and isinstance(shape[0], (tuple, list)) + and len(shape) == shapelen): + normshp = shape + for i in normshp: + if len(i) != 2: + fmt = "Unable to create correctly shaped tuple from %s" + raise ValueError(fmt % (normshp,)) + elif (isinstance(shape, (tuple, list)) + and isinstance(shape[0], (int, float, long)) + and len(shape) == 1): + normshp = ((shape[0], shape[0]), ) * shapelen + elif (isinstance(shape, (tuple, list)) + and isinstance(shape[0], (int, float, long)) + and len(shape) == 2): + normshp = (shape, ) * shapelen + if normshp is None: + fmt = "Unable to create correctly shaped tuple from %s" + raise ValueError(fmt % (shape,)) + return normshp + + +def _validate_lengths(narray, number_elements): + """ + Private function which does some checks and reformats pad_width and + stat_length using _normalize_shape. + + Parameters + ---------- + narray : ndarray + Input ndarray + number_elements : {sequence, int}, optional + The width of padding (pad_width) or the number of elements on the edge + of the narray used for statistics (stat_length). + ((before_1, after_1), ... (before_N, after_N)) unique number of + elements for each axis. + ((before, after),) yields same before and after constants for each + axis. + (constant,) or int is a shortcut for before = after = constant for all + axes. + + Returns + ------- + _validate_lengths : tuple of tuples + int => ((int, int), (int, int), ...) + [[int1, int2], [int3, int4], ...] => ((int1, int2), (int3, int4), ...) + ((int1, int2), (int3, int4), ...) => no change + [[int1, int2], ] => ((int1, int2), (int1, int2), ...) + ((int1, int2), ) => ((int1, int2), (int1, int2), ...) + [[int , ], ] => ((int, int), (int, int), ...) + ((int , ), ) => ((int, int), (int, int), ...) + + """ + normshp = _normalize_shape(narray, number_elements) + for i in normshp: + chk = [1 if x is None else x for x in i] + chk = [1 if x > 0 else -1 for x in chk] + if (chk[0] < 0) or (chk[1] < 0): + fmt = "%s cannot contain negative values." + raise ValueError(fmt % (number_elements,)) + return normshp + + +############################################################################### +# Public functions + + +def pad(array, pad_width, mode=None, **kwargs): + """ + Pads an array. + + Parameters + ---------- + array : array_like of rank N + Input array + pad_width : {sequence, int} + Number of values padded to the edges of each axis. + ((before_1, after_1), ... (before_N, after_N)) unique pad widths + for each axis. + ((before, after),) yields same before and after pad for each axis. + (pad,) or int is a shortcut for before = after = pad width for all + axes. + mode : {str, function} + One of the following string values or a user supplied function. + + 'constant' Pads with a constant value. + 'edge' Pads with the edge values of array. + 'linear_ramp' Pads with the linear ramp between end_value and the + array edge value. + 'maximum' Pads with the maximum value of all or part of the + vector along each axis. + 'mean' Pads with the mean value of all or part of the + vector along each axis. + 'median' Pads with the median value of all or part of the + vector along each axis. + 'minimum' Pads with the minimum value of all or part of the + vector along each axis. + 'reflect' Pads with the reflection of the vector mirrored on + the first and last values of the vector along each + axis. + 'symmetric' Pads with the reflection of the vector mirrored + along the edge of the array. + 'wrap' Pads with the wrap of the vector along the axis. + The first values are used to pad the end and the + end values are used to pad the beginning. + Padding function, see Notes. + stat_length : {sequence, int}, optional + Used in 'maximum', 'mean', 'median', and 'minimum'. Number of + values at edge of each axis used to calculate the statistic value. + + ((before_1, after_1), ... (before_N, after_N)) unique statistic + lengths for each axis. + + ((before, after),) yields same before and after statistic lengths + for each axis. + + (stat_length,) or int is a shortcut for before = after = statistic + length for all axes. + + Default is ``None``, to use the entire axis. + constant_values : {sequence, int}, optional + Used in 'constant'. The values to set the padded values for each + axis. + + ((before_1, after_1), ... (before_N, after_N)) unique pad constants + for each axis. + + ((before, after),) yields same before and after constants for each + axis. + + (constant,) or int is a shortcut for before = after = constant for + all axes. + + Default is 0. + end_values : {sequence, int}, optional + Used in 'linear_ramp'. The values used for the ending value of the + linear_ramp and that will form the edge of the padded array. + + ((before_1, after_1), ... (before_N, after_N)) unique end values + for each axis. + + ((before, after),) yields same before and after end values for each + axis. + + (constant,) or int is a shortcut for before = after = end value for + all axes. + + Default is 0. + reflect_type : str {'even', 'odd'}, optional + Used in 'reflect', and 'symmetric'. The 'even' style is the + default with an unaltered reflection around the edge value. For + the 'odd' style, the extented part of the array is created by + subtracting the reflected values from two times the edge value. + + Returns + ------- + pad : ndarray + Padded array of rank equal to `array` with shape increased + according to `pad_width`. + + Notes + ----- + For an array with rank greater than 1, some of the padding of later + axes is calculated from padding of previous axes. This is easiest to + think about with a rank 2 array where the corners of the padded array + are calculated by using padded values from the first axis. + + The padding function, if used, should return a rank 1 array equal in + length to the vector argument with padded values replaced. It has the + following signature: + + padding_func(vector, iaxis_pad_width, iaxis, **kwargs) + + where + + vector : ndarray + A rank 1 array already padded with zeros. Padded values are + vector[:pad_tuple[0]] and vector[-pad_tuple[1]:]. + iaxis_pad_width : tuple + A 2-tuple of ints, iaxis_pad_width[0] represents the number of + values padded at the beginning of vector where + iaxis_pad_width[1] represents the number of values padded at + the end of vector. + iaxis : int + The axis currently being calculated. + kwargs : misc + Any keyword arguments the function requires. + + Examples + -------- + >>> a = [1, 2, 3, 4, 5] + >>> np.lib.pad(a, (2,3), 'constant', constant_values=(4,6)) + array([4, 4, 1, 2, 3, 4, 5, 6, 6, 6]) + + >>> np.lib.pad(a, (2,3), 'edge') + array([1, 1, 1, 2, 3, 4, 5, 5, 5, 5]) + + >>> np.lib.pad(a, (2,3), 'linear_ramp', end_values=(5,-4)) + array([ 5, 3, 1, 2, 3, 4, 5, 2, -1, -4]) + + >>> np.lib.pad(a, (2,), 'maximum') + array([5, 5, 1, 2, 3, 4, 5, 5, 5]) + + >>> np.lib.pad(a, (2,), 'mean') + array([3, 3, 1, 2, 3, 4, 5, 3, 3]) + + >>> np.lib.pad(a, (2,), 'median') + array([3, 3, 1, 2, 3, 4, 5, 3, 3]) + + >>> a = [[1,2], [3,4]] + >>> np.lib.pad(a, ((3, 2), (2, 3)), 'minimum') + array([[1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [3, 3, 3, 4, 3, 3, 3], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1]]) + + >>> a = [1, 2, 3, 4, 5] + >>> np.lib.pad(a, (2,3), 'reflect') + array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2]) + + >>> np.lib.pad(a, (2,3), 'reflect', reflect_type='odd') + array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]) + + >>> np.lib.pad(a, (2,3), 'symmetric') + array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3]) + + >>> np.lib.pad(a, (2,3), 'symmetric', reflect_type='odd') + array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7]) + + >>> np.lib.pad(a, (2,3), 'wrap') + array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3]) + + >>> def padwithtens(vector, pad_width, iaxis, kwargs): + ... vector[:pad_width[0]] = 10 + ... vector[-pad_width[1]:] = 10 + ... return vector + + >>> a = np.arange(6) + >>> a = a.reshape((2,3)) + + >>> np.lib.pad(a, 2, padwithtens) + array([[10, 10, 10, 10, 10, 10, 10], + [10, 10, 10, 10, 10, 10, 10], + [10, 10, 0, 1, 2, 10, 10], + [10, 10, 3, 4, 5, 10, 10], + [10, 10, 10, 10, 10, 10, 10], + [10, 10, 10, 10, 10, 10, 10]]) + + """ + + narray = np.array(array) + pad_width = _validate_lengths(narray, pad_width) + + allowedkwargs = { + 'constant': ['constant_values'], + 'edge': [], + 'linear_ramp': ['end_values'], + 'maximum': ['stat_length'], + 'mean': ['stat_length'], + 'median': ['stat_length'], + 'minimum': ['stat_length'], + 'reflect': ['reflect_type'], + 'symmetric': ['reflect_type'], + 'wrap': []} + + kwdefaults = { + 'stat_length': None, + 'constant_values': 0, + 'end_values': 0, + 'reflect_type': 'even'} + + if isinstance(mode, str): + # Make sure have allowed kwargs appropriate for mode + for key in kwargs: + if key not in allowedkwargs[mode]: + raise ValueError('%s keyword not in allowed keywords %s' % + (key, allowedkwargs[mode])) + + # Set kwarg defaults + for kw in allowedkwargs[mode]: + kwargs.setdefault(kw, kwdefaults[kw]) + + # Need to only normalize particular keywords. + for i in kwargs: + if i == 'stat_length': + kwargs[i] = _validate_lengths(narray, kwargs[i]) + if i in ['end_values', 'constant_values']: + kwargs[i] = _normalize_shape(narray, kwargs[i]) + elif mode is None: + raise ValueError('Keyword "mode" must be a function or one of %s.' % + (list(allowedkwargs.keys()),)) + else: + # Drop back to old, slower np.apply_along_axis mode for user-supplied + # vector function + function = mode + + # Create a new padded array + rank = list(range(len(narray.shape))) + total_dim_increase = [np.sum(pad_width[i]) for i in rank] + offset_slices = [slice(pad_width[i][0], + pad_width[i][0] + narray.shape[i]) + for i in rank] + new_shape = np.array(narray.shape) + total_dim_increase + newmat = np.zeros(new_shape).astype(narray.dtype) + + # Insert the original array into the padded array + newmat[offset_slices] = narray + + # This is the core of pad ... + for iaxis in rank: + np.apply_along_axis(function, + iaxis, + newmat, + pad_width[iaxis], + iaxis, + kwargs) + return newmat + + # If we get here, use new padding method + newmat = narray.copy() + + # API preserved, but completely new algorithm which pads by building the + # entire block to pad before/after `arr` with in one step, for each axis. + if mode == 'constant': + for axis, ((pad_before, pad_after), (before_val, after_val)) \ + in enumerate(zip(pad_width, kwargs['constant_values'])): + newmat = _prepend_const(newmat, pad_before, before_val, axis) + newmat = _append_const(newmat, pad_after, after_val, axis) + + elif mode == 'edge': + for axis, (pad_before, pad_after) in enumerate(pad_width): + newmat = _prepend_edge(newmat, pad_before, axis) + newmat = _append_edge(newmat, pad_after, axis) + + elif mode == 'linear_ramp': + for axis, ((pad_before, pad_after), (before_val, after_val)) \ + in enumerate(zip(pad_width, kwargs['end_values'])): + newmat = _prepend_ramp(newmat, pad_before, before_val, axis) + newmat = _append_ramp(newmat, pad_after, after_val, axis) + + elif mode == 'maximum': + for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \ + in enumerate(zip(pad_width, kwargs['stat_length'])): + newmat = _prepend_max(newmat, pad_before, chunk_before, axis) + newmat = _append_max(newmat, pad_after, chunk_after, axis) + + elif mode == 'mean': + for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \ + in enumerate(zip(pad_width, kwargs['stat_length'])): + newmat = _prepend_mean(newmat, pad_before, chunk_before, axis) + newmat = _append_mean(newmat, pad_after, chunk_after, axis) + + elif mode == 'median': + for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \ + in enumerate(zip(pad_width, kwargs['stat_length'])): + newmat = _prepend_med(newmat, pad_before, chunk_before, axis) + newmat = _append_med(newmat, pad_after, chunk_after, axis) + + elif mode == 'minimum': + for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \ + in enumerate(zip(pad_width, kwargs['stat_length'])): + newmat = _prepend_min(newmat, pad_before, chunk_before, axis) + newmat = _append_min(newmat, pad_after, chunk_after, axis) + + elif mode == 'reflect': + for axis, (pad_before, pad_after) in enumerate(pad_width): + # Recursive padding along any axis where `pad_amt` is too large + # for indexing tricks. We can only safely pad the original axis + # length, to keep the period of the reflections consistent. + if ((pad_before > 0) or + (pad_after > 0)) and newmat.shape[axis] == 1: + # Extending singleton dimension for 'reflect' is legacy + # behavior; it really should raise an error. + newmat = _prepend_edge(newmat, pad_before, axis) + newmat = _append_edge(newmat, pad_after, axis) + continue + + method = kwargs['reflect_type'] + safe_pad = newmat.shape[axis] - 1 + while ((pad_before > safe_pad) or (pad_after > safe_pad)): + offset = 0 + pad_iter_b = min(safe_pad, + safe_pad * (pad_before // safe_pad)) + pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad)) + newmat = _pad_ref(newmat, (pad_iter_b, + pad_iter_a), method, axis) + pad_before -= pad_iter_b + pad_after -= pad_iter_a + if pad_iter_b > 0: + offset += 1 + if pad_iter_a > 0: + offset += 1 + safe_pad += pad_iter_b + pad_iter_a + newmat = _pad_ref(newmat, (pad_before, pad_after), method, axis) + + elif mode == 'symmetric': + for axis, (pad_before, pad_after) in enumerate(pad_width): + # Recursive padding along any axis where `pad_amt` is too large + # for indexing tricks. We can only safely pad the original axis + # length, to keep the period of the reflections consistent. + method = kwargs['reflect_type'] + safe_pad = newmat.shape[axis] + while ((pad_before > safe_pad) or + (pad_after > safe_pad)): + pad_iter_b = min(safe_pad, + safe_pad * (pad_before // safe_pad)) + pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad)) + newmat = _pad_sym(newmat, (pad_iter_b, + pad_iter_a), method, axis) + pad_before -= pad_iter_b + pad_after -= pad_iter_a + safe_pad += pad_iter_b + pad_iter_a + newmat = _pad_sym(newmat, (pad_before, pad_after), method, axis) + + elif mode == 'wrap': + for axis, (pad_before, pad_after) in enumerate(pad_width): + # Recursive padding along any axis where `pad_amt` is too large + # for indexing tricks. We can only safely pad the original axis + # length, to keep the period of the reflections consistent. + safe_pad = newmat.shape[axis] + while ((pad_before > safe_pad) or + (pad_after > safe_pad)): + pad_iter_b = min(safe_pad, + safe_pad * (pad_before // safe_pad)) + pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad)) + newmat = _pad_wrap(newmat, (pad_iter_b, pad_iter_a), axis) + + pad_before -= pad_iter_b + pad_after -= pad_iter_a + safe_pad += pad_iter_b + pad_iter_a + newmat = _pad_wrap(newmat, (pad_before, pad_after), axis) + + return newmat diff --git a/skimage/util/tests/test_arraypad.py b/skimage/util/tests/test_arraypad.py new file mode 100644 index 00000000..2c0e5ba7 --- /dev/null +++ b/skimage/util/tests/test_arraypad.py @@ -0,0 +1,522 @@ +"""Tests for the pad functions. + +""" +from __future__ import division, absolute_import, print_function + +from numpy.testing import TestCase, run_module_suite, assert_array_equal +from numpy.testing import assert_raises, assert_array_almost_equal +import numpy as np +from skimage.util import pad + + +class TestStatistic(TestCase): + def test_check_mean_stat_length(self): + a = np.arange(100).astype('f') + a = pad(a, ((25, 20), ), 'mean', stat_length=((2, 3), )) + b = np.array([ + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, + + 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., + 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., + 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., + 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., + 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., + 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., + 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., + 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., + 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., + 90., 91., 92., 93., 94., 95., 96., 97., 98., 99., + + 98., 98., 98., 98., 98., 98., 98., 98., 98., 98., + 98., 98., 98., 98., 98., 98., 98., 98., 98., 98.]) + assert_array_equal(a, b) + + def test_check_maximum_1(self): + a = np.arange(100) + a = pad(a, (25, 20), 'maximum') + b = np.array([ + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99]) + assert_array_equal(a, b) + + def test_check_maximum_2(self): + a = np.arange(100) + 1 + a = pad(a, (25, 20), 'maximum') + b = np.array([ + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, + + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]) + assert_array_equal(a, b) + + def test_check_minimum_1(self): + a = np.arange(100) + a = pad(a, (25, 20), 'minimum') + b = np.array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert_array_equal(a, b) + + def test_check_minimum_2(self): + a = np.arange(100) + 2 + a = pad(a, (25, 20), 'minimum') + b = np.array([ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, + + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]) + assert_array_equal(a, b) + + def test_check_median(self): + a = np.arange(100).astype('f') + a = pad(a, (25, 20), 'median') + b = np.array([ + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, + + 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., + 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., + 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., + 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., + 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., + 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., + 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., + 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., + 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., + 90., 91., 92., 93., 94., 95., 96., 97., 98., 99., + + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5]) + assert_array_equal(a, b) + + def test_check_median_01(self): + a = np.array([[3, 1, 4], [4, 5, 9], [9, 8, 2]]) + a = pad(a, 1, 'median') + b = np.array([ + [4, 4, 5, 4, 4], + + [3, 3, 1, 4, 3], + [5, 4, 5, 9, 5], + [8, 9, 8, 2, 8], + + [4, 4, 5, 4, 4]]) + assert_array_equal(a, b) + + def test_check_median_02(self): + a = np.array([[3, 1, 4], [4, 5, 9], [9, 8, 2]]) + a = pad(a.T, 1, 'median').T + b = np.array([ + [5, 4, 5, 4, 5], + + [3, 3, 1, 4, 3], + [5, 4, 5, 9, 5], + [8, 9, 8, 2, 8], + + [5, 4, 5, 4, 5]]) + assert_array_equal(a, b) + + def test_check_mean_shape_one(self): + a = [[4, 5, 6]] + a = pad(a, (5, 7), 'mean', stat_length=2) + b = np.array([ + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6], + [4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6]]) + assert_array_equal(a, b) + + def test_check_mean_2(self): + a = np.arange(100).astype('f') + a = pad(a, (25, 20), 'mean') + b = np.array([ + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, + + 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., + 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., + 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., + 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., + 40., 41., 42., 43., 44., 45., 46., 47., 48., 49., + 50., 51., 52., 53., 54., 55., 56., 57., 58., 59., + 60., 61., 62., 63., 64., 65., 66., 67., 68., 69., + 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., + 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., + 90., 91., 92., 93., 94., 95., 96., 97., 98., 99., + + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, + 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5, 49.5]) + assert_array_equal(a, b) + + +class TestConstant(TestCase): + def test_check_constant(self): + a = np.arange(100) + a = pad(a, (25, 20), 'constant', constant_values=(10, 20)) + b = np.array([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20]) + assert_array_equal(a, b) + + +class TestLinearRamp(TestCase): + def test_check_simple(self): + a = np.arange(100).astype('f') + a = pad(a, (25, 20), 'linear_ramp', end_values=(4, 5)) + b = np.array([ + 4.00, 3.84, 3.68, 3.52, 3.36, 3.20, 3.04, 2.88, 2.72, 2.56, + 2.40, 2.24, 2.08, 1.92, 1.76, 1.60, 1.44, 1.28, 1.12, 0.96, + 0.80, 0.64, 0.48, 0.32, 0.16, + + 0.00, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, + 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, + 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, + 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, + 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, + 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, + 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, + 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, + 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, + 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, + + 94.3, 89.6, 84.9, 80.2, 75.5, 70.8, 66.1, 61.4, 56.7, 52.0, + 47.3, 42.6, 37.9, 33.2, 28.5, 23.8, 19.1, 14.4, 9.7, 5.]) + assert_array_almost_equal(a, b, decimal=5) + + +class TestReflect(TestCase): + def test_check_simple(self): + a = np.arange(100) + a = pad(a, (25, 20), 'reflect') + b = np.array([ + 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, + 5, 4, 3, 2, 1, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, + 88, 87, 86, 85, 84, 83, 82, 81, 80, 79]) + assert_array_equal(a, b) + + def test_check_large_pad(self): + a = [[4, 5, 6], [6, 7, 8]] + a = pad(a, (5, 7), 'reflect') + b = np.array([ + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7, 8, 7, 6, 7], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5]]) + assert_array_equal(a, b) + + def test_check_shape(self): + a = [[4, 5, 6]] + a = pad(a, (5, 7), 'reflect') + b = np.array([ + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5], + [5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5, 6, 5, 4, 5]]) + assert_array_equal(a, b) + + def test_check_01(self): + a = pad([1, 2, 3], 2, 'reflect') + b = np.array([3, 2, 1, 2, 3, 2, 1]) + assert_array_equal(a, b) + + def test_check_02(self): + a = pad([1, 2, 3], 3, 'reflect') + b = np.array([2, 3, 2, 1, 2, 3, 2, 1, 2]) + assert_array_equal(a, b) + + def test_check_03(self): + a = pad([1, 2, 3], 4, 'reflect') + b = np.array([1, 2, 3, 2, 1, 2, 3, 2, 1, 2, 3]) + assert_array_equal(a, b) + + +class TestWrap(TestCase): + def test_check_simple(self): + a = np.arange(100) + a = pad(a, (25, 20), 'wrap') + b = np.array([ + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + assert_array_equal(a, b) + + def test_check_large_pad(self): + a = np.arange(12) + a = np.reshape(a, (3, 4)) + a = pad(a, (10, 12), 'wrap') + b = np.array([ + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11], + [ 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, + 3, 0, 1, 2, 3, 0, 1, 2, 3], + [ 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, + 7, 4, 5, 6, 7, 4, 5, 6, 7], + [10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, + 11, 8, 9, 10, 11, 8, 9, 10, 11]]) + assert_array_equal(a, b) + + def test_check_01(self): + a = pad([1, 2, 3], 3, 'wrap') + b = np.array([1, 2, 3, 1, 2, 3, 1, 2, 3]) + assert_array_equal(a, b) + + def test_check_02(self): + a = pad([1, 2, 3], 4, 'wrap') + b = np.array([3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1]) + assert_array_equal(a, b) + + +class TestStatLen(TestCase): + def test_check_simple(self): + a = np.arange(30) + a = np.reshape(a, (6, 5)) + a = pad(a, ((2, 3), (3, 2)), mode='mean', stat_length=(3,)) + b = np.array([[ 6, 6, 6, 5, 6, 7, 8, 9, 8, 8], + [ 6, 6, 6, 5, 6, 7, 8, 9, 8, 8], + + [ 1, 1, 1, 0, 1, 2, 3, 4, 3, 3], + [ 6, 6, 6, 5, 6, 7, 8, 9, 8, 8], + [11, 11, 11, 10, 11, 12, 13, 14, 13, 13], + [16, 16, 16, 15, 16, 17, 18, 19, 18, 18], + [21, 21, 21, 20, 21, 22, 23, 24, 23, 23], + [26, 26, 26, 25, 26, 27, 28, 29, 28, 28], + + [21, 21, 21, 20, 21, 22, 23, 24, 23, 23], + [21, 21, 21, 20, 21, 22, 23, 24, 23, 23], + [21, 21, 21, 20, 21, 22, 23, 24, 23, 23]]) + assert_array_equal(a, b) + + +class TestEdge(TestCase): + def test_check_simple(self): + a = np.arange(12) + a = np.reshape(a, (4, 3)) + a = pad(a, ((2, 3), (3, 2)), 'edge') + b = np.array([ + [0, 0, 0, 0, 1, 2, 2, 2], + [0, 0, 0, 0, 1, 2, 2, 2], + + [0, 0, 0, 0, 1, 2, 2, 2], + [3, 3, 3, 3, 4, 5, 5, 5], + [6, 6, 6, 6, 7, 8, 8, 8], + [9, 9, 9, 9, 10, 11, 11, 11], + + [9, 9, 9, 9, 10, 11, 11, 11], + [9, 9, 9, 9, 10, 11, 11, 11], + [9, 9, 9, 9, 10, 11, 11, 11]]) + assert_array_equal(a, b) + + +def test_check_too_many_pad_axes(): + arr = np.arange(30) + arr = np.reshape(arr, (6, 5)) + kwargs = dict(mode='mean', stat_length=(3, )) + assert_raises(ValueError, pad, arr, ((2, 3), (3, 2), (4, 5)), + **kwargs) + + +def test_check_negative_stat_length(): + arr = np.arange(30) + arr = np.reshape(arr, (6, 5)) + kwargs = dict(mode='mean', stat_length=(-3, )) + assert_raises(ValueError, pad, arr, ((2, 3), (3, 2)), + **kwargs) + + +def test_check_negative_pad_width(): + arr = np.arange(30) + arr = np.reshape(arr, (6, 5)) + kwargs = dict(mode='mean', stat_length=(3, )) + assert_raises(ValueError, pad, arr, ((-2, 3), (3, 2)), + **kwargs) + + +def test_pad_one_axis_three_ways(): + arr = np.arange(30) + arr = np.reshape(arr, (6, 5)) + kwargs = dict(mode='mean', stat_length=(3, )) + assert_raises(ValueError, pad, arr, ((2, 3, 4), (3, 2)), + **kwargs) + + +if __name__ == "__main__": + run_module_suite()