Skip to main content

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 info should 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_rate
  • local_global_comparison — running local/global correlations over time
  • windowed_local_global_comparison — optional windowed averages
  • auditory_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

pyleman2000-0.3.0.tar.gz (134.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyleman2000-0.3.0-py3-none-any.whl (63.1 kB view details)

Uploaded Python 3

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

Hashes for pyleman2000-0.3.0.tar.gz
Algorithm Hash digest
SHA256 a8d4a76fb96ecca778269a366643545e34f171b43540d054646720f69d532b92
MD5 ea5323e87d55edc62d82790553eb8e7e
BLAKE2b-256 81819e44f2e0846d3897ced292e60d35e2682335ecaa2f06da9262a3d4220cd8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyleman2000-0.3.0.tar.gz:

Publisher: publish-pypi.yml on cms-cambridge/pyLeman2000

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pyleman2000-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 981201f136f3d63b060eb8c2c83f4934c1387865286a239838d29869786f0141
MD5 e9da1e7b644d792cd6a87e347a5a7047
BLAKE2b-256 d2d6e827b0345c1ea5d5cc3491a7f6b321e68894255d6e923c0aea45b4f94b3f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyleman2000-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on cms-cambridge/pyLeman2000

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page