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