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 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 >>> _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, include_edge=False):
297 """
298 Pad `axis` of `arr` with reflection.
299
300 Parameters
301 ----------
302 padded : ndarray
303 Input array of arbitrary shape.
304 axis : int
305 Axis along which to pad `arr`.
306 width_pair : (int, int)
307 Pair of widths that mark the pad area on both sides in the given
308 dimension.
309 method : str
310 Controls method of reflection; options are 'even' or 'odd'.
311 include_edge : bool
312 If true, edge value is included in reflection, otherwise the edge
313 value forms the symmetric axis to the reflection.
314
315 Returns
316 -------
317 pad_amt : tuple of ints, length 2
318 New index positions of padding to do along the `axis`. If these are
319 both 0, padding is done in this dimension.
320 """
321 left_pad, right_pad = width_pair
322 old_length = padded.shape[axis] - right_pad - left_pad
323
324 if include_edge:
325 # Edge is included, we need to offset the pad amount by 1
326 edge_offset = 1
327 else:
328 edge_offset = 0 # Edge is not included, no need to offset pad amount
329 old_length -= 1 # but must be omitted from the chunk
330
331 if left_pad > 0:
332 # Pad with reflected values on left side:
333 # First limit chunk size which can't be larger than pad area
334 chunk_length = min(old_length, left_pad)
335 # Slice right to left, stop on or next to edge, start relative to stop
336 stop = left_pad - edge_offset
337 start = stop + chunk_length
338 left_slice = _slice_at_axis(slice(start, stop, -1), axis)
339 left_chunk = padded[left_slice]
340
341 if method == "odd":
342 # Negate chunk and align with edge
343 edge_slice = _slice_at_axis(slice(left_pad, left_pad + 1), axis)
344 left_chunk = 2 * padded[edge_slice] - left_chunk
345
346 # Insert chunk into padded area
347 start = left_pad - chunk_length
348 stop = left_pad
349 pad_area = _slice_at_axis(slice(start, stop), axis)
350 padded[pad_area] = left_chunk
351 # Adjust pointer to left edge for next iteration
352 left_pad -= chunk_length
353
354 if right_pad > 0:
355 # Pad with reflected values on right side:
356 # First limit chunk size which can't be larger than pad area
357 chunk_length = min(old_length, right_pad)
358 # Slice right to left, start on or next to edge, stop relative to start
359 start = -right_pad + edge_offset - 2
360 stop = start - chunk_length
361 right_slice = _slice_at_axis(slice(start, stop, -1), axis)
362 right_chunk = padded[right_slice]
363
364 if method == "odd":
365 # Negate chunk and align with edge
366 edge_slice = _slice_at_axis(
367 slice(-right_pad - 1, -right_pad), axis)
368 right_chunk = 2 * padded[edge_slice] - right_chunk
369
370 # Insert chunk into padded area
371 start = padded.shape[axis] - right_pad
372 stop = start + chunk_length
373 pad_area = _slice_at_axis(slice(start, stop), axis)
374 padded[pad_area] = right_chunk
375 # Adjust pointer to right edge for next iteration
376 right_pad -= chunk_length
377
378 return left_pad, right_pad
379
380
381def _set_wrap_both(padded, axis, width_pair, original_period):
382 """
383 Pad `axis` of `arr` with wrapped values.
384
385 Parameters
386 ----------
387 padded : ndarray
388 Input array of arbitrary shape.
389 axis : int
390 Axis along which to pad `arr`.
391 width_pair : (int, int)
392 Pair of widths that mark the pad area on both sides in the given
393 dimension.
394 original_period : int
395 Original length of data on `axis` of `arr`.
396
397 Returns
398 -------
399 pad_amt : tuple of ints, length 2
400 New index positions of padding to do along the `axis`. If these are
401 both 0, padding is done in this dimension.
402 """
403 left_pad, right_pad = width_pair
404 period = padded.shape[axis] - right_pad - left_pad
405 # Avoid wrapping with only a subset of the original area by ensuring period
406 # can only be a multiple of the original area's length.
407 period = period // original_period * original_period
408
409 # If the current dimension of `arr` doesn't contain enough valid values
410 # (not part of the undefined pad area) we need to pad multiple times.
411 # Each time the pad area shrinks on both sides which is communicated with
412 # these variables.
413 new_left_pad = 0
414 new_right_pad = 0
415
416 if left_pad > 0:
417 # Pad with wrapped values on left side
418 # First slice chunk from left side of the non-pad area.
419 # Use min(period, left_pad) to ensure that chunk is not larger than
420 # pad area.
421 slice_end = left_pad + period
422 slice_start = slice_end - min(period, left_pad)
423 right_slice = _slice_at_axis(slice(slice_start, slice_end), axis)
424 right_chunk = padded[right_slice]
425
426 if left_pad > period:
427 # Chunk is smaller than pad area
428 pad_area = _slice_at_axis(slice(left_pad - period, left_pad), axis)
429 new_left_pad = left_pad - period
430 else:
431 # Chunk matches pad area
432 pad_area = _slice_at_axis(slice(None, left_pad), axis)
433 padded[pad_area] = right_chunk
434
435 if right_pad > 0:
436 # Pad with wrapped values on right side
437 # First slice chunk from right side of the non-pad area.
438 # Use min(period, right_pad) to ensure that chunk is not larger than
439 # pad area.
440 slice_start = -right_pad - period
441 slice_end = slice_start + min(period, right_pad)
442 left_slice = _slice_at_axis(slice(slice_start, slice_end), axis)
443 left_chunk = padded[left_slice]
444
445 if right_pad > period:
446 # Chunk is smaller than pad area
447 pad_area = _slice_at_axis(
448 slice(-right_pad, -right_pad + period), axis)
449 new_right_pad = right_pad - period
450 else:
451 # Chunk matches pad area
452 pad_area = _slice_at_axis(slice(-right_pad, None), axis)
453 padded[pad_area] = left_chunk
454
455 return new_left_pad, new_right_pad
456
457
458def _as_pairs(x, ndim, as_index=False):
459 """
460 Broadcast `x` to an array with the shape (`ndim`, 2).
461
462 A helper function for `pad` that prepares and validates arguments like
463 `pad_width` for iteration in pairs.
464
465 Parameters
466 ----------
467 x : {None, scalar, array-like}
468 The object to broadcast to the shape (`ndim`, 2).
469 ndim : int
470 Number of pairs the broadcasted `x` will have.
471 as_index : bool, optional
472 If `x` is not None, try to round each element of `x` to an integer
473 (dtype `np.intp`) and ensure every element is positive.
474
475 Returns
476 -------
477 pairs : nested iterables, shape (`ndim`, 2)
478 The broadcasted version of `x`.
479
480 Raises
481 ------
482 ValueError
483 If `as_index` is True and `x` contains negative elements.
484 Or if `x` is not broadcastable to the shape (`ndim`, 2).
485 """
486 if x is None:
487 # Pass through None as a special case, otherwise np.round(x) fails
488 # with an AttributeError
489 return ((None, None),) * ndim
490
491 x = np.array(x)
492 if as_index:
493 x = np.round(x).astype(np.intp, copy=False)
494
495 if x.ndim < 3:
496 # Optimization: Possibly use faster paths for cases where `x` has
497 # only 1 or 2 elements. `np.broadcast_to` could handle these as well
498 # but is currently slower
499
500 if x.size == 1:
501 # x was supplied as a single value
502 x = x.ravel() # Ensure x[0] works for x.ndim == 0, 1, 2
503 if as_index and x < 0:
504 raise ValueError("index can't contain negative values")
505 return ((x[0], x[0]),) * ndim
506
507 if x.size == 2 and x.shape != (2, 1):
508 # x was supplied with a single value for each side
509 # but except case when each dimension has a single value
510 # which should be broadcasted to a pair,
511 # e.g. [[1], [2]] -> [[1, 1], [2, 2]] not [[1, 2], [1, 2]]
512 x = x.ravel() # Ensure x[0], x[1] works
513 if as_index and (x[0] < 0 or x[1] < 0):
514 raise ValueError("index can't contain negative values")
515 return ((x[0], x[1]),) * ndim
516
517 if as_index and x.min() < 0:
518 raise ValueError("index can't contain negative values")
519
520 # Converting the array with `tolist` seems to improve performance
521 # when iterating and indexing the result (see usage in `pad`)
522 return np.broadcast_to(x, (ndim, 2)).tolist()
523
524
525def _pad_dispatcher(array, pad_width, mode=None, **kwargs):
526 return (array,)
527
528
529###############################################################################
530# Public functions
531
532
533@array_function_dispatch(_pad_dispatcher, module='numpy')
534def pad(array, pad_width, mode='constant', **kwargs):
535 """
536 Pad an array.
537
538 Parameters
539 ----------
540 array : array_like of rank N
541 The array to pad.
542 pad_width : {sequence, array_like, int}
543 Number of values padded to the edges of each axis.
544 ``((before_1, after_1), ... (before_N, after_N))`` unique pad widths
545 for each axis.
546 ``(before, after)`` or ``((before, after),)`` yields same before
547 and after pad for each axis.
548 ``(pad,)`` or ``int`` is a shortcut for before = after = pad width
549 for all axes.
550 mode : str or function, optional
551 One of the following string values or a user supplied function.
552
553 'constant' (default)
554 Pads with a constant value.
555 'edge'
556 Pads with the edge values of array.
557 'linear_ramp'
558 Pads with the linear ramp between end_value and the
559 array edge value.
560 'maximum'
561 Pads with the maximum value of all or part of the
562 vector along each axis.
563 'mean'
564 Pads with the mean value of all or part of the
565 vector along each axis.
566 'median'
567 Pads with the median value of all or part of the
568 vector along each axis.
569 'minimum'
570 Pads with the minimum value of all or part of the
571 vector along each axis.
572 'reflect'
573 Pads with the reflection of the vector mirrored on
574 the first and last values of the vector along each
575 axis.
576 'symmetric'
577 Pads with the reflection of the vector mirrored
578 along the edge of the array.
579 'wrap'
580 Pads with the wrap of the vector along the axis.
581 The first values are used to pad the end and the
582 end values are used to pad the beginning.
583 'empty'
584 Pads with undefined values.
585
586 .. versionadded:: 1.17
587
588 <function>
589 Padding function, see Notes.
590 stat_length : sequence or int, optional
591 Used in 'maximum', 'mean', 'median', and 'minimum'. Number of
592 values at edge of each axis used to calculate the statistic value.
593
594 ``((before_1, after_1), ... (before_N, after_N))`` unique statistic
595 lengths for each axis.
596
597 ``(before, after)`` or ``((before, after),)`` yields same before
598 and after statistic lengths for each axis.
599
600 ``(stat_length,)`` or ``int`` is a shortcut for
601 ``before = after = statistic`` length for all axes.
602
603 Default is ``None``, to use the entire axis.
604 constant_values : sequence or scalar, optional
605 Used in 'constant'. The values to set the padded values for each
606 axis.
607
608 ``((before_1, after_1), ... (before_N, after_N))`` unique pad constants
609 for each axis.
610
611 ``(before, after)`` or ``((before, after),)`` yields same before
612 and after constants for each axis.
613
614 ``(constant,)`` or ``constant`` is a shortcut for
615 ``before = after = constant`` for all axes.
616
617 Default is 0.
618 end_values : sequence or scalar, optional
619 Used in 'linear_ramp'. The values used for the ending value of the
620 linear_ramp and that will form the edge of the padded array.
621
622 ``((before_1, after_1), ... (before_N, after_N))`` unique end values
623 for each axis.
624
625 ``(before, after)`` or ``((before, after),)`` yields same before
626 and after end values for each axis.
627
628 ``(constant,)`` or ``constant`` is a shortcut for
629 ``before = after = constant`` for all axes.
630
631 Default is 0.
632 reflect_type : {'even', 'odd'}, optional
633 Used in 'reflect', and 'symmetric'. The 'even' style is the
634 default with an unaltered reflection around the edge value. For
635 the 'odd' style, the extended part of the array is created by
636 subtracting the reflected values from two times the edge value.
637
638 Returns
639 -------
640 pad : ndarray
641 Padded array of rank equal to `array` with shape increased
642 according to `pad_width`.
643
644 Notes
645 -----
646 .. versionadded:: 1.7.0
647
648 For an array with rank greater than 1, some of the padding of later
649 axes is calculated from padding of previous axes. This is easiest to
650 think about with a rank 2 array where the corners of the padded array
651 are calculated by using padded values from the first axis.
652
653 The padding function, if used, should modify a rank 1 array in-place. It
654 has the following signature::
655
656 padding_func(vector, iaxis_pad_width, iaxis, kwargs)
657
658 where
659
660 vector : ndarray
661 A rank 1 array already padded with zeros. Padded values are
662 vector[:iaxis_pad_width[0]] and vector[-iaxis_pad_width[1]:].
663 iaxis_pad_width : tuple
664 A 2-tuple of ints, iaxis_pad_width[0] represents the number of
665 values padded at the beginning of vector where
666 iaxis_pad_width[1] represents the number of values padded at
667 the end of vector.
668 iaxis : int
669 The axis currently being calculated.
670 kwargs : dict
671 Any keyword arguments the function requires.
672
673 Examples
674 --------
675 >>> a = [1, 2, 3, 4, 5]
676 >>> np.pad(a, (2, 3), 'constant', constant_values=(4, 6))
677 array([4, 4, 1, ..., 6, 6, 6])
678
679 >>> np.pad(a, (2, 3), 'edge')
680 array([1, 1, 1, ..., 5, 5, 5])
681
682 >>> np.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4))
683 array([ 5, 3, 1, 2, 3, 4, 5, 2, -1, -4])
684
685 >>> np.pad(a, (2,), 'maximum')
686 array([5, 5, 1, 2, 3, 4, 5, 5, 5])
687
688 >>> np.pad(a, (2,), 'mean')
689 array([3, 3, 1, 2, 3, 4, 5, 3, 3])
690
691 >>> np.pad(a, (2,), 'median')
692 array([3, 3, 1, 2, 3, 4, 5, 3, 3])
693
694 >>> a = [[1, 2], [3, 4]]
695 >>> np.pad(a, ((3, 2), (2, 3)), 'minimum')
696 array([[1, 1, 1, 2, 1, 1, 1],
697 [1, 1, 1, 2, 1, 1, 1],
698 [1, 1, 1, 2, 1, 1, 1],
699 [1, 1, 1, 2, 1, 1, 1],
700 [3, 3, 3, 4, 3, 3, 3],
701 [1, 1, 1, 2, 1, 1, 1],
702 [1, 1, 1, 2, 1, 1, 1]])
703
704 >>> a = [1, 2, 3, 4, 5]
705 >>> np.pad(a, (2, 3), 'reflect')
706 array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2])
707
708 >>> np.pad(a, (2, 3), 'reflect', reflect_type='odd')
709 array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8])
710
711 >>> np.pad(a, (2, 3), 'symmetric')
712 array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3])
713
714 >>> np.pad(a, (2, 3), 'symmetric', reflect_type='odd')
715 array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7])
716
717 >>> np.pad(a, (2, 3), 'wrap')
718 array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3])
719
720 >>> def pad_with(vector, pad_width, iaxis, kwargs):
721 ... pad_value = kwargs.get('padder', 10)
722 ... vector[:pad_width[0]] = pad_value
723 ... vector[-pad_width[1]:] = pad_value
724 >>> a = np.arange(6)
725 >>> a = a.reshape((2, 3))
726 >>> np.pad(a, 2, pad_with)
727 array([[10, 10, 10, 10, 10, 10, 10],
728 [10, 10, 10, 10, 10, 10, 10],
729 [10, 10, 0, 1, 2, 10, 10],
730 [10, 10, 3, 4, 5, 10, 10],
731 [10, 10, 10, 10, 10, 10, 10],
732 [10, 10, 10, 10, 10, 10, 10]])
733 >>> np.pad(a, 2, pad_with, padder=100)
734 array([[100, 100, 100, 100, 100, 100, 100],
735 [100, 100, 100, 100, 100, 100, 100],
736 [100, 100, 0, 1, 2, 100, 100],
737 [100, 100, 3, 4, 5, 100, 100],
738 [100, 100, 100, 100, 100, 100, 100],
739 [100, 100, 100, 100, 100, 100, 100]])
740 """
741 array = np.asarray(array)
742 pad_width = np.asarray(pad_width)
743
744 if not pad_width.dtype.kind == 'i':
745 raise TypeError('`pad_width` must be of integral type.')
746
747 # Broadcast to shape (array.ndim, 2)
748 pad_width = _as_pairs(pad_width, array.ndim, as_index=True)
749
750 if callable(mode):
751 # Old behavior: Use user-supplied function with np.apply_along_axis
752 function = mode
753 # Create a new zero padded array
754 padded, _ = _pad_simple(array, pad_width, fill_value=0)
755 # And apply along each axis
756
757 for axis in range(padded.ndim):
758 # Iterate using ndindex as in apply_along_axis, but assuming that
759 # function operates inplace on the padded array.
760
761 # view with the iteration axis at the end
762 view = np.moveaxis(padded, axis, -1)
763
764 # compute indices for the iteration axes, and append a trailing
765 # ellipsis to prevent 0d arrays decaying to scalars (gh-8642)
766 inds = ndindex(view.shape[:-1])
767 inds = (ind + (Ellipsis,) for ind in inds)
768 for ind in inds:
769 function(view[ind], pad_width[axis], axis, kwargs)
770
771 return padded
772
773 # Make sure that no unsupported keywords were passed for the current mode
774 allowed_kwargs = {
775 'empty': [], 'edge': [], 'wrap': [],
776 'constant': ['constant_values'],
777 'linear_ramp': ['end_values'],
778 'maximum': ['stat_length'],
779 'mean': ['stat_length'],
780 'median': ['stat_length'],
781 'minimum': ['stat_length'],
782 'reflect': ['reflect_type'],
783 'symmetric': ['reflect_type'],
784 }
785 try:
786 unsupported_kwargs = set(kwargs) - set(allowed_kwargs[mode])
787 except KeyError:
788 raise ValueError("mode '{}' is not supported".format(mode)) from None
789 if unsupported_kwargs:
790 raise ValueError("unsupported keyword arguments for mode '{}': {}"
791 .format(mode, unsupported_kwargs))
792
793 stat_functions = {"maximum": np.amax, "minimum": np.amin,
794 "mean": np.mean, "median": np.median}
795
796 # Create array with final shape and original values
797 # (padded area is undefined)
798 padded, original_area_slice = _pad_simple(array, pad_width)
799 # And prepare iteration over all dimensions
800 # (zipping may be more readable than using enumerate)
801 axes = range(padded.ndim)
802
803 if mode == "constant":
804 values = kwargs.get("constant_values", 0)
805 values = _as_pairs(values, padded.ndim)
806 for axis, width_pair, value_pair in zip(axes, pad_width, values):
807 roi = _view_roi(padded, original_area_slice, axis)
808 _set_pad_area(roi, axis, width_pair, value_pair)
809
810 elif mode == "empty":
811 pass # Do nothing as _pad_simple already returned the correct result
812
813 elif array.size == 0:
814 # Only modes "constant" and "empty" can extend empty axes, all other
815 # modes depend on `array` not being empty
816 # -> ensure every empty axis is only "padded with 0"
817 for axis, width_pair in zip(axes, pad_width):
818 if array.shape[axis] == 0 and any(width_pair):
819 raise ValueError(
820 "can't extend empty axis {} using modes other than "
821 "'constant' or 'empty'".format(axis)
822 )
823 # passed, don't need to do anything more as _pad_simple already
824 # returned the correct result
825
826 elif mode == "edge":
827 for axis, width_pair in zip(axes, pad_width):
828 roi = _view_roi(padded, original_area_slice, axis)
829 edge_pair = _get_edges(roi, axis, width_pair)
830 _set_pad_area(roi, axis, width_pair, edge_pair)
831
832 elif mode == "linear_ramp":
833 end_values = kwargs.get("end_values", 0)
834 end_values = _as_pairs(end_values, padded.ndim)
835 for axis, width_pair, value_pair in zip(axes, pad_width, end_values):
836 roi = _view_roi(padded, original_area_slice, axis)
837 ramp_pair = _get_linear_ramps(roi, axis, width_pair, value_pair)
838 _set_pad_area(roi, axis, width_pair, ramp_pair)
839
840 elif mode in stat_functions:
841 func = stat_functions[mode]
842 length = kwargs.get("stat_length", None)
843 length = _as_pairs(length, padded.ndim, as_index=True)
844 for axis, width_pair, length_pair in zip(axes, pad_width, length):
845 roi = _view_roi(padded, original_area_slice, axis)
846 stat_pair = _get_stats(roi, axis, width_pair, length_pair, func)
847 _set_pad_area(roi, axis, width_pair, stat_pair)
848
849 elif mode in {"reflect", "symmetric"}:
850 method = kwargs.get("reflect_type", "even")
851 include_edge = True if mode == "symmetric" else False
852 for axis, (left_index, right_index) in zip(axes, pad_width):
853 if array.shape[axis] == 1 and (left_index > 0 or right_index > 0):
854 # Extending singleton dimension for 'reflect' is legacy
855 # behavior; it really should raise an error.
856 edge_pair = _get_edges(padded, axis, (left_index, right_index))
857 _set_pad_area(
858 padded, axis, (left_index, right_index), edge_pair)
859 continue
860
861 roi = _view_roi(padded, original_area_slice, axis)
862 while left_index > 0 or right_index > 0:
863 # Iteratively pad until dimension is filled with reflected
864 # values. This is necessary if the pad area is larger than
865 # the length of the original values in the current dimension.
866 left_index, right_index = _set_reflect_both(
867 roi, axis, (left_index, right_index),
868 method, include_edge
869 )
870
871 elif mode == "wrap":
872 for axis, (left_index, right_index) in zip(axes, pad_width):
873 roi = _view_roi(padded, original_area_slice, axis)
874 original_period = padded.shape[axis] - right_index - left_index
875 while left_index > 0 or right_index > 0:
876 # Iteratively pad until dimension is filled with wrapped
877 # values. This is necessary if the pad area is larger than
878 # the length of the original values in the current dimension.
879 left_index, right_index = _set_wrap_both(
880 roi, axis, (left_index, right_index), original_period)
881
882 return padded