_arraypad_impl.py 33 KB

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