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.
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.
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.ltsauv 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
pytestrequirements-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.
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 worksCommit 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.
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.
Found by running the existing code against controlled inputs. All are documented in the format specs with file:line references; the notable ones:
io/ioReadXWAVHeader.mhas no v2 branch. It skips the per-channeldrateanddtfields and returns wrongbyte_loc/byte_lengthon 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)- 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.
rdxwavhd.mkeeps only the last raw file'sdt. Lines 135/137 assignPARAMS.xhd.dtand.paddingunsubscripted inside the loop. May be intentional; some Remoras are believed to compensate, so not to be changed until those are audited.write_ltsahead.mcan never be called non-interactively inTriton-master. Its guard is~exist('PARAMS.ltsa.outfile','var');existcannot test a dotted field name, so it is always true anduiputfilealways opens.Triton_remorasalready carries the fix. (ltsa.md §7.1)- 24-bit x.wav cannot be read at all —
readseg.m:99asksfreadfor a nonexistent'int24'type. Latent only: no 24-bit x.wav exist. - 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.
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.
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