1"""
2The arraypad module contains a group of functions to pad values onto the edges
3of an n-dimensional array.
4
5"""
6import numpy as np
7from numpy._core.overrides import array_function_dispatch
8from numpy.lib._index_tricks_impl import ndindex
9
10
11__all__ = ['pad']
12
13
14###############################################################################
15# Private utility functions.
16
17
18def _round_if_needed(arr, dtype):
19 """
20 Rounds arr inplace if destination dtype is integer.
21
22 Parameters
23 ----------
24 arr : ndarray
25 Input array.
26 dtype : dtype
27 The dtype of the destination array.
28 """
29 if np.issubdtype(dtype, np.integer):
30 arr.round(out=arr)
31
32
33def _slice_at_axis(sl, axis):
34 """
35 Construct tuple of slices to slice an array in the given dimension.
36
37 Parameters
38 ----------
39 sl : slice
40 The slice for the given dimension.
41 axis : int
42 The axis to which `sl` is applied. All other dimensions are left
43 "unsliced".
44
45 Returns
46 -------
47 sl : tuple of slices
48 A tuple with slices matching `shape` in length.
49
50 Examples
51 --------
52 >>> np._slice_at_axis(slice(None, 3, -1), 1)
53 (slice(None, None, None), slice(None, 3, -1), (...,))
54 """
55 return (slice(None),) * axis + (sl,) + (...,)
56
57
58def _view_roi(array, original_area_slice, axis):
59 """
60 Get a view of the current region of interest during iterative padding.
61
62 When padding multiple dimensions iteratively corner values are
63 unnecessarily overwritten multiple times. This function reduces the
64 working area for the first dimensions so that corners are excluded.
65
66 Parameters
67 ----------
68 array : ndarray
69 The array with the region of interest.
70 original_area_slice : tuple of slices
71 Denotes the area with original values of the unpadded array.
72 axis : int
73 The currently padded dimension assuming that `axis` is padded before
74 `axis` + 1.
75
76 Returns
77 -------
78 roi : ndarray
79 The region of interest of the original `array`.
80 """
81 axis += 1
82 sl = (slice(None),) * axis + original_area_slice[axis:]
83 return array[sl]
84
85
86def _pad_simple(array, pad_width, fill_value=None):
87 """
88 Pad array on all sides with either a single value or undefined values.
89
90 Parameters
91 ----------
92 array : ndarray
93 Array to grow.
94 pad_width : sequence of tuple[int, int]
95 Pad width on both sides for each dimension in `arr`.
96 fill_value : scalar, optional
97 If provided the padded area is filled with this value, otherwise
98 the pad area left undefined.
99
100 Returns
101 -------
102 padded : ndarray
103 The padded array with the same dtype as`array`. Its order will default
104 to C-style if `array` is not F-contiguous.
105 original_area_slice : tuple
106 A tuple of slices pointing to the area of the original array.
107 """
108 # Allocate grown array
109 new_shape = tuple(
110 left + size + right
111 for size, (left, right) in zip(array.shape, pad_width)
112 )
113 order = 'F' if array.flags.fnc else 'C' # Fortran and not also C-order
114 padded = np.empty(new_shape, dtype=array.dtype, order=order)
115
116 if fill_value is not None:
117 padded.fill(fill_value)
118
119 # Copy old array into correct space
120 original_area_slice = tuple(
121 slice(left, left + size)
122 for size, (left, right) in zip(array.shape, pad_width)
123 )
124 padded[original_area_slice] = array
125
126 return padded, original_area_slice
127
128
129def _set_pad_area(padded, axis, width_pair, value_pair):
130 """
131 Set empty-padded area in given dimension.
132
133 Parameters
134 ----------
135 padded : ndarray
136 Array with the pad area which is modified inplace.
137 axis : int
138 Dimension with the pad area to set.
139 width_pair : (int, int)
140 Pair of widths that mark the pad area on both sides in the given
141 dimension.
142 value_pair : tuple of scalars or ndarrays
143 Values inserted into the pad area on each side. It must match or be
144 broadcastable to the shape of `arr`.
145 """
146 left_slice = _slice_at_axis(slice(None, width_pair[0]), axis)
147 padded[left_slice] = value_pair[0]
148
149 right_slice = _slice_at_axis(
150 slice(padded.shape[axis] - width_pair[1], None), axis)
151 padded[right_slice] = value_pair[1]
152
153
154def _get_edges(padded, axis, width_pair):
155 """
156 Retrieve edge values from empty-padded array in given dimension.
157
158 Parameters
159 ----------
160 padded : ndarray
161 Empty-padded array.
162 axis : int
163 Dimension in which the edges are considered.
164 width_pair : (int, int)
165 Pair of widths that mark the pad area on both sides in the given
166 dimension.
167
168 Returns
169 -------
170 left_edge, right_edge : ndarray
171 Edge values of the valid area in `padded` in the given dimension. Its
172 shape will always match `padded` except for the dimension given by
173 `axis` which will have a length of 1.
174 """
175 left_index = width_pair[0]
176 left_slice = _slice_at_axis(slice(left_index, left_index + 1), axis)
177 left_edge = padded[left_slice]
178
179 right_index = padded.shape[axis] - width_pair[1]
180 right_slice = _slice_at_axis(slice(right_index - 1, right_index), axis)
181 right_edge = padded[right_slice]
182
183 return left_edge, right_edge
184
185
186def _get_linear_ramps(padded, axis, width_pair, end_value_pair):
187 """
188 Construct linear ramps for empty-padded array in given dimension.
189
190 Parameters
191 ----------
192 padded : ndarray
193 Empty-padded array.
194 axis : int
195 Dimension in which the ramps are constructed.
196 width_pair : (int, int)
197 Pair of widths that mark the pad area on both sides in the given
198 dimension.
199 end_value_pair : (scalar, scalar)
200 End values for the linear ramps which form the edge of the fully padded
201 array. These values are included in the linear ramps.
202
203 Returns
204 -------
205 left_ramp, right_ramp : ndarray
206 Linear ramps to set on both sides of `padded`.
207 """
208 edge_pair = _get_edges(padded, axis, width_pair)
209
210 left_ramp, right_ramp = (
211 np.linspace(
212 start=end_value,
213 stop=edge.squeeze(axis), # Dimension is replaced by linspace
214 num=width,
215 endpoint=False,
216 dtype=padded.dtype,
217 axis=axis
218 )
219 for end_value, edge, width in zip(
220 end_value_pair, edge_pair, width_pair
221 )
222 )
223
224 # Reverse linear space in appropriate dimension
225 right_ramp = right_ramp[_slice_at_axis(slice(None, None, -1), axis)]
226
227 return left_ramp, right_ramp
228
229
230def _get_stats(padded, axis, width_pair, length_pair, stat_func):
231 """
232 Calculate statistic for the empty-padded array in given dimension.
233
234 Parameters
235 ----------
236 padded : ndarray
237 Empty-padded array.
238 axis : int
239 Dimension in which the statistic is calculated.
240 width_pair : (int, int)
241 Pair of widths that mark the pad area on both sides in the given
242 dimension.
243 length_pair : 2-element sequence of None or int
244 Gives the number of values in valid area from each side that is
245 taken into account when calculating the statistic. If None the entire
246 valid area in `padded` is considered.
247 stat_func : function
248 Function to compute statistic. The expected signature is
249 ``stat_func(x: ndarray, axis: int, keepdims: bool) -> ndarray``.
250
251 Returns
252 -------
253 left_stat, right_stat : ndarray
254 Calculated statistic for both sides of `padded`.
255 """
256 # Calculate indices of the edges of the area with original values
257 left_index = width_pair[0]
258 right_index = padded.shape[axis] - width_pair[1]
259 # as well as its length
260 max_length = right_index - left_index
261
262 # Limit stat_lengths to max_length
263 left_length, right_length = length_pair
264 if left_length is None or max_length < left_length:
265 left_length = max_length
266 if right_length is None or max_length < right_length:
267 right_length = max_length
268
269 if (left_length == 0 or right_length == 0) \
270 and stat_func in {np.amax, np.amin}:
271 # amax and amin can't operate on an empty array,
272 # raise a more descriptive warning here instead of the default one
273 raise ValueError("stat_length of 0 yields no value for padding")
274
275 # Calculate statistic for the left side
276 left_slice = _slice_at_axis(
277 slice(left_index, left_index + left_length), axis)
278 left_chunk = padded[left_slice]
279 left_stat = stat_func(left_chunk, axis=axis, keepdims=True)
280 _round_if_needed(left_stat, padded.dtype)
281
282 if left_length == right_length == max_length:
283 # return early as right_stat must be identical to left_stat
284 return left_stat, left_stat
285
286 # Calculate statistic for the right side
287 right_slice = _slice_at_axis(
288 slice(right_index - right_length, right_index), axis)
289 right_chunk = padded[right_slice]
290 right_stat = stat_func(right_chunk, axis=axis, keepdims=True)
291 _round_if_needed(right_stat, padded.dtype)
292
293 return left_stat, right_stat
294
295
296def _set_reflect_both(padded, axis, width_pair, method,
297 original_period, include_edge=False):
298 """
299 Pad `axis` of `arr` with reflection.
300
301 Parameters
302 ----------
303 padded : ndarray
304 Input array of arbitrary shape.
305 axis : int
306 Axis along which to pad `arr`.
307 width_pair : (int, int)
308 Pair of widths that mark the pad area on both sides in the given
309 dimension.
310 method : str
311 Controls method of reflection; options are 'even' or 'odd'.
312 original_period : int
313 Original length of data on `axis` of `arr`.
314 include_edge : bool
315 If true, edge value is included in reflection, otherwise the edge
316 value forms the symmetric axis to the reflection.
317
318 Returns
319 -------
320 pad_amt : tuple of ints, length 2
321 New index positions of padding to do along the `axis`. If these are
322 both 0, padding is done in this dimension.
323 """
324 left_pad, right_pad = width_pair
325 old_length = padded.shape[axis] - right_pad - left_pad
326
327 if include_edge:
328 # Avoid wrapping with only a subset of the original area
329 # by ensuring period can only be a multiple of the original
330 # area's length.
331 old_length = old_length // original_period * original_period
332 # Edge is included, we need to offset the pad amount by 1
333 edge_offset = 1
334 else:
335 # Avoid wrapping with only a subset of the original area
336 # by ensuring period can only be a multiple of the original
337 # area's length.
338 old_length = ((old_length - 1) // (original_period - 1)
339 * (original_period - 1) + 1)
340 edge_offset = 0 # Edge is not included, no need to offset pad amount
341 old_length -= 1 # but must be omitted from the chunk
342
343 if left_pad > 0:
344 # Pad with reflected values on left side:
345 # First limit chunk size which can't be larger than pad area
346 chunk_length = min(old_length, left_pad)
347 # Slice right to left, stop on or next to edge, start relative to stop
348 stop = left_pad - edge_offset
349 start = stop + chunk_length
350 left_slice = _slice_at_axis(slice(start, stop, -1), axis)
351 left_chunk = padded[left_slice]
352
353 if method == "odd":
354 # Negate chunk and align with edge
355 edge_slice = _slice_at_axis(slice(left_pad, left_pad + 1), axis)
356 left_chunk = 2 * padded[edge_slice] - left_chunk
357
358 # Insert chunk into padded area
359 start = left_pad - chunk_length
360 stop = left_pad
361 pad_area = _slice_at_axis(slice(start, stop), axis)
362 padded[pad_area] = left_chunk
363 # Adjust pointer to left edge for next iteration
364 left_pad -= chunk_length
365
366 if right_pad > 0:
367 # Pad with reflected values on right side:
368 # First limit chunk size which can't be larger than pad area
369 chunk_length = min(old_length, right_pad)
370 # Slice right to left, start on or next to edge, stop relative to start
371 start = -right_pad + edge_offset - 2
372 stop = start - chunk_length
373 right_slice = _slice_at_axis(slice(start, stop, -1), axis)
374 right_chunk = padded[right_slice]
375
376 if method == "odd":
377 # Negate chunk and align with edge
378 edge_slice = _slice_at_axis(
379 slice(-right_pad - 1, -right_pad), axis)
380 right_chunk = 2 * padded[edge_slice] - right_chunk
381
382 # Insert chunk into padded area
383 start = padded.shape[axis] - right_pad
384 stop = start + chunk_length
385 pad_area = _slice_at_axis(slice(start, stop), axis)
386 padded[pad_area] = right_chunk
387 # Adjust pointer to right edge for next iteration
388 right_pad -= chunk_length
389
390 return left_pad, right_pad
391
392
393def _set_wrap_both(padded, axis, width_pair, original_period):
394 """
395 Pad `axis` of `arr` with wrapped values.
396
397 Parameters
398 ----------
399 padded : ndarray
400 Input array of arbitrary shape.
401 axis : int
402 Axis along which to pad `arr`.
403 width_pair : (int, int)
404 Pair of widths that mark the pad area on both sides in the given
405 dimension.
406 original_period : int
407 Original length of data on `axis` of `arr`.
408
409 Returns
410 -------
411 pad_amt : tuple of ints, length 2
412 New index positions of padding to do along the `axis`. If these are
413 both 0, padding is done in this dimension.
414 """
415 left_pad, right_pad = width_pair
416 period = padded.shape[axis] - right_pad - left_pad
417 # Avoid wrapping with only a subset of the original area by ensuring period
418 # can only be a multiple of the original area's length.
419 period = period // original_period * original_period
420
421 # If the current dimension of `arr` doesn't contain enough valid values
422 # (not part of the undefined pad area) we need to pad multiple times.
423 # Each time the pad area shrinks on both sides which is communicated with
424 # these variables.
425 new_left_pad = 0
426 new_right_pad = 0
427
428 if left_pad > 0:
429 # Pad with wrapped values on left side
430 # First slice chunk from left side of the non-pad area.
431 # Use min(period, left_pad) to ensure that chunk is not larger than
432 # pad area.
433 slice_end = left_pad + period
434 slice_start = slice_end - min(period, left_pad)
435 right_slice = _slice_at_axis(slice(slice_start, slice_end), axis)
436 right_chunk = padded[right_slice]
437
438 if left_pad > period:
439 # Chunk is smaller than pad area
440 pad_area = _slice_at_axis(slice(left_pad - period, left_pad), axis)
441 new_left_pad = left_pad - period
442 else:
443 # Chunk matches pad area
444 pad_area = _slice_at_axis(slice(None, left_pad), axis)
445 padded[pad_area] = right_chunk
446
447 if right_pad > 0:
448 # Pad with wrapped values on right side
449 # First slice chunk from right side of the non-pad area.
450 # Use min(period, right_pad) to ensure that chunk is not larger than
451 # pad area.
452 slice_start = -right_pad - period
453 slice_end = slice_start + min(period, right_pad)
454 left_slice = _slice_at_axis(slice(slice_start, slice_end), axis)
455 left_chunk = padded[left_slice]
456
457 if right_pad > period:
458 # Chunk is smaller than pad area
459 pad_area = _slice_at_axis(
460 slice(-right_pad, -right_pad + period), axis)
461 new_right_pad = right_pad - period
462 else:
463 # Chunk matches pad area
464 pad_area = _slice_at_axis(slice(-right_pad, None), axis)
465 padded[pad_area] = left_chunk
466
467 return new_left_pad, new_right_pad
468
469
470def _as_pairs(x, ndim, as_index=False):
471 """
472 Broadcast `x` to an array with the shape (`ndim`, 2).
473
474 A helper function for `pad` that prepares and validates arguments like
475 `pad_width` for iteration in pairs.
476
477 Parameters
478 ----------
479 x : {None, scalar, array-like}
480 The object to broadcast to the shape (`ndim`, 2).
481 ndim : int
482 Number of pairs the broadcasted `x` will have.
483 as_index : bool, optional
484 If `x` is not None, try to round each element of `x` to an integer
485 (dtype `np.intp`) and ensure every element is positive.
486
487 Returns
488 -------
489 pairs : nested iterables, shape (`ndim`, 2)
490 The broadcasted version of `x`.
491
492 Raises
493 ------
494 ValueError
495 If `as_index` is True and `x` contains negative elements.
496 Or if `x` is not broadcastable to the shape (`ndim`, 2).
497 """
498 if x is None:
499 # Pass through None as a special case, otherwise np.round(x) fails
500 # with an AttributeError
501 return ((None, None),) * ndim
502
503 x = np.array(x)
504 if as_index:
505 x = np.round(x).astype(np.intp, copy=False)
506
507 if x.ndim < 3:
508 # Optimization: Possibly use faster paths for cases where `x` has
509 # only 1 or 2 elements. `np.broadcast_to` could handle these as well
510 # but is currently slower
511
512 if x.size == 1:
513 # x was supplied as a single value
514 x = x.ravel() # Ensure x[0] works for x.ndim == 0, 1, 2
515 if as_index and x < 0:
516 raise ValueError("index can't contain negative values")
517 return ((x[0], x[0]),) * ndim
518
519 if x.size == 2 and x.shape != (2, 1):
520 # x was supplied with a single value for each side
521 # but except case when each dimension has a single value
522 # which should be broadcasted to a pair,
523 # e.g. [[1], [2]] -> [[1, 1], [2, 2]] not [[1, 2], [1, 2]]
524 x = x.ravel() # Ensure x[0], x[1] works
525 if as_index and (x[0] < 0 or x[1] < 0):
526 raise ValueError("index can't contain negative values")
527 return ((x[0], x[1]),) * ndim
528
529 if as_index and x.min() < 0:
530 raise ValueError("index can't contain negative values")
531
532 # Converting the array with `tolist` seems to improve performance
533 # when iterating and indexing the result (see usage in `pad`)
534 return np.broadcast_to(x, (ndim, 2)).tolist()
535
536
537def _pad_dispatcher(array, pad_width, mode=None, **kwargs):
538 return (array,)
539
540
541###############################################################################
542# Public functions
543
544
545@array_function_dispatch(_pad_dispatcher, module='numpy')
546def pad(array, pad_width, mode='constant', **kwargs):
547 """
548 Pad an array.
549
550 Parameters
551 ----------
552 array : array_like of rank N
553 The array to pad.
554 pad_width : {sequence, array_like, int}
555 Number of values padded to the edges of each axis.
556 ``((before_1, after_1), ... (before_N, after_N))`` unique pad widths
557 for each axis.
558 ``(before, after)`` or ``((before, after),)`` yields same before
559 and after pad for each axis.
560 ``(pad,)`` or ``int`` is a shortcut for before = after = pad width
561 for all axes.
562 mode : str or function, optional
563 One of the following string values or a user supplied function.
564
565 'constant' (default)
566 Pads with a constant value.
567 'edge'
568 Pads with the edge values of array.
569 'linear_ramp'
570 Pads with the linear ramp between end_value and the
571 array edge value.
572 'maximum'
573 Pads with the maximum value of all or part of the
574 vector along each axis.
575 'mean'
576 Pads with the mean value of all or part of the
577 vector along each axis.
578 'median'
579 Pads with the median value of all or part of the
580 vector along each axis.
581 'minimum'
582 Pads with the minimum value of all or part of the
583 vector along each axis.
584 'reflect'
585 Pads with the reflection of the vector mirrored on
586 the first and last values of the vector along each
587 axis.
588 'symmetric'
589 Pads with the reflection of the vector mirrored
590 along the edge of the array.
591 'wrap'
592 Pads with the wrap of the vector along the axis.
593 The first values are used to pad the end and the
594 end values are used to pad the beginning.
595 'empty'
596 Pads with undefined values.
597
598 <function>
599 Padding function, see Notes.
600 stat_length : sequence or int, optional
601 Used in 'maximum', 'mean', 'median', and 'minimum'. Number of
602 values at edge of each axis used to calculate the statistic value.
603
604 ``((before_1, after_1), ... (before_N, after_N))`` unique statistic
605 lengths for each axis.
606
607 ``(before, after)`` or ``((before, after),)`` yields same before
608 and after statistic lengths for each axis.
609
610 ``(stat_length,)`` or ``int`` is a shortcut for
611 ``before = after = statistic`` length for all axes.
612
613 Default is ``None``, to use the entire axis.
614 constant_values : sequence or scalar, optional
615 Used in 'constant'. The values to set the padded values for each
616 axis.
617
618 ``((before_1, after_1), ... (before_N, after_N))`` unique pad constants
619 for each axis.
620
621 ``(before, after)`` or ``((before, after),)`` yields same before
622 and after constants for each axis.
623
624 ``(constant,)`` or ``constant`` is a shortcut for
625 ``before = after = constant`` for all axes.
626
627 Default is 0.
628 end_values : sequence or scalar, optional
629 Used in 'linear_ramp'. The values used for the ending value of the
630 linear_ramp and that will form the edge of the padded array.
631
632 ``((before_1, after_1), ... (before_N, after_N))`` unique end values
633 for each axis.
634
635 ``(before, after)`` or ``((before, after),)`` yields same before
636 and after end values for each axis.
637
638 ``(constant,)`` or ``constant`` is a shortcut for
639 ``before = after = constant`` for all axes.
640
641 Default is 0.
642 reflect_type : {'even', 'odd'}, optional
643 Used in 'reflect', and 'symmetric'. The 'even' style is the
644 default with an unaltered reflection around the edge value. For
645 the 'odd' style, the extended part of the array is created by
646 subtracting the reflected values from two times the edge value.
647
648 Returns
649 -------
650 pad : ndarray
651 Padded array of rank equal to `array` with shape increased
652 according to `pad_width`.
653
654 Notes
655 -----
656 For an array with rank greater than 1, some of the padding of later
657 axes is calculated from padding of previous axes. This is easiest to
658 think about with a rank 2 array where the corners of the padded array
659 are calculated by using padded values from the first axis.
660
661 The padding function, if used, should modify a rank 1 array in-place. It
662 has the following signature::
663
664 padding_func(vector, iaxis_pad_width, iaxis, kwargs)
665
666 where
667
668 vector : ndarray
669 A rank 1 array already padded with zeros. Padded values are
670 vector[:iaxis_pad_width[0]] and vector[-iaxis_pad_width[1]:].
671 iaxis_pad_width : tuple
672 A 2-tuple of ints, iaxis_pad_width[0] represents the number of
673 values padded at the beginning of vector where
674 iaxis_pad_width[1] represents the number of values padded at
675 the end of vector.
676 iaxis : int
677 The axis currently being calculated.
678 kwargs : dict
679 Any keyword arguments the function requires.
680
681 Examples
682 --------
683 >>> import numpy as np
684 >>> a = [1, 2, 3, 4, 5]
685 >>> np.pad(a, (2, 3), 'constant', constant_values=(4, 6))
686 array([4, 4, 1, ..., 6, 6, 6])
687
688 >>> np.pad(a, (2, 3), 'edge')
689 array([1, 1, 1, ..., 5, 5, 5])
690
691 >>> np.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4))
692 array([ 5, 3, 1, 2, 3, 4, 5, 2, -1, -4])
693
694 >>> np.pad(a, (2,), 'maximum')
695 array([5, 5, 1, 2, 3, 4, 5, 5, 5])
696
697 >>> np.pad(a, (2,), 'mean')
698 array([3, 3, 1, 2, 3, 4, 5, 3, 3])
699
700 >>> np.pad(a, (2,), 'median')
701 array([3, 3, 1, 2, 3, 4, 5, 3, 3])
702
703 >>> a = [[1, 2], [3, 4]]
704 >>> np.pad(a, ((3, 2), (2, 3)), 'minimum')
705 array([[1, 1, 1, 2, 1, 1, 1],
706 [1, 1, 1, 2, 1, 1, 1],
707 [1, 1, 1, 2, 1, 1, 1],
708 [1, 1, 1, 2, 1, 1, 1],
709 [3, 3, 3, 4, 3, 3, 3],
710 [1, 1, 1, 2, 1, 1, 1],
711 [1, 1, 1, 2, 1, 1, 1]])
712
713 >>> a = [1, 2, 3, 4, 5]
714 >>> np.pad(a, (2, 3), 'reflect')
715 array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2])
716
717 >>> np.pad(a, (2, 3), 'reflect', reflect_type='odd')
718 array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8])
719
720 >>> np.pad(a, (2, 3), 'symmetric')
721 array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3])
722
723 >>> np.pad(a, (2, 3), 'symmetric', reflect_type='odd')
724 array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7])
725
726 >>> np.pad(a, (2, 3), 'wrap')
727 array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3])
728
729 >>> def pad_with(vector, pad_width, iaxis, kwargs):
730 ... pad_value = kwargs.get('padder', 10)
731 ... vector[:pad_width[0]] = pad_value
732 ... vector[-pad_width[1]:] = pad_value
733 >>> a = np.arange(6)
734 >>> a = a.reshape((2, 3))
735 >>> np.pad(a, 2, pad_with)
736 array([[10, 10, 10, 10, 10, 10, 10],
737 [10, 10, 10, 10, 10, 10, 10],
738 [10, 10, 0, 1, 2, 10, 10],
739 [10, 10, 3, 4, 5, 10, 10],
740 [10, 10, 10, 10, 10, 10, 10],
741 [10, 10, 10, 10, 10, 10, 10]])
742 >>> np.pad(a, 2, pad_with, padder=100)
743 array([[100, 100, 100, 100, 100, 100, 100],
744 [100, 100, 100, 100, 100, 100, 100],
745 [100, 100, 0, 1, 2, 100, 100],
746 [100, 100, 3, 4, 5, 100, 100],
747 [100, 100, 100, 100, 100, 100, 100],
748 [100, 100, 100, 100, 100, 100, 100]])
749 """
750 array = np.asarray(array)
751 pad_width = np.asarray(pad_width)
752
753 if not pad_width.dtype.kind == 'i':
754 raise TypeError('`pad_width` must be of integral type.')
755
756 # Broadcast to shape (array.ndim, 2)
757 pad_width = _as_pairs(pad_width, array.ndim, as_index=True)
758
759 if callable(mode):
760 # Old behavior: Use user-supplied function with np.apply_along_axis
761 function = mode
762 # Create a new zero padded array
763 padded, _ = _pad_simple(array, pad_width, fill_value=0)
764 # And apply along each axis
765
766 for axis in range(padded.ndim):
767 # Iterate using ndindex as in apply_along_axis, but assuming that
768 # function operates inplace on the padded array.
769
770 # view with the iteration axis at the end
771 view = np.moveaxis(padded, axis, -1)
772
773 # compute indices for the iteration axes, and append a trailing
774 # ellipsis to prevent 0d arrays decaying to scalars (gh-8642)
775 inds = ndindex(view.shape[:-1])
776 inds = (ind + (Ellipsis,) for ind in inds)
777 for ind in inds:
778 function(view[ind], pad_width[axis], axis, kwargs)
779
780 return padded
781
782 # Make sure that no unsupported keywords were passed for the current mode
783 allowed_kwargs = {
784 'empty': [], 'edge': [], 'wrap': [],
785 'constant': ['constant_values'],
786 'linear_ramp': ['end_values'],
787 'maximum': ['stat_length'],
788 'mean': ['stat_length'],
789 'median': ['stat_length'],
790 'minimum': ['stat_length'],
791 'reflect': ['reflect_type'],
792 'symmetric': ['reflect_type'],
793 }
794 try:
795 unsupported_kwargs = set(kwargs) - set(allowed_kwargs[mode])
796 except KeyError:
797 raise ValueError("mode '{}' is not supported".format(mode)) from None
798 if unsupported_kwargs:
799 raise ValueError("unsupported keyword arguments for mode '{}': {}"
800 .format(mode, unsupported_kwargs))
801
802 stat_functions = {"maximum": np.amax, "minimum": np.amin,
803 "mean": np.mean, "median": np.median}
804
805 # Create array with final shape and original values
806 # (padded area is undefined)
807 padded, original_area_slice = _pad_simple(array, pad_width)
808 # And prepare iteration over all dimensions
809 # (zipping may be more readable than using enumerate)
810 axes = range(padded.ndim)
811
812 if mode == "constant":
813 values = kwargs.get("constant_values", 0)
814 values = _as_pairs(values, padded.ndim)
815 for axis, width_pair, value_pair in zip(axes, pad_width, values):
816 roi = _view_roi(padded, original_area_slice, axis)
817 _set_pad_area(roi, axis, width_pair, value_pair)
818
819 elif mode == "empty":
820 pass # Do nothing as _pad_simple already returned the correct result
821
822 elif array.size == 0:
823 # Only modes "constant" and "empty" can extend empty axes, all other
824 # modes depend on `array` not being empty
825 # -> ensure every empty axis is only "padded with 0"
826 for axis, width_pair in zip(axes, pad_width):
827 if array.shape[axis] == 0 and any(width_pair):
828 raise ValueError(
829 "can't extend empty axis {} using modes other than "
830 "'constant' or 'empty'".format(axis)
831 )
832 # passed, don't need to do anything more as _pad_simple already
833 # returned the correct result
834
835 elif mode == "edge":
836 for axis, width_pair in zip(axes, pad_width):
837 roi = _view_roi(padded, original_area_slice, axis)
838 edge_pair = _get_edges(roi, axis, width_pair)
839 _set_pad_area(roi, axis, width_pair, edge_pair)
840
841 elif mode == "linear_ramp":
842 end_values = kwargs.get("end_values", 0)
843 end_values = _as_pairs(end_values, padded.ndim)
844 for axis, width_pair, value_pair in zip(axes, pad_width, end_values):
845 roi = _view_roi(padded, original_area_slice, axis)
846 ramp_pair = _get_linear_ramps(roi, axis, width_pair, value_pair)
847 _set_pad_area(roi, axis, width_pair, ramp_pair)
848
849 elif mode in stat_functions:
850 func = stat_functions[mode]
851 length = kwargs.get("stat_length", None)
852 length = _as_pairs(length, padded.ndim, as_index=True)
853 for axis, width_pair, length_pair in zip(axes, pad_width, length):
854 roi = _view_roi(padded, original_area_slice, axis)
855 stat_pair = _get_stats(roi, axis, width_pair, length_pair, func)
856 _set_pad_area(roi, axis, width_pair, stat_pair)
857
858 elif mode in {"reflect", "symmetric"}:
859 method = kwargs.get("reflect_type", "even")
860 include_edge = mode == "symmetric"
861 for axis, (left_index, right_index) in zip(axes, pad_width):
862 if array.shape[axis] == 1 and (left_index > 0 or right_index > 0):
863 # Extending singleton dimension for 'reflect' is legacy
864 # behavior; it really should raise an error.
865 edge_pair = _get_edges(padded, axis, (left_index, right_index))
866 _set_pad_area(
867 padded, axis, (left_index, right_index), edge_pair)
868 continue
869
870 roi = _view_roi(padded, original_area_slice, axis)
871 while left_index > 0 or right_index > 0:
872 # Iteratively pad until dimension is filled with reflected
873 # values. This is necessary if the pad area is larger than
874 # the length of the original values in the current dimension.
875 left_index, right_index = _set_reflect_both(
876 roi, axis, (left_index, right_index),
877 method, array.shape[axis], include_edge
878 )
879
880 elif mode == "wrap":
881 for axis, (left_index, right_index) in zip(axes, pad_width):
882 roi = _view_roi(padded, original_area_slice, axis)
883 original_period = padded.shape[axis] - right_index - left_index
884 while left_index > 0 or right_index > 0:
885 # Iteratively pad until dimension is filled with wrapped
886 # values. This is necessary if the pad area is larger than
887 # the length of the original values in the current dimension.
888 left_index, right_index = _set_wrap_both(
889 roi, axis, (left_index, right_index), original_period)
890
891 return padded