pyLeman2000
Python wrapper for Leman's (2000) tonal contextuality model.
The original model was published in a 2000 Music Perception paper, and was shown to provide a psychoacoustic account of the Krumhansl-Kessler probe-tone data (Leman, 2000). Leman and colleagues released this model as part of the IPEM Toolbox, which now only runs on old MATLAB versions. This package runs the model in Docker (via the Docker SDK for Python): by default a compiled MATLAB Runtime worker, with an optional license-free GNU Octave backend.
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. On 44.1 kHz input, running correlations typically agree with the
archived MATLAB/R snapshots to about 3e-6 (not bit-identical). Feed
22.05 kHz audio if you need closer cross-implementation agreement.
This is a Python port of leman2000R.
Requirements
- Python 3.10+
- Docker installed and running
(
docker infoshould succeed) - On first use, the default GHCR image is pulled automatically (progress on
stderr). Silence with
leman2000(..., show_progress=False).
Input and output files are copied in and out of the container rather than
bind-mounted, so no Docker file sharing configuration is needed. Analyses work
regardless of where the audio lives, including paths that Docker Desktop does
not share by default (such as WAV files inside site-packages).
Note: The image targets
linux/amd64. On Apple Silicon, enable Docker Desktop's amd64/Rosetta or QEMU emulation, then verify withdocker run --platform=linux/amd64 --rm hello-world. Emulated runs are slower than native amd64 hardware. For many analyses in one process, reuse a warm container withLeman2000Session(see below).
Installation
python3 -m pip install pyLeman2000
From a specific Git tag (or before the PyPI upload lands):
python3 -m pip install git+https://github.com/cms-cambridge/pyLeman2000.git@v0.2.0
For local development:
python3 -m pip install -e ".[dev]"
Optional: pre-pull the default MATLAB image (otherwise the package pulls it on
first leman2000(...) call):
docker pull "$(python3 -c 'from pyleman2000 import DEFAULT_MATLAB_IMAGE; print(DEFAULT_MATLAB_IMAGE)')"
Building images locally
Octave (optional backend):
./scripts/build_octave_image.sh
# then: leman2000(..., backend="octave", docker_image="pyleman2000-octave:dev")
MATLAB (default backend; needs MATLAB Compiler on Linux amd64):
./scripts/build_matlab_image.sh # local pyleman2000-matlab:dev
./scripts/build_matlab_image.sh --tag 0.1.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.1.0 (and :latest). Package defaults are
digest pins of the 0.1.0 images, so installs stay reproducible while :dev
keeps moving. See docker/matlab/README.md for the MATLAB worker protocol.
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, which is what this package facilitates: pass sequences for
local_decay_sec and global_decay_sec to obtain all pairwise results. See
Bigand et al. (2014) for an example of using Leman's model across parameter
settings when relating auditory short-term memory accounts to musical syntax
findings.
local_global_comparison gives the running correlation between local and
global tonal images over time. Higher values mean the short-term (local) and
longer-term (global) representations are more similar at that moment. Optional
windows summarise those correlations over closed time intervals of interest
(for example chord or phrase spans). This differs from leman2000R, which uses
half-open intervals [start, end): pyLeman2000 includes both endpoints, so a
sample that falls exactly on a shared boundary contributes to both adjacent
windows.
Input files must use a .wav extension. The Dockerised model inherits the
IPEM WAV constraints of the original toolbox; if a file fails inside the
container, try a standard PCM WAV (for example mono or stereo, 16-bit,
44.1 kHz).
Example
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)
Repeated analyses (warm worker)
Each one-shot leman2000(...) call starts a fresh container. When analysing
many files, keep one worker alive (default MATLAB backend reuses the compiled
Runtime process):
from pyleman2000 import Leman2000Session, example_wav_path
with Leman2000Session() as session:
first = session.run(
input_file=example_wav_path(),
local_decay_sec=0.1,
global_decay_sec=1.0,
)
second = session.run(
input_file=example_wav_path(),
local_decay_sec=[0.1, 0.5],
global_decay_sec=[1.0, 2.0],
)
Optional Octave backend
Pass backend="octave" for the license-free Octave image
(DEFAULT_IMAGE). Sessions still help on Apple Silicon by warming filesystem
caches, even though Octave restarts inside the container on each run:
with Leman2000Session(backend="octave") as session:
result = session.run(
input_file=example_wav_path(),
local_decay_sec=0.1,
global_decay_sec=1.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
result.windowed_local_global_comparison
local_decay_sec global_decay_sec window_id window_start window_end local_global_correlation
0 0.1 1.0 1 0.0 0.1 0.999977
1 0.5 1.0 1 0.0 0.1 1.000000
2 0.1 2.0 1 0.0 0.1 0.999974
3 0.5 2.0 1 0.0 0.1 0.999999
4 0.1 1.0 2 0.1 0.2 0.998674
5 0.5 1.0 2 0.1 0.2 0.999988
6 0.1 2.0 2 0.1 0.2 0.998546
7 0.5 2.0 2 0.1 0.2 0.999972
Window intervals include both endpoints. A timestamp on a boundary shared by
adjacent windows contributes to both windows. This is an intentional divergence
from leman2000R, which uses half-open [start, end). window_id is 1-based,
and windowed rows are ordered window-major (all parameter combinations for
window 1, then window 2, and so on).
leman2000 returns a Leman2000Result dataclass with:
audio_length_sec,num_channels,sample_ratelocal_global_comparison— pandas DataFrame of running local/global correlationswindowed_local_global_comparison— optional windowed averagesauditory_nerve/periodicity_pitch— optional heavy intermediate outputs (nested dictionaries from the Octave model; omit unless you need them)
The result object itself 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. Result equality is identity-based; compare DataFrames or fields explicitly when needed.
Tests
Unit tests do not require Docker:
python3 -m pip install -e ".[dev]"
python3 -m pytest -v -m "not integration and not matlab"
Octave integration and R-snapshot tests require Docker. The package pulls
DEFAULT_IMAGE from GHCR on first use (or build locally if you prefer):
# optional pre-pull; otherwise leman2000() pulls automatically
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 pull DEFAULT_MATLAB_IMAGE (no Compiler needed):
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). Octave and MATLAB CI
jobs compare against those archives with a looser tolerance (~1e-5).
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.2.0.tar.gz.
File metadata
- Download URL: pyleman2000-0.2.0.tar.gz
- Upload date:
- Size: 84.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9999e18b7110cfe9a38d8212ef7a9c4cabe3e1db47fe642cd04034c2aa4a55ec
|
|
| MD5 |
31f4366528fab05e3734bfe85cd81779
|
|
| BLAKE2b-256 |
6b5d5af22e6fe1a442039047f79c5084e4c286822bbbc4021508d5de4b8a026e
|
Provenance
The following attestation bundles were made for pyleman2000-0.2.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.2.0.tar.gz -
Subject digest:
9999e18b7110cfe9a38d8212ef7a9c4cabe3e1db47fe642cd04034c2aa4a55ec - Sigstore transparency entry: 2370581634
- Sigstore integration time:
-
Permalink:
cms-cambridge/pyLeman2000@82d3d153daa4e79f3003c1969fc5ca8340e7a1e0 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cms-cambridge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@82d3d153daa4e79f3003c1969fc5ca8340e7a1e0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyleman2000-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pyleman2000-0.2.0-py3-none-any.whl
- Upload date:
- Size: 51.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
590a5d4aac11edf47c37a3bb46143c141d612106ee4d5f14ade1ccadad9d3416
|
|
| MD5 |
79138c87f9fb9b0450881736db10a554
|
|
| BLAKE2b-256 |
a711a05e358b74f27d9e5af45da0935d54231e6c9819840e88ab0e8ff4d5ebd5
|
Provenance
The following attestation bundles were made for pyleman2000-0.2.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.2.0-py3-none-any.whl -
Subject digest:
590a5d4aac11edf47c37a3bb46143c141d612106ee4d5f14ade1ccadad9d3416 - Sigstore transparency entry: 2370581696
- Sigstore integration time:
-
Permalink:
cms-cambridge/pyLeman2000@82d3d153daa4e79f3003c1969fc5ca8340e7a1e0 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/cms-cambridge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@82d3d153daa4e79f3003c1969fc5ca8340e7a1e0 -
Trigger Event:
release
-
Statement type: