YORU Tracker Identities and trajectories for YORU detections

Tracker API

The tracker can be used from Python, without the window: data types, TrackerBase, configuration, plugins and versioning. This page describes tracker API v1. Everything here is importable from yoru_tracker.core; importing it loads neither OpenCV, a detector, nor the GUI.

from yoru_tracker.core import Detection, TrackerConfig, create_tracker

tracker = create_tracker(TrackerConfig())          # mode "lite"
for frame_id, detections in enumerate(frames):     # detections: list[Detection]
    result = tracker.update(detections, frame_id, timestamp=frame_id / fps)
    for t in result.tracked:
        print(frame_id, t.track_id, t.track_state.value, t.cx, t.cy, t.predicted)

The contract

class TrackerBase:
    info: ClassVar[TrackerInfo]
    api_version: ClassVar[int] = TRACKER_API_VERSION   # 1

    def __init__(self, config: TrackerConfig | None = None): ...
    def reset(self) -> None: ...
    def update(self, detections, frame_id: int, timestamp: float | None = None,
               frame=None) -> TrackingResult: ...
    def tracks(self) -> tuple[Track, ...]: ...         # optional, for inspection
  • update is called once per frame, in frame order. frame_id must increase from call to call — an equal or smaller one raises ValueError rather than resetting anything silently. A gap in frame_id means frames were skipped (a live camera running faster than the detector) and is a longer motion step. timestamp is carried into the result untouched. frame is the image, for trackers whose info.requires_frame is true; Lite ignores it.
  • reset drops every track; IDs restart at 0. If there was any track to drop, the first result after the reset carries a reset event.
  • Determinism. Given the same detections, frame IDs, timestamps and configuration, a tracker whose info.deterministic is true returns equal results — == on TrackingResult. Realtime, video and stored-detection runs therefore make the same decisions for the same input.

The configuration is validated in TrackerBase.__init__; no tracker runs on a configuration that would be refused on load.


Data types

All are frozen dataclasses of plain values, with to_dict() for JSON. No filter state, history or detector object is reachable from them.

Detection

One detector output, independent of the detector family.

Field  
x1, y1, x2, y2 Upright box (pixels).
cx, cy, w, h, angle The box itself; rotated for OBB models, angle in radians; identical to the upright box with angle 0 otherwise.
confidence, class_id, class_name  

Constructors: from_yoru(dict) (a YORU DetectorBase.detect() item), from_row(row) (YORU’s DETECTION_COLUMNS order), from_obb(...), from_xyxy(...), from_dict(...). is_valid() is false for non-finite numbers or an empty box; such detections end up in TrackingResult.rejected.

A rectangle’s angle is defined modulo π. It is an axis, never a heading.

TrackingResult

Field  
frame_id, timestamp As passed to update.
tracked One TrackedDetection per confirmed track, sorted by track_id. Lost tracks are included with predicted=True.
unassigned Valid detections not part of a confirmed track this frame (a newcomer still tentative, a detection left over when the population is full, a doubtful or repeated box no track took).
rejected Invalid detections.
events TrackEvents of this frame.

Properties: active_ids, lost_ids. A result holds no timing: wall-clock numbers would make equal inputs give unequal results. Runtimes measure latency around the call instead.

TrackedDetection

Field  
track_id Contiguous from 0; given when a track is confirmed.
track_state TrackState: tentative, active, lost, occluded (reserved).
box (cx, cy, w, h, angle) this frame — the detection’s, or the prediction when predicted.
predicted True when no detection was matched this frame.
detection The matched Detection, or None.
class_id, class_name Of the latest matched detection.
track_confidence Smoothed detector confidence, discounted while unseen.
velocity (vx, vy), pixels per frame.
hits, age, missed_frames Counted in tracker updates.
association_cost Cost of this frame’s match, for debugging; None for new or predicted rows.

Track

A summary of every track the tracker holds (tracker.tracks()), tentative ones included with track_id=None: state, class, box, velocity, counts, confidence, first_frame_id, last_seen_frame_id.

TrackEvent

kind (EventKind: created, lost, recovered, retired, reset), frame_id, track_id, detail. With log_events: true in the configuration they are also written to the log.


Configuration — TrackerConfig

Frozen and validated; to_dict() / from_dict(), to_yaml() / from_yaml(), save(path) / load(path) (YAML, or JSON by extension). Every key, its default and its allowed range are on the Tracker Settings page.

Unknown keys, wrong types, out-of-range values, non-finite numbers (.nan, .inf), an unsupported config_version, and advanced.* options on a mode other than advanced are all ConfigErrors, whose problems list every reason at once. A file may give only some keys; the rest take their defaults.

config_version changes when a key is renamed or changes meaning; from_dict then learns to read the old version. Version 1 predates high_confidence, duplicate_iou and hidden_guard: a version-1 file is read with them off (0, 0, false), so it tracks exactly as it did when it was written, and is saved again as version 2.


Capabilities — TrackerInfo

name, version, api_version, description, realtime_capable, requires_frame, requires_gpu, supports_obb, supports_variable_population, deterministic. The GUI reads these rather than tracker names — a tracker that is not realtime_capable is refused for live tracking.


Modes and plugins

create_tracker(config) builds the tracker config.mode names. available_modes() lists the built-ins (lite, baseline) and installed plugins. advanced is reserved for the planned Advanced tracker: asking for it is a TrackerUnavailableError saying so — never a silent fallback to Lite.

A plugin package registers a TrackerBase subclass in its pyproject.toml:

[project.entry-points."yoru_tracker.backends"]
my_tracker = "my_tracker.plugin:MyTracker"

It must set info and api_version. One written for another tracker API version is refused with IncompatibleTrackerError (“requires tracker API v2; this application provides v1”); one that fails to import is a TrackerUnavailableError carrying the import error. Once installed in the environment, the plugin’s mode appears in the settings window and can be given to --mode and bench --tracker.


Versioning

TRACKER_API_VERSION (now 1) changes when the contract above changes incompatibly: a method’s meaning, a required field, the result types. Adding an optional field to a result type is not a change of version.


Other useful modules

Module  
yoru_tracker.runtime.detections_file load_detections(path) reads the three detection-CSV layouts; write_detections_csv(path, frames).
yoru_tracker.runtime.frames track_frames(tracker, frames) runs a tracker over stored frames.
yoru_tracker.export write_tracks_csv, TRACK_COLUMNS, read_metadata, config_from_metadata.
yoru_tracker.evaluation Metrics, synthetic scenarios and the benchmark.

For example, re-tracking a detections file in Python:

from yoru_tracker.core import TrackerConfig, create_tracker
from yoru_tracker.runtime.detections_file import load_detections
from yoru_tracker.runtime.frames import track_frames

layout, frames = load_detections("results/movie_detections.csv")
tracker = create_tracker(TrackerConfig.load("two_flies.yaml"))
results = track_frames(tracker, frames)
print(layout, len(results), "frames")