Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_arraypad_impl.py927 linesDownload Raw Back to lib
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
927 
codekingpro/portable-devtools · Team Ai