1"""

2 The arraypad module contains a group of functions to pad values onto the edges

3 of an n-dimensional array.

4

5"""

6 import numpy as np

7 from numpy.core.overrides import array_function_dispatch

8 from numpy.lib.index_tricks import ndindex

9

10

11__all__ = ['pad']

12

13

14###############################################################################

15# Private utility functions.

16

17

18 def _round_if_needed(arr, dtype):

19 """

20 Rounds arr inplace if destination dtype is integer.

21

22 Parameters

23 ----------

24 arr : ndarray

25 Input array.

26 dtype : dtype

27 The dtype of the destination array.

28 """

29 if np.issubdtype(dtype, np.integer):

30 arr.round(out=arr)

31

32

33 def _slice_at_axis(sl, axis):

34 """

35 Construct tuple of slices to slice an array in the given dimension.

36

37 Parameters

38 ----------

39 sl : slice

40 The slice for the given dimension.

41 axis : int

42 The axis to which `sl` is applied. All other dimensions are left

43 "unsliced".

44

45 Returns

46 -------

47 sl : tuple of slices

48 A tuple with slices matching `shape` in length.

49

50 Examples

51 --------

52 >>> _slice_at_axis(slice(None, 3, -1), 1)

53 (slice(None, None, None), slice(None, 3, -1), (...,))

54 """

55 return (slice(None),) * axis + (sl,) + (...,)

56

57

58 def _view_roi(array, original_area_slice, axis):

59 """

60 Get a view of the current region of interest during iterative padding.

61

62 When padding multiple dimensions iteratively corner values are

63 unnecessarily overwritten multiple times. This function reduces the

64 working area for the first dimensions so that corners are excluded.

65

66 Parameters

67 ----------

68 array : ndarray

69 The array with the region of interest.

70 original_area_slice : tuple of slices

71 Denotes the area with original values of the unpadded array.

72 axis : int

73 The currently padded dimension assuming that `axis` is padded before

74 `axis` + 1.

75

76 Returns

77 -------

78 roi : ndarray

79 The region of interest of the original `array`.

80 """

81 axis += 1

82 sl = (slice(None),) * axis + original_area_slice[axis:]

83 return array[sl]

84

85

86 def _pad_simple(array, pad_width, fill_value=None):

87 """

88 Pad array on all sides with either a single value or undefined values.

89

90 Parameters

91 ----------

92 array : ndarray

93 Array to grow.

94 pad_width : sequence of tuple[int, int]

95 Pad width on both sides for each dimension in `arr`.

96 fill_value : scalar, optional

97 If provided the padded area is filled with this value, otherwise

98 the pad area left undefined.

99

100 Returns

101 -------

102 padded : ndarray

103 The padded array with the same dtype as`array`. Its order will default

104 to C-style if `array` is not F-contiguous.

105 original_area_slice : tuple

106 A tuple of slices pointing to the area of the original array.

107 """

108 # Allocate grown array

109 new_shape = tuple(

110 left + size + right

111 for size, (left, right) in zip(array.shape, pad_width)

112 )

113 order = 'F' if array.flags.fnc else 'C' # Fortran and not also C-order

114 padded = np.empty(new_shape, dtype=array.dtype, order=order)

115

116 if fill_value is not None:

117 padded.fill(fill_value)

118

119 # Copy old array into correct space

120 original_area_slice = tuple(

121 slice(left, left + size)

122 for size, (left, right) in zip(array.shape, pad_width)

123 )

124 padded[original_area_slice] = array

125

126 return padded, original_area_slice

127

128

129 def _set_pad_area(padded, axis, width_pair, value_pair):

130 """

131 Set empty-padded area in given dimension.

132

133 Parameters

134 ----------

135 padded : ndarray

136 Array with the pad area which is modified inplace.

137 axis : int

138 Dimension with the pad area to set.

139 width_pair : (int, int)

140 Pair of widths that mark the pad area on both sides in the given

141 dimension.

142 value_pair : tuple of scalars or ndarrays

143 Values inserted into the pad area on each side. It must match or be

144 broadcastable to the shape of `arr`.

145 """

146 left_slice = _slice_at_axis(slice(None, width_pair[0]), axis)

147 padded[left_slice] = value_pair[0]

148

149 right_slice = _slice_at_axis(

150 slice(padded.shape[axis] - width_pair[1], None), axis)

151 padded[right_slice] = value_pair[1]

152

153

154 def _get_edges(padded, axis, width_pair):

155 """

156 Retrieve edge values from empty-padded array in given dimension.

157

158 Parameters

159 ----------

160 padded : ndarray

161 Empty-padded array.

162 axis : int

163 Dimension in which the edges are considered.

164 width_pair : (int, int)

165 Pair of widths that mark the pad area on both sides in the given

166 dimension.

167

168 Returns

169 -------

170 left_edge, right_edge : ndarray

171 Edge values of the valid area in `padded` in the given dimension. Its

172 shape will always match `padded` except for the dimension given by

173 `axis` which will have a length of 1.

174 """

175 left_index = width_pair[0]

176 left_slice = _slice_at_axis(slice(left_index, left_index + 1), axis)

177 left_edge = padded[left_slice]

178

179 right_index = padded.shape[axis] - width_pair[1]

180 right_slice = _slice_at_axis(slice(right_index - 1, right_index), axis)

181 right_edge = padded[right_slice]

182

183 return left_edge, right_edge

184

185

186 def _get_linear_ramps(padded, axis, width_pair, end_value_pair):

187 """

188 Construct linear ramps for empty-padded array in given dimension.

189

190 Parameters

191 ----------

192 padded : ndarray

193 Empty-padded array.

194 axis : int

195 Dimension in which the ramps are constructed.

196 width_pair : (int, int)

197 Pair of widths that mark the pad area on both sides in the given

198 dimension.

199 end_value_pair : (scalar, scalar)

200 End values for the linear ramps which form the edge of the fully padded

201 array. These values are included in the linear ramps.

202

203 Returns

204 -------

205 left_ramp, right_ramp : ndarray

206 Linear ramps to set on both sides of `padded`.

207 """

208 edge_pair = _get_edges(padded, axis, width_pair)

209

210 left_ramp, right_ramp = (

211 np.linspace(

212 start=end_value,

213 stop=edge.squeeze(axis), # Dimension is replaced by linspace

214 num=width,

215 endpoint=False,

216 dtype=padded.dtype,

217 axis=axis

218 )

219 for end_value, edge, width in zip(

220 end_value_pair, edge_pair, width_pair

221 )

222 )

223

224 # Reverse linear space in appropriate dimension

225 right_ramp = right_ramp[_slice_at_axis(slice(None, None, -1), axis)]

226

227 return left_ramp, right_ramp

228

229

230 def _get_stats(padded, axis, width_pair, length_pair, stat_func):

231 """

232 Calculate statistic for the empty-padded array in given dimension.

233

234 Parameters

235 ----------

236 padded : ndarray

237 Empty-padded array.

238 axis : int

239 Dimension in which the statistic is calculated.

240 width_pair : (int, int)

241 Pair of widths that mark the pad area on both sides in the given

242 dimension.

243 length_pair : 2-element sequence of None or int

244 Gives the number of values in valid area from each side that is

245 taken into account when calculating the statistic. If None the entire

246 valid area in `padded` is considered.

247 stat_func : function

248 Function to compute statistic. The expected signature is

249 ``stat_func(x: ndarray, axis: int, keepdims: bool) -> ndarray``.

250

251 Returns

252 -------

253 left_stat, right_stat : ndarray

254 Calculated statistic for both sides of `padded`.

255 """

256 # Calculate indices of the edges of the area with original values

257 left_index = width_pair[0]

258 right_index = padded.shape[axis] - width_pair[1]

259 # as well as its length

260 max_length = right_index - left_index

261

262 # Limit stat_lengths to max_length

263 left_length, right_length = length_pair

264 if left_length is None or max_length < left_length:

265 left_length = max_length

266 if right_length is None or max_length < right_length:

267 right_length = max_length

268

269 if (left_length == 0 or right_length == 0) \

270 and stat_func in {np.amax, np.amin}:

271 # amax and amin can't operate on an empty array,

272 # raise a more descriptive warning here instead of the default one

273 raise ValueError("stat_length of 0 yields no value for padding")

274

275 # Calculate statistic for the left side

276 left_slice = _slice_at_axis(

277 slice(left_index, left_index + left_length), axis)

278 left_chunk = padded[left_slice]

279 left_stat = stat_func(left_chunk, axis=axis, keepdims=True)

280 _round_if_needed(left_stat, padded.dtype)

281

282 if left_length == right_length == max_length:

283 # return early as right_stat must be identical to left_stat

284 return left_stat, left_stat

285

286 # Calculate statistic for the right side

287 right_slice = _slice_at_axis(

288 slice(right_index - right_length, right_index), axis)

289 right_chunk = padded[right_slice]

290 right_stat = stat_func(right_chunk, axis=axis, keepdims=True)

291 _round_if_needed(right_stat, padded.dtype)

292

293 return left_stat, right_stat

294

295

296 def _set_reflect_both(padded, axis, width_pair, method, include_edge=False):

297 """

298 Pad `axis` of `arr` with reflection.

299

300 Parameters

301 ----------

302 padded : ndarray

303 Input array of arbitrary shape.

304 axis : int

305 Axis along which to pad `arr`.

306 width_pair : (int, int)

307 Pair of widths that mark the pad area on both sides in the given

308 dimension.

309 method : str

310 Controls method of reflection; options are 'even' or 'odd'.

311 include_edge : bool

312 If true, edge value is included in reflection, otherwise the edge

313 value forms the symmetric axis to the reflection.

314

315 Returns

316 -------

317 pad_amt : tuple of ints, length 2

318 New index positions of padding to do along the `axis`. If these are

319 both 0, padding is done in this dimension.

320 """

321 left_pad, right_pad = width_pair

322 old_length = padded.shape[axis] - right_pad - left_pad

323

324 if include_edge:

325 # Edge is included, we need to offset the pad amount by 1

326 edge_offset = 1

327 else:

328 edge_offset = 0 # Edge is not included, no need to offset pad amount

329 old_length -= 1 # but must be omitted from the chunk

330

331 if left_pad > 0:

332 # Pad with reflected values on left side:

333 # First limit chunk size which can't be larger than pad area

334 chunk_length = min(old_length, left_pad)

335 # Slice right to left, stop on or next to edge, start relative to stop

336 stop = left_pad - edge_offset

337 start = stop + chunk_length

338 left_slice = _slice_at_axis(slice(start, stop, -1), axis)

339 left_chunk = padded[left_slice]

340

341 if method == "odd":

342 # Negate chunk and align with edge

343 edge_slice = _slice_at_axis(slice(left_pad, left_pad + 1), axis)

344 left_chunk = 2 * padded[edge_slice] - left_chunk

345

346 # Insert chunk into padded area

347 start = left_pad - chunk_length

348 stop = left_pad

349 pad_area = _slice_at_axis(slice(start, stop), axis)

350 padded[pad_area] = left_chunk

351 # Adjust pointer to left edge for next iteration

352 left_pad -= chunk_length

353

354 if right_pad > 0:

355 # Pad with reflected values on right side:

356 # First limit chunk size which can't be larger than pad area

357 chunk_length = min(old_length, right_pad)

358 # Slice right to left, start on or next to edge, stop relative to start

359 start = -right_pad + edge_offset - 2

360 stop = start - chunk_length

361 right_slice = _slice_at_axis(slice(start, stop, -1), axis)

362 right_chunk = padded[right_slice]

363

364 if method == "odd":

365 # Negate chunk and align with edge

366 edge_slice = _slice_at_axis(

367 slice(-right_pad - 1, -right_pad), axis)

368 right_chunk = 2 * padded[edge_slice] - right_chunk

369

370 # Insert chunk into padded area

371 start = padded.shape[axis] - right_pad

372 stop = start + chunk_length

373 pad_area = _slice_at_axis(slice(start, stop), axis)

374 padded[pad_area] = right_chunk

375 # Adjust pointer to right edge for next iteration

376 right_pad -= chunk_length

377

378 return left_pad, right_pad

379

380

381 def _set_wrap_both(padded, axis, width_pair):

382 """

383 Pad `axis` of `arr` with wrapped values.

384

385 Parameters

386 ----------

387 padded : ndarray

388 Input array of arbitrary shape.

389 axis : int

390 Axis along which to pad `arr`.

391 width_pair : (int, int)

392 Pair of widths that mark the pad area on both sides in the given

393 dimension.

394

395 Returns

396 -------

397 pad_amt : tuple of ints, length 2

398 New index positions of padding to do along the `axis`. If these are

399 both 0, padding is done in this dimension.

400 """

401 left_pad, right_pad = width_pair

402 period = padded.shape[axis] - right_pad - left_pad

403

404 # If the current dimension of `arr` doesn't contain enough valid values

405 # (not part of the undefined pad area) we need to pad multiple times.

406 # Each time the pad area shrinks on both sides which is communicated with

407 # these variables.

408 new_left_pad = 0

409 new_right_pad = 0

410

411 if left_pad > 0:

412 # Pad with wrapped values on left side

413 # First slice chunk from right side of the non-pad area.

414 # Use min(period, left_pad) to ensure that chunk is not larger than

415 # pad area

416 right_slice = _slice_at_axis(

417 slice(-right_pad - min(period, left_pad),

418 -right_pad if right_pad != 0 else None),

419 axis

420 )

421 right_chunk = padded[right_slice]

422

423 if left_pad > period:

424 # Chunk is smaller than pad area

425 pad_area = _slice_at_axis(slice(left_pad - period, left_pad), axis)

426 new_left_pad = left_pad - period

427 else:

428 # Chunk matches pad area

429 pad_area = _slice_at_axis(slice(None, left_pad), axis)

430 padded[pad_area] = right_chunk

431

432 if right_pad > 0:

433 # Pad with wrapped values on right side

434 # First slice chunk from left side of the non-pad area.

435 # Use min(period, right_pad) to ensure that chunk is not larger than

436 # pad area

437 left_slice = _slice_at_axis(

438 slice(left_pad, left_pad + min(period, right_pad),), axis)

439 left_chunk = padded[left_slice]

440

441 if right_pad > period:

442 # Chunk is smaller than pad area

443 pad_area = _slice_at_axis(

444 slice(-right_pad, -right_pad + period), axis)

445 new_right_pad = right_pad - period

446 else:

447 # Chunk matches pad area

448 pad_area = _slice_at_axis(slice(-right_pad, None), axis)

449 padded[pad_area] = left_chunk

450

451 return new_left_pad, new_right_pad

452

453

454 def _as_pairs(x, ndim, as_index=False):

455 """

456 Broadcast `x` to an array with the shape (`ndim`, 2).

457

458 A helper function for `pad` that prepares and validates arguments like

459 `pad_width` for iteration in pairs.

460

461 Parameters

462 ----------

463 x : {None, scalar, array-like}

464 The object to broadcast to the shape (`ndim`, 2).

465 ndim : int

466 Number of pairs the broadcasted `x` will have.

467 as_index : bool, optional

468 If `x` is not None, try to round each element of `x` to an integer

469 (dtype `np.intp`) and ensure every element is positive.

470

471 Returns

472 -------

473 pairs : nested iterables, shape (`ndim`, 2)

474 The broadcasted version of `x`.

475

476 Raises

477 ------

478 ValueError

479 If `as_index` is True and `x` contains negative elements.

480 Or if `x` is not broadcastable to the shape (`ndim`, 2).

481 """

482 if x is None:

483 # Pass through None as a special case, otherwise np.round(x) fails

484 # with an AttributeError

485 return ((None, None),) * ndim

486

487 x = np.array(x)

488 if as_index:

489 x = np.round(x).astype(np.intp, copy=False)

490

491 if x.ndim < 3:

492 # Optimization: Possibly use faster paths for cases where `x` has

493 # only 1 or 2 elements. `np.broadcast_to` could handle these as well

494 # but is currently slower

495

496 if x.size == 1:

497 # x was supplied as a single value

498 x = x.ravel() # Ensure x[0] works for x.ndim == 0, 1, 2

499 if as_index and x < 0:

500 raise ValueError("index can't contain negative values")

501 return ((x[0], x[0]),) * ndim

502

503 if x.size == 2 and x.shape != (2, 1):

504 # x was supplied with a single value for each side

505 # but except case when each dimension has a single value

506 # which should be broadcasted to a pair,

507 # e.g. [[1], [2]] -> [[1, 1], [2, 2]] not [[1, 2], [1, 2]]

508 x = x.ravel() # Ensure x[0], x[1] works

509 if as_index and (x[0] < 0 or x[1] < 0):

510 raise ValueError("index can't contain negative values")

511 return ((x[0], x[1]),) * ndim

512

513 if as_index and x.min() < 0:

514 raise ValueError("index can't contain negative values")

515

516 # Converting the array with `tolist` seems to improve performance

517 # when iterating and indexing the result (see usage in `pad`)

518 return np.broadcast_to(x, (ndim, 2)).tolist()

519

520

521 def _pad_dispatcher(array, pad_width, mode=None, **kwargs):

522 return (array,)

523

524

525###############################################################################

526# Public functions

527

528

529@array_function_dispatch(_pad_dispatcher, module='numpy')

530 def pad(array, pad_width, mode='constant', **kwargs):

531 """

532 Pad an array.

533

534 Parameters

535 ----------

536 array : array_like of rank N

537 The array to pad.

538 pad_width : {sequence, array_like, int}

539 Number of values padded to the edges of each axis.

540 ``((before_1, after_1), ... (before_N, after_N))`` unique pad widths

541 for each axis.

542 ``(before, after)`` or ``((before, after),)`` yields same before

543 and after pad for each axis.

544 ``(pad,)`` or ``int`` is a shortcut for before = after = pad width

545 for all axes.

546 mode : str or function, optional

547 One of the following string values or a user supplied function.

548

549 'constant' (default)

550 Pads with a constant value.

551 'edge'

552 Pads with the edge values of array.

553 'linear_ramp'

554 Pads with the linear ramp between end_value and the

555 array edge value.

556 'maximum'

557 Pads with the maximum value of all or part of the

558 vector along each axis.

559 'mean'

560 Pads with the mean value of all or part of the

561 vector along each axis.

562 'median'

563 Pads with the median value of all or part of the

564 vector along each axis.

565 'minimum'

566 Pads with the minimum value of all or part of the

567 vector along each axis.

568 'reflect'

569 Pads with the reflection of the vector mirrored on

570 the first and last values of the vector along each

571 axis.

572 'symmetric'

573 Pads with the reflection of the vector mirrored

574 along the edge of the array.

575 'wrap'

576 Pads with the wrap of the vector along the axis.

577 The first values are used to pad the end and the

578 end values are used to pad the beginning.

579 'empty'

580 Pads with undefined values.

581

582 .. versionadded:: 1.17

583

584 <function>

585 Padding function, see Notes.

586 stat_length : sequence or int, optional

587 Used in 'maximum', 'mean', 'median', and 'minimum'. Number of

588 values at edge of each axis used to calculate the statistic value.

589

590 ``((before_1, after_1), ... (before_N, after_N))`` unique statistic

591 lengths for each axis.

592

593 ``(before, after)`` or ``((before, after),)`` yields same before

594 and after statistic lengths for each axis.

595

596 ``(stat_length,)`` or ``int`` is a shortcut for

597 ``before = after = statistic`` length for all axes.

598

599 Default is ``None``, to use the entire axis.

600 constant_values : sequence or scalar, optional

601 Used in 'constant'. The values to set the padded values for each

602 axis.

603

604 ``((before_1, after_1), ... (before_N, after_N))`` unique pad constants

605 for each axis.

606

607 ``(before, after)`` or ``((before, after),)`` yields same before

608 and after constants for each axis.

609

610 ``(constant,)`` or ``constant`` is a shortcut for

611 ``before = after = constant`` for all axes.

612

613 Default is 0.

614 end_values : sequence or scalar, optional

615 Used in 'linear_ramp'. The values used for the ending value of the

616 linear_ramp and that will form the edge of the padded array.

617

618 ``((before_1, after_1), ... (before_N, after_N))`` unique end values

619 for each axis.

620

621 ``(before, after)`` or ``((before, after),)`` yields same before

622 and after end values for each axis.

623

624 ``(constant,)`` or ``constant`` is a shortcut for

625 ``before = after = constant`` for all axes.

626

627 Default is 0.

628 reflect_type : {'even', 'odd'}, optional

629 Used in 'reflect', and 'symmetric'. The 'even' style is the

630 default with an unaltered reflection around the edge value. For

631 the 'odd' style, the extended part of the array is created by

632 subtracting the reflected values from two times the edge value.

633

634 Returns

635 -------

636 pad : ndarray

637 Padded array of rank equal to `array` with shape increased

638 according to `pad_width`.

639

640 Notes

641 -----

642 .. versionadded:: 1.7.0

643

644 For an array with rank greater than 1, some of the padding of later

645 axes is calculated from padding of previous axes. This is easiest to

646 think about with a rank 2 array where the corners of the padded array

647 are calculated by using padded values from the first axis.

648

649 The padding function, if used, should modify a rank 1 array in-place. It

650 has the following signature::

651

652 padding_func(vector, iaxis_pad_width, iaxis, kwargs)

653

654 where

655

656 vector : ndarray

657 A rank 1 array already padded with zeros. Padded values are

658 vector[:iaxis_pad_width[0]] and vector[-iaxis_pad_width[1]:].

659 iaxis_pad_width : tuple

660 A 2-tuple of ints, iaxis_pad_width[0] represents the number of

661 values padded at the beginning of vector where

662 iaxis_pad_width[1] represents the number of values padded at

663 the end of vector.

664 iaxis : int

665 The axis currently being calculated.

666 kwargs : dict

667 Any keyword arguments the function requires.

668

669 Examples

670 --------

671 >>> a = [1, 2, 3, 4, 5]

672 >>> np.pad(a, (2, 3), 'constant', constant_values=(4, 6))

673 array([4, 4, 1, ..., 6, 6, 6])

674

675 >>> np.pad(a, (2, 3), 'edge')

676 array([1, 1, 1, ..., 5, 5, 5])

677

678 >>> np.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4))

679 array([ 5, 3, 1, 2, 3, 4, 5, 2, -1, -4])

680

681 >>> np.pad(a, (2,), 'maximum')

682 array([5, 5, 1, 2, 3, 4, 5, 5, 5])

683

684 >>> np.pad(a, (2,), 'mean')

685 array([3, 3, 1, 2, 3, 4, 5, 3, 3])

686

687 >>> np.pad(a, (2,), 'median')

688 array([3, 3, 1, 2, 3, 4, 5, 3, 3])

689

690 >>> a = [[1, 2], [3, 4]]

691 >>> np.pad(a, ((3, 2), (2, 3)), 'minimum')

692 array([[1, 1, 1, 2, 1, 1, 1],

693 [1, 1, 1, 2, 1, 1, 1],

694 [1, 1, 1, 2, 1, 1, 1],

695 [1, 1, 1, 2, 1, 1, 1],

696 [3, 3, 3, 4, 3, 3, 3],

697 [1, 1, 1, 2, 1, 1, 1],

698 [1, 1, 1, 2, 1, 1, 1]])

699

700 >>> a = [1, 2, 3, 4, 5]

701 >>> np.pad(a, (2, 3), 'reflect')

702 array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2])

703

704 >>> np.pad(a, (2, 3), 'reflect', reflect_type='odd')

705 array([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8])

706

707 >>> np.pad(a, (2, 3), 'symmetric')

708 array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3])

709

710 >>> np.pad(a, (2, 3), 'symmetric', reflect_type='odd')

711 array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7])

712

713 >>> np.pad(a, (2, 3), 'wrap')

714 array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3])

715

716 >>> def pad_with(vector, pad_width, iaxis, kwargs):

717 ... pad_value = kwargs.get('padder', 10)

718 ... vector[:pad_width[0]] = pad_value

719 ... vector[-pad_width[1]:] = pad_value

720 >>> a = np.arange(6)

721 >>> a = a.reshape((2, 3))

722 >>> np.pad(a, 2, pad_with)

723 array([[10, 10, 10, 10, 10, 10, 10],

724 [10, 10, 10, 10, 10, 10, 10],

725 [10, 10, 0, 1, 2, 10, 10],

726 [10, 10, 3, 4, 5, 10, 10],

727 [10, 10, 10, 10, 10, 10, 10],

728 [10, 10, 10, 10, 10, 10, 10]])

729 >>> np.pad(a, 2, pad_with, padder=100)

730 array([[100, 100, 100, 100, 100, 100, 100],

731 [100, 100, 100, 100, 100, 100, 100],

732 [100, 100, 0, 1, 2, 100, 100],

733 [100, 100, 3, 4, 5, 100, 100],

734 [100, 100, 100, 100, 100, 100, 100],

735 [100, 100, 100, 100, 100, 100, 100]])

736 """

737 array = np.asarray(array)

738 pad_width = np.asarray(pad_width)

739

740 if not pad_width.dtype.kind == 'i':

741 raise TypeError('`pad_width` must be of integral type.')

742

743 # Broadcast to shape (array.ndim, 2)

744 pad_width = _as_pairs(pad_width, array.ndim, as_index=True)

745

746 if callable(mode):

747 # Old behavior: Use user-supplied function with np.apply_along_axis

748 function = mode

749 # Create a new zero padded array

750 padded, _ = _pad_simple(array, pad_width, fill_value=0)

751 # And apply along each axis

752

753 for axis in range(padded.ndim):

754 # Iterate using ndindex as in apply_along_axis, but assuming that

755 # function operates inplace on the padded array.

756

757 # view with the iteration axis at the end

758 view = np.moveaxis(padded, axis, -1)

759

760 # compute indices for the iteration axes, and append a trailing

761 # ellipsis to prevent 0d arrays decaying to scalars (gh-8642)

762 inds = ndindex(view.shape[:-1])

763 inds = (ind + (Ellipsis,) for ind in inds)

764 for ind in inds:

765 function(view[ind], pad_width[axis], axis, kwargs)

766

767 return padded

768

769 # Make sure that no unsupported keywords were passed for the current mode

770 allowed_kwargs = {

771 'empty': [], 'edge': [], 'wrap': [],

772 'constant': ['constant_values'],

773 'linear_ramp': ['end_values'],

774 'maximum': ['stat_length'],

775 'mean': ['stat_length'],

776 'median': ['stat_length'],

777 'minimum': ['stat_length'],

778 'reflect': ['reflect_type'],

779 'symmetric': ['reflect_type'],

780 }

781 try:

782 unsupported_kwargs = set(kwargs) - set(allowed_kwargs[mode])

783 except KeyError:

784 raise ValueError("mode '{}' is not supported".format(mode)) from None

785 if unsupported_kwargs:

786 raise ValueError("unsupported keyword arguments for mode '{}': {}"

787 .format(mode, unsupported_kwargs))

788

789 stat_functions = {"maximum": np.amax, "minimum": np.amin,

790 "mean": np.mean, "median": np.median}

791

792 # Create array with final shape and original values

793 # (padded area is undefined)

794 padded, original_area_slice = _pad_simple(array, pad_width)

795 # And prepare iteration over all dimensions

796 # (zipping may be more readable than using enumerate)

797 axes = range(padded.ndim)

798

799 if mode == "constant":

800 values = kwargs.get("constant_values", 0)

801 values = _as_pairs(values, padded.ndim)

802 for axis, width_pair, value_pair in zip(axes, pad_width, values):

803 roi = _view_roi(padded, original_area_slice, axis)

804 _set_pad_area(roi, axis, width_pair, value_pair)

805

806 elif mode == "empty":

807 pass # Do nothing as _pad_simple already returned the correct result

808

809 elif array.size == 0:

810 # Only modes "constant" and "empty" can extend empty axes, all other

811 # modes depend on `array` not being empty

812 # -> ensure every empty axis is only "padded with 0"

813 for axis, width_pair in zip(axes, pad_width):

814 if array.shape[axis] == 0 and any(width_pair):

815 raise ValueError(

816 "can't extend empty axis {} using modes other than "

817 "'constant' or 'empty'".format(axis)

818 )

819 # passed, don't need to do anything more as _pad_simple already

820 # returned the correct result

821

822 elif mode == "edge":

823 for axis, width_pair in zip(axes, pad_width):

824 roi = _view_roi(padded, original_area_slice, axis)

825 edge_pair = _get_edges(roi, axis, width_pair)

826 _set_pad_area(roi, axis, width_pair, edge_pair)

827

828 elif mode == "linear_ramp":

829 end_values = kwargs.get("end_values", 0)

830 end_values = _as_pairs(end_values, padded.ndim)

831 for axis, width_pair, value_pair in zip(axes, pad_width, end_values):

832 roi = _view_roi(padded, original_area_slice, axis)

833 ramp_pair = _get_linear_ramps(roi, axis, width_pair, value_pair)

834 _set_pad_area(roi, axis, width_pair, ramp_pair)

835

836 elif mode in stat_functions:

837 func = stat_functions[mode]

838 length = kwargs.get("stat_length", None)

839 length = _as_pairs(length, padded.ndim, as_index=True)

840 for axis, width_pair, length_pair in zip(axes, pad_width, length):

841 roi = _view_roi(padded, original_area_slice, axis)

842 stat_pair = _get_stats(roi, axis, width_pair, length_pair, func)

843 _set_pad_area(roi, axis, width_pair, stat_pair)

844

845 elif mode in {"reflect", "symmetric"}:

846 method = kwargs.get("reflect_type", "even")

847 include_edge = True if mode == "symmetric" else False

848 for axis, (left_index, right_index) in zip(axes, pad_width):

849 if array.shape[axis] == 1 and (left_index > 0 or right_index > 0):

850 # Extending singleton dimension for 'reflect' is legacy

851 # behavior; it really should raise an error.

852 edge_pair = _get_edges(padded, axis, (left_index, right_index))

853 _set_pad_area(

854 padded, axis, (left_index, right_index), edge_pair)

855 continue

856

857 roi = _view_roi(padded, original_area_slice, axis)

858 while left_index > 0 or right_index > 0:

859 # Iteratively pad until dimension is filled with reflected

860 # values. This is necessary if the pad area is larger than

861 # the length of the original values in the current dimension.

862 left_index, right_index = _set_reflect_both(

863 roi, axis, (left_index, right_index),

864 method, include_edge

865 )

866

867 elif mode == "wrap":

868 for axis, (left_index, right_index) in zip(axes, pad_width):

869 roi = _view_roi(padded, original_area_slice, axis)

870 while left_index > 0 or right_index > 0:

871 # Iteratively pad until dimension is filled with wrapped

872 # values. This is necessary if the pad area is larger than

873 # the length of the original values in the current dimension.

874 left_index, right_index = _set_wrap_both(

875 roi, axis, (left_index, right_index))

876

877 return padded