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:
@@ -0,0 +1,93 @@
|
||||
"""Registry audit for one chapter, after vision + identity + reconcile and before anything downstream.
|
||||
|
||||
Runs inside manga-orchestrator (reads /data/manga.db). `audit_speakers.py` answers the attribution
|
||||
questions and needs the dialogue stage; this one answers the questions that decide whether dialogue is
|
||||
worth running at all:
|
||||
|
||||
1. did the bbox fix land — are stored boxes pixels, or still gemma's 0-1000 grid,
|
||||
2. how many characters did the rebaseline mint, and did one of them absorb the chapter again,
|
||||
3. what happened on panel 7, the worked example.
|
||||
|
||||
Usage: docker exec manga-orchestrator python3 /app/audit_registry.py [chapter_id] [panel_index]
|
||||
"""
|
||||
import collections
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
CHAPTER = sys.argv[1] if len(sys.argv) > 1 else "7c944dd4-e972-42c7-ba60-9f6939548e80"
|
||||
WORKED_EXAMPLE = int(sys.argv[2]) if len(sys.argv) > 2 else 7
|
||||
|
||||
c = sqlite3.connect("/data/manga.db")
|
||||
c.row_factory = sqlite3.Row
|
||||
manga_id = c.execute("SELECT manga_id FROM chapters WHERE chapter_id=?", (CHAPTER,)).fetchone()[0]
|
||||
|
||||
reg = {r["character_id"]: dict(r) for r in c.execute(
|
||||
"SELECT character_id, name, aliases, gender, ref_image_uris, embedding_uri "
|
||||
"FROM characters WHERE manga_id=?", (manga_id,))}
|
||||
|
||||
panels = c.execute(
|
||||
'SELECT panel_id, panel_index, page_index, bbox FROM panels WHERE chapter_id=? ORDER BY panel_order',
|
||||
(CHAPTER,)).fetchall()
|
||||
|
||||
# 1. coordinate space. A 0-1000 grid box on a panel wider or taller than 1000px cannot exceed 1000,
|
||||
# and clamps AT 1000. Real pixel boxes track the panel and scatter past it. The tell is the ratio of
|
||||
# the largest coordinate to the panel dimension, plus how many boxes sit exactly on 1000.
|
||||
detections = 0
|
||||
past_1000 = at_1000 = 0
|
||||
max_ratio = 0.0
|
||||
assigned_total = 0
|
||||
per_char = collections.Counter()
|
||||
worked = None
|
||||
|
||||
for p in panels:
|
||||
row = c.execute("SELECT result_json FROM vision_results WHERE panel_id=?", (p["panel_id"],)).fetchone()
|
||||
if not row:
|
||||
continue
|
||||
v = json.loads(row["result_json"])
|
||||
# the vision blob carries no panel size. panels.bbox is the panel's box on its page and is
|
||||
# [x, y, w, h], not corners — panel 3 of this chapter is [0, 615, 900, 106].
|
||||
pb = json.loads(p["bbox"] or "null")
|
||||
pw, ph = (pb[2], pb[3]) if pb and len(pb) == 4 else (None, None)
|
||||
assigns = {a["local_id"]: (a["character_id"], a["confidence"]) for a in c.execute(
|
||||
"SELECT local_id, character_id, confidence FROM identity_assignments WHERE panel_id=?",
|
||||
(p["panel_id"],))}
|
||||
assigned_total += len(assigns)
|
||||
for cid, _ in assigns.values():
|
||||
per_char[reg.get(cid, {}).get("name") or cid[:16]] += 1
|
||||
people = [ch for ch in (v.get("characters") or []) if ch.get("bbox")]
|
||||
detections += len(people)
|
||||
for ch in people:
|
||||
x1, y1, x2, y2 = ch["bbox"]
|
||||
past_1000 += 1 if max(x2, y2) > 1000 else 0
|
||||
at_1000 += 1 if 1000 in (x2, y2) else 0
|
||||
if pw and ph:
|
||||
max_ratio = max(max_ratio, x2 / pw, y2 / ph)
|
||||
if p["panel_index"] == WORKED_EXAMPLE:
|
||||
worked = (p, v, people, assigns, pw, ph)
|
||||
|
||||
named = [r for r in reg.values() if (r["name"] or "").strip()]
|
||||
print(f"registry: {len(reg)} characters, {len(named)} named -> {sorted((r['name'] or '') for r in named)}")
|
||||
print(f"detections: {detections} assignments: {assigned_total} "
|
||||
f"= {100*assigned_total/max(detections,1):.0f}% coverage")
|
||||
if per_char:
|
||||
top, n = per_char.most_common(1)[0]
|
||||
print(f"assignment spread: {dict(per_char.most_common(8))}")
|
||||
print(f" top character holds {n}/{assigned_total} = {100*n/max(assigned_total,1):.0f}% "
|
||||
f"({'ABSORBING, same signature as before' if n > 0.5 * assigned_total else 'ok'})")
|
||||
print(f"bbox space: {past_1000}/{detections} boxes exceed 1000, {at_1000} sit exactly on 1000, "
|
||||
f"largest coord/panel-dimension = {max_ratio:.2f}")
|
||||
print(f" verdict: {'PIXELS' if past_1000 or max_ratio > 0.02 and at_1000 == 0 else 'STILL 0-1000 GRID'}")
|
||||
missing_refs = [k for k, r in reg.items() if not r["ref_image_uris"] or not r["embedding_uri"]]
|
||||
print(f"characters missing a ref crop or embedding: {len(missing_refs)}")
|
||||
|
||||
if worked:
|
||||
p, v, people, assigns, pw, ph = worked
|
||||
print(f"\npanel_index {WORKED_EXAMPLE} ({p['panel_id']}), {pw}x{ph}:")
|
||||
for ch in people:
|
||||
cid, conf = assigns.get(ch["local_id"], (None, None))
|
||||
name = reg.get(cid, {}).get("name") or (cid[:16] if cid else "-- none --")
|
||||
print(f" {ch['local_id']:10} {str(ch['bbox']):28} {name:22} "
|
||||
f"{'' if conf is None else f'{conf:.2f}'}")
|
||||
else:
|
||||
print(f"\npanel_index {WORKED_EXAMPLE} not found in this chapter")
|
||||
Reference in New Issue
Block a user