Embedding (re-ID) tracking¶
Track animals by appearance in addition to — or instead of — pose and position. An
embedding (re-ID) model turns each instance crop into a
learned appearance vector; the tracker can then score detection-to-track association by
the cosine similarity of those vectors, rather than only by where the animal was.
This is the tracker-side counterpart of the embedding model: the model produces the
vectors (--save_embeddings), and the tracker consumes them either as a blend weight
on top of a geometric score (--appearance_weight) or as the sole feature
(--features embeddings, auto-paired with --scoring_method cosine_sim).
It works on both carriers — pose PredictedInstances and segmentation
PredictedSegmentationMasks. Appearance matching is image-free, so the motion models
(--use_flow / --use_kalman) do not apply.
Which regime you want¶
Appearance is a complementary cue, not a strictly better one. Whether it helps, and how much, depends on how informative geometry already is — which is a property of your data, not of the model:
| regime | how | when it wins |
|---|---|---|
| Blended (recommended) | a geometric --features + --appearance_weight 0.15-0.5 |
dense continuous video, where geometry is informative and appearance breaks its ties |
| Appearance-only | --features embeddings |
sparse frames and post-occlusion recovery, where geometry has no signal |
| Geometry-only | the default; no embeddings needed | when identities never need recovering — and as the baseline you should beat |
What was measured¶
On two held-out gerbil sessions of dense continuous video (per-frame displacement ~4-6% of body length), geometry alone clearly beats appearance alone:
| 237 center session | ID switches | IDF1 |
|---|---|---|
| keypoints / OKS | 32 | 0.849 |
| embeddings / cosine (appearance-only) | 227 | 0.800 |
Blending the two is better than either: IDF1 0.849 → 0.878 and, on the second session, 0.855 → 0.982, with fewer ID switches on one of them.
The picture inverts when geometry stops being informative. On a temporally sparse set (consecutive "frames" ~8.7 body lengths apart, so same-animal mask IoU is 0.000 for most pairs), appearance-only is transformative:
| sparse gerbil set | ID switches | IDF1 |
|---|---|---|
| masks / mask IoU | 169 | 0.245 |
| embeddings / cosine (appearance-only) | 12 | 0.967 |
How far this generalizes
Two sessions, one species, one model. The direction of the effect is well founded —
and mechanical, since it turns on whether consecutive detections of one animal
overlap at all — but the specific weights are not tuned for your data. Check where
your own recording sits with
sleap_nn.evaluation.motion_diagnostic(labels, "pose"): a step_over_size well
below 0.5 is the dense regime (blend), well above it is the sparse regime
(appearance-only), and sleap-nn eval-tracking will score any choice you make
against tracked ground truth.
The core idea¶
| Pose tracking | Embedding tracking |
|---|---|
feature = keypoints (--features keypoints) |
feature = appearance vector (--features embeddings) |
score = OKS (--scoring_method oks) |
score = cosine similarity (--scoring_method cosine_sim) |
| identity follows position/pose | identity follows appearance |
Blending is the two together: the geometric score for the pair, plus
appearance_weight times their appearance similarity. At the default 0.0 the
appearance term is never computed, so a geometry-only run is unchanged.
Everything else in the tracker is unchanged: the same candidate makers
(fixed_window / local_queues), windowing, score reduction, and Hungarian/greedy
assignment. Only the per-detection feature and the pairwise score differ.
Pair with local_queues
--candidates_method local_queues keeps a per-track deque of recent vectors — a
lightweight appearance gallery. Matching a new detection against that window
(reduced by --scoring_reduction, e.g. mean = soft prototype) is a robust,
no-extra-infrastructure re-ID step.
Blending appearance with geometry¶
# Recommended default on continuous video: keep OKS as the primary cue and let
# appearance break its ties.
sleap-nn predict -i video.mp4 -m centroid_dir -m centered_instance_dir -m embedding_dir \
-t --appearance_weight 0.3
--appearance_weight w scores each candidate pair as (1 - w) * geometry + w * appearance:
0.0(default) — geometry only. Inert: the appearance term is not computed.0.15-0.5— the measured sweet spot on dense video.1.0— appearance only, but still gated by geometry's candidate set; prefer--features embeddingsif that is what you want.
Two properties worth knowing:
- A detection with no embedding keeps its geometric score. It is not blended toward a missing value, so it still matches on geometry instead of being dropped and spawning a spurious track.
- The blend cannot invent matches geometry rejected. Where geometry has no valid
candidate (nothing passed
--min_match_points), the pair stays unmatched.
Pair it with a geometric --features; combining it with --features embeddings is
rejected, since that would blend appearance with itself.
Distance scores need --euclidean_scale
Blending only means something when both terms live on the same scale. oks,
iou and mask_iou are already bounded, so they blend as-is.
euclidean_dist is not — it returns negative pixels, so at any realistic
image scale the geometric term dominates by two orders of magnitude and the
weight would be numerically inert.
It is therefore mapped through a bounded kernel first:
a (0, 1] value comparable to the appearance cosine. The kernel is strictly
monotone in distance, so the candidate ordering geometry alone would have
produced is unchanged — only the scale is.
--euclidean_scale is a length in pixels with no universal default, so it
is required for this combination rather than guessed. Pass the typical
inter-frame displacement of one animal — the distance at which geometric
similarity falls to ~0.37. motion_diagnostic reports it for your data.
This is the path centroid-only detections take: a single-node skeleton
auto-selects euclidean_dist, so the guide's centroid + embedding fused
command needs the scale.
sleap-nn predict -i video.mp4 -m centroid_dir -m embedding_dir \
-t --appearance_weight 0.3 --euclidean_scale 25
Two consequences worth knowing: the mapping happens only when blending, so
a geometry-only distance run is byte-identical to before; and a blended
distance run's tracking_score is a (0, 1] similarity rather than negative
pixels.
Every incoherent combination is rejected before inference starts, not after the detection stack and embedding pass have run.
Workflow 1 — track an existing .slp of embeddings¶
You already ran an embedding model with --save_embeddings slp, so each
detection in the .slp carries its appearance vector. Track it (no model, no GPU):
--features embeddings auto-selects --scoring_method cosine_sim. This is the
track-only / retrack path — omit --model_paths. The
prior tracks (if any) are reassigned from scratch by appearance.
If the labels carry no appearance embedding, you get a clear error pointing you to run the embedding model first.
Workflow 2 — embed + track in one command¶
Run the embedding model on a .slp of detections (tracked or untracked) and
track them by appearance in a single command. Every detection is embedded (the
embedding model runs in "include-untracked" mode), the vectors are attached, and the
tracker assigns sio.Tracks by cosine similarity:
- The output is a tracked
.slp(default<input>.tracked.slp, or-o out.slp). --features/--scoring_methoddefault toembeddings/cosine_simfor an embedding model; you can still override them.--trackinglifts the usual requirement to pass--save_embeddingsfor an embedding model (the tracked.slpis the output).
Persisting the vectors¶
--save_embeddings controls whether the appearance vectors are stored in the tracked
.slp (independent of the tracks themselves):
--save_embeddings |
Tracked .slp contents |
|---|---|
none (default) |
tracks only — vectors are stripped after tracking |
slp |
tracks and the appearance vectors (for later re-tracking, retrieval, clustering) |
# Track AND keep the appearance vectors in the output for later reuse:
sleap-nn predict -m models/embedding/ -i detections.slp -t --save_embeddings slp
Workflow 3 — detect, embed, and track a video (fused)¶
Pass a detection stack and the embedding model together to run detection,
embedding, and appearance tracking on a raw video in one command. The detection models
(a centroid, optionally plus a centered-instance model, i.e. top-down) run first to
produce the per-frame detections, those detections are embedded, and the tracker
assigns sio.Tracks by cosine similarity:
# top-down detect (centroid + centered_instance) -> embed -> track, in one command
sleap-nn predict -m models/centroid/ -m models/centered_instance/ \
-m models/embedding/ -i video.mp4 -t --max_tracks 6 -o tracked.slp
This is exactly Workflow 2 with the detection step folded in — equivalent to running
sleap-nn predict -m centroid -m centered_instance -i video.mp4 -o poses.slp and then
sleap-nn predict -m embedding -i poses.slp -t -o tracked.slp. --save_embeddings slp
keeps the vectors in the output as above; a centroid-only detector (no
centered-instance model) yields single-node detections to embed + track.
Output: tracks, not global identities¶
This path emits per-video sio.Tracks only (classification-as-tracking). It does
not fabricate a global sio.Identity from track/class names — a track name is not
a global animal identity. (Persisting predicted global identities from appearance is a
separate, future step; multi_class models can emit sio.Identity via
class_output="identity".)
Parameters¶
| Parameter | Description | Default |
|---|---|---|
--appearance_weight w |
Blend appearance into a geometric score: (1 - w) * geometry + w * appearance. 0.15-0.5 is the measured range; 0.0 is inert |
0.0 |
--features embeddings |
Track by appearance ALONE (the sparse / post-occlusion regime) | — |
--scoring_method cosine_sim |
Cosine similarity (auto-selected for embeddings; euclidean_dist also allowed) |
auto |
--euclidean_scale px |
Length scale for the distance→similarity kernel. Required with --appearance_weight when the geometric score is euclidean_dist (i.e. centroid-only detections); ignored otherwise |
— |
--candidates_method local_queues |
Per-track appearance gallery (recommended) | fixed_window |
--save_embeddings {none,slp} |
Persist vectors in the tracked .slp (WF2/WF3) |
none |
All other tracking parameters
(--tracking_window_size, --scoring_reduction, --max_tracks,
--track_matching_method, …) apply unchanged. Motion models (--use_flow,
--use_kalman) are not supported with appearance features.
Troubleshooting¶
features='embeddings' but no detection ... carries an appearance embedding
The input .slp has no appearance vectors. Run the embedding model first
(--save_embeddings slp), or use Workflow 2 to embed + track in one command.
--appearance_weight on vector-less labels is rejected the same way, rather
than silently degrading to a geometry-only run.
appearance_weight ... requires euclidean_scale
Your --scoring_method resolved to euclidean_dist — usually because the
detections are centroid-only (a single-node skeleton auto-selects it). Pass
--euclidean_scale <px>, the typical inter-frame displacement of one animal.
See the warning under Blending appearance with
geometry for how the kernel works, or
switch to a bounded metric (oks / iou / mask_iou).
Identity still switches
- If your video is continuous, try the blend rather than appearance alone:
a geometric
--featureswith--appearance_weight 0.3. Appearance-only was measurably worse than geometry on dense video (227 ID switches against 32). - Use
--candidates_method local_queueswith a larger--tracking_window_size. - Check the embedding model actually separates your animals (validate retrieval metrics) — appearance tracking is only as good as the embeddings.
- Cap identities with
--max_tracks Nwhen the animal count is known.
How do I know whether the blend helped?
Score it against tracked ground truth:
That reports ID switches, IDF1, MT/PT/ML, fragmentation and track purity, so a weight can be chosen on evidence rather than by feel. Compare arms over the SAME detections (retrack one prediction file at several weights) so the difference you read is the tracker's and not the detector's.
Retracking chains: a .slp that carries appearance vectors keeps them through
a track-only run, so each arm can retrack the same embedded file. Pass
--save_embeddings none if you explicitly want a tracks-only output.
appearance_weight was rejected with 'already appearance-only'
--features embeddings scores by appearance alone, so blending appearance into it
is not meaningful. Either drop --appearance_weight (appearance-only regime) or
switch to a geometric --features and keep the weight (blended regime).