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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/pygaborstm)](https://pypi.org/project/pygaborstm/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/pygaborstm)](https://pypi.org/project/pygaborstm/)
41
+ [![CI](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml/badge.svg)](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml)
42
+ [![Docs](https://readthedocs.org/projects/pygaborstm/badge/?version=latest)](https://pygaborstm.readthedocs.io/en/latest/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/pygaborstm)](https://pypi.org/project/pygaborstm/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/pygaborstm)](https://pypi.org/project/pygaborstm/)
5
+ [![CI](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml/badge.svg)](https://github.com/JHU-LCAP/PyGaborSTM/actions/workflows/ci.yml)
6
+ [![Docs](https://readthedocs.org/projects/pygaborstm/badge/?version=latest)](https://pygaborstm.readthedocs.io/en/latest/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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)