fix: gate the live cosine aggregate at the gallery's own line
Some checks failed
tests / model-free (pull_request) Has been cancelled

The previous commit stopped the live path re-gating gallery scores at
COSINE_ACCEPT, but it passed zero accept lines. `aggregate_votes` divides by the
whole observation window, so one rejected crop beside one accepted 0.45 averages
to 0.225 - and a zero gate labelled that, at a score the gallery itself calls
unknown.

The aggregate now clears the gallery's own line: `head_threshold` (0.40) when a
fine-tuned head is loaded, `threshold` (0.55) otherwise. The margin gate is
still the only extra gate, so a head match between 0.40 and 0.60 is labelled.

Regression test: a rejected crop followed by an accepted 0.45 must not label the
track (fails on the zero-gate version).

Verified: `python -m unittest discover -s code -p 'test_*.py'` - 258 tests, OK,
2 skipped.
This commit is contained in:
kiyo9w 2026-09-28 17:36:43 +09:00
parent 84ca113b90
commit 004212a8cf
2 changed files with 48 additions and 3 deletions

View file

@ -1147,13 +1147,20 @@ class LiveSession:
mean_scores = aggregate_votes(track.observations)
if getattr(self.engine, "identity_scale", "inlier") == "cosine":
# score_crop only returns a score the gallery already accepted, so
# score_crop only returns scores the gallery already accepted, so
# the aggregate must not be re-gated at COSINE_ACCEPT: that line is
# above the head's own 0.40 and refused matches the gallery had
# accepted. Only the margin gate is left to apply.
# accepted. It still has to clear the gallery's own line, because
# aggregate_votes divides by the whole window - one rejected crop
# beside one accepted 0.45 would otherwise average to 0.225 and be
# labelled at a score the gallery calls unknown.
gallery = self.engine.identity_gallery()
line = 0.0
if gallery is not None:
line = gallery.head_threshold if gallery.head_loaded() else gallery.threshold
decision = decide_identity(
mean_scores, method=method, scale="cosine",
min_score=0.0, min_confidence=0.0,
min_score=line, min_confidence=line,
)
else:
decision = decide_identity(mean_scores, method=method)