python-peass
This project was ported by Gemini 3.5 Flash from https://gitlab.inria.fr/bass-db/peass/-/tree/22c7fc4ef670f8bb6eea9ab4abea98323006b769/v2.0.1
A Python port of the PEASS v2.0.1 (Perceptual Evaluation methods for Audio Source Separation) toolkit [1].
Installation
For standard execution, you can install the package directly:
pip install "python-peass[numba]"
If you require high-speed execution (using optimized vector libraries like Intel MKL or Apple Accelerate), it is recommended to install NumPy and SciPy via Conda first, and then install the package:
conda install numpy scipy
pip install "python-peass[numba]"
Intel OpenMP conflict on Windows
If you install PyTorch as a PyPI wheel into a conda environment whose NumPy is built against MKL — the exact combination the section above recommends — the process can abort on the first call:
OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
Fatal Python error: Aborted
conda's MKL ships Library/bin/libiomp5md.dll and the torch wheel ships its own copy
in site-packages/torch/lib; whichever initializes second kills the interpreter. There
is no Python traceback, and the abort surfaces inside whichever MKL routine happened to
trigger the second initialization, so it reads like a numerical bug in the backend
rather than a link-time collision.
Merely having torch installed is not enough to trigger it. Dispatch decides whether
an input is a tensor by consulting sys.modules, so a process that never imports torch
never loads the second runtime and NumPy-only usage is unaffected. (Dispatch used to
import torch unconditionally, which made this abort reachable from pure-NumPy code that
never asked for it; test_numpy_dispatch_does_not_import_torch guards against the
regression.) You are exposed once torch is genuinely in the process — using the PyTorch
backend, or importing torch yourself alongside an MKL-backed NumPy.
The real fix is to leave only one OpenMP runtime in the environment, e.g. by taking NumPy and PyTorch from the same toolchain instead of mixing conda MKL with a PyPI wheel. Failing that, set one of these before the first import (both runtimes read their configuration at load time, so setting it afterwards is too late):
| workaround | supported? | cost |
|---|---|---|
KMP_DUPLICATE_LIB_OK=TRUE |
no — Intel documents it as an unsafe escape hatch | none measured; this is what the project's results are recorded under |
MKL_THREADING_LAYER=SEQUENTIAL |
yes | single-threads MKL; shifts the last digit of BLAS reductions (~1e-14 relative) |
The test suite sets KMP_DUPLICATE_LIB_OK for you in the root conftest.py, so
pytest works out of the box; an explicit setting in your environment overrides it.
Application code gets no such help — set it yourself, or fix the environment.
Quick Start Examples
1. Perceptual Quality Score Evaluation
Evaluate estimated audio files saved on disk:
from peass import predict_perceptual_evaluation_scores
original_files = [
"audio/target_source.wav",
"audio/interference_1.wav",
"audio/interference_2.wav"
]
estimate_file = "audio/estimated_target.wav"
scores = predict_perceptual_evaluation_scores(original_files, estimate_file)
print(f"Overall Perceptual Score (OPS): {scores.overall_perceptual_score:.1f}/100")
print(f"Target Preservation Score (TPS): {scores.target_perceptual_score:.1f}/100")
print(f"Interference Rejection (IPS): {scores.interference_perceptual_score:.1f}/100")
print(f"Artifact-free Score (APS): {scores.artifact_perceptual_score:.1f}/100")
2. Score Evaluation with Waveform and File Expositions
To run the full perceptual scoring pipeline and simultaneously output the physically separated WAV files (True target, Target distortion, Interference, and Artifacts) to a specific output folder:
from peass import predict_perceptual_evaluation_scores, DecompositionConfiguration
original_files = [
"audio/target_source.wav",
"audio/interferer.wav"
]
estimate_file = "audio/estimated_target.wav"
# 1. Configure the output directory for file-writing
config = DecompositionConfiguration(destination_directory="./output_directory/")
# 2. Set return_decomposition=True to expose the physical waveforms and file paths
scores = predict_perceptual_evaluation_scores(
original_files,
estimate_file,
configuration=config,
return_decomposition=True
)
# 3. Print overall scores
print(f"Overall Perceptual Score (OPS): {scores.overall_perceptual_score:.1f}/100")
# 4. Access the file paths of the generated WAV files on disk
print(f"True target file saved at: {scores.decomposition_files.true_target}")
print(f"Interference file saved at: {scores.decomposition_files.interference}")
print(f"Artifacts file saved at: {scores.decomposition_files.artifacts}")
# 5. Read the raw NumPy arrays directly from memory
target_distortion_array = scores.decomposition_waveforms.target_distortion
3. Independent Subband Least-Squares Decomposition
You can run the auditory Gammatone/least-squares decomposition engine independently to obtain the isolated physical sub-components:
import numpy as np
from peass import decompose_distortion_components
# In-memory arrays
target_array = np.random.randn(16000, 1)
noise_array = np.random.randn(16000, 1)
estimate_array = target_array + 0.05 * noise_array
# Run subband least-squares decomposer
result = decompose_distortion_components(
source_files=[target_array, noise_array],
estimate_file=estimate_array,
sampling_frequency_hz=16000.0
)
waveforms = result.waveforms
true_target, target_distortion, interference, artifacts = (
waveforms.true_target,
waveforms.target_distortion,
waveforms.interference,
waveforms.artifacts
)
Scientific Highlights
Traditional evaluation metrics rely purely on linear energy ratios [1]. However, human hearing relies on non-linear auditory transduction, temporal masking, and cognitive thresholds [2]. This package replaces traditional energy ratio metrics (SDR, SIR, SAR) with perceptually motivated objective scores— OPS, TPS, IPS, and APS—which align closely with subjective human listening evaluations [1].
peass executes a multi-stage cognitive simulation pipeline to assess separation quality:
- Subband Least-Squares Decomposition: Signals are divided into subbands using a complex-valued Hohmann Gammatone Filterbank [1, 3]. Overlapping temporal frames are projected onto estimated subspaces to isolate physical target distortion, interference, and artifact components [1].
- Inner Hair Cell Transduction: Approximates the shearing limits of physical hair bundles via half-wave rectification and first-order 1 kHz membrane-limit lowpass filters [1, 2].
- Auditory Nerve Adaptation: Models physiological forward masking and metabolic neural depletion via five cascaded stages of non-linear feedback loops [2].
- Perceptual Assimilation: Models cognitive threshold masking where noise below a target reference threshold is partially assimilated or masked [2].
- Score Prediction:
Feeds weighted similarity percentiles into a multi-criteria trained sigmoidal neural network to output scores scaled
from
0to100[1].
Test Suite & CI/CD
Installation for Development & Testing
The validation suite implements rigorous numerical, physical, and integration checks.
You will need to pip install -r requirements.txt to set up.
You should also install the package in editable mode along with its dependencies:
pip install -e .
pytest -n auto --cov=peass --cov-report=json
Alternatively, if you want to run tests without installing the package, run pytest as a Python module:
python -m pytest -n auto --cov=peass --cov-report=json
Regression Verification
We test our output waveforms directly against the original .wav reference waveforms generated by the official MATLAB
PEASS toolbox (located in references/peass_master_22c7fc4e/v2.0.1/example/).
Python's outputs must achieve a cross-correlation coefficient exceeding $0.95$ with the MATLAB reference to pass.
NumPy vs PyTorch backends
The package dispatches to a NumPy backend for array/file inputs and a PyTorch
backend for tensor inputs (selected automatically by input type). The NumPy
backend is the numerical reference and matches the MATLAB toolbox very closely
(cross-correlation > 0.999 with the default full-order resampling; lower
DecompositionConfiguration.resample_filter_half_length_factor from 10 toward
3 to trade a little fidelity for ~25% faster decomposition). The PyTorch backend is designed to be fully
differentiable (usable inside a training loop): it replaces the hard
non-linearities and IIR recursions of the reference with smooth, backprop-safe
surrogates (softplus, FIR-truncated filters). As a result its outputs match the
NumPy backend by high correlation rather than to floating-point precision.
NumPy backend performance and numerical reproducibility
The NumPy backend carries a few single-threaded optimizations (no threads or subprocesses are spawned; the speedup comes from SIMD and from removing per-call overhead). Worth ~1.2x end-to-end on the reference example:
| change | speedup contribution | bitwise effect |
|---|---|---|
Numba polyphase resampler replacing SciPy upfirdn |
~1.13x | reassociated, see below |
LAPACK ?posv called directly instead of scipy.linalg.solve(assume_a='pos') |
11x on that call | bit-identical |
sources pre-padded once instead of a per-frame np.vstack |
— | bit-identical |
| hoisted the conjugate transpose that was built twice per frame | — | bit-identical |
| one block-diagonal matmul instead of one per source | — | ~1 ULP (1.9e-15) |
None of these is an approximation: every one computes the same quantity in exact arithmetic. Two of them reassociate floating-point sums, and floating-point addition is not associative, so output is not bitwise identical to older releases.
The observed difference is ~2e-11 absolute (~1e-10 of full scale). It looks larger in
relative terms for artifacts (~1e-9) purely because that component is a difference
of comparable quantities, so cancellation shrinks the denominator; true_target shows
2.5e-15. Correlation against the previous output is 1.0 to all 15 digits, and the four
quality features (and hence OPS/TPS/IPS/APS) agree to 10 significant figures.
To remove the dominant term, disable the Numba resampler before the first call:
import peass.backend_numpy.gammatone as gammatone
gammatone.USE_NUMBA_RESAMPLER = False
That falls back to the SciPy path (itself bit-exact against the pre-optimization
resampler) and costs the ~1.13x. Measured against the pre-optimization output, this
takes the difference from 2.3e-11 down to 1.9e-15 — not to zero, because the
block-diagonal projection matmul in perform_least_squares_projection also
reassociates, at about 1 ULP. If you need exactly zero difference, revert that hunk
too; it is worth ~1.03x on its own.
Do not instead edit fastmath=False onto the resampler kernels: that is bit-exact
but measures ~5% slower than SciPy, so it loses both ways, and it requires
clearing Numba's on-disk cache to take effect.
Note that this backend was never bitwise reproducible across machines: any BLAS build or CPU that reassociates differently is subject to the same ~1e-10 amplification, because the per-frame least-squares systems are ill-conditioned (regularized at only 1e-15). Treat ~1e-10 as the backend's reproducibility floor rather than expecting exactness.
Parallel Execution
To run the test suite across multiple CPU cores using pytest-xdist, execute:
pytest -n auto
Known deviations from the MATLAB reference
segmentation_factor is accepted but ignored
DecompositionConfiguration.segmentation_factor exists for API compatibility with the
MATLAB options.segmentationFactor, but nothing in this package reads it. Setting it
to anything other than 1 silently has no effect: the signal is always decomposed in one
piece.
MATLAB takes a separate code path when segmentationFactor > 1
(extractDistortionComponents.m, the branch at ~lines 107-110 dispatching to
aux_segmentAndDecompose at ~lines 270-386): it cuts the signal into that many segments,
decomposes each independently with the shade-in/out suppressed on interior edges, and
overlap-adds the four components under a periodic Hann window, dividing by the accumulated
window. That path was never ported. It is tracked in TODO.md.
+0.257% level offset against the MATLAB gold WAVs
The port's decomposition output is a flat, frequency-independent factor 1.0025651
(+0.0223 dB) larger in amplitude than the MATLAB v2.0.1 gold WAVs in
tests/resources/matlab_reference/, on all four output components. This is understood,
deliberate, and not fixed.
The cause is resampler filter normalization. scipy.signal.firwin defaults to
scale=True, which normalizes the Kaiser-windowed sinc to exactly unit DC gain
(peass/backend_numpy/gammatone.py:707, peass/backend_torch/utils.py:85). MATLAB's
resample filter is not DC-normalized; its raw DC gain is 0.9993253. The decomposition
performs four resamples per signal path (16k→24k, decimate by Ndec, interpolate by
Ndec, 24k→16k), so MATLAB accumulates:
| step | MATLAB filter DC gain |
|---|---|
| 16k → 24k and 24k → 16k | 0.999394194 each |
decimate by Ndec and interpolate by Ndec |
0.999325320 each |
| product | 0.997441484 |
| reciprocal | 1.00256514 |
against a measured 1.0025651. Because the filter half-length is 10*pqmax and the cutoff
is 1/(2*pqmax), the tap set is scale-invariant in pqmax, so the factor is identical for
every Ndec from 14 to 409 — which is why the discrepancy is perfectly frequency-flat
rather than a filter-shape difference.
Confirmed by a literal line-by-line MATLAB transcription: flipping only that one normalization moves the ratio from 1.0025651 to 1.0000001, and the remaining 5.4e-4 residual is pure PCM16 quantization noise (0.575 LSB rms measured, 0.577 expected for a sum of four independent 16-bit roundings, white spectrum).
Note also that the gold WAVs are byte-identical to the precomputed outputs shipped in the
PEASS distribution's example/ folder, and the v2.0 and v2.0.1 shipped outputs are
identical to each other — the authors never regenerated them for 2.0.1. They are stale
artifacts, not a fresh run of the v2.0.1 source.
Why it is not fixed: a resampler should preserve DC gain. Matching the reference would mean deliberately introducing a 0.26% (0.022 dB) attenuation to chase a reference that is itself arguably wrong.
The offset originally went unflagged because the regression test asserted on
cross-correlation, which is scale-invariant, plus a 0.5 < ratio < 2.0 sanity band that
0.26% passes trivially. tests/regression/test_matlab_regression.py now asserts the RMS
ratio equals 1.0025651 to within 1e-3 instead, so the known offset is locked rather than
merely tolerated: any new gain regression, in either direction, breaks the test. The worst
measured deviation from the constant is 1.5e-5, on the artifacts component — the quietest
of the four, and so the one whose gold WAV sits closest to the PCM16 quantization floor.
References
- V. Emiya, E. Vincent, N. Harlander, and V. Hohmann, "Subjective and objective quality assessment of audio source separation", IEEE Transactions on Audio, Speech, and Language Processing, 19(7):2046–2057, 2011.
- R. Huber and B. Kollmeier, "PEMO-Q — A New Method for Objective Audio Quality Assessment Using a Model of Auditory Perception", IEEE Transactions on Audio, Speech, and Language Processing, 14(6):1902–1911, 2006.
- V. Hohmann, "Frequency analysis and synthesis using a Gammatone filterbank", Acustica/Acta Acustica, 88(3):433–442, 2002.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_peass-2.0.1.5.tar.gz.
File metadata
- Download URL: python_peass-2.0.1.5.tar.gz
- Upload date:
- Size: 75.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a39997c3863adb7522e4d38d5150ef659871b6639b262bca1ec581d9c998e14
|
|
| MD5 |
8abffe538f4d4e00e22182c5b24626b0
|
|
| BLAKE2b-256 |
8b1a477abf761ccdea89ef2d44c67b92fd77aa87613a4681734a9d8c607071c5
|
Provenance
The following attestation bundles were made for python_peass-2.0.1.5.tar.gz:
Publisher:
release.yml on averykhoo/python-peass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_peass-2.0.1.5.tar.gz -
Subject digest:
2a39997c3863adb7522e4d38d5150ef659871b6639b262bca1ec581d9c998e14 - Sigstore transparency entry: 2342138199
- Sigstore integration time:
-
Permalink:
averykhoo/python-peass@4ab223a9451a8a6980189fcaaa25044c3c3d1dc0 -
Branch / Tag:
refs/tags/v2.0.1.5 - Owner: https://github.com/averykhoo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4ab223a9451a8a6980189fcaaa25044c3c3d1dc0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file python_peass-2.0.1.5-py3-none-any.whl.
File metadata
- Download URL: python_peass-2.0.1.5-py3-none-any.whl
- Upload date:
- Size: 78.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbffb87b645b5259087811b476c403e07f2bfb12bf0bab1a7f016fd5459d17b8
|
|
| MD5 |
4351a4f6896911a0452f32bbbafbe338
|
|
| BLAKE2b-256 |
aeaec2b2d1fd512db18dcacab5d1c2c66cd0e7c1c791388bb10561e908fa2442
|
Provenance
The following attestation bundles were made for python_peass-2.0.1.5-py3-none-any.whl:
Publisher:
release.yml on averykhoo/python-peass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_peass-2.0.1.5-py3-none-any.whl -
Subject digest:
bbffb87b645b5259087811b476c403e07f2bfb12bf0bab1a7f016fd5459d17b8 - Sigstore transparency entry: 2342138205
- Sigstore integration time:
-
Permalink:
averykhoo/python-peass@4ab223a9451a8a6980189fcaaa25044c3c3d1dc0 -
Branch / Tag:
refs/tags/v2.0.1.5 - Owner: https://github.com/averykhoo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4ab223a9451a8a6980189fcaaa25044c3c3d1dc0 -
Trigger Event:
push
-
Statement type: