Skip to content

Repository files navigation

Triton — Python port

Python reimplementation of Triton, the MATLAB passive-acoustic analysis package developed at the Scripps Whale Acoustics Lab.

Status: Phase 0 (specification and golden fixtures) — complete. No implementation code yet.

See PORTING_PLAN.md for the architecture, the phase plan, and the reasoning behind the design decisions, and HANDOFF.md for current state, open questions, and next actions — start there if you are picking this up fresh.


What Phase 0 produced

docs/formats/xwav.md Byte-exact x.wav spec (v0/v1/v2), the semantics that matter, and eight known inconsistencies in the MATLAB implementation
docs/formats/ltsa.md Byte-exact LTSA spec (v1–v4), how the values are computed, four known issues
docs/formats/timebase.md The year-2000 datenum offset, why it is load-bearing, and the Python representation that replaces it
tools/make_fixtures.py Generates 12 synthetic fixtures covering the format matrix; deterministic and byte-reproducible
tools/matlab/dump_reference.m Runs unmodified Triton over the fixtures and dumps ground truth
tests/test_fixture_integrity.py The fixtures satisfy every rule Triton's reader enforces
tests/test_reference_dump.py The MATLAB dump is complete and self-consistent
tests/test_parity_phase1.py Phase 1's definition of done — written now, skipped until the code exists

The reference dump is committed (fixtures/reference/, ~3.7 MB) so the parity suite runs without a MATLAB licence.

Installing

Two ways in. The first gives everyone byte-identical environments and is the one to use; the second is for a machine where installing another tool is not an option.

With uv (install it once):

git clone https://github.com/MarineBioAcousticsRC/Triton_python.git
cd Triton_python
uv sync --all-extras        # creates .venv with exactly the versions in uv.lock
uv run pytest               # 159 tests, about 15 s -- most of it is Qt loading
uv run python -m triton.gui path/to/file.x.wav path/to/file.ltsa

uv sync reads uv.lock, which pins every package and every package they depend on, so two people running it on different days get the same environment. That is the difference between this and pip install: pip resolves versions afresh each time, so a clone made next month can pick up a numpy or Qt release nobody has tested here.

With plain pip, if uv is not available:

python -m venv .venv && .venv/Scripts/activate     # Windows; use bin/activate elsewhere
pip install -r requirements-lock.txt               # the same pinned versions
pip install -e . --no-deps
pytest

requirements-lock.txt is exported from uv.lock and carries the same pins. It has to be regenerated by hand when the lock changes -- see below -- so if the two ever disagree, uv.lock is the one that is right.

Python version. .python-version says 3.12, which is what everything here has been run on. requires-python in pyproject.toml allows 3.11 and up, but that is a floor nobody has tested, and uv will use 3.12 automatically.

Which extras you need. uv sync --all-extras installs everything. To be selective:

extra what it adds who needs it
(none) numpy, scipy -- reading files, computing spectra, making LTSAs scripts, batch jobs, CI
gui PySide6, pyqtgraph, sounddevice -- the viewer anyone looking at data
io soundfile -- plain wav and flac Phase 2 onward
export matplotlib -- publication figures not used by anything yet
dev pytest, ruff anyone changing code

The core library deliberately has no GUI dependency, and a test enforces it: a compute node with no display can read every file format and build LTSAs.

Changing a dependency

Edit the lists in pyproject.toml, then:

uv lock                                                            # update uv.lock
uv export --all-extras --no-hashes --no-emit-project -o requirements-lock.txt
uv sync --all-extras && uv run pytest                              # prove it still works

Commit all three files together. uv lock --check exits non-zero if pyproject.toml and uv.lock have drifted, which is the check a CI job should run.

Regenerating the MATLAB ground truth

Needs MATLAB and a checkout of Triton_remoras (not Triton-master: its write_ltsahead guard is fixed there, which is what lets the LTSA fixtures build headlessly with no Save dialog). No Triton GUI is started; tools/matlab/tr_headless_handles.m supplies the few HANDLES that core functions touch, so readseg, check_time, mkspecgram and read_ltsadata run for real rather than being re-implemented in the dump script.

addpath('D:\Code\Triton_remoras')
addpath('D:\Code\Triton_python\tools\matlab')
make_ltsa_fixture      % builds the .ltsa fixtures, headless against Triton_remoras
dump_reference         % writes fixtures/reference/

Or from a shell:

matlab -batch "addpath('D:/Code/Triton_remoras'); addpath('D:/Code/Triton_python/tools/matlab'); dump_reference"

_environment.json records the MATLAB release and toolbox list. Numeric output can shift between releases; when it does, we want the diff to be visible.


Things Phase 0 turned up in the MATLAB code

Found by running the existing code against controlled inputs. All are documented in the format specs with file:line references; the notable ones:

  1. io/ioReadXWAVHeader.m has no v2 branch. It skips the per-channel drate and dt fields and returns wrong byte_loc/byte_length on a v2 file, silently. Copies of this reader live in four Remoras, so they inherit it. If v2 files exist in the archive, results those Remoras produced from them are suspect. Deferred to the Remora phase by agreement. (xwav.md §6.8)
  2. Two local Triton checkouts disagree in 19 base files, including most of the LTSA pipeline, and neither is a superset of the other. Measured impact so far: none — running the reference dump against both trees gives byte-identical output on every path Phase 0 covers. The LTSA generation path is not yet covered. HANDOFF.md §4.
  3. rdxwavhd.m keeps only the last raw file's dt. Lines 135/137 assign PARAMS.xhd.dt and .padding unsubscripted inside the loop. May be intentional; some Remoras are believed to compensate, so not to be changed until those are audited.
  4. write_ltsahead.m can never be called non-interactively in Triton-master. Its guard is ~exist('PARAMS.ltsa.outfile','var'); exist cannot test a dotted field name, so it is always true and uiputfile always opens. Triton_remoras already carries the fix. (ltsa.md §7.1)
  5. 24-bit x.wav cannot be read at allreadseg.m:99 asks fread for a nonexistent 'int24' type. Latent only: no 24-bit x.wav exist.
  6. The datenum precision argument, measured. At the real epoch a float datenum resolves 10.06 µs; one sample at 200 kHz is 5 µs. With Triton's 2000-year shift it resolves 0.039 µs. Numbers come from MATLAB itself (fixtures/reference/datenum_precision.json), not from our arithmetic.

What we need from the team

Real deployment files, to sit in fixtures/real/ (gitignored). Synthetic fixtures prove we implemented the spec; real files prove we implemented reality. The list, and the three open questions they would settle, is in fixtures/README.md.

Layout

docs/formats/     byte-exact format specifications
tools/            make_fixtures.py + matlab/ reference-dump scripts
fixtures/
  generated/      synthetic corpus (committed)
  real/           real deployment data (gitignored)
  reference/      MATLAB ground truth (committed)
tests/            fixture integrity, reference sanity, Phase 1 parity
src/triton/       implementation -- Phase 1

About

Work in progress to port Triton to Python

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages