diff --git a/docs/assets/images/cafe_society_rapid_cut.jpg b/docs/assets/images/cafe_society_rapid_cut.jpg new file mode 100644 index 0000000..a70f117 Binary files /dev/null and b/docs/assets/images/cafe_society_rapid_cut.jpg differ diff --git a/docs/assets/images/downton_crew_fn.jpg b/docs/assets/images/downton_crew_fn.jpg new file mode 100644 index 0000000..3b5667f Binary files /dev/null and b/docs/assets/images/downton_crew_fn.jpg differ diff --git a/docs/assets/images/downton_tp_example.jpg b/docs/assets/images/downton_tp_example.jpg new file mode 100644 index 0000000..3b5667f Binary files /dev/null and b/docs/assets/images/downton_tp_example.jpg differ diff --git a/docs/assets/images/downton_wedding_couple.jpg b/docs/assets/images/downton_wedding_couple.jpg new file mode 100644 index 0000000..3b5667f Binary files /dev/null and b/docs/assets/images/downton_wedding_couple.jpg differ diff --git a/docs/assets/images/lord_of_war_fpi_reagan_photo.jpg b/docs/assets/images/lord_of_war_fpi_reagan_photo.jpg new file mode 100644 index 0000000..b21d60c Binary files /dev/null and b/docs/assets/images/lord_of_war_fpi_reagan_photo.jpg differ diff --git a/docs/assets/images/lord_of_war_fpi_reddick.jpg b/docs/assets/images/lord_of_war_fpi_reddick.jpg new file mode 100644 index 0000000..0122f0e Binary files /dev/null and b/docs/assets/images/lord_of_war_fpi_reddick.jpg differ diff --git a/docs/assets/images/lord_of_war_fpi_shumbris.jpg b/docs/assets/images/lord_of_war_fpi_shumbris.jpg new file mode 100644 index 0000000..a07a204 Binary files /dev/null and b/docs/assets/images/lord_of_war_fpi_shumbris.jpg differ diff --git a/docs/assets/images/lovelace_fpi_sevigny.jpg b/docs/assets/images/lovelace_fpi_sevigny.jpg new file mode 100644 index 0000000..9be63c8 Binary files /dev/null and b/docs/assets/images/lovelace_fpi_sevigny.jpg differ diff --git a/docs/assets/images/lovelace_perfect_second.jpg b/docs/assets/images/lovelace_perfect_second.jpg index 2e0b16f..97f0939 100644 Binary files a/docs/assets/images/lovelace_perfect_second.jpg and b/docs/assets/images/lovelace_perfect_second.jpg differ diff --git a/docs/assets/images/lovelace_polygraph_bridged.jpg b/docs/assets/images/lovelace_polygraph_bridged.jpg new file mode 100644 index 0000000..97f0939 Binary files /dev/null and b/docs/assets/images/lovelace_polygraph_bridged.jpg differ diff --git a/docs/assets/images/lovelace_robert_patrick_fpi.jpg b/docs/assets/images/lovelace_robert_patrick_fpi.jpg new file mode 100644 index 0000000..adabb6f Binary files /dev/null and b/docs/assets/images/lovelace_robert_patrick_fpi.jpg differ diff --git a/docs/assets/images/many_saints_fpi_gardner.jpg b/docs/assets/images/many_saints_fpi_gardner.jpg new file mode 100644 index 0000000..8f02d5f Binary files /dev/null and b/docs/assets/images/many_saints_fpi_gardner.jpg differ diff --git a/docs/assets/images/many_saints_fpi_yates.jpg b/docs/assets/images/many_saints_fpi_yates.jpg new file mode 100644 index 0000000..53bd664 Binary files /dev/null and b/docs/assets/images/many_saints_fpi_yates.jpg differ diff --git a/docs/assets/images/many_saints_outofcast_fpi.jpg b/docs/assets/images/many_saints_outofcast_fpi.jpg new file mode 100644 index 0000000..f2fae1d Binary files /dev/null and b/docs/assets/images/many_saints_outofcast_fpi.jpg differ diff --git a/docs/assets/images/scarface_fpi_alley.jpg b/docs/assets/images/scarface_fpi_alley.jpg new file mode 100644 index 0000000..5a08b47 Binary files /dev/null and b/docs/assets/images/scarface_fpi_alley.jpg differ diff --git a/docs/assets/images/valerian_screen_call.jpg b/docs/assets/images/valerian_screen_call.jpg index dca6167..0a8d0b1 100644 Binary files a/docs/assets/images/valerian_screen_call.jpg and b/docs/assets/images/valerian_screen_call.jpg differ diff --git a/docs/lvface-deep-dive-2026-07.md b/docs/lvface-deep-dive-2026-07.md index 817bef4..ab5ba99 100644 --- a/docs/lvface-deep-dive-2026-07.md +++ b/docs/lvface-deep-dive-2026-07.md @@ -176,10 +176,10 @@ ground-truth gap, not a model error. Archie Yates, t=2521s, 78% confidence. A real detected face, a genuine lookalike confusion. -![Zooey Deschanel, third out-of-cast name in Many Saints](assets/images/many_saints_fpi_deschanel.jpg) - -Zooey Deschanel, t=2819s, 99% confidence. A real detected face at a dinner -table, high-confidence lookalike confusion. +Zooey Deschanel, t=2819s, 99% confidence — a high-confidence lookalike +confusion in the July pipeline. **The current opencv5 pipeline no longer makes +this identification**; the tighter tracker/registry and re-tuned matching removed +it, so there is no annotated frame for it here. ![Talia Balsam, fourth out-of-cast name in Many Saints](assets/images/many_saints_fpi_balsam.jpg) diff --git a/scripts/scene_detector/rematch_frames.py b/scripts/scene_detector/rematch_frames.py new file mode 100644 index 0000000..b2b93e7 --- /dev/null +++ b/scripts/scene_detector/rematch_frames.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +rematch_frames.py — remake each named July frame example against the CURRENT +pipeline. For a file named _<...>_.jpg, find a second in this film's +replay where that actor is drawn in the matching class (FP for *_fpi_*, TP for +*_tp/perfect*), extract + annotate it, and write it over the doc asset. Reports +which July examples no longer reproduce (honest — the config/model changed). + +Needs the per-film raw replay (experiments/dumps + replay --raw-out already run by +regen_frame_examples.sh into the scratch predictions). Reads those. +""" +from __future__ import annotations +import json, sys, subprocess, re +from pathlib import Path +sys.path.insert(0, "scripts/optimizer"); sys.path.insert(0, "scripts/validation") +import dump_error_frames as D +from second_score import load_second_timeline, _match + +SP = Path("/tmp/claude-1000/-home-dtourolle-Development-scene-actor-extraction/" + "c579f8cf-2974-4cbd-be88-afec68dbbf58/scratchpad") +ASSETS = Path("docs/assets/images") +LUT = json.load(open("experiments/file-lut.json")) +FILMS = json.load(open("experiments/manifests/films_LVFace_opencv5.json")) +XR = {f["slug"]: f["xray"] for f in FILMS} + +# filename → (film slug, actor substring, class). class: "fp" | "tp". +# actor substring is matched case-insensitively against drawn names. +JOBS = { + "lord_of_war_fpi_reddick.jpg": ("Lord_of_War", "reddick", "fp"), + "lord_of_war_fpi_shumbris.jpg": ("Lord_of_War", "shumbris", "fp"), + "lord_of_war_fpi_reagan_photo.jpg": ("Lord_of_War", "reagan", "fp"), + "lovelace_fpi_sevigny.jpg": ("Lovelace", "sevigny", "fp"), + "lovelace_robert_patrick_fpi.jpg": ("Lovelace", "patrick", "fp"), + "lovelace_perfect_second.jpg": ("Lovelace", None, "tp"), + "lovelace_polygraph_bridged.jpg": ("Lovelace", None, "tp"), + "many_saints_fpi_deschanel.jpg": ("The_Many_Saints_of_Newark", "deschanel", "fp"), + "many_saints_fpi_gardner.jpg": ("The_Many_Saints_of_Newark", "gardner", "fp"), + "many_saints_fpi_yates.jpg": ("The_Many_Saints_of_Newark", "yates", "fp"), + "many_saints_outofcast_fpi.jpg": ("The_Many_Saints_of_Newark", None, "fp"), + "scarface_fpi_alley.jpg": ("Scarface", "alley", "fp"), + "downton_crew_fn.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), + "downton_wedding_couple.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), + "downton_tp_example.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), + "valerian_screen_call.jpg": ("Valerian_and_the_City_of_a_Thousand_Plan", None, "tp"), + "cafe_society_rapid_cut.jpg": ("Café_Society", None, "tp"), + # germar_beats_xray / downton_funeral_19of20 are July-narrative-specific; skip. +} + + +def gt_keysets(slug): + tl, _, _ = load_second_timeline(XR[slug]) + return tl + + +def main(): + made, missing = [], [] + for fname, (slug, actor, cls) in JOBS.items(): + raw = SP / f"{slug}_raw.jsonl" + if not raw.exists(): + missing.append((fname, "no raw replay")); continue + tl = gt_keysets(slug) + best = None # (t, actor_dict, fp_keys) + for line in open(raw): + d = json.loads(line) + t = int(d["timestamp_sec"]) + drawn = [a for a in d.get("visible_actors", []) if a.get("actor_idx", -1) >= 0] + if not drawn: + continue + gt = tl.get(t, []) + fp_keys = {D._name_key(a["name"]) for a in drawn + if not any(D._name_key(a["name"]) in g for g in gt)} + for a in drawn: + nk = D._name_key(a["name"]); is_fp = nk in fp_keys + if actor and actor not in a["name"].lower(): + continue + match = (is_fp if cls == "fp" else not is_fp) + if not match: + continue + # prefer high similarity + a clean single-subject frame + score = a["similarity"] - 0.05*len(drawn) + if best is None or score > best[3]: + best = (t, d, fp_keys, score) + if best is None: + missing.append((fname, f"no current {cls} for {actor or 'any'}")); continue + t, d, fp_keys, _ = best + # FN names at t: X-Ray scene cast whose keyset matches no drawn face. + gt = tl.get(t, []) + drawn_keys = [set(D._name_key(a["name"]).replace("name:", "") for _ in [0]) + for a in d.get("visible_actors", []) if a.get("actor_idx", -1) >= 0] + drawn_ks = [D._name_key(a["name"]) for a in d.get("visible_actors", []) + if a.get("actor_idx", -1) >= 0] + fn_names = [] + for ga in gt: + if not any(dk in ga for dk in drawn_ks): + readable = sorted(x for x in ga + if not x.startswith("imdb:") and not x.startswith("tmdb:") + and not x.startswith("jf:")) + if readable: + fn_names.append(readable[0]) + out = ASSETS / fname + try: + D.extract_frame(LUT[slug], t, out) + D.draw_annotations(out, d["visible_actors"], fp_keys=fp_keys, + fn_names=fn_names) + made.append((fname, slug, t)) + except subprocess.CalledProcessError: + missing.append((fname, "ffmpeg failed")) + + print("=== remade ===") + for f, s, t in made: print(f" {f} ({s} t={t}s)") + print("=== no current equivalent (left as-is / flag in doc) ===") + for f, why in missing: print(f" {f} — {why}") + + +if __name__ == "__main__": + main()