decipher.catalog

Query a curated cetacean audio corpus over HTTPS and materialise matching clips as listenable AudioSource objects. The round-trip invariant holds: every fetched clip lands on disk as a playable WAV.

The catalog is a parquet index served by a small HTTP front door over a Modal volume. The toolkit caches the parquet locally with a short TTL; clips are cached content-addressably by sha256 and are never re-downloaded once present.

Configuration

  • DECIPHER_CATALOG_URL — base URL of the server. Defaults to the public endpoint.
  • DECIPHER_CACHE_DIR — where to stash cached parquet + clips. Defaults to ~/.cache/decipher.
function decipher.catalog.catalog
def catalog(
    *,
    paper_id: str | Sequence[str] | None = None,
    species: str | Sequence[str] | None = None,
    substrate: str | Sequence[str] | None = None,
    clan: str | Sequence[str] | None = None,
    min_duration_s: float | None = None,
    max_duration_s: float | None = None,
    label_eq: dict[str, Any] | None = None,
    limit: int | None = None,
    refresh: bool = False,
) -> pandas.DataFrame

Return filtered catalog rows as a pandas DataFrame. Filters AND together; string filters accept a single value or a sequence (membership). label_eq matches paper-specific JSON labels exactly. Set refresh=True to bypass the local parquet cache.

function decipher.catalog.fetch
def fetch(
    *,
    paper_id: str | Sequence[str] | None = None,
    species: str | Sequence[str] | None = None,
    substrate: str | Sequence[str] | None = None,
    clan: str | Sequence[str] | None = None,
    min_duration_s: float | None = None,
    max_duration_s: float | None = None,
    label_eq: dict[str, Any] | None = None,
    limit: int | None = None,
    refresh: bool = False,
) -> list[AudioSource]

Same filter semantics as catalog, but materialises matching clips as AudioSource objects. Each returned object's path points into the local content-addressable cache, so writing the samples back via write_wav yields a playable WAV.

function decipher.catalog.healthcheck
def healthcheck() -> dict

Hit the server's /healthz and return the parsed JSON.

function decipher.catalog.clear_cache
def clear_cache(*, catalog_only: bool = False) -> None

Delete the local parquet cache. When catalog_only=False (the default), also remove every cached clip.

Example

from decipher import catalog, fetch

# Browse without downloading audio
df = catalog(species="physeter_macrocephalus", min_duration_s=1.0, limit=20)
print(df[["paper_id", "clip_id", "duration_s"]].head())

# Download + load a handful as AudioSources
clips = fetch(paper_id="005_sharma_2024", limit=5)
for c in clips:
    print(c.path, c.sr, c.duration_s)

What fetch returns

Each of these was produced by one fetch(...) call — the clip is cached content-addressably by sha256 at ~/.cache/decipher/clips/<sha>.wav, so a second call hits the local cache.

fetch(paper_id='001_payne_mcvay_1971', substrate='humpback_song_raw', limit=1)
fetch(paper_id='001_payne_mcvay_1971', substrate='humpback_song_raw', limit=1)
Returned as a list with a single AudioSource. 44100 Hz 30 s
fetch(paper_id='004_ford_1991', substrate='ford_source_clips', limit=1)
fetch(paper_id='004_ford_1991', substrate='ford_source_clips', limit=1)
250 kHz recording; the clip is short but the bandwidth is high. 250000 Hz 1.7 s
fetch(paper_id='005_sharma_2024', substrate='DSWP-HF', min_duration_s=0.5, limit=1)
fetch(paper_id='005_sharma_2024', substrate='DSWP-HF', min_duration_s=0.5, limit=1)
A sperm whale coda — two-second rhythmic click train. 44100 Hz 2.2 s

Catalog at a glance

catalog duration histogram
Clip-duration distribution across all 22,913 catalog rows.
clips per paper
Top-12 papers by clip count.