pyLeman2000
Python wrapper for Leman's (2000) tonal contextuality model.
The model was published in Music Perception and accounts for the
Krumhansl–Kessler probe-tone data (Leman, 2000). This package runs it in
Docker: by default a compiled MATLAB Runtime worker, with an optional
license-free GNU Octave backend. It is a Python port of
leman2000R.
Requirements
- Python 3.10+
- Docker installed and running
(
docker infoshould succeed)
The default image is pulled automatically on first use.
Installation
python3 -m pip install pyLeman2000
Quick start
from pyleman2000 import example_wav_path, leman2000
result = leman2000(
input_file=example_wav_path(),
local_decay_sec=[0.1, 0.5],
global_decay_sec=[1.0, 2.0],
windows=[(0.0, 0.1), (0.1, 0.2)],
)
result.audio_length_sec, result.num_channels, result.sample_rate
(0.3707936508, 1, 44100.0)
result.local_global_comparison.head()
local_decay_sec global_decay_sec time_sec running_correlation
0 0.1 1.0 0.000000 1.000000
1 0.1 1.0 0.014832 0.999999
2 0.1 1.0 0.029663 0.999998
3 0.1 1.0 0.044495 0.999996
4 0.1 1.0 0.059327 0.999993
leman2000 returns a Leman2000Result with:
audio_length_sec,num_channels,sample_ratelocal_global_comparison— running local/global correlations over timewindowed_local_global_comparison— optional windowed averagesauditory_nerve/periodicity_pitch— optional heavy intermediates (omit unless you need them)
Input files must use a .wav extension (standard PCM WAV works best, for
example mono or stereo, 16-bit, 44.1 kHz).
Choosing parameters
There is not much clarity in the literature on which local/global decay
parameters are best. Previous work has therefore grid-searched many
combinations: pass sequences for local_decay_sec and global_decay_sec to
obtain all pairwise results. See Bigand et al. (2014) for an example.
local_global_comparison is the running correlation between local and global
tonal images. Higher values mean the short-term and longer-term
representations are more similar at that moment. Optional windows summarise
those correlations over closed time intervals (for example chord or phrase
spans). Unlike leman2000R's half-open [start, end), both endpoints are
included here.
Analysing many files
For many files, use leman2000_batch: it opens a worker pool, picks a worker
count from the backend, total audio duration, CPU count, and available RAM
(override with workers= or PYLEMAN2000_WORKERS), shows progress, and
returns stacked DataFrames:
from pyleman2000 import example_wav_path, leman2000_batch
paths = [example_wav_path(), example_wav_path()]
batch = leman2000_batch(
paths,
local_decay_sec=0.1,
global_decay_sec=1.0,
)
batch.files
batch.local_global_comparison.head()
Batch progress is shown by default. Pass progress=False to disable it, or
provide an object implementing start(n_files, n_workers),
update(completed), and close() to bridge progress into another UI.
batch.files always has one row per input and an ok or error status.
Its audio metadata columns use pandas nullable numeric dtypes, so their schema
stays stable when metadata is unavailable. batch.results is input-aligned
and contains each per-file Leman2000Result when you need keep_* payloads.
By default, an error in any file stops the batch. Pass
continue_on_error=True to finish the remaining files instead. The failed
file keeps its input-aligned position as None in batch.results, and
batch.failures records its file ID, path, exception type, message, and
original exception object. Aggregate correlation tables contain successful
files and retain their original file IDs. When batch progress is enabled, a
continued batch also prints a short failure summary to standard error, so the
completed N/N count does not conceal errors.
For lower-level pool use, Leman2000Pool.map() remains fail-fast, while
Leman2000Pool.map_with_errors() processes every file and returns aligned
(results, errors) lists. A timeout, dead container, or other
Leman2000WorkerError restarts that worker before reuse; ordinary request
errors keep the healthy warm worker running.
Extra workers only pay off once each has enough audio to offset the ~5 s
MATLAB worker startup, so short MATLAB files stay sequential by default.
Octave starts for every file and parallelizes even short batches, but its
memory grows much faster with audio length. See
artifacts/benchmark/batch_scaling.md and
artifacts/benchmark/batch_scaling_octave.md for the measurements behind
these choices.
Passing workers= (or PYLEMAN2000_WORKERS) overrides the automatic choice
and is honoured as given, so it can exceed what memory comfortably allows.
Octave backend
Pass backend="octave" for the license-free image:
from pyleman2000 import example_wav_path, leman2000
result = leman2000(
input_file=example_wav_path(),
local_decay_sec=0.1,
global_decay_sec=1.0,
backend="octave",
)
Developer notes
Local development
python3 -m pip install -e ".[dev]"
From a specific Git tag:
python3 -m pip install git+https://github.com/cms-cambridge/pyLeman2000.git@v0.3.0
Optional pre-pull of the default MATLAB image:
docker pull "$(python3 -c 'from pyleman2000 import DEFAULT_MATLAB_IMAGE; print(DEFAULT_MATLAB_IMAGE)')"
Platform and Docker details
The default image is
ghcr.io/cms-cambridge/pyleman2000-matlab
(linux/amd64), digest-pinned as DEFAULT_MATLAB_IMAGE. Pass
backend="octave" for the Octave image built from docker/octave/ against a
pinned cms-cambridge/IPEMToolbox
commit.
Input and output files are copied in and out of the container rather than bind-mounted, so no Docker file-sharing configuration is needed.
On Apple Silicon, enable Docker Desktop's amd64/Rosetta or QEMU emulation,
then verify with docker run --platform=linux/amd64 --rm hello-world.
Emulated runs are slower than native amd64. On 44.1 kHz input, running
correlations typically agree with archived MATLAB/R snapshots to about
3e-6 (not bit-identical); feed 22.05 kHz audio for closer
cross-implementation agreement.
Silence single-analysis pull/run progress with show_progress=False.
Building images locally
Octave:
./scripts/build_octave_image.sh
# then: leman2000(..., backend="octave", docker_image="pyleman2000-octave:dev")
MATLAB (needs MATLAB Compiler on Linux amd64):
./scripts/build_matlab_image.sh # local pyleman2000-matlab:dev
./scripts/build_matlab_image.sh --tag 0.2.0 --push
Local pyleman2000-octave:* / pyleman2000-matlab:* tags are never pulled
from a registry; if missing you get an error pointing at the build script.
Octave publishing to GHCR is handled by .github/workflows/docker-publish.yml
(linux/amd64). Pushes to main refresh :dev; manual workflow dispatch
publishes version tags such as :0.2.0 (and :latest). Package defaults are
digest pins of the 0.2.0 images. See docker/matlab/README.md for the MATLAB
worker protocol.
Tests
Unit tests do not require Docker:
python3 -m pytest -v -m "not integration and not matlab"
Octave integration tests:
docker pull "$(python3 -c 'from pyleman2000 import DEFAULT_IMAGE; print(DEFAULT_IMAGE)')"
# or: ./scripts/build_octave_image.sh && use docker_image='pyleman2000-octave:dev'
python3 -m pytest -v -m integration
Compiled MATLAB smoke tests (no Compiler needed to run them):
docker pull "$(python3 -c 'from pyleman2000 import DEFAULT_MATLAB_IMAGE; print(DEFAULT_MATLAB_IMAGE)')"
python3 -m pytest -v -m matlab
Snapshot CSVs under tests/snapshots/ were generated from
leman2000R via
scripts/generate_r_snapshots.R (MATLAB backend). CI compares against those
archives with a looser tolerance (~1e-5).
Result object notes
Leman2000Result is frozen (attribute reassignment is blocked), but embedded
DataFrames remain mutable through pandas APIs. Constructor inputs are copied
so later edits to caller-owned objects do not alter the result. Equality is
identity-based; compare DataFrames or fields explicitly when needed.
window_id is 1-based, and windowed rows are ordered window-major.
References
Bigand, E., Delbé, C., Poulin-Charronnat, B., Leman, M., & Tillmann, B. (2014). Empirical evidence for musical syntax processing? Computer simulations reveal the contribution of auditory short-term memory. Frontiers in Systems Neuroscience, 8, 94. https://doi.org/10.3389/fnsys.2014.00094
Leman, M. (2000). An auditory model of the role of short-term memory in probe-tone ratings. Music Perception, 17(4), 481–509.
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 pyleman2000-0.3.0.tar.gz.
File metadata
- Download URL: pyleman2000-0.3.0.tar.gz
- Upload date:
- Size: 134.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8d4a76fb96ecca778269a366643545e34f171b43540d054646720f69d532b92
|
|
| MD5 |
ea5323e87d55edc62d82790553eb8e7e
|
|
| BLAKE2b-256 |
81819e44f2e0846d3897ced292e60d35e2682335ecaa2f06da9262a3d4220cd8
|
Provenance
The following attestation bundles were made for pyleman2000-0.3.0.tar.gz:
Publisher:
publish-pypi.yml on cms-cambridge/pyLeman2000
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyleman2000-0.3.0.tar.gz -
Subject digest:
a8d4a76fb96ecca778269a366643545e34f171b43540d054646720f69d532b92 - Sigstore transparency entry: 2560916014
- Sigstore integration time:
-
Permalink:
cms-cambridge/pyLeman2000@1ef838707d50366e0271327a619ec5d7d803a1b2 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cms-cambridge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@1ef838707d50366e0271327a619ec5d7d803a1b2 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyleman2000-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pyleman2000-0.3.0-py3-none-any.whl
- Upload date:
- Size: 63.1 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 |
981201f136f3d63b060eb8c2c83f4934c1387865286a239838d29869786f0141
|
|
| MD5 |
e9da1e7b644d792cd6a87e347a5a7047
|
|
| BLAKE2b-256 |
d2d6e827b0345c1ea5d5cc3491a7f6b321e68894255d6e923c0aea45b4f94b3f
|
Provenance
The following attestation bundles were made for pyleman2000-0.3.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on cms-cambridge/pyLeman2000
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyleman2000-0.3.0-py3-none-any.whl -
Subject digest:
981201f136f3d63b060eb8c2c83f4934c1387865286a239838d29869786f0141 - Sigstore transparency entry: 2560916065
- Sigstore integration time:
-
Permalink:
cms-cambridge/pyLeman2000@1ef838707d50366e0271327a619ec5d7d803a1b2 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cms-cambridge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@1ef838707d50366e0271327a619ec5d7d803a1b2 -
Trigger Event:
workflow_dispatch
-
Statement type: