Ship each crop's embedding so a resolver NONE can mint
Two identity fixes, neither yet run on a GPU. has_face, the extras gate: fixing the bbox coordinate space made extras worse, because crops finally landed on their subjects and a background extra bound to the lead at 1.00. /vision stamps has_face per character using face_detect and _pair_faces_to_present, so containment and its margin match the speaker path. Identity skips has_face is False before it crops or embeds. Fails open on a missing or raising detector, and gates on `is False` so an older vision blob behaves as before. The NONE mint: /identity/resolve now writes each crop's embedding to the crop's key with a .npy suffix and returns emb_uri. The orchestrator cannot compute an embedding of its own (siglip and gemma cannot both be resident), which is why it was clearing a deliberate NONE instead of minting an anonymous character. Also: audit_registry.py, and the tmux respawn-window trap, which leaves a bare shell rather than re-running the window command. decisions/identity-bbox.md#face-gates-enrollment decisions/identity-bbox.md#none-mints-an-anonymous-character Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -200,6 +200,13 @@ async def resolve(data: IdentityInput):
|
||||
|
||||
assignments, backfill, new_chars, shortlists = [], [], [], []
|
||||
for ch in data.vision_characters:
|
||||
# no detected face inside the box -> a background extra, a figure on a poster, or scenery
|
||||
# gemma called a person. Embedding it pollutes the registry and, once the boxes were pixels,
|
||||
# bound an extra to the lead at confidence 1.00. Abstain instead. `/vision` stamps this and
|
||||
# fails open, so a panel it could not gate arrives with has_face=True on every character
|
||||
# (decisions/identity-bbox.md#face-gates-enrollment). Absent key = an older vision blob.
|
||||
if ch.get("has_face") is False:
|
||||
continue
|
||||
crop = _crop_bbox(img, ch["bbox"])
|
||||
if crop.size == 0: # degenerate/out-of-bounds bbox -> nothing to embed, skip
|
||||
continue
|
||||
@@ -207,10 +214,17 @@ async def resolve(data: IdentityInput):
|
||||
|
||||
# shortlist for gemma's decider, from the roster as it stood before this crop's own outcome.
|
||||
sl = shortlist(emb, known, data.k, ch.get("gender"))
|
||||
crop_uri = f"s3://manga/{data.manga_id}/characters/_crops/{data.panel_id}_{ch['local_id']}.png"
|
||||
# the embedding ships with the crop. gemma's decider can answer "none of these", and the
|
||||
# orchestrator has to mint a character from that crop — which needs an embedding it cannot
|
||||
# compute (siglip is resident here, gemma is resident there, and the mutex forbids both).
|
||||
# Uploading it now is what removes the third siglip pass
|
||||
# (`decisions/identity-bbox.md#none-mints-an-anonymous-character`).
|
||||
key = f"{data.manga_id}/characters/_crops/{data.panel_id}_{ch['local_id']}"
|
||||
crop_uri, emb_uri = f"s3://manga/{key}.png", f"s3://manga/{key}.npy"
|
||||
cp = f"{SHM}/cc_{uuid.uuid4().hex[:8]}.png"; cv2.imwrite(cp, crop)
|
||||
transport.put(cp, crop_uri); os.remove(cp)
|
||||
shortlists.append({"local_id": ch["local_id"], "crop_uri": crop_uri,
|
||||
_save_npy(emb, emb_uri)
|
||||
shortlists.append({"local_id": ch["local_id"], "crop_uri": crop_uri, "emb_uri": emb_uri,
|
||||
"candidates": [{"character_id": c["character_id"], "name": c.get("name"),
|
||||
"gender": c.get("gender"), "species": c.get("species"),
|
||||
"appearance": c.get("description"), "cosine": c["cosine"],
|
||||
|
||||
Reference in New Issue
Block a user