Skip to content

Commit 1ff0132

Browse files
committed
add n-dimensional support to pad
correct boundary mode name: zeros->zero
1 parent 3cb5436 commit 1ff0132

File tree

3 files changed

+72
-39
lines changed

3 files changed

+72
-39
lines changed

doc/source/pyplots/plot_boundary_modes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
In practice, which signal extension mode is beneficial will depend on the
77
signal characteristics. For this particular signal, some modes such as
8-
"periodic", "antisymmetric" and "zeros" result in large discontinuities that
8+
"periodic", "antisymmetric" and "zero" result in large discontinuities that
99
would lead to large amplitude boundary coefficients in the detail coefficients
1010
of a discrete wavelet transform.
1111
"""
@@ -28,5 +28,5 @@
2828
boundary_mode_subplot(x, 'periodization', axes[5], symw=False)
2929
boundary_mode_subplot(x, 'smooth', axes[6], symw=False)
3030
boundary_mode_subplot(x, 'constant', axes[7], symw=False)
31-
boundary_mode_subplot(x, 'zeros', axes[8], symw=False)
31+
boundary_mode_subplot(x, 'zero', axes[8], symw=False)
3232
plt.show()

pywt/_doc_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def boundary_mode_subplot(x, mode, ax, symw=True):
181181
left -= 0.5
182182
step = len(x)
183183
rng = range(-2, 4)
184-
if mode in ['smooth', 'constant', 'zeros']:
184+
if mode in ['smooth', 'constant', 'zero']:
185185
rng = range(0, 2)
186186
for rep in rng:
187187
ax.plot((left + rep * step) * o2, [xp.min() - .5, xp.max() + .5], 'k-')

pywt/_dwt.py

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ def upcoef(part, coeffs, wavelet, level=1, take=0):
406406
def pad(x, pad_widths, mode):
407407
"""Extend a 1D signal using a given boundary mode.
408408
409-
This is like `numpy.pad` but supports all PyWavelets boundary modes.
409+
This function operates like `numpy.pad` but supports all signal extension
410+
modes that can be used by PyWavelets discrete wavelet transforms.
410411
411412
Parameters
412413
----------
@@ -427,54 +428,86 @@ def pad(x, pad_widths, mode):
427428
Padded array of rank equal to array with shape increased according to
428429
`pad_width`.
429430
431+
Notes
432+
-----
433+
The performance of padding in dimensions > 1 will be substantially slower
434+
for modes `smooth` and `antisymmetric` as these modes are not supported in
435+
an efficient manner by the underlying `numpy.pad` function.
430436
"""
431437
if np.isscalar(pad_widths):
432-
pad_widths = (pad_widths, pad_widths)
433-
434-
if x.ndim > 1:
435-
raise ValueError("This padding function is only for 1D signals.")
438+
pad_widths = (pad_widths, )
439+
if len(pad_widths) == 1:
440+
pad_widths = (pad_widths[0], ) * x.ndim
441+
pad_widths = [(p, p) if np.isscalar(p) else p for p in pad_widths]
436442

437443
if mode in ['symmetric', 'reflect']:
438444
xp = np.pad(x, pad_widths, mode=mode)
439445
elif mode in ['periodic', 'periodization']:
440-
if mode == 'periodization' and x.size % 2 == 1:
441-
raise ValueError("periodization expects an even length signal.")
446+
if mode == 'periodization':
447+
# Promote odd-sized dimensions to even length by duplicating the
448+
# last value.
449+
edge_pad_widths = [(0, x.shape[ax] % 2)
450+
for ax in range(x.ndim)]
451+
x = np.pad(x, edge_pad_widths, mode='edge')
442452
xp = np.pad(x, pad_widths, mode='wrap')
443-
elif mode == 'zeros':
453+
elif mode == 'zero':
444454
xp = np.pad(x, pad_widths, mode='constant', constant_values=0)
445455
elif mode == 'constant':
446456
xp = np.pad(x, pad_widths, mode='edge')
447457
elif mode == 'smooth':
448-
xp = np.pad(x, pad_widths, mode='linear_ramp',
449-
end_values=(x[0] + pad_widths[0] * (x[0] - x[1]),
450-
x[-1] + pad_widths[1] * (x[-1] - x[-2])))
458+
def pad_smooth(vector, pad_width, iaxis, kwargs):
459+
# smooth extension to left
460+
left = vector[pad_width[0]]
461+
slope_left = (left - vector[pad_width[0] + 1])
462+
vector[:pad_width[0]] = \
463+
left + np.arange(pad_width[0], 0, -1) * slope_left
464+
465+
# smooth extension to right
466+
right = vector[-pad_width[1] - 1]
467+
slope_right = (right - vector[-pad_width[1] - 2])
468+
vector[-pad_width[1]:] = \
469+
right + np.arange(1, pad_width[1] + 1) * slope_right
470+
return vector
471+
xp = np.pad(x, pad_widths, pad_smooth)
451472
elif mode == 'antisymmetric':
452-
# implement by flipping portions symmetric padding
453-
npad_l, npad_r = pad_widths
454-
xp = np.pad(x, pad_widths, mode='symmetric')
455-
r_edge = npad_l + x.size - 1
456-
l_edge = npad_l
457-
# width of each reflected segment
458-
seg_width = x.size
459-
# flip reflected segments on the right of the original signal
460-
n = 1
461-
while r_edge <= xp.size:
462-
segment_slice = slice(r_edge + 1,
463-
min(r_edge + 1 + seg_width, xp.size))
464-
if n % 2:
465-
xp[segment_slice] *= -1
466-
r_edge += seg_width
467-
n += 1
468-
469-
# flip reflected segments on the left of the original signal
470-
n = 1
471-
while l_edge >= 0:
472-
segment_slice = slice(max(0, l_edge - seg_width), l_edge)
473-
if n % 2:
474-
xp[segment_slice] *= -1
475-
l_edge -= seg_width
476-
n += 1
473+
def pad_antisymmetric(vector, pad_width, iaxis, kwargs):
474+
# smooth extension to left
475+
# implement by flipping portions symmetric padding
476+
npad_l, npad_r = pad_width
477+
vsize_nonpad = vector.size - npad_l - npad_r
478+
# Note: must modify vector in-place
479+
vector[:] = np.pad(vector[pad_width[0]:-pad_width[-1]],
480+
pad_width, mode='symmetric')
481+
vp = vector
482+
r_edge = npad_l + vsize_nonpad - 1
483+
l_edge = npad_l
484+
# width of each reflected segment
485+
seg_width = vsize_nonpad
486+
# flip reflected segments on the right of the original signal
487+
n = 1
488+
while r_edge <= vp.size:
489+
segment_slice = slice(r_edge + 1,
490+
min(r_edge + 1 + seg_width, vp.size))
491+
if n % 2:
492+
vp[segment_slice] *= -1
493+
r_edge += seg_width
494+
n += 1
495+
496+
# flip reflected segments on the left of the original signal
497+
n = 1
498+
while l_edge >= 0:
499+
segment_slice = slice(max(0, l_edge - seg_width), l_edge)
500+
if n % 2:
501+
vp[segment_slice] *= -1
502+
l_edge -= seg_width
503+
n += 1
504+
return vector
505+
xp = np.pad(x, pad_widths, pad_antisymmetric)
477506
elif mode == 'antireflect':
478507
npad_l, npad_r = pad_widths
479508
xp = np.pad(x, pad_widths, mode='reflect', reflect_type='odd')
509+
else:
510+
raise ValueError(
511+
("unsupported mode: {}. The supported modes are {}").format(
512+
mode, Modes.modes))
480513
return xp

0 commit comments

Comments
 (0)