pygaborstm 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.
- pygaborstm-0.1.0/LICENSE +21 -0
- pygaborstm-0.1.0/PKG-INFO +251 -0
- pygaborstm-0.1.0/README.md +214 -0
- pygaborstm-0.1.0/pygaborstm/__init__.py +214 -0
- pygaborstm-0.1.0/pygaborstm/_optional.py +34 -0
- pygaborstm-0.1.0/pygaborstm/analysis.py +348 -0
- pygaborstm-0.1.0/pygaborstm/backend.py +271 -0
- pygaborstm-0.1.0/pygaborstm/config.py +187 -0
- pygaborstm-0.1.0/pygaborstm/constants.py +29 -0
- pygaborstm-0.1.0/pygaborstm/core.py +139 -0
- pygaborstm-0.1.0/pygaborstm/gabor.py +598 -0
- pygaborstm-0.1.0/pygaborstm/gammatone_kernel.py +222 -0
- pygaborstm-0.1.0/pygaborstm/plot.py +495 -0
- pygaborstm-0.1.0/pygaborstm/py.typed +0 -0
- pygaborstm-0.1.0/pygaborstm/spectrogram.py +382 -0
- pygaborstm-0.1.0/pygaborstm/structs.py +221 -0
- pygaborstm-0.1.0/pyproject.toml +131 -0
pygaborstm-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Johns Hopkins University - Laboratory for Computational Audio Perception
|
|
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,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygaborstm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Rate-Scale-Frequency (RSF) representations from audio via bio-inspired auditory spectrograms and 2D Gabor filterbanks.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: audio,speech,auditory-model,spectro-temporal-modulation,gabor-filterbank,cochlear-model,rate-scale-frequency,signal-processing
|
|
8
|
+
Author: Johns Hopkins University - Laboratory for Computational Audio Perception
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Provides-Extra: cuda12
|
|
22
|
+
Provides-Extra: cuda13
|
|
23
|
+
Provides-Extra: viz
|
|
24
|
+
Requires-Dist: cupy-cuda12x (>=13.0,<15.0) ; (sys_platform != "darwin") and (extra == "cuda12")
|
|
25
|
+
Requires-Dist: cupy-cuda13x (>=14.0.1,<15.0) ; (sys_platform != "darwin") and (extra == "cuda13")
|
|
26
|
+
Requires-Dist: matplotlib (>=3.10,<4.0) ; extra == "viz"
|
|
27
|
+
Requires-Dist: numpy (>=2.3.3,<3.0.0)
|
|
28
|
+
Requires-Dist: psutil (>=5.9)
|
|
29
|
+
Requires-Dist: scipy (>=1.16.2,<2.0.0)
|
|
30
|
+
Requires-Dist: seaborn (>=0.13,<0.14) ; extra == "viz"
|
|
31
|
+
Project-URL: Documentation, https://pygaborstm.readthedocs.io/
|
|
32
|
+
Project-URL: Homepage, https://github.com/JHU-LCAP/PyGaborSTM
|
|
33
|
+
Project-URL: Issues, https://github.com/JHU-LCAP/PyGaborSTM/issues
|
|
34
|
+
Project-URL: Repository, https://github.com/JHU-LCAP/PyGaborSTM
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# PyGaborSTM
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/pygaborstm/)
|
|
40
|
+
[](https://pypi.org/project/pygaborstm/)
|
|
41
|
+
[](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml)
|
|
42
|
+
[](https://pygaborstm.readthedocs.io/en/latest/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
PyGaborSTM is a Python library for extracting Rate-Scale-Frequency (RSF) representations from audio signals using bio-inspired auditory spectrograms and 2D Gabor filterbanks. Documentation can be found [here](https://pygaborstm.readthedocs.io/en/latest/).
|
|
46
|
+
|
|
47
|
+
The project is `PyGaborSTM`; the package you install and import is lowercase
|
|
48
|
+
`pygaborstm`.
|
|
49
|
+
|
|
50
|
+
> **Status:** 0.1.0, a first public release. The API may still change in
|
|
51
|
+
> backwards-incompatible ways before 1.0.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install pygaborstm
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This is the CPU install and works on macOS, Linux and Windows. It pulls only
|
|
60
|
+
NumPy, SciPy and psutil.
|
|
61
|
+
|
|
62
|
+
Plotting (`pygaborstm.plot` and `pygaborstm.analysis`) needs matplotlib, which
|
|
63
|
+
is an extra so the core install stays small:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install 'pygaborstm[viz]'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### GPU (optional, NVIDIA only)
|
|
70
|
+
Pick the extra matching your CUDA version, which `nvidia-smi` reports:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install 'pygaborstm[cuda12]' # CUDA 12.x
|
|
74
|
+
pip install 'pygaborstm[cuda13]' # CUDA 13.x
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Install only one: both provide the `cupy` module. CuPy has no macOS wheels, so
|
|
78
|
+
on macOS these extras install nothing and the library runs on CPU.
|
|
79
|
+
|
|
80
|
+
Then set `use_gpu=True`:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
model = stm.PyGaborSTM(stm.Config(use_gpu=True))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If CuPy is missing or no CUDA device is present, the library warns and falls
|
|
87
|
+
back to NumPy rather than failing. Check what it actually resolved with
|
|
88
|
+
`model.device.on_gpu`.
|
|
89
|
+
|
|
90
|
+
### From source
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/JHU-LCAP/PyGaborSTM.git
|
|
93
|
+
cd PyGaborSTM
|
|
94
|
+
poetry install
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Depending on unreleased work
|
|
98
|
+
|
|
99
|
+
Nothing is published between releases. To build against `main`, pin a commit:
|
|
100
|
+
|
|
101
|
+
```toml
|
|
102
|
+
# your pyproject.toml
|
|
103
|
+
pygaborstm = { git = "https://github.com/JHU-LCAP/PyGaborSTM.git", rev = "<sha>" }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Between releases the version reads `X.Y.Z.dev0` for every commit, so it tells
|
|
107
|
+
you which release is being worked toward, not which commit you have. The commit
|
|
108
|
+
is recorded by pip at install time and read back with:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
>>> import pygaborstm as stm
|
|
112
|
+
>>> stm.build_info()
|
|
113
|
+
{'version': '0.2.0.dev0', 'source': 'git', 'commit': 'a1b2c3d...', 'url': '...'}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This works even if you pinned a branch: pip records the commit it resolved.
|
|
117
|
+
An install from PyPI reports `source: 'index'` and no commit, because there the
|
|
118
|
+
version identifies the build.
|
|
119
|
+
|
|
120
|
+
To see what a pinned commit contains relative to the last release:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git log --oneline v0.1.0..<sha>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### CUDA Toolkit
|
|
127
|
+
The CuPy wheels bundle the CUDA runtime, so a separate toolkit install is only
|
|
128
|
+
needed if you want `nvcc` and the profiling tools.
|
|
129
|
+
|
|
130
|
+
Get it from https://developer.nvidia.com/cuda-toolkit, then add to your shell
|
|
131
|
+
profile:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
export PATH=/usr/local/cuda/bin:$PATH
|
|
135
|
+
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Quick Start
|
|
139
|
+
```python
|
|
140
|
+
import pygaborstm as stm
|
|
141
|
+
|
|
142
|
+
# Create model (CPU)
|
|
143
|
+
model = stm.PyGaborSTM()
|
|
144
|
+
|
|
145
|
+
# Create model (GPU)
|
|
146
|
+
model = stm.PyGaborSTM(config=stm.Config(use_gpu=True))
|
|
147
|
+
|
|
148
|
+
# Compute spectrogram and RSF
|
|
149
|
+
spec = model.spectrogram(audio)
|
|
150
|
+
rsf = model.rsf(spec)
|
|
151
|
+
|
|
152
|
+
# Visualization
|
|
153
|
+
stm.plot.plt_spectrogram(spec)
|
|
154
|
+
stm.plot.plt_rsf(rsf)
|
|
155
|
+
stm.plot.plt_rsf(rsf, fold=True) # Symmetric folding
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
See `notebooks/example_usage.ipynb` for more examples.
|
|
159
|
+
|
|
160
|
+
### Notebooks and their audio
|
|
161
|
+
|
|
162
|
+
The notebooks are not self-contained: `data/` and `notebooks/assets/` are
|
|
163
|
+
gitignored, so the audio they read is absent from a fresh clone.
|
|
164
|
+
|
|
165
|
+
- `chi2005_validation.ipynb` regenerates most of its own stimuli, so it runs
|
|
166
|
+
after executing its generation cell.
|
|
167
|
+
- `example_usage.ipynb` and `benchmark.ipynb` need audio you supply; point them
|
|
168
|
+
at any mono file.
|
|
169
|
+
- `chi99_validation.ipynb` and `mvripfft_validation.ipynb` need the ripple set,
|
|
170
|
+
which `notebooks/nb_utils/audio_generator.py:generate_ripple_set` produces.
|
|
171
|
+
Note the default rates there are narrower than the set `chi99_validation`
|
|
172
|
+
expects, so it reproduces the method rather than the exact figure.
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
```python
|
|
176
|
+
config = stm.Config(
|
|
177
|
+
# General
|
|
178
|
+
use_gpu=False, # Enable GPU acceleration
|
|
179
|
+
sample_rate=16000, # Audio sample rate
|
|
180
|
+
|
|
181
|
+
# Spectrogram
|
|
182
|
+
n_filters=128, # Number of frequency channels
|
|
183
|
+
f_min=180.0, # Minimum frequency (Hz)
|
|
184
|
+
octaves=5.3, # Frequency range in octaves
|
|
185
|
+
|
|
186
|
+
# RSF / Gabor
|
|
187
|
+
resolution="low", # "low", "medium", "high", "ultra", "max", "overkill"
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Directory Structure
|
|
192
|
+
```
|
|
193
|
+
PyGaborSTM/
|
|
194
|
+
├── pygaborstm/
|
|
195
|
+
│ ├── __init__.py # Public API
|
|
196
|
+
│ ├── config.py # Config dataclass
|
|
197
|
+
│ ├── structs.py # Spectrogram, RSF dataclasses
|
|
198
|
+
│ ├── spectrogram.py # AuditorySpectrogram
|
|
199
|
+
│ ├── gabor.py # GaborFilterbank
|
|
200
|
+
│ ├── core.py # PyGaborSTM class
|
|
201
|
+
│ ├── plot.py # Plotting functions (needs [viz])
|
|
202
|
+
│ ├── analysis.py # MTF analysis helpers (needs [viz])
|
|
203
|
+
│ ├── backend.py # Device resolution, NumPy/CuPy switching
|
|
204
|
+
│ ├── _optional.py # Optional-dependency errors
|
|
205
|
+
│ └── gammatone_kernel.py # Custom CUDA SOS kernel
|
|
206
|
+
├── notebooks/
|
|
207
|
+
└── tests/
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
```bash
|
|
212
|
+
poetry install # Install all dependencies
|
|
213
|
+
poetry run jupyter notebook # Run notebooks
|
|
214
|
+
poetry run pytest -m "not gpu" # Run all tests excluding GPU kernel tests (used in CI/CD)
|
|
215
|
+
poetry run pytest -v # Run all tests including GPU kernel tests
|
|
216
|
+
poetry run ruff check --fix . # lint and fix
|
|
217
|
+
poetry run ruff format . # format code
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Serve Docs locally
|
|
221
|
+
```bash
|
|
222
|
+
poetry run mkdocs serve
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Note: Please lint and format before pushing, as CI will fail otherwise.
|
|
226
|
+
|
|
227
|
+
### Jupyter Kernel
|
|
228
|
+
Ensure your notebook uses the correct Poetry environment:
|
|
229
|
+
```bash
|
|
230
|
+
# Check Poetry env path
|
|
231
|
+
poetry env info --path
|
|
232
|
+
|
|
233
|
+
# Register kernel (if needed)
|
|
234
|
+
poetry run python -m ipykernel install --user --name pygaborstm
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Citing
|
|
238
|
+
|
|
239
|
+
If you use PyGaborSTM in published work, please cite the software (see
|
|
240
|
+
`CITATION.cff`, or the "Cite this repository" button on GitHub) along with the
|
|
241
|
+
papers it implements.
|
|
242
|
+
|
|
243
|
+
## References
|
|
244
|
+
- Chi, T., Ru, P., & Shamma, S. A. (2005). Multiresolution spectrotemporal analysis of complex sounds. *The Journal of the Acoustical Society of America*, 118(2), 887-906. [doi:10.1121/1.1945807](https://doi.org/10.1121/1.1945807)
|
|
245
|
+
|
|
246
|
+
The auditory spectrogram model implemented here.
|
|
247
|
+
|
|
248
|
+
- Bellur, A., & Elhilali, M. (2017). Feedback-driven sensory mapping adaptation for robust speech activity detection. *IEEE/ACM Transactions on Audio, Speech, and Language Processing*, 25(3), 481-492. [doi:10.1109/TASLP.2016.2639322](https://doi.org/10.1109/TASLP.2016.2639322)
|
|
249
|
+
|
|
250
|
+
The Gabor filterbank formulation used for the RSF stage.
|
|
251
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# PyGaborSTM
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pygaborstm/)
|
|
4
|
+
[](https://pypi.org/project/pygaborstm/)
|
|
5
|
+
[](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml)
|
|
6
|
+
[](https://pygaborstm.readthedocs.io/en/latest/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
PyGaborSTM is a Python library for extracting Rate-Scale-Frequency (RSF) representations from audio signals using bio-inspired auditory spectrograms and 2D Gabor filterbanks. Documentation can be found [here](https://pygaborstm.readthedocs.io/en/latest/).
|
|
10
|
+
|
|
11
|
+
The project is `PyGaborSTM`; the package you install and import is lowercase
|
|
12
|
+
`pygaborstm`.
|
|
13
|
+
|
|
14
|
+
> **Status:** 0.1.0, a first public release. The API may still change in
|
|
15
|
+
> backwards-incompatible ways before 1.0.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install pygaborstm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This is the CPU install and works on macOS, Linux and Windows. It pulls only
|
|
24
|
+
NumPy, SciPy and psutil.
|
|
25
|
+
|
|
26
|
+
Plotting (`pygaborstm.plot` and `pygaborstm.analysis`) needs matplotlib, which
|
|
27
|
+
is an extra so the core install stays small:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install 'pygaborstm[viz]'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### GPU (optional, NVIDIA only)
|
|
34
|
+
Pick the extra matching your CUDA version, which `nvidia-smi` reports:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install 'pygaborstm[cuda12]' # CUDA 12.x
|
|
38
|
+
pip install 'pygaborstm[cuda13]' # CUDA 13.x
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Install only one: both provide the `cupy` module. CuPy has no macOS wheels, so
|
|
42
|
+
on macOS these extras install nothing and the library runs on CPU.
|
|
43
|
+
|
|
44
|
+
Then set `use_gpu=True`:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
model = stm.PyGaborSTM(stm.Config(use_gpu=True))
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If CuPy is missing or no CUDA device is present, the library warns and falls
|
|
51
|
+
back to NumPy rather than failing. Check what it actually resolved with
|
|
52
|
+
`model.device.on_gpu`.
|
|
53
|
+
|
|
54
|
+
### From source
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/JHU-LCAP/PyGaborSTM.git
|
|
57
|
+
cd PyGaborSTM
|
|
58
|
+
poetry install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Depending on unreleased work
|
|
62
|
+
|
|
63
|
+
Nothing is published between releases. To build against `main`, pin a commit:
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
# your pyproject.toml
|
|
67
|
+
pygaborstm = { git = "https://github.com/JHU-LCAP/PyGaborSTM.git", rev = "<sha>" }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Between releases the version reads `X.Y.Z.dev0` for every commit, so it tells
|
|
71
|
+
you which release is being worked toward, not which commit you have. The commit
|
|
72
|
+
is recorded by pip at install time and read back with:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
>>> import pygaborstm as stm
|
|
76
|
+
>>> stm.build_info()
|
|
77
|
+
{'version': '0.2.0.dev0', 'source': 'git', 'commit': 'a1b2c3d...', 'url': '...'}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This works even if you pinned a branch: pip records the commit it resolved.
|
|
81
|
+
An install from PyPI reports `source: 'index'` and no commit, because there the
|
|
82
|
+
version identifies the build.
|
|
83
|
+
|
|
84
|
+
To see what a pinned commit contains relative to the last release:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git log --oneline v0.1.0..<sha>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### CUDA Toolkit
|
|
91
|
+
The CuPy wheels bundle the CUDA runtime, so a separate toolkit install is only
|
|
92
|
+
needed if you want `nvcc` and the profiling tools.
|
|
93
|
+
|
|
94
|
+
Get it from https://developer.nvidia.com/cuda-toolkit, then add to your shell
|
|
95
|
+
profile:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export PATH=/usr/local/cuda/bin:$PATH
|
|
99
|
+
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Quick Start
|
|
103
|
+
```python
|
|
104
|
+
import pygaborstm as stm
|
|
105
|
+
|
|
106
|
+
# Create model (CPU)
|
|
107
|
+
model = stm.PyGaborSTM()
|
|
108
|
+
|
|
109
|
+
# Create model (GPU)
|
|
110
|
+
model = stm.PyGaborSTM(config=stm.Config(use_gpu=True))
|
|
111
|
+
|
|
112
|
+
# Compute spectrogram and RSF
|
|
113
|
+
spec = model.spectrogram(audio)
|
|
114
|
+
rsf = model.rsf(spec)
|
|
115
|
+
|
|
116
|
+
# Visualization
|
|
117
|
+
stm.plot.plt_spectrogram(spec)
|
|
118
|
+
stm.plot.plt_rsf(rsf)
|
|
119
|
+
stm.plot.plt_rsf(rsf, fold=True) # Symmetric folding
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
See `notebooks/example_usage.ipynb` for more examples.
|
|
123
|
+
|
|
124
|
+
### Notebooks and their audio
|
|
125
|
+
|
|
126
|
+
The notebooks are not self-contained: `data/` and `notebooks/assets/` are
|
|
127
|
+
gitignored, so the audio they read is absent from a fresh clone.
|
|
128
|
+
|
|
129
|
+
- `chi2005_validation.ipynb` regenerates most of its own stimuli, so it runs
|
|
130
|
+
after executing its generation cell.
|
|
131
|
+
- `example_usage.ipynb` and `benchmark.ipynb` need audio you supply; point them
|
|
132
|
+
at any mono file.
|
|
133
|
+
- `chi99_validation.ipynb` and `mvripfft_validation.ipynb` need the ripple set,
|
|
134
|
+
which `notebooks/nb_utils/audio_generator.py:generate_ripple_set` produces.
|
|
135
|
+
Note the default rates there are narrower than the set `chi99_validation`
|
|
136
|
+
expects, so it reproduces the method rather than the exact figure.
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
```python
|
|
140
|
+
config = stm.Config(
|
|
141
|
+
# General
|
|
142
|
+
use_gpu=False, # Enable GPU acceleration
|
|
143
|
+
sample_rate=16000, # Audio sample rate
|
|
144
|
+
|
|
145
|
+
# Spectrogram
|
|
146
|
+
n_filters=128, # Number of frequency channels
|
|
147
|
+
f_min=180.0, # Minimum frequency (Hz)
|
|
148
|
+
octaves=5.3, # Frequency range in octaves
|
|
149
|
+
|
|
150
|
+
# RSF / Gabor
|
|
151
|
+
resolution="low", # "low", "medium", "high", "ultra", "max", "overkill"
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Directory Structure
|
|
156
|
+
```
|
|
157
|
+
PyGaborSTM/
|
|
158
|
+
├── pygaborstm/
|
|
159
|
+
│ ├── __init__.py # Public API
|
|
160
|
+
│ ├── config.py # Config dataclass
|
|
161
|
+
│ ├── structs.py # Spectrogram, RSF dataclasses
|
|
162
|
+
│ ├── spectrogram.py # AuditorySpectrogram
|
|
163
|
+
│ ├── gabor.py # GaborFilterbank
|
|
164
|
+
│ ├── core.py # PyGaborSTM class
|
|
165
|
+
│ ├── plot.py # Plotting functions (needs [viz])
|
|
166
|
+
│ ├── analysis.py # MTF analysis helpers (needs [viz])
|
|
167
|
+
│ ├── backend.py # Device resolution, NumPy/CuPy switching
|
|
168
|
+
│ ├── _optional.py # Optional-dependency errors
|
|
169
|
+
│ └── gammatone_kernel.py # Custom CUDA SOS kernel
|
|
170
|
+
├── notebooks/
|
|
171
|
+
└── tests/
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
```bash
|
|
176
|
+
poetry install # Install all dependencies
|
|
177
|
+
poetry run jupyter notebook # Run notebooks
|
|
178
|
+
poetry run pytest -m "not gpu" # Run all tests excluding GPU kernel tests (used in CI/CD)
|
|
179
|
+
poetry run pytest -v # Run all tests including GPU kernel tests
|
|
180
|
+
poetry run ruff check --fix . # lint and fix
|
|
181
|
+
poetry run ruff format . # format code
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Serve Docs locally
|
|
185
|
+
```bash
|
|
186
|
+
poetry run mkdocs serve
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Note: Please lint and format before pushing, as CI will fail otherwise.
|
|
190
|
+
|
|
191
|
+
### Jupyter Kernel
|
|
192
|
+
Ensure your notebook uses the correct Poetry environment:
|
|
193
|
+
```bash
|
|
194
|
+
# Check Poetry env path
|
|
195
|
+
poetry env info --path
|
|
196
|
+
|
|
197
|
+
# Register kernel (if needed)
|
|
198
|
+
poetry run python -m ipykernel install --user --name pygaborstm
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Citing
|
|
202
|
+
|
|
203
|
+
If you use PyGaborSTM in published work, please cite the software (see
|
|
204
|
+
`CITATION.cff`, or the "Cite this repository" button on GitHub) along with the
|
|
205
|
+
papers it implements.
|
|
206
|
+
|
|
207
|
+
## References
|
|
208
|
+
- Chi, T., Ru, P., & Shamma, S. A. (2005). Multiresolution spectrotemporal analysis of complex sounds. *The Journal of the Acoustical Society of America*, 118(2), 887-906. [doi:10.1121/1.1945807](https://doi.org/10.1121/1.1945807)
|
|
209
|
+
|
|
210
|
+
The auditory spectrogram model implemented here.
|
|
211
|
+
|
|
212
|
+
- Bellur, A., & Elhilali, M. (2017). Feedback-driven sensory mapping adaptation for robust speech activity detection. *IEEE/ACM Transactions on Audio, Speech, and Language Processing*, 25(3), 481-492. [doi:10.1109/TASLP.2016.2639322](https://doi.org/10.1109/TASLP.2016.2639322)
|
|
213
|
+
|
|
214
|
+
The Gabor filterbank formulation used for the RSF stage.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""PyGaborSTM: spectro-temporal modulation analysis.
|
|
2
|
+
|
|
3
|
+
Extracts Rate-Scale-Frequency (RSF) representations from audio via a
|
|
4
|
+
bio-inspired auditory spectrogram followed by a 2D Gabor filterbank,
|
|
5
|
+
following Chi, Ru & Shamma (2005) and Bellur & Elhilali (2017).
|
|
6
|
+
|
|
7
|
+
Public API
|
|
8
|
+
----------
|
|
9
|
+
PyGaborSTM
|
|
10
|
+
Main user-facing class. Use :meth:`PyGaborSTM.compute` for the full
|
|
11
|
+
pipeline, or :meth:`PyGaborSTM.spectrogram` and
|
|
12
|
+
:meth:`PyGaborSTM.rsf` for the individual stages.
|
|
13
|
+
Config
|
|
14
|
+
Configuration dataclass for all pipeline parameters.
|
|
15
|
+
Spectrogram, RSF
|
|
16
|
+
Output dataclasses returned by the corresponding stages.
|
|
17
|
+
structs
|
|
18
|
+
Namespaced submodule of the output dataclasses.
|
|
19
|
+
plot, analysis
|
|
20
|
+
Namespaced submodules, imported on first attribute access. They need
|
|
21
|
+
the plotting extra::
|
|
22
|
+
|
|
23
|
+
pip install 'pygaborstm[viz]'
|
|
24
|
+
|
|
25
|
+
Importing :mod:`pygaborstm` itself never imports matplotlib.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
>>> import pygaborstm as stm
|
|
30
|
+
>>> model = stm.PyGaborSTM(config=stm.Config(use_gpu=True))
|
|
31
|
+
>>> spec = model.spectrogram(audio)
|
|
32
|
+
>>> rsf = model.rsf(spec)
|
|
33
|
+
>>> stm.plot.plt_spectrogram(spec) # needs pygaborstm[viz]
|
|
34
|
+
>>> stm.plot.plt_rsf(rsf)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from importlib import import_module
|
|
40
|
+
from importlib.metadata import PackageNotFoundError
|
|
41
|
+
from importlib.metadata import version as _dist_version
|
|
42
|
+
from types import ModuleType
|
|
43
|
+
from typing import TYPE_CHECKING
|
|
44
|
+
|
|
45
|
+
from . import structs
|
|
46
|
+
from ._optional import VIZ_MODULES, missing_extra_message
|
|
47
|
+
from .config import Config
|
|
48
|
+
from .core import PyGaborSTM
|
|
49
|
+
from .structs import RSF, Spectrogram
|
|
50
|
+
|
|
51
|
+
# Resolved by type checkers and IDEs without importing matplotlib at runtime.
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
# Redundant aliases mark these as re-exports; they are public but kept out
|
|
54
|
+
# of __all__ so star-import does not require the viz extra.
|
|
55
|
+
from . import analysis as analysis
|
|
56
|
+
from . import plot as plot
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
__version__ = _dist_version("pygaborstm")
|
|
60
|
+
except PackageNotFoundError: # source tree that was never installed
|
|
61
|
+
__version__ = "0.0.0.dev0"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_info() -> dict[str, str | None]:
|
|
65
|
+
"""Where this installation came from.
|
|
66
|
+
|
|
67
|
+
Between releases the version alone does not identify a build, since every
|
|
68
|
+
commit reports the same ``X.Y.Z.dev0``. This reads the record pip writes at
|
|
69
|
+
install time (PEP 610 ``direct_url.json``) to recover the exact commit.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
dict
|
|
74
|
+
``version`` always. ``source`` is one of ``"git"``, ``"vcs"``,
|
|
75
|
+
``"archive"``, ``"local"``, ``"editable"``, ``"index"`` or
|
|
76
|
+
``"unknown"``. ``commit`` is the resolved sha for a git install and
|
|
77
|
+
None otherwise; ``url`` is the origin when one was recorded.
|
|
78
|
+
|
|
79
|
+
Examples
|
|
80
|
+
--------
|
|
81
|
+
>>> import pygaborstm as stm
|
|
82
|
+
>>> stm.build_info()["commit"]
|
|
83
|
+
'a1b2c3d4e5f6...'
|
|
84
|
+
|
|
85
|
+
Notes
|
|
86
|
+
-----
|
|
87
|
+
A git install records the commit pip actually resolved, so pinning a branch
|
|
88
|
+
still yields a concrete sha. Released installs from an index carry no such
|
|
89
|
+
record: there the version is the identifier.
|
|
90
|
+
|
|
91
|
+
This never raises. Anything it cannot establish is reported as
|
|
92
|
+
``"unknown"`` rather than guessed, because a confident wrong answer about
|
|
93
|
+
provenance is worse than admitting ignorance.
|
|
94
|
+
"""
|
|
95
|
+
info: dict[str, str | None] = {
|
|
96
|
+
"version": __version__,
|
|
97
|
+
"source": "unknown",
|
|
98
|
+
"commit": None,
|
|
99
|
+
"url": None,
|
|
100
|
+
}
|
|
101
|
+
try:
|
|
102
|
+
return _provenance(info)
|
|
103
|
+
except Exception: # pragma: no cover - diagnostics must never break import
|
|
104
|
+
return info
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _record_describes_this_import(dist) -> bool:
|
|
108
|
+
"""True if ``dist``'s files are the ones that actually got imported."""
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
located = dist.locate_file("pygaborstm/__init__.py")
|
|
113
|
+
if located is None:
|
|
114
|
+
return False
|
|
115
|
+
return Path(located).resolve() == Path(__file__).resolve()
|
|
116
|
+
except Exception:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _provenance(info: dict[str, str | None]) -> dict[str, str | None]:
|
|
121
|
+
"""Fill in ``info`` from the installed distribution's PEP 610 record."""
|
|
122
|
+
import json
|
|
123
|
+
from importlib.metadata import PackageNotFoundError, distribution
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
dist = distribution("pygaborstm")
|
|
127
|
+
except (PackageNotFoundError, ValueError):
|
|
128
|
+
# A source tree that was never installed. Not an index install.
|
|
129
|
+
return info
|
|
130
|
+
|
|
131
|
+
raw = dist.read_text("direct_url.json")
|
|
132
|
+
if raw is None:
|
|
133
|
+
# Installed, but with no direct URL recorded: came from an index.
|
|
134
|
+
info["source"] = "index"
|
|
135
|
+
return info
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
record = json.loads(raw)
|
|
139
|
+
except ValueError:
|
|
140
|
+
return info
|
|
141
|
+
if not isinstance(record, dict):
|
|
142
|
+
return info
|
|
143
|
+
|
|
144
|
+
url = record.get("url")
|
|
145
|
+
info["url"] = url if isinstance(url, str) else None
|
|
146
|
+
|
|
147
|
+
vcs_info = record.get("vcs_info")
|
|
148
|
+
dir_info = record.get("dir_info")
|
|
149
|
+
if isinstance(vcs_info, dict):
|
|
150
|
+
# PEP 610 allows hg, bzr and svn too; only git carries a sha we can
|
|
151
|
+
# hand back as a commit.
|
|
152
|
+
if vcs_info.get("vcs") == "git":
|
|
153
|
+
commit = vcs_info.get("commit_id")
|
|
154
|
+
info["source"] = "git"
|
|
155
|
+
info["commit"] = commit if isinstance(commit, str) else None
|
|
156
|
+
if not _record_describes_this_import(dist):
|
|
157
|
+
# Metadata lookup returns the first match on sys.path, which
|
|
158
|
+
# need not be the copy that got imported. A commit from some
|
|
159
|
+
# other installation is worse than no commit at all.
|
|
160
|
+
info["source"] = "unknown"
|
|
161
|
+
info["commit"] = None
|
|
162
|
+
else:
|
|
163
|
+
info["source"] = "vcs"
|
|
164
|
+
elif isinstance(dir_info, dict):
|
|
165
|
+
info["source"] = "editable" if dir_info.get("editable") else "local"
|
|
166
|
+
elif isinstance(record.get("archive_info"), dict):
|
|
167
|
+
info["source"] = "archive"
|
|
168
|
+
return info
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
#: Submodules imported on first attribute access (PEP 562), so that
|
|
172
|
+
#: `import pygaborstm` costs numpy and scipy and nothing else.
|
|
173
|
+
_LAZY_SUBMODULES = frozenset({"plot", "analysis"})
|
|
174
|
+
|
|
175
|
+
__all__ = [ # noqa: RUF022 - grouped by meaning, not alphabetised
|
|
176
|
+
# Main class
|
|
177
|
+
"PyGaborSTM",
|
|
178
|
+
# Config
|
|
179
|
+
"Config",
|
|
180
|
+
# Data structures
|
|
181
|
+
"Spectrogram",
|
|
182
|
+
"RSF",
|
|
183
|
+
# Namespaced modules. plot and analysis are deliberately absent: they
|
|
184
|
+
# are reachable as attributes, but listing them here would make
|
|
185
|
+
# `from pygaborstm import *` require the viz extra.
|
|
186
|
+
"structs",
|
|
187
|
+
# Metadata
|
|
188
|
+
"__version__",
|
|
189
|
+
"build_info",
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def __getattr__(name: str) -> ModuleType:
|
|
194
|
+
if name not in _LAZY_SUBMODULES:
|
|
195
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
196
|
+
try:
|
|
197
|
+
module = import_module(f".{name}", __name__)
|
|
198
|
+
except ImportError as exc:
|
|
199
|
+
# Only translate a missing optional dependency. A genuine ImportError
|
|
200
|
+
# from inside plot.py or analysis.py must surface unchanged.
|
|
201
|
+
missing = (exc.name or "").split(".")[0]
|
|
202
|
+
if missing not in VIZ_MODULES:
|
|
203
|
+
raise
|
|
204
|
+
raise ImportError(
|
|
205
|
+
missing_extra_message(missing, extra="viz", feature=f"pygaborstm.{name}")
|
|
206
|
+
) from exc
|
|
207
|
+
globals()[name] = module
|
|
208
|
+
return module
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def __dir__() -> list[str]:
|
|
212
|
+
# _LAZY_SUBMODULES explicitly: they are public and should be discoverable
|
|
213
|
+
# before they have been touched, even though they are not in __all__.
|
|
214
|
+
return sorted(set(globals()) | set(__all__) | _LAZY_SUBMODULES)
|