#!/usr/bin/env python3 """ downstream_presence.py — does the XGBoost scene detector actually improve ACTOR PRESENCE accuracy? Boundary-F1 is only a proxy; this is the number that decides whether the detector ships. For each film, compares presence (per-second X-Ray F1) under three regimes: A. track_extent — no flood-fill (claim = [first_seen, last_seen]) B. flood + histogram cuts — current shipped flood (snaps to is_cut) C. flood + XGBoost bounds — inject the detector's boundaries into is_scene_boundary (flood prefers it over is_cut) Injection: write a copy of each dump with frames/is_scene_boundary set from the XGBoost knee boundaries, then replay --presence-mode flood against that copy. Uses the shipped model (all-9 fit). Scored with second_score at the 10-knob optimum config. """ from __future__ import annotations import sys, json, shutil, subprocess, tempfile, os from pathlib import Path import numpy as np import h5py sys.path.insert(0, "scripts/scene_detector") sys.path.insert(0, "scripts/optimizer") sys.path.insert(0, "scripts/validation") import train_xgb_boundary as XB from second_score import score_seconds from sample_eval import load_gallery_keys import xgboost as xgb GAL = "experiments/galleries/gallery_LVFace-B_Glint360K.h5" MODEL = "experiments/results/scene_boundary/xgb_boundary_shipped.json" # 10-knob presence optimum (shipped config) CFG = ["--prob-threshold", "0.485", "--ownership-logodds", "1.72", "--track-extinction-sec", "31", "--track-alpha", "0.435", "--evidence-rho-max", "0.204", "--evidence-admit-below", "0.784", "--match-prior", "0.433", "--expand-band-lo", "0.804", "--expand-band-hi", "0.952", "--expand-gallery"] def xgb_boundary_seconds(reg, dump): X, yb, ic = XB.per_second_matrix(dump, xr_for(dump), "experiments/dumps/audio_features") prob = np.clip(reg.predict(X), 0, 1) return set(XB.knee_boundaries(prob)) FILMS = json.load(open("experiments/manifests/films_LVFace_opencv5.json")) _XR = {f["dump"]: f["xray"] for f in FILMS} def xr_for(dump): return _XR[dump] def inject_boundaries(dump, second_set, out_path): """Copy dump, set frames/is_scene_boundary=1 at the given integer seconds.""" shutil.copy(dump, out_path) with h5py.File(out_path, "r+") as f: ts = f["frames/timestamp_sec"][:] bnd = np.zeros(len(ts), np.uint8) for i, t in enumerate(ts): if int(round(t)) in second_set: bnd[i] = 1 if "frames/is_scene_boundary" in f: f["frames/is_scene_boundary"][:] = bnd else: f["frames"].create_dataset("is_scene_boundary", data=bnd) def replay(dump, out, mode): argv = [".venv-rocm/bin/python" if False else sys.executable, "scripts/optimizer/replay.py", "--dump", dump, "--gallery", GAL, "--out", out] + CFG if mode: argv += ["--presence-mode", mode] subprocess.run(argv, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=300) return json.loads(Path(out).read_text()) def main(): reg = xgb.XGBRegressor(); reg.load_model(MODEL) gk = load_gallery_keys(GAL) tmp = tempfile.mkdtemp() print(f"{'film':24s} {'trackext':>9} {'flood+hist':>11} {'flood+XGB':>10}") agg = {"track_extent": [], "flood_hist": [], "flood_xgb": []} for f in FILMS: dump, xr = f["dump"], f["xray"] out = f"{tmp}/out.json" # A. track_extent a = score_seconds(replay(dump, out, "track_extent"), xr, gallery_keys=gk) # B. flood + histogram cuts (original dump's is_cut; is_scene_boundary=0) b = score_seconds(replay(dump, out, "flood"), xr, gallery_keys=gk) # C. flood + XGBoost boundaries injected inj = f"{tmp}/inj_{f['slug']}.h5" inject_boundaries(dump, xgb_boundary_seconds(reg, dump), inj) c = score_seconds(replay(inj, out, "flood"), xr, gallery_keys=gk) os.unlink(inj) agg["track_extent"].append(a["f1"]); agg["flood_hist"].append(b["f1"]) agg["flood_xgb"].append(c["f1"]) print(f"{f['name'][:24]:24s} {a['f1']*100:8.1f}% {b['f1']*100:10.1f}% " f"{c['f1']*100:9.1f}%") print(f"\n{'MACRO-MEAN':24s} {np.mean(agg['track_extent'])*100:8.1f}% " f"{np.mean(agg['flood_hist'])*100:10.1f}% {np.mean(agg['flood_xgb'])*100:9.1f}%") json.dump({k: float(np.mean(v)) for k, v in agg.items()}, open("experiments/results/scene_boundary/downstream_presence.json", "w"), indent=2) if __name__ == "__main__": main()