Source code for annie.parsers.track
"""Parser for ``.track`` / ``__track{N}.csv`` files (one tracked face per file).
A track file is derived from a ``.vdet`` by a tracking heuristic and follows a
single face across frames — exactly one row per frame it appears in. The track
index is encoded in the filename after a **double** underscore, e.g.
``{video_id}__track0.csv``.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import TYPE_CHECKING
from annie.parsers.base import group_rows_by_frame, read_csv_rows
if TYPE_CHECKING:
from annie.core.models import FrameAnnotation
#: Captures the integer ``N`` from a ``...__track{N}`` filename stem.
_TRACK_ID_RE = re.compile(r"__track(\d+)$")
[docs]
def track_id_from_name(path: str | Path) -> int:
"""Extract the track index ``N`` from a ``...__track{N}`` filename.
Args:
path: The track file path or name.
Returns:
The parsed track index.
Raises:
ValueError: If the name does not contain a ``__track{N}`` segment.
"""
stem = Path(path).stem
match = _TRACK_ID_RE.search(stem)
if match is None:
raise ValueError(f"not a '__track{{N}}' filename: {Path(path).name!r}")
return int(match.group(1))
[docs]
def load_track(path: str | Path) -> tuple[int, list[FrameAnnotation]]:
"""Load a track file into its track id and per-frame annotations.
Every box is stamped with the track id parsed from the filename. A
well-formed track has exactly one box per frame, but the loader does not
enforce that — it groups defensively like the vdet loader.
Args:
path: Path to the ``__track{N}.csv`` file.
Returns:
A ``(track_id, frames)`` tuple where ``frames`` is ordered by ascending
frame index.
"""
track_id = track_id_from_name(path)
rows = read_csv_rows(path)
return track_id, group_rows_by_frame(rows, track_id=track_id)