speech-selib 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speech_selib-0.1.0/LICENSE +21 -0
- speech_selib-0.1.0/PKG-INFO +133 -0
- speech_selib-0.1.0/README.md +102 -0
- speech_selib-0.1.0/pyproject.toml +38 -0
- speech_selib-0.1.0/selib/__init__.py +7 -0
- speech_selib-0.1.0/selib/metrics/__init__.py +31 -0
- speech_selib-0.1.0/selib/metrics/basic.py +245 -0
- speech_selib-0.1.0/selib/metrics/dnsmos.py +151 -0
- speech_selib-0.1.0/selib/models/__init__.py +11 -0
- speech_selib-0.1.0/selib/models/core.py +153 -0
- speech_selib-0.1.0/selib/models/deepfilter/__init__.py +11 -0
- speech_selib-0.1.0/selib/models/deepfilter/features.py +309 -0
- speech_selib-0.1.0/selib/models/deepfilter/ops.py +190 -0
- speech_selib-0.1.0/selib/models/deepfilter/runtime.py +253 -0
- speech_selib-0.1.0/selib/models/factory.py +165 -0
- speech_selib-0.1.0/selib/models/se_models.py +185 -0
- speech_selib-0.1.0/selib/urls.py +29 -0
- speech_selib-0.1.0/selib/utils/__init__.py +8 -0
- speech_selib-0.1.0/selib/utils/audio.py +200 -0
- speech_selib-0.1.0/selib/utils/noise.py +161 -0
- speech_selib-0.1.0/setup.cfg +4 -0
- speech_selib-0.1.0/setup.py +57 -0
- speech_selib-0.1.0/speech_selib.egg-info/PKG-INFO +133 -0
- speech_selib-0.1.0/speech_selib.egg-info/SOURCES.txt +30 -0
- speech_selib-0.1.0/speech_selib.egg-info/dependency_links.txt +1 -0
- speech_selib-0.1.0/speech_selib.egg-info/requires.txt +8 -0
- speech_selib-0.1.0/speech_selib.egg-info/top_level.txt +4 -0
- speech_selib-0.1.0/tmp/profile_onnx.py +28 -0
- speech_selib-0.1.0/tmp/test.py +149 -0
- speech_selib-0.1.0/tmp/test_df.py +25 -0
- speech_selib-0.1.0/tmp/test_df_ref.py +36 -0
- speech_selib-0.1.0/tmp/test_dnsmos.py +37 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 github.com/nipponjo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speech-selib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple speech enhancement library using ONNX models
|
|
5
|
+
Home-page: https://github.com/nipponjo/selib
|
|
6
|
+
Author: nipponjo
|
|
7
|
+
Author-email: nipponjo <nipponjo.git@gmail.com>
|
|
8
|
+
Project-URL: Homepage, https://github.com/nipponjo/selib
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Telecommunications Industry
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Multimedia
|
|
17
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
18
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.8.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Requires-Dist: librosa
|
|
25
|
+
Requires-Dist: onnxruntime-gpu; sys_platform != "darwin"
|
|
26
|
+
Requires-Dist: onnxruntime; sys_platform == "darwin"
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
|
|
32
|
+
# SeLib
|
|
33
|
+
|
|
34
|
+
SeLib is a small speech-enhancement library for ONNX models. It provides a
|
|
35
|
+
simple `enhance()` function, lightweight model wrappers, and utility functions for audio/noise experiments.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Install the package directly from GitHub while the project is still small and moving quickly.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Install SeLib and its Python dependencies.
|
|
43
|
+
pip install git+https://github.com/nipponjo/selib.git
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Basic Use
|
|
47
|
+
|
|
48
|
+
The shortest path is `selib.enhance(wave, model_id)`. Pass a mono NumPy waveform at the sample rate expected by the model.
|
|
49
|
+
```python
|
|
50
|
+
import librosa
|
|
51
|
+
import selib
|
|
52
|
+
|
|
53
|
+
from selib.utils import NoiseGenerator, snr_mixer
|
|
54
|
+
|
|
55
|
+
# Load clean speech at the model sample rate.
|
|
56
|
+
wave_clean, sr = librosa.load("./data/clean_freesound_33711.wav", sr=48000)
|
|
57
|
+
|
|
58
|
+
# Create artificial noise and mix it with the clean speech at 5 dB SNR.
|
|
59
|
+
noise_gen = NoiseGenerator(sample_rate=sr)
|
|
60
|
+
noise = noise_gen.sample(n_samples=len(wave_clean))
|
|
61
|
+
wave_noisy = snr_mixer(wave_clean, noise, snr=5)
|
|
62
|
+
|
|
63
|
+
# Enhance with a registered model id.
|
|
64
|
+
# The ONNX model is downloaded and cached automatically on first use.
|
|
65
|
+
wave_enhanced = selib.enhance(wave_noisy, "deepfilternet3")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can also save the enhanced waveform directly. The loaded model is cached by
|
|
69
|
+
default, so repeated calls with the same `model_id` do not reload the ONNX
|
|
70
|
+
session.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Save as 24-bit PCM WAV while returning the enhanced NumPy array.
|
|
74
|
+
wave_enhanced = selib.enhance(
|
|
75
|
+
wave_noisy,
|
|
76
|
+
"deepfilternet3",
|
|
77
|
+
save_to="enhanced.wav",
|
|
78
|
+
bits_per_sample=24,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Model Objects
|
|
83
|
+
|
|
84
|
+
For more control, load the model wrapper once and call its
|
|
85
|
+
`enhance()` method directly.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from selib import load_model
|
|
89
|
+
from selib.models import DeepFilterNetOnnx
|
|
90
|
+
|
|
91
|
+
# load_model() chooses the correct wrapper from the registry metadata.
|
|
92
|
+
model: DeepFilterNetOnnx = load_model("deepfilternet3")
|
|
93
|
+
|
|
94
|
+
# DeepFilterNetOnnx supports extra options such as attenuation limiting.
|
|
95
|
+
wave_enhanced = model.enhance(wave_noisy, atten_lim_db=12)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Magnitude-mask models can also be used directly. They predict a mask, multiply
|
|
99
|
+
it with the noisy magnitude spectrogram, and reuse the noisy phase.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from selib import MagnitudeMaskModel
|
|
103
|
+
|
|
104
|
+
# ul_unas_16k is a 16 kHz magnitude-mask model.
|
|
105
|
+
mask_model = MagnitudeMaskModel("ul_unas_16k")
|
|
106
|
+
wave_enhanced = mask_model.enhance(wave_noisy_16k)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Metrics
|
|
110
|
+
|
|
111
|
+
SeLib includes a few quick NumPy-only metrics that do not require PESQ/STOI or
|
|
112
|
+
other external scoring libraries.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from selib.metrics import snr, segsnr, si_sdr
|
|
116
|
+
|
|
117
|
+
# Compare clean reference speech against an enhanced waveform.
|
|
118
|
+
print("SNR:", snr(wave_clean, wave_enhanced))
|
|
119
|
+
print("segSNR:", segsnr(wave_clean, wave_enhanced, sample_rate=sr))
|
|
120
|
+
print("SI-SDR:", si_sdr(wave_clean, wave_enhanced))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Available Models
|
|
124
|
+
|
|
125
|
+
| Model ID | Type | Sample rate | #params | Paper | Repository |
|
|
126
|
+
| --- | --- | --- | --- | --- | --- |
|
|
127
|
+
| `deepfilternet3` | DeepFilterNet | 48 kHz | 2.13M | [arXiv:2305.08227](https://arxiv.org/abs/2305.08227) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
128
|
+
| `deepfilternet2` | DeepFilterNet | 48 kHz | 2.31M | [arXiv:2205.05474](https://arxiv.org/abs/2205.05474) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
129
|
+
| `deepfilternet1` | DeepFilterNet | 48 kHz | 1.78M | [arXiv:2110.05588](https://arxiv.org/abs/2110.05588) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
130
|
+
| `ul_unas_16k` | Magnitude mask | 16 kHz | 0.171M | [arXiv:2503.00340](https://arxiv.org/abs/2503.00340) | [GitHub](https://github.com/Xiaobin-Rong/ul-unas) |
|
|
131
|
+
|
|
132
|
+
DeepFilterNet models use ERB masks plus deep-filter coefficients. Magnitude-mask
|
|
133
|
+
models predict a spectrogram mask and reconstruct audio with the noisy phase.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# SeLib
|
|
2
|
+
|
|
3
|
+
SeLib is a small speech-enhancement library for ONNX models. It provides a
|
|
4
|
+
simple `enhance()` function, lightweight model wrappers, and utility functions for audio/noise experiments.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Install the package directly from GitHub while the project is still small and moving quickly.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Install SeLib and its Python dependencies.
|
|
12
|
+
pip install git+https://github.com/nipponjo/selib.git
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic Use
|
|
16
|
+
|
|
17
|
+
The shortest path is `selib.enhance(wave, model_id)`. Pass a mono NumPy waveform at the sample rate expected by the model.
|
|
18
|
+
```python
|
|
19
|
+
import librosa
|
|
20
|
+
import selib
|
|
21
|
+
|
|
22
|
+
from selib.utils import NoiseGenerator, snr_mixer
|
|
23
|
+
|
|
24
|
+
# Load clean speech at the model sample rate.
|
|
25
|
+
wave_clean, sr = librosa.load("./data/clean_freesound_33711.wav", sr=48000)
|
|
26
|
+
|
|
27
|
+
# Create artificial noise and mix it with the clean speech at 5 dB SNR.
|
|
28
|
+
noise_gen = NoiseGenerator(sample_rate=sr)
|
|
29
|
+
noise = noise_gen.sample(n_samples=len(wave_clean))
|
|
30
|
+
wave_noisy = snr_mixer(wave_clean, noise, snr=5)
|
|
31
|
+
|
|
32
|
+
# Enhance with a registered model id.
|
|
33
|
+
# The ONNX model is downloaded and cached automatically on first use.
|
|
34
|
+
wave_enhanced = selib.enhance(wave_noisy, "deepfilternet3")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can also save the enhanced waveform directly. The loaded model is cached by
|
|
38
|
+
default, so repeated calls with the same `model_id` do not reload the ONNX
|
|
39
|
+
session.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# Save as 24-bit PCM WAV while returning the enhanced NumPy array.
|
|
43
|
+
wave_enhanced = selib.enhance(
|
|
44
|
+
wave_noisy,
|
|
45
|
+
"deepfilternet3",
|
|
46
|
+
save_to="enhanced.wav",
|
|
47
|
+
bits_per_sample=24,
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Model Objects
|
|
52
|
+
|
|
53
|
+
For more control, load the model wrapper once and call its
|
|
54
|
+
`enhance()` method directly.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from selib import load_model
|
|
58
|
+
from selib.models import DeepFilterNetOnnx
|
|
59
|
+
|
|
60
|
+
# load_model() chooses the correct wrapper from the registry metadata.
|
|
61
|
+
model: DeepFilterNetOnnx = load_model("deepfilternet3")
|
|
62
|
+
|
|
63
|
+
# DeepFilterNetOnnx supports extra options such as attenuation limiting.
|
|
64
|
+
wave_enhanced = model.enhance(wave_noisy, atten_lim_db=12)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Magnitude-mask models can also be used directly. They predict a mask, multiply
|
|
68
|
+
it with the noisy magnitude spectrogram, and reuse the noisy phase.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from selib import MagnitudeMaskModel
|
|
72
|
+
|
|
73
|
+
# ul_unas_16k is a 16 kHz magnitude-mask model.
|
|
74
|
+
mask_model = MagnitudeMaskModel("ul_unas_16k")
|
|
75
|
+
wave_enhanced = mask_model.enhance(wave_noisy_16k)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Metrics
|
|
79
|
+
|
|
80
|
+
SeLib includes a few quick NumPy-only metrics that do not require PESQ/STOI or
|
|
81
|
+
other external scoring libraries.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from selib.metrics import snr, segsnr, si_sdr
|
|
85
|
+
|
|
86
|
+
# Compare clean reference speech against an enhanced waveform.
|
|
87
|
+
print("SNR:", snr(wave_clean, wave_enhanced))
|
|
88
|
+
print("segSNR:", segsnr(wave_clean, wave_enhanced, sample_rate=sr))
|
|
89
|
+
print("SI-SDR:", si_sdr(wave_clean, wave_enhanced))
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Available Models
|
|
93
|
+
|
|
94
|
+
| Model ID | Type | Sample rate | #params | Paper | Repository |
|
|
95
|
+
| --- | --- | --- | --- | --- | --- |
|
|
96
|
+
| `deepfilternet3` | DeepFilterNet | 48 kHz | 2.13M | [arXiv:2305.08227](https://arxiv.org/abs/2305.08227) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
97
|
+
| `deepfilternet2` | DeepFilterNet | 48 kHz | 2.31M | [arXiv:2205.05474](https://arxiv.org/abs/2205.05474) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
98
|
+
| `deepfilternet1` | DeepFilterNet | 48 kHz | 1.78M | [arXiv:2110.05588](https://arxiv.org/abs/2110.05588) | [GitHub](https://github.com/rikorose/deepfilternet) |
|
|
99
|
+
| `ul_unas_16k` | Magnitude mask | 16 kHz | 0.171M | [arXiv:2503.00340](https://arxiv.org/abs/2503.00340) | [GitHub](https://github.com/Xiaobin-Rong/ul-unas) |
|
|
100
|
+
|
|
101
|
+
DeepFilterNet models use ERB masks plus deep-filter coefficients. Magnitude-mask
|
|
102
|
+
models predict a spectrogram mask and reconstruct audio with the noisy phase.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "speech-selib"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A simple speech enhancement library using ONNX models"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8.0"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "nipponjo", email = "nipponjo.git@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy",
|
|
16
|
+
"librosa",
|
|
17
|
+
"onnxruntime-gpu; sys_platform != 'darwin'",
|
|
18
|
+
"onnxruntime; sys_platform == 'darwin'",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Intended Audience :: Telecommunications Industry",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Topic :: Multimedia",
|
|
29
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
30
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/nipponjo/selib"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["."]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .basic import (
|
|
2
|
+
mae,
|
|
3
|
+
mse,
|
|
4
|
+
power_db,
|
|
5
|
+
rms,
|
|
6
|
+
rmse,
|
|
7
|
+
segmental_snr,
|
|
8
|
+
segsnr,
|
|
9
|
+
si_sdr,
|
|
10
|
+
si_snr,
|
|
11
|
+
snr,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from .dnsmos import DnsMosSigBakOvrOnnx
|
|
16
|
+
except ImportError:
|
|
17
|
+
DnsMosSigBakOvrOnnx = None
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DnsMosSigBakOvrOnnx",
|
|
21
|
+
"mae",
|
|
22
|
+
"mse",
|
|
23
|
+
"power_db",
|
|
24
|
+
"rms",
|
|
25
|
+
"rmse",
|
|
26
|
+
"segmental_snr",
|
|
27
|
+
"segsnr",
|
|
28
|
+
"si_sdr",
|
|
29
|
+
"si_snr",
|
|
30
|
+
"snr",
|
|
31
|
+
]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Lightweight NumPy metrics for speech enhancement.
|
|
2
|
+
|
|
3
|
+
These metrics do not depend on external perceptual-metric packages such as
|
|
4
|
+
PESQ, STOI, or DNSMOS. They are useful for quick checks, regression tests, and
|
|
5
|
+
objective comparisons when a clean reference signal is available.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Optional, Sequence, Union
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
ArrayLike = Union[np.ndarray, Sequence[float]]
|
|
16
|
+
_WARNED_UNEQUAL_LENGTH = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _as_1d_float(name: str, x: ArrayLike) -> np.ndarray:
|
|
20
|
+
"""Convert input audio to a contiguous 1-D float64 array."""
|
|
21
|
+
x = np.asarray(x, dtype=np.float64).reshape(-1)
|
|
22
|
+
if x.size == 0:
|
|
23
|
+
raise ValueError(f"{name} must contain at least one sample")
|
|
24
|
+
return x
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _align_pair(reference: ArrayLike,
|
|
28
|
+
estimate: ArrayLike) -> tuple[np.ndarray, np.ndarray]:
|
|
29
|
+
"""Convert and truncate two signals to a shared length."""
|
|
30
|
+
global _WARNED_UNEQUAL_LENGTH
|
|
31
|
+
reference = _as_1d_float("reference", reference)
|
|
32
|
+
estimate = _as_1d_float("estimate", estimate)
|
|
33
|
+
n = min(reference.size, estimate.size)
|
|
34
|
+
if n == 0:
|
|
35
|
+
raise ValueError("reference and estimate must contain samples")
|
|
36
|
+
if reference.size != estimate.size and not _WARNED_UNEQUAL_LENGTH:
|
|
37
|
+
warnings.warn(
|
|
38
|
+
"reference and estimate have unequal lengths "
|
|
39
|
+
f"({reference.size} vs {estimate.size}); aligning by truncating "
|
|
40
|
+
f"both signals to the first {n} samples.",
|
|
41
|
+
RuntimeWarning,
|
|
42
|
+
stacklevel=2,
|
|
43
|
+
)
|
|
44
|
+
_WARNED_UNEQUAL_LENGTH = True
|
|
45
|
+
return reference[:n], estimate[:n]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def power_db(x: ArrayLike, eps: float = 1e-12) -> float:
|
|
49
|
+
"""Return average signal power in dB.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
x : ndarray
|
|
54
|
+
Input waveform.
|
|
55
|
+
eps : float, default=1e-12
|
|
56
|
+
Numerical floor applied before ``log10``.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
float
|
|
61
|
+
``10 * log10(mean(x ** 2))`` in dB.
|
|
62
|
+
"""
|
|
63
|
+
x = _as_1d_float("x", x)
|
|
64
|
+
return float(10.0 * np.log10(np.mean(np.square(x)) + eps))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def rms(x: ArrayLike) -> float:
|
|
68
|
+
"""Return root-mean-square amplitude."""
|
|
69
|
+
x = _as_1d_float("x", x)
|
|
70
|
+
return float(np.sqrt(np.mean(np.square(x))))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def mse(reference: ArrayLike, estimate: ArrayLike) -> float:
|
|
74
|
+
"""Return mean squared error between two aligned waveforms."""
|
|
75
|
+
reference, estimate = _align_pair(reference, estimate)
|
|
76
|
+
return float(np.mean(np.square(reference - estimate)))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def rmse(reference: ArrayLike, estimate: ArrayLike) -> float:
|
|
80
|
+
"""Return root mean squared error between two aligned waveforms."""
|
|
81
|
+
return float(np.sqrt(mse(reference, estimate)))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def mae(reference: ArrayLike, estimate: ArrayLike) -> float:
|
|
85
|
+
"""Return mean absolute error between two aligned waveforms."""
|
|
86
|
+
reference, estimate = _align_pair(reference, estimate)
|
|
87
|
+
return float(np.mean(np.abs(reference - estimate)))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def snr(reference: ArrayLike,
|
|
91
|
+
estimate: ArrayLike,
|
|
92
|
+
eps: float = 1e-12) -> float:
|
|
93
|
+
"""Return signal-to-noise ratio in dB.
|
|
94
|
+
|
|
95
|
+
``reference`` is normally the clean speech signal. ``estimate`` may be a
|
|
96
|
+
noisy or enhanced signal. The residual ``reference - estimate`` is treated
|
|
97
|
+
as noise/error.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
reference : ndarray
|
|
102
|
+
Clean reference waveform.
|
|
103
|
+
estimate : ndarray
|
|
104
|
+
Estimated waveform to compare against ``reference``.
|
|
105
|
+
eps : float, default=1e-12
|
|
106
|
+
Numerical floor for the denominator.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
float
|
|
111
|
+
SNR in dB.
|
|
112
|
+
"""
|
|
113
|
+
reference, estimate = _align_pair(reference, estimate)
|
|
114
|
+
signal_power = np.sum(np.square(reference))
|
|
115
|
+
noise_power = np.sum(np.square(reference - estimate))
|
|
116
|
+
return float(10.0 * np.log10((signal_power + eps) / (noise_power + eps)))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def segmental_snr(reference: ArrayLike,
|
|
120
|
+
estimate: ArrayLike,
|
|
121
|
+
sample_rate: int = 16000,
|
|
122
|
+
frame_ms: float = 20.0,
|
|
123
|
+
hop_ms: Optional[float] = None,
|
|
124
|
+
min_snr_db: float = -10.0,
|
|
125
|
+
max_snr_db: float = 35.0,
|
|
126
|
+
eps: float = 1e-12) -> float:
|
|
127
|
+
"""Return mean clipped frame-wise SNR in dB.
|
|
128
|
+
|
|
129
|
+
Segmental SNR computes SNR per short frame and averages the clipped values.
|
|
130
|
+
Clipping keeps silent or near-silent frames from dominating the score.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
reference : ndarray
|
|
135
|
+
Clean reference waveform.
|
|
136
|
+
estimate : ndarray
|
|
137
|
+
Estimated waveform to compare against ``reference``.
|
|
138
|
+
sample_rate : int, default=16000
|
|
139
|
+
Sample rate in Hz, used to convert frame and hop durations to samples.
|
|
140
|
+
frame_ms : float, default=20.0
|
|
141
|
+
Frame length in milliseconds.
|
|
142
|
+
hop_ms : float, optional
|
|
143
|
+
Hop length in milliseconds. Defaults to ``frame_ms``.
|
|
144
|
+
min_snr_db : float, default=-10.0
|
|
145
|
+
Lower clipping bound for each frame SNR.
|
|
146
|
+
max_snr_db : float, default=35.0
|
|
147
|
+
Upper clipping bound for each frame SNR.
|
|
148
|
+
eps : float, default=1e-12
|
|
149
|
+
Numerical floor for frame powers.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
float
|
|
154
|
+
Mean clipped segmental SNR in dB.
|
|
155
|
+
"""
|
|
156
|
+
reference, estimate = _align_pair(reference, estimate)
|
|
157
|
+
if hop_ms is None:
|
|
158
|
+
hop_ms = frame_ms
|
|
159
|
+
|
|
160
|
+
frame_len = max(1, int(round(sample_rate * frame_ms / 1000.0)))
|
|
161
|
+
hop_len = max(1, int(round(sample_rate * hop_ms / 1000.0)))
|
|
162
|
+
if reference.size < frame_len:
|
|
163
|
+
return snr(reference, estimate, eps=eps)
|
|
164
|
+
|
|
165
|
+
values = []
|
|
166
|
+
for start in range(0, reference.size - frame_len + 1, hop_len):
|
|
167
|
+
ref_frame = reference[start:start + frame_len]
|
|
168
|
+
err_frame = ref_frame - estimate[start:start + frame_len]
|
|
169
|
+
frame_snr = 10.0 * np.log10(
|
|
170
|
+
(np.sum(np.square(ref_frame)) + eps)
|
|
171
|
+
/ (np.sum(np.square(err_frame)) + eps)
|
|
172
|
+
)
|
|
173
|
+
values.append(np.clip(frame_snr, min_snr_db, max_snr_db))
|
|
174
|
+
return float(np.mean(values))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def segsnr(reference: ArrayLike,
|
|
178
|
+
estimate: ArrayLike,
|
|
179
|
+
sample_rate: int = 16000,
|
|
180
|
+
frame_ms: float = 20.0,
|
|
181
|
+
hop_ms: Optional[float] = None,
|
|
182
|
+
min_snr_db: float = -10.0,
|
|
183
|
+
max_snr_db: float = 35.0,
|
|
184
|
+
eps: float = 1e-12) -> float:
|
|
185
|
+
"""Alias for ``segmental_snr``."""
|
|
186
|
+
return segmental_snr(
|
|
187
|
+
reference,
|
|
188
|
+
estimate,
|
|
189
|
+
sample_rate=sample_rate,
|
|
190
|
+
frame_ms=frame_ms,
|
|
191
|
+
hop_ms=hop_ms,
|
|
192
|
+
min_snr_db=min_snr_db,
|
|
193
|
+
max_snr_db=max_snr_db,
|
|
194
|
+
eps=eps,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def si_sdr(reference: ArrayLike,
|
|
199
|
+
estimate: ArrayLike,
|
|
200
|
+
zero_mean: bool = True,
|
|
201
|
+
eps: float = 1e-12) -> float:
|
|
202
|
+
"""Return scale-invariant signal-to-distortion ratio in dB.
|
|
203
|
+
|
|
204
|
+
SI-SDR removes gain differences between ``reference`` and ``estimate``
|
|
205
|
+
before measuring residual error. This is often more useful than plain SNR
|
|
206
|
+
when an enhancement model changes output loudness.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
reference : ndarray
|
|
211
|
+
Clean reference waveform.
|
|
212
|
+
estimate : ndarray
|
|
213
|
+
Estimated waveform to compare against ``reference``.
|
|
214
|
+
zero_mean : bool, default=True
|
|
215
|
+
If True, remove the mean from both signals before projection.
|
|
216
|
+
eps : float, default=1e-12
|
|
217
|
+
Numerical floor for divisions and logarithms.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
float
|
|
222
|
+
SI-SDR in dB.
|
|
223
|
+
"""
|
|
224
|
+
reference, estimate = _align_pair(reference, estimate)
|
|
225
|
+
if zero_mean:
|
|
226
|
+
reference = reference - np.mean(reference)
|
|
227
|
+
estimate = estimate - np.mean(estimate)
|
|
228
|
+
|
|
229
|
+
scale = np.sum(estimate * reference) / (np.sum(np.square(reference)) + eps)
|
|
230
|
+
target = scale * reference
|
|
231
|
+
noise = estimate - target
|
|
232
|
+
return float(
|
|
233
|
+
10.0 * np.log10(
|
|
234
|
+
(np.sum(np.square(target)) + eps)
|
|
235
|
+
/ (np.sum(np.square(noise)) + eps)
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def si_snr(reference: ArrayLike,
|
|
241
|
+
estimate: ArrayLike,
|
|
242
|
+
zero_mean: bool = True,
|
|
243
|
+
eps: float = 1e-12) -> float:
|
|
244
|
+
"""Alias for ``si_sdr``."""
|
|
245
|
+
return si_sdr(reference, estimate, zero_mean=zero_mean, eps=eps)
|