Files
paddlepaddle--paddle/test/legacy_test/test_signal.py
T
2026-07-13 12:40:42 +08:00

1055 lines
34 KiB
Python

# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import sys
import unittest
import numpy as np
import scipy.signal
from numpy import fft
from numpy.lib.stride_tricks import as_strided
from op_test import get_device_place, is_custom_device
import paddle
paddle.set_default_dtype('float64')
DEVICES = [paddle.CPUPlace()]
if paddle.is_compiled_with_cuda() or is_custom_device():
DEVICES.append(get_device_place())
TEST_CASE_NAME = 'test_case'
# Constrain STFT block sizes to 256 KB
MAX_MEM_BLOCK = 2**8 * 2**10
def fix_length(data, size, axis=-1, **kwargs):
kwargs.setdefault("mode", "constant")
n = data.shape[axis]
if n > size:
slices = [slice(None)] * data.ndim
slices[axis] = slice(0, size)
return data[tuple(slices)]
elif n < size:
lengths = [(0, 0)] * data.ndim
lengths[axis] = (0, size - n)
return np.pad(data, lengths, **kwargs)
return data
def tiny(x):
# Make sure we have an array view
x = np.asarray(x)
# Only floating types generate a tiny
if np.issubdtype(x.dtype, np.floating) or np.issubdtype(
x.dtype, np.complexfloating
):
dtype = x.dtype
else:
dtype = np.float32
return np.finfo(dtype).tiny
def normalize(S, norm=np.inf, axis=0, threshold=None, fill=None):
# Avoid div-by-zero
if threshold is None:
threshold = tiny(S)
elif threshold <= 0:
raise Exception(f"threshold={threshold} must be strictly positive")
if fill not in [None, False, True]:
raise Exception(f"fill={fill} must be None or boolean")
if not np.all(np.isfinite(S)):
raise Exception("Input must be finite")
# All norms only depend on magnitude, let's do that first
mag = np.abs(S).astype(np.float64)
# For max/min norms, filling with 1 works
fill_norm = 1
if norm == np.inf:
length = np.max(mag, axis=axis, keepdims=True)
elif norm == -np.inf:
length = np.min(mag, axis=axis, keepdims=True)
elif norm == 0:
if fill is True:
raise Exception("Cannot normalize with norm=0 and fill=True")
length = np.sum(mag > 0, axis=axis, keepdims=True, dtype=mag.dtype)
elif np.issubdtype(type(norm), np.number) and norm > 0:
length = np.sum(mag**norm, axis=axis, keepdims=True) ** (1.0 / norm)
if axis is None:
fill_norm = mag.size ** (-1.0 / norm)
else:
fill_norm = mag.shape[axis] ** (-1.0 / norm)
elif norm is None:
return S
else:
raise Exception(f"Unsupported norm: {norm!r}")
# indices where norm is below the threshold
small_idx = length < threshold
Snorm = np.empty_like(S)
if fill is None:
# Leave small indices un-normalized
length[small_idx] = 1.0
Snorm[:] = S / length
elif fill:
# If we have a non-zero fill value, we locate those entries by
# doing a nan-divide.
# If S was finite, then length is finite (except for small positions)
length[small_idx] = np.nan
Snorm[:] = S / length
Snorm[np.isnan(Snorm)] = fill_norm
else:
# Set small values to zero by doing an inf-divide.
# This is safe (by IEEE-754) as long as S is finite.
length[small_idx] = np.inf
Snorm[:] = S / length
return Snorm
def __window_ss_fill(x, win_sq, n_frames, hop_length): # pragma: no cover
"""Helper function for window sum-square calculation."""
n = len(x)
n_fft = len(win_sq)
for i in range(n_frames):
sample = i * hop_length
x[sample : min(n, sample + n_fft)] += win_sq[
: max(0, min(n_fft, n - sample))
]
def window_sumsquare(
window,
n_frames,
hop_length=512,
win_length=None,
n_fft=2048,
dtype=np.float32,
norm=None,
):
if win_length is None:
win_length = n_fft
n = n_fft + hop_length * (n_frames - 1)
x = np.zeros(n, dtype=dtype)
# Compute the squared window at the desired length
win_sq = get_window(window, win_length)
win_sq = normalize(win_sq, norm=norm) ** 2
win_sq = pad_center(win_sq, n_fft)
# Fill the envelope
__window_ss_fill(x, win_sq, n_frames, hop_length)
return x
def dtype_c2r(d, default=np.float32):
mapping = {
np.dtype(np.complex64): np.float32,
np.dtype(np.complex128): np.float64,
}
# If we're given a real type already, return it
dt = np.dtype(d)
if dt.kind == "f":
return dt
# Otherwise, try to map the dtype.
# If no match is found, return the default.
return np.dtype(mapping.get(np.dtype(d), default))
def dtype_r2c(d, default=np.complex64):
mapping = {
np.dtype(np.float32): np.complex64,
np.dtype(np.float64): np.complex128,
}
# If we're given a complex type already, return it
dt = np.dtype(d)
if dt.kind == "c":
return dt
# Otherwise, try to map the dtype.
# If no match is found, return the default.
return np.dtype(mapping.get(dt, default))
def frame(x, frame_length, hop_length, axis=-1):
if not isinstance(x, np.ndarray):
raise Exception(
f"Input must be of type numpy.ndarray, given type(x)={type(x)}"
)
if x.shape[axis] < frame_length:
raise Exception(
f"Input is too short (n={x.shape[axis]:d})"
f" for frame_length={frame_length:d}"
)
if hop_length < 1:
raise Exception(f"Invalid hop_length: {hop_length:d}")
if axis == -1 and not x.flags["F_CONTIGUOUS"]:
print(
f"librosa.util.frame called with axis={axis} "
"on a non-contiguous input. This will result in a copy."
)
x = np.asfortranarray(x)
elif axis == 0 and not x.flags["C_CONTIGUOUS"]:
print(
f"librosa.util.frame called with axis={axis} "
"on a non-contiguous input. This will result in a copy."
)
x = np.ascontiguousarray(x)
n_frames = 1 + (x.shape[axis] - frame_length) // hop_length
strides = np.asarray(x.strides)
new_stride = np.prod(strides[strides > 0] // x.itemsize) * x.itemsize
if axis == -1:
shape = [*list(x.shape)[:-1], frame_length, n_frames]
strides = [*list(strides), hop_length * new_stride]
elif axis == 0:
shape = [n_frames, frame_length, *list(x.shape)[1:]]
strides = [hop_length * new_stride, *list(strides)]
else:
raise Exception(f"Frame axis={axis} must be either 0 or -1")
return as_strided(x, shape=shape, strides=strides)
def pad_center(data, size, axis=-1, **kwargs):
kwargs.setdefault("mode", "constant")
n = data.shape[axis]
lpad = int((size - n) // 2)
lengths = [(0, 0)] * data.ndim
lengths[axis] = (lpad, int(size - n - lpad))
if lpad < 0:
raise Exception(
f"Target size ({size:d}) must be at least input size ({n:d})"
)
return np.pad(data, lengths, **kwargs)
def get_window(window, Nx, fftbins=True):
if callable(window):
return window(Nx)
elif isinstance(window, (str, tuple)) or np.isscalar(window):
# TODO: if we add custom window functions in librosa, call them here
return scipy.signal.get_window(window, Nx, fftbins=fftbins)
elif isinstance(window, (np.ndarray, list)):
if len(window) == Nx:
return np.asarray(window)
raise Exception(f"Window size mismatch: {len(window):d} != {Nx:d}")
else:
raise Exception(f"Invalid window specification: {window}")
def __overlap_add(y, ytmp, hop_length):
# numba-accelerated overlap add for inverse stft
# y is the pre-allocated output buffer
# ytmp is the windowed inverse-stft frames
# hop_length is the hop-length of the STFT analysis
n_fft = ytmp.shape[0]
for frame in range(ytmp.shape[1]):
sample = frame * hop_length
y[sample : (sample + n_fft)] += ytmp[:, frame]
def stft(
x,
n_fft=2048,
hop_length=None,
win_length=None,
window="hann",
center=True,
pad_mode="reflect",
):
y = x
input_rank = len(y.shape)
if input_rank == 2:
assert y.shape[0] == 1 # Only 1d input supported in librosa
y = y.squeeze(0)
dtype = None
# By default, use the entire frame
if win_length is None:
win_length = n_fft
# Set the default hop, if it's not already specified
if hop_length is None:
hop_length = int(win_length // 4)
fft_window = get_window(window, win_length, fftbins=True)
# Pad the window out to n_fft size
fft_window = pad_center(fft_window, n_fft)
# Reshape so that the window can be broadcast
fft_window = fft_window.reshape((-1, 1))
# Pad the time series so that frames are centered
if center:
if n_fft > y.shape[-1]:
print(
f"n_fft={n_fft} is too small for input signal of length={y.shape[-1]}"
)
y = np.pad(y, int(n_fft // 2), mode=pad_mode)
elif n_fft > y.shape[-1]:
raise Exception(
f"n_fft={n_fft} is too large for input signal of length={y.shape[-1]}"
)
# Window the time series.
y_frames = frame(y, frame_length=n_fft, hop_length=hop_length)
if dtype is None:
dtype = dtype_r2c(y.dtype)
# Pre-allocate the STFT matrix
stft_matrix = np.empty(
(int(1 + n_fft // 2), y_frames.shape[1]), dtype=dtype, order="F"
)
# how many columns can we fit within MAX_MEM_BLOCK?
n_columns = MAX_MEM_BLOCK // (stft_matrix.shape[0] * stft_matrix.itemsize)
n_columns = max(n_columns, 1)
for bl_s in range(0, stft_matrix.shape[1], n_columns):
bl_t = min(bl_s + n_columns, stft_matrix.shape[1])
stft_matrix[:, bl_s:bl_t] = fft.rfft(
fft_window * y_frames[:, bl_s:bl_t], axis=0
)
if input_rank == 2:
stft_matrix = np.expand_dims(stft_matrix, 0)
return stft_matrix
def istft(
x,
hop_length=None,
win_length=None,
window="hann",
center=True,
length=None,
):
stft_matrix = x
input_rank = len(stft_matrix.shape)
if input_rank == 3:
assert stft_matrix.shape[0] == 1 # Only 2d input supported in librosa
stft_matrix = stft_matrix.squeeze(0)
dtype = None
n_fft = 2 * (stft_matrix.shape[0] - 1)
# By default, use the entire frame
if win_length is None:
win_length = n_fft
# Set the default hop, if it's not already specified
if hop_length is None:
hop_length = int(win_length // 4)
ifft_window = get_window(window, win_length, fftbins=True)
# Pad out to match n_fft, and add a broadcasting axis
ifft_window = pad_center(ifft_window, n_fft)[:, np.newaxis]
# For efficiency, trim STFT frames according to signal length if available
if length:
if center:
padded_length = length + int(n_fft)
else:
padded_length = length
n_frames = min(
stft_matrix.shape[1], int(np.ceil(padded_length / hop_length))
)
else:
n_frames = stft_matrix.shape[1]
expected_signal_len = n_fft + hop_length * (n_frames - 1)
if dtype is None:
dtype = dtype_c2r(stft_matrix.dtype)
y = np.zeros(expected_signal_len, dtype=dtype)
n_columns = MAX_MEM_BLOCK // (stft_matrix.shape[0] * stft_matrix.itemsize)
n_columns = min(n_columns, 1)
frame = 0
for bl_s in range(0, n_frames, n_columns):
bl_t = min(bl_s + n_columns, n_frames)
# invert the block and apply the window function
ytmp = ifft_window * fft.irfft(stft_matrix[:, bl_s:bl_t], axis=0)
# Overlap-add the istft block starting at the i'th frame
__overlap_add(y[frame * hop_length :], ytmp, hop_length)
frame += bl_t - bl_s
# Normalize by sum of squared window
ifft_window_sum = window_sumsquare(
window,
n_frames,
win_length=win_length,
n_fft=n_fft,
hop_length=hop_length,
dtype=dtype,
)
approx_nonzero_indices = ifft_window_sum > tiny(ifft_window_sum)
y[approx_nonzero_indices] /= ifft_window_sum[approx_nonzero_indices]
if length is None:
# If we don't need to control length, just do the usual center trimming
# to eliminate padded data
if center:
y = y[int(n_fft // 2) : -int(n_fft // 2)]
else:
if center:
# If we're centering, crop off the first n_fft//2 samples
# and then trim/pad to the target length.
# We don't trim the end here, so that if the signal is zero-padded
# to a longer duration, the decay is smooth by windowing
start = int(n_fft // 2)
else:
# If we're not centering, start at 0 and trim/pad as necessary
start = 0
y = fix_length(y[start:], length)
if input_rank == 3:
y = np.expand_dims(y, 0)
return y
def frame_for_api_test(x, frame_length, hop_length, axis=-1):
if axis == -1 and not x.flags["C_CONTIGUOUS"]:
x = np.ascontiguousarray(x)
elif axis == 0 and not x.flags["F_CONTIGUOUS"]:
x = np.asfortranarray(x)
n_frames = 1 + (x.shape[axis] - frame_length) // hop_length
strides = np.asarray(x.strides)
if axis == -1:
shape = [*list(x.shape)[:-1], frame_length, n_frames]
strides = [*list(strides), hop_length * x.itemsize]
elif axis == 0:
shape = [n_frames, frame_length, *list(x.shape)[1:]]
strides = [hop_length * x.itemsize, *list(strides)]
else:
raise ValueError(f"Frame axis={axis} must be either 0 or -1")
return as_strided(x, shape=shape, strides=strides)
def overlap_add_for_api_test(x, hop_length, axis=-1):
assert axis in [0, -1], 'axis should be 0/-1.'
assert len(x.shape) >= 2, 'Input dims shoulb be >= 2.'
squeeze_output = False
if len(x.shape) == 2:
squeeze_output = True
dim = 0 if axis == -1 else -1
x = np.expand_dims(x, dim) # batch
n_frames = x.shape[axis]
frame_length = x.shape[1] if axis == 0 else x.shape[-2]
# Assure no gaps between frames.
assert 0 < hop_length <= frame_length, (
f'hop_length should be in (0, frame_length({frame_length})], but got {hop_length}.'
)
seq_length = (n_frames - 1) * hop_length + frame_length
reshape_output = False
if len(x.shape) > 3:
reshape_output = True
if axis == 0:
target_shape = [seq_length, *list(x.shape[2:])]
x = x.reshape(n_frames, frame_length, np.prod(x.shape[2:]))
else:
target_shape = [*list(x.shape[:-2]), seq_length]
x = x.reshape(np.prod(x.shape[:-2]), frame_length, n_frames)
if axis == 0:
x = x.transpose((2, 1, 0))
y = np.zeros(shape=[np.prod(x.shape[:-2]), seq_length], dtype=x.dtype)
for i in range(x.shape[0]):
for frame in range(x.shape[-1]):
sample = frame * hop_length
y[i, sample : sample + frame_length] += x[i, :, frame]
if axis == 0:
y = y.transpose((1, 0))
if reshape_output:
y = y.reshape(target_shape)
if squeeze_output:
y = y.squeeze(-1) if axis == 0 else y.squeeze(0)
return y
def place(devices, key='place'):
def decorate(cls):
module = sys.modules[cls.__module__].__dict__
raw_classes = {
k: v for k, v in module.items() if k.startswith(cls.__name__)
}
for raw_name, raw_cls in raw_classes.items():
for d in devices:
test_cls = dict(raw_cls.__dict__)
test_cls.update({key: d})
new_name = raw_name + '.' + d.__class__.__name__
module[new_name] = type(new_name, (raw_cls,), test_cls)
del module[raw_name]
return cls
return decorate
def setUpModule():
global rtol
global atol
# All test case will use float64 for compare precision, refs:
# https://github.com/PaddlePaddle/Paddle/wiki/Upgrade-OP-Precision-to-Float64
rtol = {
'float32': 1e-06,
'float64': 1e-7,
'complex64': 1e-06,
'complex128': 1e-7,
}
atol = {
'float32': 0.0,
'float64': 0.0,
'complex64': 0.0,
'complex128': 0.0,
}
def tearDownModule():
pass
def rand_x(
dims=1,
dtype='float64',
min_dim_len=1,
max_dim_len=10,
shape=None,
complex=False,
):
if shape is None:
shape = [
np.random.randint(min_dim_len, max_dim_len) for i in range(dims)
]
if complex:
return np.random.randn(*shape).astype(dtype) + 1.0j * np.random.randn(
*shape
).astype(dtype)
else:
return np.random.randn(*shape).astype(dtype)
def parameterize(attrs, input_values=None):
if isinstance(attrs, str):
attrs = [attrs]
input_dicts = (
attrs
if input_values is None
else [dict(zip(attrs, vals)) for vals in input_values]
)
def decorator(base_class):
test_class_module = sys.modules[base_class.__module__].__dict__
for idx, input_dict in enumerate(input_dicts):
test_class_dict = dict(base_class.__dict__)
test_class_dict.update(input_dict)
name = class_name(base_class, idx, input_dict)
test_class_module[name] = type(name, (base_class,), test_class_dict)
for method_name in list(base_class.__dict__):
if method_name.startswith("test"):
delattr(base_class, method_name)
return base_class
return decorator
def class_name(cls, num, params_dict):
suffix = to_safe_name(
next((v for v in params_dict.values() if isinstance(v, str)), "")
)
if TEST_CASE_NAME in params_dict:
suffix = to_safe_name(params_dict["test_case"])
return "{}_{}{}".format(cls.__name__, num, suffix and "_" + suffix)
def to_safe_name(s):
return str(re.sub("[^a-zA-Z0-9_]+", "_", s))
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'frame_length', 'hop_length', 'axis'),
[
('test_1d_input1', rand_x(1, np.float64, shape=[150]), 50, 15, 0),
('test_1d_input2', rand_x(1, np.float64, shape=[150]), 50, 15, -1),
('test_2d_input1', rand_x(2, np.float64, shape=[150, 8]), 50, 15, 0),
('test_2d_input2', rand_x(2, np.float64, shape=[8, 150]), 50, 15, -1),
('test_3d_input1', rand_x(3, np.float64, shape=[150, 4, 2]), 50, 15, 0),
('test_3d_input2', rand_x(3, np.float64, shape=[4, 2, 150]), 50, 15, -1),
]) # fmt: skip
class TestFrame(unittest.TestCase):
def test_frame(self):
np.testing.assert_allclose(
frame_for_api_test(
self.x, self.frame_length, self.hop_length, self.axis
),
paddle.signal.frame(
paddle.to_tensor(self.x),
self.frame_length,
self.hop_length,
self.axis,
),
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'frame_length', 'hop_length', 'axis'),
[
('test_1d_input1', rand_x(1, np.float64, shape=[150]), 50, 15, 0),
('test_1d_input2', rand_x(1, np.float64, shape=[150]), 50, 15, -1),
('test_2d_input1', rand_x(2, np.float64, shape=[150, 8]), 50, 15, 0),
('test_2d_input2', rand_x(2, np.float64, shape=[8, 150]), 50, 15, -1),
('test_3d_input1', rand_x(3, np.float64, shape=[150, 4, 2]), 50, 15, 0),
('test_3d_input2', rand_x(3, np.float64, shape=[4, 2, 150]), 50, 15, -1),
]) # fmt: skip
class TestFrameStatic(unittest.TestCase):
def test_frame_static(self):
paddle.enable_static()
mp, sp = paddle.static.Program(), paddle.static.Program()
with paddle.static.program_guard(mp, sp):
input = paddle.static.data(
'input', self.x.shape, dtype=self.x.dtype
)
output = (
paddle.signal.frame(
input, self.frame_length, self.hop_length, self.axis
),
)
exe = paddle.static.Executor(self.place)
exe.run(sp)
[output] = exe.run(mp, feed={'input': self.x}, fetch_list=[output])
paddle.disable_static()
np.testing.assert_allclose(
frame_for_api_test(
self.x, self.frame_length, self.hop_length, self.axis
),
output,
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'frame_length', 'hop_length', 'axis', 'expect_exception'),
[
('test_axis', rand_x(1, np.float64, shape=[150]), 50, 15, 2, ValueError),
('test_hop_length', rand_x(1, np.float64, shape=[150]), 50, 0, -1, ValueError),
('test_frame_length1', rand_x(2, np.float64, shape=[150, 8]), 0, 15, 0, ValueError),
('test_frame_length2', rand_x(2, np.float64, shape=[150, 8]), 151, 15, 0, ValueError),
]) # fmt: skip
class TestFrameException(unittest.TestCase):
def test_frame(self):
with self.assertRaises(self.expect_exception):
paddle.signal.frame(
paddle.to_tensor(self.x),
self.frame_length,
self.hop_length,
self.axis,
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'hop_length', 'axis'),
[
('test_2d_input1', rand_x(2, np.float64, shape=[3, 50]), 4, 0),
('test_2d_input2', rand_x(2, np.float64, shape=[50, 3]), 4, -1),
('test_3d_input1', rand_x(3, np.float64, shape=[5, 40, 2]), 10, 0),
('test_3d_input2', rand_x(3, np.float64, shape=[2, 40, 5]), 10, -1),
('test_4d_input1', rand_x(4, np.float64, shape=[8, 12, 5, 3]), 5, 0),
('test_4d_input2', rand_x(4, np.float64, shape=[3, 5, 12, 8]), 5, -1),
]) # fmt: skip
class TestOverlapAdd(unittest.TestCase):
def test_overlap_add(self):
np.testing.assert_allclose(
overlap_add_for_api_test(self.x, self.hop_length, self.axis),
paddle.signal.overlap_add(
paddle.to_tensor(self.x), self.hop_length, self.axis
),
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'hop_length', 'axis'),
[
('test_2d_input1', rand_x(2, np.float64, shape=[3, 50]), 4, 0),
('test_2d_input2', rand_x(2, np.float64, shape=[50, 3]), 4, -1),
('test_3d_input1', rand_x(3, np.float64, shape=[5, 40, 2]), 10, 0),
('test_3d_input2', rand_x(3, np.float64, shape=[2, 40, 5]), 10, -1),
('test_4d_input1', rand_x(4, np.float64, shape=[8, 12, 5, 3]), 5, 0),
('test_4d_input2', rand_x(4, np.float64, shape=[3, 5, 12, 8]), 5, -1),
]) # fmt: skip
class TestOverlapAddStatic(unittest.TestCase):
def test_overlap_add_static(self):
paddle.enable_static()
mp, sp = paddle.static.Program(), paddle.static.Program()
with paddle.static.program_guard(mp, sp):
input = paddle.static.data(
'input', self.x.shape, dtype=self.x.dtype
)
output = (
paddle.signal.overlap_add(input, self.hop_length, self.axis),
)
exe = paddle.static.Executor(self.place)
exe.run(sp)
[output] = exe.run(mp, feed={'input': self.x}, fetch_list=[output])
paddle.disable_static()
np.testing.assert_allclose(
overlap_add_for_api_test(self.x, self.hop_length, self.axis),
output,
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'hop_length', 'axis', 'expect_exception'),
[
('test_axis', rand_x(2, np.float64, shape=[3, 50]), 4, 2, ValueError),
('test_hop_length', rand_x(2, np.float64, shape=[50, 3]), -1, -1, ValueError),
]) # fmt: skip
class TestOverlapAddException(unittest.TestCase):
def test_overlap_add(self):
with self.assertRaises(self.expect_exception):
paddle.signal.overlap_add(
paddle.to_tensor(self.x), self.hop_length, self.axis
)
# ================= STFT
# common args
# x
# n_fft,
# hop_length=None,
# win_length=None,
# window=None,
# center=True,
# pad_mode='reflect',
# paddle only
# normalized=False,
# onesided=True,
# ================= ISTFT
# common args
# x,
# hop_length=None,
# win_length=None,
# window=None,
# center=True,
# length=None,
# paddle only
# n_fft,
# normalized=False,
# onesided=True,
# return_complex=False,
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'n_fft', 'hop_length', 'win_length', 'window', 'center', 'pad_mode', 'normalized', 'onesided'),
[
('test_1d_input', rand_x(1, np.float64, shape=[160000]), 512,
None, None, get_window('hann', 512), True, 'reflect', False, True),
('test_2d_input', rand_x(2, np.float64, shape=[1, 160000]), 512,
None, None, get_window('hann', 512), True, 'reflect', False, True),
('test_hop_length', rand_x(2, np.float64, shape=[1, 160000]), 512,
255, None, get_window('hann', 512), True, 'reflect', False, True),
('test_win_length', rand_x(2, np.float64, shape=[1, 160000]), 512,
255, 499, get_window('hann', 499), True, 'reflect', False, True),
('test_window', rand_x(2, np.float64, shape=[1, 160000]), 512,
None, None, None, True, 'reflect', False, True),
('test_center', rand_x(2, np.float64, shape=[1, 160000]), 512,
None, None, None, False, 'reflect', False, True),
]) # fmt: skip
class TestStft(unittest.TestCase):
def test_stft(self):
if self.window is None:
win_p = None
win_l = 'boxcar' # rectangular window
else:
win_p = paddle.to_tensor(self.window)
win_l = self.window
np.testing.assert_allclose(
stft(
self.x,
self.n_fft,
self.hop_length,
self.win_length,
win_l,
self.center,
self.pad_mode,
),
paddle.signal.stft(
paddle.to_tensor(self.x),
self.n_fft,
self.hop_length,
self.win_length,
win_p,
self.center,
self.pad_mode,
self.normalized,
self.onesided,
),
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'n_fft', 'hop_length', 'win_length', 'window', 'center', 'pad_mode', 'normalized', 'onesided', 'expect_exception'),
[
('test_dims', rand_x(1, np.float64, shape=[1, 2, 3]), 512,
None, None, None, True, 'reflect', False, True, AssertionError),
('test_hop_length', rand_x(1, np.float64, shape=[16000]), 512,
0, None, None, True, 'reflect', False, True, AssertionError),
('test_nfft1', rand_x(1, np.float64, shape=[16000]), 0,
None, None, None, True, 'reflect', False, True, AssertionError),
('test_nfft2', rand_x(1, np.float64, shape=[16000]), 16001,
None, None, None, True, 'reflect', False, True, AssertionError),
('test_win_length', rand_x(1, np.float64, shape=[16000]), 512,
None, 0, None, True, 'reflect', False, True, AssertionError),
('test_win_length', rand_x(1, np.float64, shape=[16000]), 512,
None, 513, None, True, 'reflect', False, True, AssertionError),
('test_pad_mode', rand_x(1, np.float64, shape=[16000]), 512,
None, None, None, True, 'nonsense', False, True, AssertionError),
('test_complex_onesided', rand_x(1, np.float64, shape=[16000], complex=True), 512,
None, None, None, False, 'reflect', False, True, AssertionError),
]) # fmt: skip
class TestStftException(unittest.TestCase):
def test_stft(self):
if self.window is None:
win_p = None
else:
win_p = paddle.to_tensor(self.window)
with self.assertRaises(self.expect_exception):
paddle.signal.stft(
paddle.to_tensor(self.x),
self.n_fft,
self.hop_length,
self.win_length,
win_p,
self.center,
self.pad_mode,
self.normalized,
self.onesided,
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'n_fft', 'hop_length', 'win_length', 'window', 'center', 'normalized', 'onesided', 'length', 'return_complex'),
[
('test_2d_input', rand_x(2, np.float64, shape=[257, 471], complex=True), 512,
None, None, get_window('hann', 512), True, False, True, None, False),
('test_3d_input', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, get_window('hann', 512), True, False, True, None, False),
('test_hop_length', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
99, None, get_window('hann', 512), True, False, True, None, False),
('test_win_length', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
99, 299, get_window('hann', 299), True, False, True, None, False),
('test_window', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, None, True, False, True, None, False),
('test_center', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, None, False, False, True, None, False),
('test_length', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, None, False, False, True, 1888, False),
]) # fmt: skip
class TestIstft(unittest.TestCase):
def test_istft(self):
if self.window is None:
win_p = None
win_l = 'boxcar' # rectangular window
else:
win_p = paddle.to_tensor(self.window)
win_l = self.window
np.testing.assert_allclose(
istft(
self.x,
self.hop_length,
self.win_length,
win_l,
self.center,
self.length,
),
paddle.signal.istft(
paddle.to_tensor(self.x),
self.n_fft,
self.hop_length,
self.win_length,
win_p,
self.center,
self.normalized,
self.onesided,
self.length,
self.return_complex,
),
rtol=rtol.get(str(self.x.dtype)),
atol=atol.get(str(self.x.dtype)),
)
@place(DEVICES)
@parameterize(
(TEST_CASE_NAME, 'x', 'n_fft', 'hop_length', 'win_length', 'window', 'center', 'normalized', 'onesided', 'length', 'return_complex', 'expect_exception'),
[
('test_dims', rand_x(4, np.float64, shape=[1, 2, 3, 4], complex=True), 512,
None, None, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_n_fft', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 257,
None, None, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_hop_length1', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
0, None, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_hop_length2', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
513, None, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_win_length1', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, 0, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_win_length2', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, 513, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_onesided1', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 20,
None, None, get_window('hann', 512), True, False, True, None, False, AssertionError),
('test_onesided2', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 256,
None, None, None, True, False, False, None, False, AssertionError),
('test_window', rand_x(3, np.float64, shape=[1, 512, 471], complex=True), 512,
None, 511, get_window('hann', 512), True, False, False, None, False, AssertionError),
('test_return_complex1', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, get_window('hann', 512), True, False, True, None, True, AssertionError),
('test_return_complex2', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
None, None, rand_x(1, np.float64, shape=[512], complex=True), True, False, True, None, False, AssertionError),
('test_NOLA', rand_x(3, np.float64, shape=[1, 257, 471], complex=True), 512,
512, None, get_window('hann', 512), True, False, True, None, False, ValueError),
]) # fmt: skip
class TestIstftException(unittest.TestCase):
def test_istft(self):
if self.window is None:
win_p = None
else:
win_p = paddle.to_tensor(self.window)
with self.assertRaises(self.expect_exception):
paddle.signal.istft(
paddle.to_tensor(self.x),
self.n_fft,
self.hop_length,
self.win_length,
win_p,
self.center,
self.normalized,
self.onesided,
self.length,
self.return_complex,
)
class TestIstftException_ZeroSize(unittest.TestCase):
def test_istft(self):
self.x = np.random.random([5, 0])
with self.assertRaises(AssertionError):
paddle.signal.istft(paddle.to_tensor(self.x), 512)
if __name__ == '__main__':
unittest.main()