Recipe: two-stage cascade tracker
Recipe: two-stage cascade tracker
A cascade tracker matches high-confidence detections by one cue and the remainder by another. This recipe matches high-score instances by appearance (cosine over embeddings) first, then matches the residual plus low-score instances by mask-IoU — the appearance cue is trusted where the detector is confident, with overlap as the fallback.
It maps onto 2.0 as a Sequential of two Filter stages. Each Filter
splits the detections on a score predicate (on="ds") and routes its slice
into a class-gated Pipe; Sequential threads the residual of the first
stage into the second. hi is the cascade's internal score split,
independent of any min_score floor applied upstream.
import torch
import unitrack
from unitrack.assignment import Associate, Jonker
from unitrack.costs import Cosine, MaskIoU
from unitrack.data import TensorSpec
from unitrack.gates import ClassGate
from unitrack.lifecycle import IncludeAll, NoLifecycle
from unitrack.pipeline import Filter, Gated, Pipe, Sequential
from unitrack.states import FromDetectionField, Identity, Replace, State
EMBED_DIM = 256
def build_cascade_tracker(
*,
height: int,
width: int,
hi: float = 0.5,
cos_threshold: float = 0.5,
iou_threshold: float = 0.5,
embed_dim: int = EMBED_DIM,
) -> unitrack.Tracker:
# The class gate wraps each stage's Pipe (a per-pair gate biases a cost,
# so it must sit on the Pipe, not on the Sequential cascade).
hi_stage = Filter(
predicate=lambda ds: ds.score >= hi,
then=Gated(
gate=ClassGate("category"),
then=Pipe(
cost=Cosine("embedding"),
assoc=Associate(Jonker(threshold=cos_threshold)),
),
),
on="ds",
)
lo_stage = Filter(
predicate=lambda ds: ds.score < hi,
then=Gated(
gate=ClassGate("category"),
then=Pipe(
cost=MaskIoU("mask"),
assoc=Associate(Jonker(threshold=iou_threshold)),
),
),
on="ds",
)
pipeline = Sequential([hi_stage, lo_stage])
return unitrack.Tracker(
root=pipeline,
states={
"embedding": State(
schema=TensorSpec(shape=(embed_dim,), dtype=torch.float32),
process=Identity("embedding"),
observation=Replace("embedding"),
init=FromDetectionField("embedding"),
),
"mask": State(
schema=TensorSpec(shape=(height, width), dtype=torch.bool),
process=Identity("mask"),
observation=Replace("mask"),
init=FromDetectionField("mask"),
),
"category": State(
schema=TensorSpec(shape=(), dtype=torch.int64),
process=Identity("category"),
observation=Replace("category"),
init=FromDetectionField("category"),
),
"score": State(
schema=TensorSpec(shape=(), dtype=torch.float32),
process=Identity("score"),
observation=Replace("score"),
init=FromDetectionField("score"),
),
},
lifecycle=NoLifecycle(),
visibility=IncludeAll(),
)
Detection requirements:
embedding—(M, embed_dim)appearance vectors for the high-score stage.mask—(M, height, width)boolean instance masks for the low-score stage.category—(M,)int64 semantic class, for theClassGatein each stage.score—(M,)float32 confidence, used by theFiltersplit predicates.