scene_service.state.data_assoc

Cross-frame data association: turn a batch of per-frame `Detection`s into either updates of existing SceneObject records or allocations of new ones. v1 algorithm:

  1. spatial gating per class (_GATE_RADIUS_M)

  2. class-match (only same-class candidates considered)

  3. Hungarian (scipy.optimize.linear_sum_assignment) min-cost matching

  4. unmatched detections → allocate new SceneObject

  5. unmatched objects → mark_stale handles them; we don’t touch them here

  6. matched pairs → ObjectRegistry.update_object_pose (EMA blend)

Not implemented in v1 (deferred): Kalman filtering, IoU-based gating, appearance embeddings, learned association.

Functions

associate(registry, detections, *[, now])

Resolve detections against the registry.

Classes

Detection(cls, pose, bbox, confidence[, source])

One per-frame perception output.

class scene_service.state.data_assoc.Detection(cls: str, pose: Pose3D, bbox: BBox3D, confidence: float, source: str = 'perception')[source]

Bases: object

One per-frame perception output. Stable id is NOT supplied — it’s this layer’s job to assign / find one. pose is in map frame (ingest does the TF transform before producing Detection).

bbox: BBox3D
cls: str
confidence: float
pose: Pose3D
source: str = 'perception'
scene_service.state.data_assoc.associate(registry: ObjectRegistry, detections: list[Detection], *, now: float | None = None) tuple[list[str], list[str]][source]

Resolve detections against the registry. Caller must hold registry.lock().

Returns (matched_ids, new_ids) for logging / metrics. The registry is mutated in place: matched detections EMA-update existing records, unmatched detections allocate new ones, unmatched objects are NOT touched (mark_stale runs separately on a periodic tick).