Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions pywt/_dwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,16 +461,27 @@ def pad(x, pad_widths, mode):
xp = np.pad(x, pad_widths, mode='edge')
elif mode == 'smooth':
def pad_smooth(vector, pad_width, iaxis, kwargs):
# Note: indices are measured from the start of the vector so that
# a pad width of 0 gives an empty slice rather than the full one.
iright = vector.size - pad_width[1] - 1 # last unpadded sample
vsize_nonpad = iright + 1 - pad_width[0]
if vsize_nonpad < 2:
# The slope is undefined for a signal shorter than two
# samples, so use constant edge extension instead. This is
# what the transforms do as well (see the MODE_SMOOTH case in
# convolution.template.c).
if vsize_nonpad == 1:
vector[:pad_width[0]] = vector[iright]
vector[iright + 1:] = vector[iright]
return vector

# smooth extension to left
left = vector[pad_width[0]]
slope_left = (left - vector[pad_width[0] + 1])
vector[:pad_width[0]] = \
left + np.arange(pad_width[0], 0, -1) * slope_left

# smooth extension to right
# Note: indices are measured from the start of the vector so that
# a pad width of 0 gives an empty slice rather than the full one.
iright = vector.size - pad_width[1] - 1
right = vector[iright]
slope_right = (right - vector[iright - 1])
vector[iright + 1:] = \
Expand All @@ -479,10 +490,13 @@ def pad_smooth(vector, pad_width, iaxis, kwargs):
xp = np.pad(x, pad_widths, pad_smooth)
elif mode == 'antisymmetric':
def pad_antisymmetric(vector, pad_width, iaxis, kwargs):
# smooth extension to left
# implement by flipping portions symmetric padding
# implement by flipping portions of symmetric padding
npad_l, npad_r = pad_width
vsize_nonpad = vector.size - npad_l - npad_r
if vsize_nonpad == 0:
# Nothing to reflect. The reflected segments below would have
# zero width, so the loops over them would never terminate.
return vector
# Note: must modify vector in-place
vector[:] = np.pad(vector[npad_l:vector.size - npad_r],
pad_width, mode='symmetric')
Expand Down
32 changes: 32 additions & 0 deletions pywt/tests/test_dwt_idwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,19 @@ def test_pad_zero_width():
assert_array_equal(pywt.pad(x, 0, mode), x,
err_msg=f"mode={mode}, ndim={ndim}")

assert_array_equal(pywt.pad([1, 2, 3], 0, 'periodization'), [1, 2, 3, 3])


def test_pad_zero_size_axis():
# a zero-size axis has nothing to extend from, so a zero pad width leaves
# it alone rather than raising or (for 'antisymmetric') hanging (gh-589)
x = np.ones((0, 4))
for mode in pywt.Modes.modes:
assert_array_equal(pywt.pad(x, 0, mode), x, err_msg=f"mode={mode}")

# padding along the other axis only
assert_(pywt.pad(x, ((0, 0), (2, 2)), 'antisymmetric').shape == (0, 8))


def test_pad_one_sided():
# a zero pad width on only one side of the axis (gh-589)
Expand All @@ -310,6 +323,25 @@ def test_pad_one_sided():
err_msg=f"mode={mode}")


def test_pad_smooth_short_signal():
# the slope used by 'smooth' is undefined for a signal shorter than two
# samples, so constant edge extension is used instead (gh-589)
assert_array_equal(pywt.pad([1.], 0, 'smooth'), [1.])
assert_array_equal(pywt.pad([1.], (2, 3), 'smooth'),
pywt.pad([1.], (2, 3), 'constant'))

# only the length 1 axis of an nd array falls back
x = np.arange(3.0).reshape(1, 3)
assert_array_equal(pywt.pad(x, ((0, 0), (2, 2)), 'smooth'),
[[-2, -1, 0, 1, 2, 3, 4]])
assert_array_equal(pywt.pad(x, ((2, 2), (0, 0)), 'smooth'),
np.tile(x, (5, 1)))

# matches the fallback used by the transforms
assert_allclose(pywt.dwt([1.], 'db2', 'smooth'),
pywt.dwt([1.], 'db2', 'constant'))


def test_pad_errors():
# negative pad width
x = [1, 2, 3]
Expand Down
Loading