Files
manga-recap-pipeline/decisions/identity-bbox.md
T
kami 8113bdfc8b Fix the real A/V gap: a stream copy across mixed frame rates
The rebuild after 1457556 came out byte-identical to the broken file,
which proved the xfade fix never runs for this chapter. An all-cut chapter
goes down the concat demuxer with -c copy, which writes the output in the
FIRST input's time_base and reinterprets every later packet in it. 14 of
49 clips are 30/1 at 1/15360 against 35 at 25/1 at 1/12800, so those 14
play 15360/12800 = 1.2 too long with their audio untouched. collage_cmd
hardcoded -r 30 and yesterday's FPS sweep missed it.

collage_cmd now emits -r FPS, and assemble probes r_frame_rate across the
clips and routes mixed rates through the re-encoding tree. Rebuilt
chapter.mp4 is 364.120s video against 364.122s audio at 25/1, from
436.392 over 363.675.

Also settle the bbox coordinate space, measured over all 113 detections:
47 boxes have x2 past the 900px panel width, none has y2 past 1000 on
panels up to 2307px tall, and the range is exactly [0, 1000]. It is
gemma's normalized grid, not pixels, whatever the prompt asks for.
/vision converts before returning, which fixes identity's crop, the gated
face pairing that was comparing pixel face boxes against grid boxes, the
set-of-mark boxes and the review UI at once. Checked by eye on panel 7:
five of six boxes now land on their subject, including the foreground
character who had no identity.

The registry still holds boxes and embeddings enrolled from the wrong
space. vision and identity have to re-run, which is GPU work and was not
started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 12:16:22 +04:00

2.5 KiB

identity-bbox

The coordinate space of a vision character box, and what reads it.

A vision bbox arrives on gemma's 0-1000 grid, and /vision converts it to pixels

Closed, 2026-08-12.

build_detect_prompt asks for a "pixel bounding box". The model answers on its own normalized grid regardless. Measured over all 113 detections of job 778297bc, read straight from /review/identity:

test result
boxes with x2 past the 900px panel width 47 of 113
boxes with y2 past 1000, on panels 1257 to 2307px tall 0 of 113
boxes clamped at exactly 1000 21 in x, 5 in y
coordinate range over every box [0, 1000]

Pixels cannot behave that way. A person standing in the lower half of a 2307px panel needs y2 near 2000, and it never once exceeds 1000.

Consumed as pixels the boxes collapse into the top-left corner of the panel. Four consumers were reading them:

  • worker_identity._crop_bbox at worker_identity.py:200, which embeds the crop. This is why a crop of a speech balloon's edge matched Choi Haeseon at 0.9.
  • _pair_faces_to_present in worker_vision.py, which compares real detector face boxes, in pixels, against these. The gate could almost never pass, which is the mechanism behind the 7 unknown results out of 7 som_face lines already recorded at worker_vision.py:169.
  • the set-of-mark boxes drawn for attribution.
  • the review UI, which crops client-side off the panel PNG.

/vision now calls _bbox_to_pixels(characters, w, h) before returning, so all four see pixels and no consumer needs to know the grid existed. Verified by drawing the converted boxes on panel 7. Five of six land on their subject, including person_5, who is Seonho in the foreground and had no identity. person_1 still frames a window mullion with nobody in it, which is #extras-as-cast, not this.

The prompt text still says "pixel bounding box". Rewording it changes what the model emits and needs a GPU run to re-verify, so the boundary converts instead. The ponytail: note on _bbox_to_pixels records that. It also records the trap: a model that really answered in pixels would be scaled down here.

Consequence: every assignment in the registry came from a wrong crop. The existing embeddings and ref_image_uris are enrolled on balloons and window frames. Re-running identity is what makes the registry mean anything. The anonymous-identity and extra-versus-cast work cannot be judged until that rerun happens.