1"""
2The arraypad module contains a group of functions to pad values onto the edges
3of an n-dimensional array.
4
5"""
6from __future__ import division, absolute_import, print_function
7
8import numpy as np
9
10
11__all__ = ['pad']
12
13
14###############################################################################
15# Private utility functions.
16
17
18def _arange_ndarray(arr, shape, axis, reverse=False):
19    """
20    Create an ndarray of `shape` with increments along specified `axis`
21
22    Parameters
23    ----------
24    arr : ndarray
25        Input array of arbitrary shape.
26    shape : tuple of ints
27        Shape of desired array. Should be equivalent to `arr.shape` except
28        `shape[axis]` which may have any positive value.
29    axis : int
30        Axis to increment along.
31    reverse : bool
32        If False, increment in a positive fashion from 1 to `shape[axis]`,
33        inclusive. If True, the bounds are the same but the order reversed.
34
35    Returns
36    -------
37    padarr : ndarray
38        Output array sized to pad `arr` along `axis`, with linear range from
39        1 to `shape[axis]` along specified `axis`.
40
41    Notes
42    -----
43    The range is deliberately 1-indexed for this specific use case. Think of
44    this algorithm as broadcasting `np.arange` to a single `axis` of an
45    arbitrarily shaped ndarray.
46
47    """
48    initshape = tuple(1 if i != axis else shape[axis]
49                      for (i, x) in enumerate(arr.shape))
50    if not reverse:
51        padarr = np.arange(1, shape[axis] + 1)
52    else:
53        padarr = np.arange(shape[axis], 0, -1)
54    padarr = padarr.reshape(initshape)
55    for i, dim in enumerate(shape):
56        if padarr.shape[i] != dim:
57            padarr = padarr.repeat(dim, axis=i)
58    return padarr
59
60
61def _round_ifneeded(arr, dtype):
62    """
63    Rounds arr inplace if destination dtype is integer.
64
65    Parameters
66    ----------
67    arr : ndarray
68        Input array.
69    dtype : dtype
70        The dtype of the destination array.
71
72    """
73    if np.issubdtype(dtype, np.integer):
74        arr.round(out=arr)
75
76
77def _prepend_const(arr, pad_amt, val, axis=-1):
78    """
79    Prepend constant `val` along `axis` of `arr`.
80
81    Parameters
82    ----------
83    arr : ndarray
84        Input array of arbitrary shape.
85    pad_amt : int
86        Amount of padding to prepend.
87    val : scalar
88        Constant value to use. For best results should be of type `arr.dtype`;
89        if not `arr.dtype` will be cast to `arr.dtype`.
90    axis : int
91        Axis along which to pad `arr`.
92
93    Returns
94    -------
95    padarr : ndarray
96        Output array, with `pad_amt` constant `val` prepended along `axis`.
97
98    """
99    if pad_amt == 0:
100        return arr
101    padshape = tuple(x if i != axis else pad_amt
102                     for (i, x) in enumerate(arr.shape))
103    if val == 0:
104        return np.concatenate((np.zeros(padshape, dtype=arr.dtype), arr),
105                              axis=axis)
106    else:
107        return np.concatenate(((np.zeros(padshape) + val).astype(arr.dtype),
108                               arr), axis=axis)
109
110
111def _append_const(arr, pad_amt, val, axis=-1):
112    """
113    Append constant `val` along `axis` of `arr`.
114
115    Parameters
116    ----------
117    arr : ndarray
118        Input array of arbitrary shape.
119    pad_amt : int
120        Amount of padding to append.
121    val : scalar
122        Constant value to use. For best results should be of type `arr.dtype`;
123        if not `arr.dtype` will be cast to `arr.dtype`.
124    axis : int
125        Axis along which to pad `arr`.
126
127    Returns
128    -------
129    padarr : ndarray
130        Output array, with `pad_amt` constant `val` appended along `axis`.
131
132    """
133    if pad_amt == 0:
134        return arr
135    padshape = tuple(x if i != axis else pad_amt
136                     for (i, x) in enumerate(arr.shape))
137    if val == 0:
138        return np.concatenate((arr, np.zeros(padshape, dtype=arr.dtype)),
139                              axis=axis)
140    else:
141        return np.concatenate(
142            (arr, (np.zeros(padshape) + val).astype(arr.dtype)), axis=axis)
143
144
145def _prepend_edge(arr, pad_amt, axis=-1):
146    """
147    Prepend `pad_amt` to `arr` along `axis` by extending edge values.
148
149    Parameters
150    ----------
151    arr : ndarray
152        Input array of arbitrary shape.
153    pad_amt : int
154        Amount of padding to prepend.
155    axis : int
156        Axis along which to pad `arr`.
157
158    Returns
159    -------
160    padarr : ndarray
161        Output array, extended by `pad_amt` edge values appended along `axis`.
162
163    """
164    if pad_amt == 0:
165        return arr
166
167    edge_slice = tuple(slice(None) if i != axis else 0
168                       for (i, x) in enumerate(arr.shape))
169
170    # Shape to restore singleton dimension after slicing
171    pad_singleton = tuple(x if i != axis else 1
172                          for (i, x) in enumerate(arr.shape))
173    edge_arr = arr[edge_slice].reshape(pad_singleton)
174    return np.concatenate((edge_arr.repeat(pad_amt, axis=axis), arr),
175                          axis=axis)
176
177
178def _append_edge(arr, pad_amt, axis=-1):
179    """
180    Append `pad_amt` to `arr` along `axis` by extending edge values.
181
182    Parameters
183    ----------
184    arr : ndarray
185        Input array of arbitrary shape.
186    pad_amt : int
187        Amount of padding to append.
188    axis : int
189        Axis along which to pad `arr`.
190
191    Returns
192    -------
193    padarr : ndarray
194        Output array, extended by `pad_amt` edge values prepended along
195        `axis`.
196
197    """
198    if pad_amt == 0:
199        return arr
200
201    edge_slice = tuple(slice(None) if i != axis else arr.shape[axis] - 1
202                       for (i, x) in enumerate(arr.shape))
203
204    # Shape to restore singleton dimension after slicing
205    pad_singleton = tuple(x if i != axis else 1
206                          for (i, x) in enumerate(arr.shape))
207    edge_arr = arr[edge_slice].reshape(pad_singleton)
208    return np.concatenate((arr, edge_arr.repeat(pad_amt, axis=axis)),
209                          axis=axis)
210
211
212def _prepend_ramp(arr, pad_amt, end, axis=-1):
213    """
214    Prepend linear ramp along `axis`.
215
216    Parameters
217    ----------
218    arr : ndarray
219        Input array of arbitrary shape.
220    pad_amt : int
221        Amount of padding to prepend.
222    end : scalar
223        Constal value to use. For best results should be of type `arr.dtype`;
224        if not `arr.dtype` will be cast to `arr.dtype`.
225    axis : int
226        Axis along which to pad `arr`.
227
228    Returns
229    -------
230    padarr : ndarray
231        Output array, with `pad_amt` values prepended along `axis`. The
232        prepended region ramps linearly from the edge value to `end`.
233
234    """
235    if pad_amt == 0:
236        return arr
237
238    # Generate shape for final concatenated array
239    padshape = tuple(x if i != axis else pad_amt
240                     for (i, x) in enumerate(arr.shape))
241
242    # Generate an n-dimensional array incrementing along `axis`
243    ramp_arr = _arange_ndarray(arr, padshape, axis,
244                               reverse=True).astype(np.float64)
245
246    # Appropriate slicing to extract n-dimensional edge along `axis`
247    edge_slice = tuple(slice(None) if i != axis else 0
248                       for (i, x) in enumerate(arr.shape))
249
250    # Shape to restore singleton dimension after slicing
251    pad_singleton = tuple(x if i != axis else 1
252                          for (i, x) in enumerate(arr.shape))
253
254    # Extract edge, reshape to original rank, and extend along `axis`
255    edge_pad = arr[edge_slice].reshape(pad_singleton).repeat(pad_amt, axis)
256
257    # Linear ramp
258    slope = (end - edge_pad) / float(pad_amt)
259    ramp_arr = ramp_arr * slope
260    ramp_arr += edge_pad
261    _round_ifneeded(ramp_arr, arr.dtype)
262
263    # Ramp values will most likely be float, cast them to the same type as arr
264    return np.concatenate((ramp_arr.astype(arr.dtype), arr), axis=axis)
265
266
267def _append_ramp(arr, pad_amt, end, axis=-1):
268    """
269    Append linear ramp along `axis`.
270
271    Parameters
272    ----------
273    arr : ndarray
274        Input array of arbitrary shape.
275    pad_amt : int
276        Amount of padding to append.
277    end : scalar
278        Constal value to use. For best results should be of type `arr.dtype`;
279        if not `arr.dtype` will be cast to `arr.dtype`.
280    axis : int
281        Axis along which to pad `arr`.
282
283    Returns
284    -------
285    padarr : ndarray
286        Output array, with `pad_amt` values appended along `axis`. The
287        appended region ramps linearly from the edge value to `end`.
288
289    """
290    if pad_amt == 0:
291        return arr
292
293    # Generate shape for final concatenated array
294    padshape = tuple(x if i != axis else pad_amt
295                     for (i, x) in enumerate(arr.shape))
296
297    # Generate an n-dimensional array incrementing along `axis`
298    ramp_arr = _arange_ndarray(arr, padshape, axis,
299                               reverse=False).astype(np.float64)
300
301    # Slice a chunk from the edge to calculate stats on
302    edge_slice = tuple(slice(None) if i != axis else -1
303                       for (i, x) in enumerate(arr.shape))
304
305    # Shape to restore singleton dimension after slicing
306    pad_singleton = tuple(x if i != axis else 1
307                          for (i, x) in enumerate(arr.shape))
308
309    # Extract edge, reshape to original rank, and extend along `axis`
310    edge_pad = arr[edge_slice].reshape(pad_singleton).repeat(pad_amt, axis)
311
312    # Linear ramp
313    slope = (end - edge_pad) / float(pad_amt)
314    ramp_arr = ramp_arr * slope
315    ramp_arr += edge_pad
316    _round_ifneeded(ramp_arr, arr.dtype)
317
318    # Ramp values will most likely be float, cast them to the same type as arr
319    return np.concatenate((arr, ramp_arr.astype(arr.dtype)), axis=axis)
320
321
322def _prepend_max(arr, pad_amt, num, axis=-1):
323    """
324    Prepend `pad_amt` maximum values along `axis`.
325
326    Parameters
327    ----------
328    arr : ndarray
329        Input array of arbitrary shape.
330    pad_amt : int
331        Amount of padding to prepend.
332    num : int
333        Depth into `arr` along `axis` to calculate maximum.
334        Range: [1, `arr.shape[axis]`] or None (entire axis)
335    axis : int
336        Axis along which to pad `arr`.
337
338    Returns
339    -------
340    padarr : ndarray
341        Output array, with `pad_amt` values appended along `axis`. The
342        prepended region is the maximum of the first `num` values along
343        `axis`.
344
345    """
346    if pad_amt == 0:
347        return arr
348
349    # Equivalent to edge padding for single value, so do that instead
350    if num == 1:
351        return _prepend_edge(arr, pad_amt, axis)
352
353    # Use entire array if `num` is too large
354    if num is not None:
355        if num >= arr.shape[axis]:
356            num = None
357
358    # Slice a chunk from the edge to calculate stats on
359    max_slice = tuple(slice(None) if i != axis else slice(num)
360                      for (i, x) in enumerate(arr.shape))
361
362    # Shape to restore singleton dimension after slicing
363    pad_singleton = tuple(x if i != axis else 1
364                          for (i, x) in enumerate(arr.shape))
365
366    # Extract slice, calculate max, reshape to add singleton dimension back
367    max_chunk = arr[max_slice].max(axis=axis).reshape(pad_singleton)
368
369    # Concatenate `arr` with `max_chunk`, extended along `axis` by `pad_amt`
370    return np.concatenate((max_chunk.repeat(pad_amt, axis=axis), arr),
371                          axis=axis)
372
373
374def _append_max(arr, pad_amt, num, axis=-1):
375    """
376    Pad one `axis` of `arr` with the maximum of the last `num` elements.
377
378    Parameters
379    ----------
380    arr : ndarray
381        Input array of arbitrary shape.
382    pad_amt : int
383        Amount of padding to append.
384    num : int
385        Depth into `arr` along `axis` to calculate maximum.
386        Range: [1, `arr.shape[axis]`] or None (entire axis)
387    axis : int
388        Axis along which to pad `arr`.
389
390    Returns
391    -------
392    padarr : ndarray
393        Output array, with `pad_amt` values appended along `axis`. The
394        appended region is the maximum of the final `num` values along `axis`.
395
396    """
397    if pad_amt == 0:
398        return arr
399
400    # Equivalent to edge padding for single value, so do that instead
401    if num == 1:
402        return _append_edge(arr, pad_amt, axis)
403
404    # Use entire array if `num` is too large
405    if num is not None:
406        if num >= arr.shape[axis]:
407            num = None
408
409    # Slice a chunk from the edge to calculate stats on
410    end = arr.shape[axis] - 1
411    if num is not None:
412        max_slice = tuple(
413            slice(None) if i != axis else slice(end, end - num, -1)
414            for (i, x) in enumerate(arr.shape))
415    else:
416        max_slice = tuple(slice(None) for x in arr.shape)
417
418    # Shape to restore singleton dimension after slicing
419    pad_singleton = tuple(x if i != axis else 1
420                          for (i, x) in enumerate(arr.shape))
421
422    # Extract slice, calculate max, reshape to add singleton dimension back
423    max_chunk = arr[max_slice].max(axis=axis).reshape(pad_singleton)
424
425    # Concatenate `arr` with `max_chunk`, extended along `axis` by `pad_amt`
426    return np.concatenate((arr, max_chunk.repeat(pad_amt, axis=axis)),
427                          axis=axis)
428
429
430def _prepend_mean(arr, pad_amt, num, axis=-1):
431    """
432    Prepend `pad_amt` mean values along `axis`.
433
434    Parameters
435    ----------
436    arr : ndarray
437        Input array of arbitrary shape.
438    pad_amt : int
439        Amount of padding to prepend.
440    num : int
441        Depth into `arr` along `axis` to calculate mean.
442        Range: [1, `arr.shape[axis]`] or None (entire axis)
443    axis : int
444        Axis along which to pad `arr`.
445
446    Returns
447    -------
448    padarr : ndarray
449        Output array, with `pad_amt` values prepended along `axis`. The
450        prepended region is the mean of the first `num` values along `axis`.
451
452    """
453    if pad_amt == 0:
454        return arr
455
456    # Equivalent to edge padding for single value, so do that instead
457    if num == 1:
458        return _prepend_edge(arr, pad_amt, axis)
459
460    # Use entire array if `num` is too large
461    if num is not None:
462        if num >= arr.shape[axis]:
463            num = None
464
465    # Slice a chunk from the edge to calculate stats on
466    mean_slice = tuple(slice(None) if i != axis else slice(num)
467                       for (i, x) in enumerate(arr.shape))
468
469    # Shape to restore singleton dimension after slicing
470    pad_singleton = tuple(x if i != axis else 1
471                          for (i, x) in enumerate(arr.shape))
472
473    # Extract slice, calculate mean, reshape to add singleton dimension back
474    mean_chunk = arr[mean_slice].mean(axis).reshape(pad_singleton)
475    _round_ifneeded(mean_chunk, arr.dtype)
476
477    # Concatenate `arr` with `mean_chunk`, extended along `axis` by `pad_amt`
478    return np.concatenate((mean_chunk.repeat(pad_amt, axis).astype(arr.dtype),
479                           arr), axis=axis)
480
481
482def _append_mean(arr, pad_amt, num, axis=-1):
483    """
484    Append `pad_amt` mean values along `axis`.
485
486    Parameters
487    ----------
488    arr : ndarray
489        Input array of arbitrary shape.
490    pad_amt : int
491        Amount of padding to append.
492    num : int
493        Depth into `arr` along `axis` to calculate mean.
494        Range: [1, `arr.shape[axis]`] or None (entire axis)
495    axis : int
496        Axis along which to pad `arr`.
497
498    Returns
499    -------
500    padarr : ndarray
501        Output array, with `pad_amt` values appended along `axis`. The
502        appended region is the maximum of the final `num` values along `axis`.
503
504    """
505    if pad_amt == 0:
506        return arr
507
508    # Equivalent to edge padding for single value, so do that instead
509    if num == 1:
510        return _append_edge(arr, pad_amt, axis)
511
512    # Use entire array if `num` is too large
513    if num is not None:
514        if num >= arr.shape[axis]:
515            num = None
516
517    # Slice a chunk from the edge to calculate stats on
518    end = arr.shape[axis] - 1
519    if num is not None:
520        mean_slice = tuple(
521            slice(None) if i != axis else slice(end, end - num, -1)
522            for (i, x) in enumerate(arr.shape))
523    else:
524        mean_slice = tuple(slice(None) for x in arr.shape)
525
526    # Shape to restore singleton dimension after slicing
527    pad_singleton = tuple(x if i != axis else 1
528                          for (i, x) in enumerate(arr.shape))
529
530    # Extract slice, calculate mean, reshape to add singleton dimension back
531    mean_chunk = arr[mean_slice].mean(axis=axis).reshape(pad_singleton)
532    _round_ifneeded(mean_chunk, arr.dtype)
533
534    # Concatenate `arr` with `mean_chunk`, extended along `axis` by `pad_amt`
535    return np.concatenate(
536        (arr, mean_chunk.repeat(pad_amt, axis).astype(arr.dtype)), axis=axis)
537
538
539def _prepend_med(arr, pad_amt, num, axis=-1):
540    """
541    Prepend `pad_amt` median values along `axis`.
542
543    Parameters
544    ----------
545    arr : ndarray
546        Input array of arbitrary shape.
547    pad_amt : int
548        Amount of padding to prepend.
549    num : int
550        Depth into `arr` along `axis` to calculate median.
551        Range: [1, `arr.shape[axis]`] or None (entire axis)
552    axis : int
553        Axis along which to pad `arr`.
554
555    Returns
556    -------
557    padarr : ndarray
558        Output array, with `pad_amt` values prepended along `axis`. The
559        prepended region is the median of the first `num` values along `axis`.
560
561    """
562    if pad_amt == 0:
563        return arr
564
565    # Equivalent to edge padding for single value, so do that instead
566    if num == 1:
567        return _prepend_edge(arr, pad_amt, axis)
568
569    # Use entire array if `num` is too large
570    if num is not None:
571        if num >= arr.shape[axis]:
572            num = None
573
574    # Slice a chunk from the edge to calculate stats on
575    med_slice = tuple(slice(None) if i != axis else slice(num)
576                      for (i, x) in enumerate(arr.shape))
577
578    # Shape to restore singleton dimension after slicing
579    pad_singleton = tuple(x if i != axis else 1
580                          for (i, x) in enumerate(arr.shape))
581
582    # Extract slice, calculate median, reshape to add singleton dimension back
583    med_chunk = np.median(arr[med_slice], axis=axis).reshape(pad_singleton)
584    _round_ifneeded(med_chunk, arr.dtype)
585
586    # Concatenate `arr` with `med_chunk`, extended along `axis` by `pad_amt`
587    return np.concatenate(
588        (med_chunk.repeat(pad_amt, axis).astype(arr.dtype), arr), axis=axis)
589
590
591def _append_med(arr, pad_amt, num, axis=-1):
592    """
593    Append `pad_amt` median values along `axis`.
594
595    Parameters
596    ----------
597    arr : ndarray
598        Input array of arbitrary shape.
599    pad_amt : int
600        Amount of padding to append.
601    num : int
602        Depth into `arr` along `axis` to calculate median.
603        Range: [1, `arr.shape[axis]`] or None (entire axis)
604    axis : int
605        Axis along which to pad `arr`.
606
607    Returns
608    -------
609    padarr : ndarray
610        Output array, with `pad_amt` values appended along `axis`. The
611        appended region is the median of the final `num` values along `axis`.
612
613    """
614    if pad_amt == 0:
615        return arr
616
617    # Equivalent to edge padding for single value, so do that instead
618    if num == 1:
619        return _append_edge(arr, pad_amt, axis)
620
621    # Use entire array if `num` is too large
622    if num is not None:
623        if num >= arr.shape[axis]:
624            num = None
625
626    # Slice a chunk from the edge to calculate stats on
627    end = arr.shape[axis] - 1
628    if num is not None:
629        med_slice = tuple(
630            slice(None) if i != axis else slice(end, end - num, -1)
631            for (i, x) in enumerate(arr.shape))
632    else:
633        med_slice = tuple(slice(None) for x in arr.shape)
634
635    # Shape to restore singleton dimension after slicing
636    pad_singleton = tuple(x if i != axis else 1
637                          for (i, x) in enumerate(arr.shape))
638
639    # Extract slice, calculate median, reshape to add singleton dimension back
640    med_chunk = np.median(arr[med_slice], axis=axis).reshape(pad_singleton)
641    _round_ifneeded(med_chunk, arr.dtype)
642
643    # Concatenate `arr` with `med_chunk`, extended along `axis` by `pad_amt`
644    return np.concatenate(
645        (arr, med_chunk.repeat(pad_amt, axis).astype(arr.dtype)), axis=axis)
646
647
648def _prepend_min(arr, pad_amt, num, axis=-1):
649    """
650    Prepend `pad_amt` minimum values along `axis`.
651
652    Parameters
653    ----------
654    arr : ndarray
655        Input array of arbitrary shape.
656    pad_amt : int
657        Amount of padding to prepend.
658    num : int
659        Depth into `arr` along `axis` to calculate minimum.
660        Range: [1, `arr.shape[axis]`] or None (entire axis)
661    axis : int
662        Axis along which to pad `arr`.
663
664    Returns
665    -------
666    padarr : ndarray
667        Output array, with `pad_amt` values prepended along `axis`. The
668        prepended region is the minimum of the first `num` values along
669        `axis`.
670
671    """
672    if pad_amt == 0:
673        return arr
674
675    # Equivalent to edge padding for single value, so do that instead
676    if num == 1:
677        return _prepend_edge(arr, pad_amt, axis)
678
679    # Use entire array if `num` is too large
680    if num is not None:
681        if num >= arr.shape[axis]:
682            num = None
683
684    # Slice a chunk from the edge to calculate stats on
685    min_slice = tuple(slice(None) if i != axis else slice(num)
686                      for (i, x) in enumerate(arr.shape))
687
688    # Shape to restore singleton dimension after slicing
689    pad_singleton = tuple(x if i != axis else 1
690                          for (i, x) in enumerate(arr.shape))
691
692    # Extract slice, calculate min, reshape to add singleton dimension back
693    min_chunk = arr[min_slice].min(axis=axis).reshape(pad_singleton)
694
695    # Concatenate `arr` with `min_chunk`, extended along `axis` by `pad_amt`
696    return np.concatenate((min_chunk.repeat(pad_amt, axis=axis), arr),
697                          axis=axis)
698
699
700def _append_min(arr, pad_amt, num, axis=-1):
701    """
702    Append `pad_amt` median values along `axis`.
703
704    Parameters
705    ----------
706    arr : ndarray
707        Input array of arbitrary shape.
708    pad_amt : int
709        Amount of padding to append.
710    num : int
711        Depth into `arr` along `axis` to calculate minimum.
712        Range: [1, `arr.shape[axis]`] or None (entire axis)
713    axis : int
714        Axis along which to pad `arr`.
715
716    Returns
717    -------
718    padarr : ndarray
719        Output array, with `pad_amt` values appended along `axis`. The
720        appended region is the minimum of the final `num` values along `axis`.
721
722    """
723    if pad_amt == 0:
724        return arr
725
726    # Equivalent to edge padding for single value, so do that instead
727    if num == 1:
728        return _append_edge(arr, pad_amt, axis)
729
730    # Use entire array if `num` is too large
731    if num is not None:
732        if num >= arr.shape[axis]:
733            num = None
734
735    # Slice a chunk from the edge to calculate stats on
736    end = arr.shape[axis] - 1
737    if num is not None:
738        min_slice = tuple(
739            slice(None) if i != axis else slice(end, end - num, -1)
740            for (i, x) in enumerate(arr.shape))
741    else:
742        min_slice = tuple(slice(None) for x in arr.shape)
743
744    # Shape to restore singleton dimension after slicing
745    pad_singleton = tuple(x if i != axis else 1
746                          for (i, x) in enumerate(arr.shape))
747
748    # Extract slice, calculate min, reshape to add singleton dimension back
749    min_chunk = arr[min_slice].min(axis=axis).reshape(pad_singleton)
750
751    # Concatenate `arr` with `min_chunk`, extended along `axis` by `pad_amt`
752    return np.concatenate((arr, min_chunk.repeat(pad_amt, axis=axis)),
753                          axis=axis)
754
755
756def _pad_ref(arr, pad_amt, method, axis=-1):
757    """
758    Pad `axis` of `arr` by reflection.
759
760    Parameters
761    ----------
762    arr : ndarray
763        Input array of arbitrary shape.
764    pad_amt : tuple of ints, length 2
765        Padding to (prepend, append) along `axis`.
766    method : str
767        Controls method of reflection; options are 'even' or 'odd'.
768    axis : int
769        Axis along which to pad `arr`.
770
771    Returns
772    -------
773    padarr : ndarray
774        Output array, with `pad_amt[0]` values prepended and `pad_amt[1]`
775        values appended along `axis`. Both regions are padded with reflected
776        values from the original array.
777
778    Notes
779    -----
780    This algorithm does not pad with repetition, i.e. the edges are not
781    repeated in the reflection. For that behavior, use `mode='symmetric'`.
782
783    The modes 'reflect', 'symmetric', and 'wrap' must be padded with a
784    single function, lest the indexing tricks in non-integer multiples of the
785    original shape would violate repetition in the final iteration.
786
787    """
788    # Implicit booleanness to test for zero (or None) in any scalar type
789    if pad_amt[0] == 0 and pad_amt[1] == 0:
790        return arr
791
792    ##########################################################################
793    # Prepended region
794
795    # Slice off a reverse indexed chunk from near edge to pad `arr` before
796    ref_slice = tuple(slice(None) if i != axis else slice(pad_amt[0], 0, -1)
797                      for (i, x) in enumerate(arr.shape))
798
799    ref_chunk1 = arr[ref_slice]
800
801    # Shape to restore singleton dimension after slicing
802    pad_singleton = tuple(x if i != axis else 1
803                          for (i, x) in enumerate(arr.shape))
804    if pad_amt[0] == 1:
805        ref_chunk1 = ref_chunk1.reshape(pad_singleton)
806
807    # Memory/computationally more expensive, only do this if `method='odd'`
808    if 'odd' in method and pad_amt[0] > 0:
809        edge_slice1 = tuple(slice(None) if i != axis else 0
810                            for (i, x) in enumerate(arr.shape))
811        edge_chunk = arr[edge_slice1].reshape(pad_singleton)
812        ref_chunk1 = 2 * edge_chunk - ref_chunk1
813        del edge_chunk
814
815    ##########################################################################
816    # Appended region
817
818    # Slice off a reverse indexed chunk from far edge to pad `arr` after
819    start = arr.shape[axis] - pad_amt[1] - 1
820    end = arr.shape[axis] - 1
821    ref_slice = tuple(slice(None) if i != axis else slice(start, end)
822                      for (i, x) in enumerate(arr.shape))
823    rev_idx = tuple(slice(None) if i != axis else slice(None, None, -1)
824                    for (i, x) in enumerate(arr.shape))
825    ref_chunk2 = arr[ref_slice][rev_idx]
826
827    if pad_amt[1] == 1:
828        ref_chunk2 = ref_chunk2.reshape(pad_singleton)
829
830    if 'odd' in method:
831        edge_slice2 = tuple(slice(None) if i != axis else -1
832                            for (i, x) in enumerate(arr.shape))
833        edge_chunk = arr[edge_slice2].reshape(pad_singleton)
834        ref_chunk2 = 2 * edge_chunk - ref_chunk2
835        del edge_chunk
836
837    # Concatenate `arr` with both chunks, extending along `axis`
838    return np.concatenate((ref_chunk1, arr, ref_chunk2), axis=axis)
839
840
841def _pad_sym(arr, pad_amt, method, axis=-1):
842    """
843    Pad `axis` of `arr` by symmetry.
844
845    Parameters
846    ----------
847    arr : ndarray
848        Input array of arbitrary shape.
849    pad_amt : tuple of ints, length 2
850        Padding to (prepend, append) along `axis`.
851    method : str
852        Controls method of symmetry; options are 'even' or 'odd'.
853    axis : int
854        Axis along which to pad `arr`.
855
856    Returns
857    -------
858    padarr : ndarray
859        Output array, with `pad_amt[0]` values prepended and `pad_amt[1]`
860        values appended along `axis`. Both regions are padded with symmetric
861        values from the original array.
862
863    Notes
864    -----
865    This algorithm DOES pad with repetition, i.e. the edges are repeated.
866    For padding without repeated edges, use `mode='reflect'`.
867
868    The modes 'reflect', 'symmetric', and 'wrap' must be padded with a
869    single function, lest the indexing tricks in non-integer multiples of the
870    original shape would violate repetition in the final iteration.
871
872    """
873    # Implicit booleanness to test for zero (or None) in any scalar type
874    if pad_amt[0] == 0 and pad_amt[1] == 0:
875        return arr
876
877    ##########################################################################
878    # Prepended region
879
880    # Slice off a reverse indexed chunk from near edge to pad `arr` before
881    sym_slice = tuple(slice(None) if i != axis else slice(0, pad_amt[0])
882                      for (i, x) in enumerate(arr.shape))
883    rev_idx = tuple(slice(None) if i != axis else slice(None, None, -1)
884                    for (i, x) in enumerate(arr.shape))
885    sym_chunk1 = arr[sym_slice][rev_idx]
886
887    # Shape to restore singleton dimension after slicing
888    pad_singleton = tuple(x if i != axis else 1
889                          for (i, x) in enumerate(arr.shape))
890    if pad_amt[0] == 1:
891        sym_chunk1 = sym_chunk1.reshape(pad_singleton)
892
893    # Memory/computationally more expensive, only do this if `method='odd'`
894    if 'odd' in method and pad_amt[0] > 0:
895        edge_slice1 = tuple(slice(None) if i != axis else 0
896                            for (i, x) in enumerate(arr.shape))
897        edge_chunk = arr[edge_slice1].reshape(pad_singleton)
898        sym_chunk1 = 2 * edge_chunk - sym_chunk1
899        del edge_chunk
900
901    ##########################################################################
902    # Appended region
903
904    # Slice off a reverse indexed chunk from far edge to pad `arr` after
905    start = arr.shape[axis] - pad_amt[1]
906    end = arr.shape[axis]
907    sym_slice = tuple(slice(None) if i != axis else slice(start, end)
908                      for (i, x) in enumerate(arr.shape))
909    sym_chunk2 = arr[sym_slice][rev_idx]
910
911    if pad_amt[1] == 1:
912        sym_chunk2 = sym_chunk2.reshape(pad_singleton)
913
914    if 'odd' in method:
915        edge_slice2 = tuple(slice(None) if i != axis else -1
916                            for (i, x) in enumerate(arr.shape))
917        edge_chunk = arr[edge_slice2].reshape(pad_singleton)
918        sym_chunk2 = 2 * edge_chunk - sym_chunk2
919        del edge_chunk
920
921    # Concatenate `arr` with both chunks, extending along `axis`
922    return np.concatenate((sym_chunk1, arr, sym_chunk2), axis=axis)
923
924
925def _pad_wrap(arr, pad_amt, axis=-1):
926    """
927    Pad `axis` of `arr` via wrapping.
928
929    Parameters
930    ----------
931    arr : ndarray
932        Input array of arbitrary shape.
933    pad_amt : tuple of ints, length 2
934        Padding to (prepend, append) along `axis`.
935    axis : int
936        Axis along which to pad `arr`.
937
938    Returns
939    -------
940    padarr : ndarray
941        Output array, with `pad_amt[0]` values prepended and `pad_amt[1]`
942        values appended along `axis`. Both regions are padded wrapped values
943        from the opposite end of `axis`.
944
945    Notes
946    -----
947    This method of padding is also known as 'tile' or 'tiling'.
948
949    The modes 'reflect', 'symmetric', and 'wrap' must be padded with a
950    single function, lest the indexing tricks in non-integer multiples of the
951    original shape would violate repetition in the final iteration.
952
953    """
954    # Implicit booleanness to test for zero (or None) in any scalar type
955    if pad_amt[0] == 0 and pad_amt[1] == 0:
956        return arr
957
958    ##########################################################################
959    # Prepended region
960
961    # Slice off a reverse indexed chunk from near edge to pad `arr` before
962    start = arr.shape[axis] - pad_amt[0]
963    end = arr.shape[axis]
964    wrap_slice = tuple(slice(None) if i != axis else slice(start, end)
965                       for (i, x) in enumerate(arr.shape))
966    wrap_chunk1 = arr[wrap_slice]
967
968    # Shape to restore singleton dimension after slicing
969    pad_singleton = tuple(x if i != axis else 1
970                          for (i, x) in enumerate(arr.shape))
971    if pad_amt[0] == 1:
972        wrap_chunk1 = wrap_chunk1.reshape(pad_singleton)
973
974    ##########################################################################
975    # Appended region
976
977    # Slice off a reverse indexed chunk from far edge to pad `arr` after
978    wrap_slice = tuple(slice(None) if i != axis else slice(0, pad_amt[1])
979                       for (i, x) in enumerate(arr.shape))
980    wrap_chunk2 = arr[wrap_slice]
981
982    if pad_amt[1] == 1:
983        wrap_chunk2 = wrap_chunk2.reshape(pad_singleton)
984
985    # Concatenate `arr` with both chunks, extending along `axis`
986    return np.concatenate((wrap_chunk1, arr, wrap_chunk2), axis=axis)
987
988
989def _normalize_shape(ndarray, shape, cast_to_int=True):
990    """
991    Private function which does some checks and normalizes the possibly
992    much simpler representations of 'pad_width', 'stat_length',
993    'constant_values', 'end_values'.
994
995    Parameters
996    ----------
997    narray : ndarray
998        Input ndarray
999    shape : {sequence, array_like, float, int}, optional
1000        The width of padding (pad_width), the number of elements on the
1001        edge of the narray used for statistics (stat_length), the constant
1002        value(s) to use when filling padded regions (constant_values), or the
1003        endpoint target(s) for linear ramps (end_values).
1004        ((before_1, after_1), ... (before_N, after_N)) unique number of
1005        elements for each axis where `N` is rank of `narray`.
1006        ((before, after),) yields same before and after constants for each
1007        axis.
1008        (constant,) or val is a shortcut for before = after = constant for
1009        all axes.
1010    cast_to_int : bool, optional
1011        Controls if values in ``shape`` will be rounded and cast to int
1012        before being returned.
1013
1014    Returns
1015    -------
1016    normalized_shape : tuple of tuples
1017        val                               => ((val, val), (val, val), ...)
1018        [[val1, val2], [val3, val4], ...] => ((val1, val2), (val3, val4), ...)
1019        ((val1, val2), (val3, val4), ...) => no change
1020        [[val1, val2], ]                  => ((val1, val2), (val1, val2), ...)
1021        ((val1, val2), )                  => ((val1, val2), (val1, val2), ...)
1022        [[val ,     ], ]                  => ((val, val), (val, val), ...)
1023        ((val ,     ), )                  => ((val, val), (val, val), ...)
1024
1025    """
1026    ndims = ndarray.ndim
1027
1028    # Shortcut shape=None
1029    if shape is None:
1030        return ((None, None), ) * ndims
1031
1032    # Convert any input `info` to a NumPy array
1033    arr = np.asarray(shape)
1034
1035    # Switch based on what input looks like
1036    if arr.ndim <= 1:
1037        if arr.shape == () or arr.shape == (1,):   # Single scalar input
1038            # Create new array of ones, multiply by the scalar
1039            arr = np.ones((ndims, 2), dtype=ndarray.dtype) * arr
1040        elif arr.shape == (2,):    # Apply padding (before, after) each axis
1041            # Create new axis 0, repeat along it for every axis
1042            arr = arr[np.newaxis, :].repeat(ndims, axis=0)
1043        else:
1044            fmt = "Unable to create correctly shaped tuple from %s"
1045            raise ValueError(fmt % (shape,))
1046
1047    elif arr.ndim == 2:
1048        if arr.shape[1] == 1 and arr.shape[0] == ndims:
1049            # Padded before and after by the same amount
1050            arr = arr.repeat(2, axis=1)
1051        elif arr.shape[0] == ndims:
1052            # Input correctly formatted, pass it on as `arr`
1053            arr = shape
1054        else:
1055            fmt = "Unable to create correctly shaped tuple from %s"
1056            raise ValueError(fmt % (shape,))
1057
1058    else:
1059        fmt = "Unable to create correctly shaped tuple from %s"
1060        raise ValueError(fmt % (shape,))
1061
1062    # Cast if necessary
1063    if cast_to_int is True:
1064        arr = np.round(arr).astype(int)
1065
1066    # Convert list of lists to tuple of tuples
1067    return tuple(tuple(axis) for axis in arr.tolist())
1068
1069
1070def _validate_lengths(narray, number_elements):
1071    """
1072    Private function which does some checks and reformats pad_width and
1073    stat_length using _normalize_shape.
1074
1075    Parameters
1076    ----------
1077    narray : ndarray
1078        Input ndarray
1079    number_elements : {sequence, int}, optional
1080        The width of padding (pad_width) or the number of elements on the edge
1081        of the narray used for statistics (stat_length).
1082        ((before_1, after_1), ... (before_N, after_N)) unique number of
1083        elements for each axis.
1084        ((before, after),) yields same before and after constants for each
1085        axis.
1086        (constant,) or int is a shortcut for before = after = constant for all
1087        axes.
1088
1089    Returns
1090    -------
1091    _validate_lengths : tuple of tuples
1092        int                               => ((int, int), (int, int), ...)
1093        [[int1, int2], [int3, int4], ...] => ((int1, int2), (int3, int4), ...)
1094        ((int1, int2), (int3, int4), ...) => no change
1095        [[int1, int2], ]                  => ((int1, int2), (int1, int2), ...)
1096        ((int1, int2), )                  => ((int1, int2), (int1, int2), ...)
1097        [[int ,     ], ]                  => ((int, int), (int, int), ...)
1098        ((int ,     ), )                  => ((int, int), (int, int), ...)
1099
1100    """
1101    normshp = _normalize_shape(narray, number_elements)
1102    for i in normshp:
1103        chk = [1 if x is None else x for x in i]
1104        chk = [1 if x >= 0 else -1 for x in chk]
1105        if (chk[0] < 0) or (chk[1] < 0):
1106            fmt = "%s cannot contain negative values."
1107            raise ValueError(fmt % (number_elements,))
1108    return normshp
1109
1110
1111###############################################################################
1112# Public functions
1113
1114
1115def pad(array, pad_width, mode=None, **kwargs):
1116    """
1117    Pads an array.
1118
1119    Parameters
1120    ----------
1121    array : array_like of rank N
1122        Input array
1123    pad_width : {sequence, array_like, int}
1124        Number of values padded to the edges of each axis.
1125        ((before_1, after_1), ... (before_N, after_N)) unique pad widths
1126        for each axis.
1127        ((before, after),) yields same before and after pad for each axis.
1128        (pad,) or int is a shortcut for before = after = pad width for all
1129        axes.
1130    mode : str or function
1131        One of the following string values or a user supplied function.
1132
1133        'constant'
1134            Pads with a constant value.
1135        'edge'
1136            Pads with the edge values of array.
1137        'linear_ramp'
1138            Pads with the linear ramp between end_value and the
1139            array edge value.
1140        'maximum'
1141            Pads with the maximum value of all or part of the
1142            vector along each axis.
1143        'mean'
1144            Pads with the mean value of all or part of the
1145            vector along each axis.
1146        'median'
1147            Pads with the median value of all or part of the
1148            vector along each axis.
1149        'minimum'
1150            Pads with the minimum value of all or part of the
1151            vector along each axis.
1152        'reflect'
1153            Pads with the reflection of the vector mirrored on
1154            the first and last values of the vector along each
1155            axis.
1156        'symmetric'
1157            Pads with the reflection of the vector mirrored
1158            along the edge of the array.
1159        'wrap'
1160            Pads with the wrap of the vector along the axis.
1161            The first values are used to pad the end and the
1162            end values are used to pad the beginning.
1163        <function>
1164            Padding function, see Notes.
1165    stat_length : sequence or int, optional
1166        Used in 'maximum', 'mean', 'median', and 'minimum'.  Number of
1167        values at edge of each axis used to calculate the statistic value.
1168
1169        ((before_1, after_1), ... (before_N, after_N)) unique statistic
1170        lengths for each axis.
1171
1172        ((before, after),) yields same before and after statistic lengths
1173        for each axis.
1174
1175        (stat_length,) or int is a shortcut for before = after = statistic
1176        length for all axes.
1177
1178        Default is ``None``, to use the entire axis.
1179    constant_values : sequence or int, optional
1180        Used in 'constant'.  The values to set the padded values for each
1181        axis.
1182
1183        ((before_1, after_1), ... (before_N, after_N)) unique pad constants
1184        for each axis.
1185
1186        ((before, after),) yields same before and after constants for each
1187        axis.
1188
1189        (constant,) or int is a shortcut for before = after = constant for
1190        all axes.
1191
1192        Default is 0.
1193    end_values : sequence or int, optional
1194        Used in 'linear_ramp'.  The values used for the ending value of the
1195        linear_ramp and that will form the edge of the padded array.
1196
1197        ((before_1, after_1), ... (before_N, after_N)) unique end values
1198        for each axis.
1199
1200        ((before, after),) yields same before and after end values for each
1201        axis.
1202
1203        (constant,) or int is a shortcut for before = after = end value for
1204        all axes.
1205
1206        Default is 0.
1207    reflect_type : {'even', 'odd'}, optional
1208        Used in 'reflect', and 'symmetric'.  The 'even' style is the
1209        default with an unaltered reflection around the edge value.  For
1210        the 'odd' style, the extented part of the array is created by
1211        subtracting the reflected values from two times the edge value.
1212
1213    Returns
1214    -------
1215    pad : ndarray
1216        Padded array of rank equal to `array` with shape increased
1217        according to `pad_width`.
1218
1219    Notes
1220    -----
1221    This function exists in NumPy >= 1.7.0, but is included in
1222    ``scikit-fuzzy`` for backwards compatibility with earlier versions.
1223
1224    For an array with rank greater than 1, some of the padding of later
1225    axes is calculated from padding of previous axes.  This is easiest to
1226    think about with a rank 2 array where the corners of the padded array
1227    are calculated by using padded values from the first axis.
1228
1229    The padding function, if used, should return a rank 1 array equal in
1230    length to the vector argument with padded values replaced. It has the
1231    following signature::
1232
1233        padding_func(vector, iaxis_pad_width, iaxis, **kwargs)
1234
1235    where
1236
1237        vector : ndarray
1238            A rank 1 array already padded with zeros.  Padded values are
1239            vector[:pad_tuple[0]] and vector[-pad_tuple[1]:].
1240        iaxis_pad_width : tuple
1241            A 2-tuple of ints, iaxis_pad_width[0] represents the number of
1242            values padded at the beginning of vector where
1243            iaxis_pad_width[1] represents the number of values padded at
1244            the end of vector.
1245        iaxis : int
1246            The axis currently being calculated.
1247        kwargs : misc
1248            Any keyword arguments the function requires.
1249
1250    Examples
1251    --------
1252    >>> import skfuzzy as fuzz
1253    >>> a = [1, 2, 3, 4, 5]
1254    >>> fuzz.pad(a, (2,3), 'constant', constant_values=(4, 6))
1255    array([4, 4, 1, 2, 3, 4, 5, 6, 6, 6])
1256
1257    >>> fuzz.pad(a, (2, 3), 'edge')
1258    array([1, 1, 1, 2, 3, 4, 5, 5, 5, 5])
1259
1260    >>> fuzz.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4))
1261    array([ 5,  3,  1,  2,  3,  4,  5,  2, -1, -4])
1262
1263    >>> fuzz.pad(a, (2,), 'maximum')
1264    array([5, 5, 1, 2, 3, 4, 5, 5, 5])
1265
1266    >>> fuzz.pad(a, (2,), 'mean')
1267    array([3, 3, 1, 2, 3, 4, 5, 3, 3])
1268
1269    >>> fuzz.pad(a, (2,), 'median')
1270    array([3, 3, 1, 2, 3, 4, 5, 3, 3])
1271
1272    >>> a = [[1, 2], [3, 4]]
1273    >>> fuzz.pad(a, ((3, 2), (2, 3)), 'minimum')
1274    array([[1, 1, 1, 2, 1, 1, 1],
1275           [1, 1, 1, 2, 1, 1, 1],
1276           [1, 1, 1, 2, 1, 1, 1],
1277           [1, 1, 1, 2, 1, 1, 1],
1278           [3, 3, 3, 4, 3, 3, 3],
1279           [1, 1, 1, 2, 1, 1, 1],
1280           [1, 1, 1, 2, 1, 1, 1]])
1281
1282    >>> a = [1, 2, 3, 4, 5]
1283    >>> fuzz.pad(a, (2, 3), 'reflect')
1284    array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2])
1285
1286    >>> fuzz.pad(a, (2, 3), 'reflect', reflect_type='odd')
1287    array([-1,  0,  1,  2,  3,  4,  5,  6,  7,  8])
1288
1289    >>> fuzz.pad(a, (2, 3), 'symmetric')
1290    array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3])
1291
1292    >>> fuzz.pad(a, (2, 3), 'symmetric', reflect_type='odd')
1293    array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7])
1294
1295    >>> fuzz.pad(a, (2, 3), 'wrap')
1296    array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3])
1297
1298    >>> def padwithtens(vector, pad_width, iaxis, kwargs):
1299    ...     vector[:pad_width[0]] = 10
1300    ...     vector[-pad_width[1]:] = 10
1301    ...     return vector
1302
1303    >>> a = np.arange(6)
1304    >>> a = a.reshape((2, 3))
1305
1306    >>> fuzz.pad(a, 2, padwithtens)
1307    array([[10, 10, 10, 10, 10, 10, 10],
1308           [10, 10, 10, 10, 10, 10, 10],
1309           [10, 10,  0,  1,  2, 10, 10],
1310           [10, 10,  3,  4,  5, 10, 10],
1311           [10, 10, 10, 10, 10, 10, 10],
1312           [10, 10, 10, 10, 10, 10, 10]])
1313    """
1314    if not np.asarray(pad_width).dtype.kind == 'i':
1315        raise TypeError('`pad_width` must be of integral type.')
1316
1317    narray = np.array(array)
1318    pad_width = _validate_lengths(narray, pad_width)
1319
1320    allowedkwargs = {
1321        'constant': ['constant_values'],
1322        'edge': [],
1323        'linear_ramp': ['end_values'],
1324        'maximum': ['stat_length'],
1325        'mean': ['stat_length'],
1326        'median': ['stat_length'],
1327        'minimum': ['stat_length'],
1328        'reflect': ['reflect_type'],
1329        'symmetric': ['reflect_type'],
1330        'wrap': [],
1331        }
1332
1333    kwdefaults = {
1334        'stat_length': None,
1335        'constant_values': 0,
1336        'end_values': 0,
1337        'reflect_type': 'even',
1338        }
1339
1340    if isinstance(mode, str):
1341        # Make sure have allowed kwargs appropriate for mode
1342        for key in kwargs:
1343            if key not in allowedkwargs[mode]:
1344                raise ValueError('%s keyword not in allowed keywords %s' %
1345                                 (key, allowedkwargs[mode]))
1346
1347        # Set kwarg defaults
1348        for kw in allowedkwargs[mode]:
1349            kwargs.setdefault(kw, kwdefaults[kw])
1350
1351        # Need to only normalize particular keywords.
1352        for i in kwargs:
1353            if i == 'stat_length':
1354                kwargs[i] = _validate_lengths(narray, kwargs[i])
1355            if i in ['end_values', 'constant_values']:
1356                kwargs[i] = _normalize_shape(narray, kwargs[i],
1357                                             cast_to_int=False)
1358    elif mode is None:
1359        raise ValueError('Keyword "mode" must be a function or one of %s.' %
1360                         (list(allowedkwargs.keys()),))
1361    else:
1362        # Drop back to old, slower np.apply_along_axis mode for user-supplied
1363        # vector function
1364        function = mode
1365
1366        # Create a new padded array
1367        rank = list(range(len(narray.shape)))
1368        total_dim_increase = [np.sum(pad_width[i]) for i in rank]
1369        offset_slices = [slice(pad_width[i][0],
1370                               pad_width[i][0] + narray.shape[i])
1371                         for i in rank]
1372        new_shape = np.array(narray.shape) + total_dim_increase
1373        newmat = np.zeros(new_shape, narray.dtype)
1374
1375        # Insert the original array into the padded array
1376        newmat[offset_slices] = narray
1377
1378        # This is the core of pad ...
1379        for iaxis in rank:
1380            np.apply_along_axis(function,
1381                                iaxis,
1382                                newmat,
1383                                pad_width[iaxis],
1384                                iaxis,
1385                                kwargs)
1386        return newmat
1387
1388    # If we get here, use new padding method
1389    newmat = narray.copy()
1390
1391    # API preserved, but completely new algorithm which pads by building the
1392    # entire block to pad before/after `arr` with in one step, for each axis.
1393    if mode == 'constant':
1394        for axis, ((pad_before, pad_after), (before_val, after_val)) \
1395                in enumerate(zip(pad_width, kwargs['constant_values'])):
1396            newmat = _prepend_const(newmat, pad_before, before_val, axis)
1397            newmat = _append_const(newmat, pad_after, after_val, axis)
1398
1399    elif mode == 'edge':
1400        for axis, (pad_before, pad_after) in enumerate(pad_width):
1401            newmat = _prepend_edge(newmat, pad_before, axis)
1402            newmat = _append_edge(newmat, pad_after, axis)
1403
1404    elif mode == 'linear_ramp':
1405        for axis, ((pad_before, pad_after), (before_val, after_val)) \
1406                in enumerate(zip(pad_width, kwargs['end_values'])):
1407            newmat = _prepend_ramp(newmat, pad_before, before_val, axis)
1408            newmat = _append_ramp(newmat, pad_after, after_val, axis)
1409
1410    elif mode == 'maximum':
1411        for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \
1412                in enumerate(zip(pad_width, kwargs['stat_length'])):
1413            newmat = _prepend_max(newmat, pad_before, chunk_before, axis)
1414            newmat = _append_max(newmat, pad_after, chunk_after, axis)
1415
1416    elif mode == 'mean':
1417        for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \
1418                in enumerate(zip(pad_width, kwargs['stat_length'])):
1419            newmat = _prepend_mean(newmat, pad_before, chunk_before, axis)
1420            newmat = _append_mean(newmat, pad_after, chunk_after, axis)
1421
1422    elif mode == 'median':
1423        for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \
1424                in enumerate(zip(pad_width, kwargs['stat_length'])):
1425            newmat = _prepend_med(newmat, pad_before, chunk_before, axis)
1426            newmat = _append_med(newmat, pad_after, chunk_after, axis)
1427
1428    elif mode == 'minimum':
1429        for axis, ((pad_before, pad_after), (chunk_before, chunk_after)) \
1430                in enumerate(zip(pad_width, kwargs['stat_length'])):
1431            newmat = _prepend_min(newmat, pad_before, chunk_before, axis)
1432            newmat = _append_min(newmat, pad_after, chunk_after, axis)
1433
1434    elif mode == 'reflect':
1435        for axis, (pad_before, pad_after) in enumerate(pad_width):
1436            # Recursive padding along any axis where `pad_amt` is too large
1437            # for indexing tricks. We can only safely pad the original axis
1438            # length, to keep the period of the reflections consistent.
1439            if ((pad_before > 0) or
1440                    (pad_after > 0)) and newmat.shape[axis] == 1:
1441                # Extending singleton dimension for 'reflect' is legacy
1442                # behavior; it really should raise an error.
1443                newmat = _prepend_edge(newmat, pad_before, axis)
1444                newmat = _append_edge(newmat, pad_after, axis)
1445                continue
1446
1447            method = kwargs['reflect_type']
1448            safe_pad = newmat.shape[axis] - 1
1449            while ((pad_before > safe_pad) or (pad_after > safe_pad)):
1450                pad_iter_b = min(safe_pad,
1451                                 safe_pad * (pad_before // safe_pad))
1452                pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad))
1453                newmat = _pad_ref(newmat, (pad_iter_b,
1454                                           pad_iter_a), method, axis)
1455                pad_before -= pad_iter_b
1456                pad_after -= pad_iter_a
1457                safe_pad += pad_iter_b + pad_iter_a
1458            newmat = _pad_ref(newmat, (pad_before, pad_after), method, axis)
1459
1460    elif mode == 'symmetric':
1461        for axis, (pad_before, pad_after) in enumerate(pad_width):
1462            # Recursive padding along any axis where `pad_amt` is too large
1463            # for indexing tricks. We can only safely pad the original axis
1464            # length, to keep the period of the reflections consistent.
1465            method = kwargs['reflect_type']
1466            safe_pad = newmat.shape[axis]
1467            while ((pad_before > safe_pad) or
1468                   (pad_after > safe_pad)):
1469                pad_iter_b = min(safe_pad,
1470                                 safe_pad * (pad_before // safe_pad))
1471                pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad))
1472                newmat = _pad_sym(newmat, (pad_iter_b,
1473                                           pad_iter_a), method, axis)
1474                pad_before -= pad_iter_b
1475                pad_after -= pad_iter_a
1476                safe_pad += pad_iter_b + pad_iter_a
1477            newmat = _pad_sym(newmat, (pad_before, pad_after), method, axis)
1478
1479    elif mode == 'wrap':
1480        for axis, (pad_before, pad_after) in enumerate(pad_width):
1481            # Recursive padding along any axis where `pad_amt` is too large
1482            # for indexing tricks. We can only safely pad the original axis
1483            # length, to keep the period of the reflections consistent.
1484            safe_pad = newmat.shape[axis]
1485            while ((pad_before > safe_pad) or
1486                   (pad_after > safe_pad)):
1487                pad_iter_b = min(safe_pad,
1488                                 safe_pad * (pad_before // safe_pad))
1489                pad_iter_a = min(safe_pad, safe_pad * (pad_after // safe_pad))
1490                newmat = _pad_wrap(newmat, (pad_iter_b, pad_iter_a), axis)
1491
1492                pad_before -= pad_iter_b
1493                pad_after -= pad_iter_a
1494                safe_pad += pad_iter_b + pad_iter_a
1495            newmat = _pad_wrap(newmat, (pad_before, pad_after), axis)
1496
1497    return newmat
1498