diff --git a/experiments/regen_frame_examples.sh b/experiments/regen_frame_examples.sh new file mode 100755 index 0000000..ff1a9bc --- /dev/null +++ b/experiments/regen_frame_examples.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Regenerate annotated TP/FP/FN frame examples for ALL 9 films against the current +# opencv5 pipeline (learned-boundary flood, shipped config). Replays each film with +# --raw-out for bboxes, then dump_error_frames.py draws GT-aware boxes +# (green TP / red FP / orange unknown / blue FN panel). Frames land in +# experiments/dump_review// (regenerable; gitignored). Hand-pick the ones a +# doc needs from there. +set -uo pipefail +REPO="/home/dtourolle/Development/scene-actor-extraction"; cd "$REPO" +export MIOPEN_USER_DB_PATH="$HOME/.cache/miopen-sae" +GAL=experiments/galleries/gallery_LVFace-B_Glint360K.h5 +LUT=experiments/file-lut.json +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 --presence-mode flood) +mapfile -t ROWS < <(python3 -c ' +import json +for f in json.load(open("experiments/manifests/films_LVFace_opencv5.json")): + print(f["slug"]+"\t"+f["xray"])') +SP=/tmp/claude-1000/-home-dtourolle-Development-scene-actor-extraction/c579f8cf-2974-4cbd-be88-afec68dbbf58/scratchpad +for row in "${ROWS[@]}"; do + slug="${row%%$'\t'*}"; xray="${row#*$'\t'}" + movie="$(python3 -c "import json;print(json.load(open('$LUT'))['$slug'])")" + echo "=== $slug ===" + [ -f "experiments/dump_review/$slug/manifest.json" ] && { echo " exists, skip"; continue; } + # replay the learned-boundary (LOO) dump so frames reflect true generalization + dump="experiments/dumps/injected_loo/${slug}.h5" + [ -f "$dump" ] || dump="experiments/dumps/LVFace-B_Glint360K_opencv5/dump_${slug}.h5" + for try in 1 2 3; do + timeout 280 python scripts/optimizer/replay.py --dump "$dump" --gallery "$GAL" \ + --out "$SP/${slug}_pred.json" --raw-out "$SP/${slug}_raw.jsonl" "${CFG[@]}" \ + >"$SP/${slug}_replay.log" 2>&1 && break + echo " replay try $try failed, retrying" + done + [ -s "$SP/${slug}_raw.jsonl" ] || { echo " no raw output, skip"; continue; } + python3 scripts/optimizer/dump_error_frames.py \ + --pred "$SP/${slug}_pred.json" --raw "$SP/${slug}_raw.jsonl" \ + --xray "$xray" --movie "$movie" --gallery "$GAL" \ + --out-dir "experiments/dump_review/$slug" --n-per-bucket 4 \ + >"$SP/${slug}_frames.log" 2>&1 + echo " $(grep -oE 'wrote [0-9]+ frames' "$SP/${slug}_frames.log" | tail -1)" +done +echo "=== DONE ===" diff --git a/scripts/scene_detector/make_figures.py b/scripts/scene_detector/make_figures.py index e4689b8..1087700 100644 --- a/scripts/scene_detector/make_figures.py +++ b/scripts/scene_detector/make_figures.py @@ -68,5 +68,51 @@ def fig_evolution(): ax.set_ylim(0,18) fig.tight_layout(); fig.savefig(OUT/"scene_detector_evolution.png"); plt.close(fig) -fig_presence(); fig_macro(); fig_evolution() -print("wrote:", *(p.name for p in sorted(OUT.glob("scene_*.png")))) +import csv as _csv + +# ── Figure 4: DE convergence (the 10-knob presence sweep) ──────────────────── +def fig_de(): + import json + rows=[json.loads(l) for l in open("experiments/trajectories/lvface_opencv5_10knob.FINAL.jsonl")] + f1=[r["f1"]*100 for r in rows] + run_best=np.maximum.accumulate(f1) + fig,ax=plt.subplots(figsize=(8,4.5)) + ax.scatter(range(len(f1)),f1,s=8,alpha=0.35,color="#9aa7b4",label="candidate") + ax.plot(run_best,color="#3d7ea6",lw=2,label="best so far") + ax.set_xlabel("DE evaluation"); ax.set_ylabel("macro presence F1 (%)") + ax.set_title("10-knob presence sweep (Differential Evolution)") + ax.legend(loc="lower right"); ax.set_ylim(0, max(f1)+8) + ax.text(0.02,0.95,f"optimum {max(f1):.1f}%",transform=ax.transAxes,va="top", + fontsize=10,bbox=dict(boxstyle="round",fc="#f4f4f4",ec="#ccc")) + fig.tight_layout(); fig.savefig(OUT/"de_search_landscape.png"); plt.close(fig) + +# ── Figure 5: calibration curve (similarity → P(match)) ────────────────────── +def fig_calibration(): + sims,ps=[],[] + with open("experiments/galleries/gallery_LVFace-B_Glint360K.h5.calib_cache.csv") as f: + for r in _csv.DictReader(f): + sims.append(float(r["similarity"])); ps.append(float(r["p_match"])) + fig,ax=plt.subplots(figsize=(6.5,4.5)) + ax.plot(sims,ps,color="#3d7ea6",lw=2) + ax.axhline(0.485,ls="--",color="#e07a5f",lw=1,label="shipped threshold 0.485") + ax.set_xlabel("cosine similarity"); ax.set_ylabel("calibrated P(match)") + ax.set_title("LVFace-B Glint360K calibration"); ax.set_xlim(-1,1); ax.legend() + fig.tight_layout(); fig.savefig(OUT/"calibration_curves.png"); plt.close(fig) + +# ── Figure 6: holdout F1 by film (learned detector, LOO) ───────────────────── +def fig_holdout(): + order=np.argsort(FL) + fig,ax=plt.subplots(figsize=(8,4.5)) + y=np.arange(len(FILMS)) + ax.barh(y,[FL[i] for i in order],color="#3d7ea6") + ax.set_yticks(y); ax.set_yticklabels([FILMS[i] for i in order]) + ax.set_xlabel("presence F1 (%), learned detector (LOO)") + ax.set_title("Per-film presence F1 — leave-one-out") + ax.axvline(np.mean(FL),ls="--",color="#333",lw=1) + ax.text(np.mean(FL)+1,0.2,f"macro {np.mean(FL):.1f}%",fontsize=9) + for i,idx in enumerate(order): ax.text(FL[idx]+0.5,i,f"{FL[idx]:.0f}",va="center",fontsize=8) + ax.set_xlim(0,100) + fig.tight_layout(); fig.savefig(OUT/"holdout_f1_by_film.png"); plt.close(fig) + +fig_presence(); fig_macro(); fig_evolution(); fig_de(); fig_calibration(); fig_holdout() +print("wrote:", *(p.name for p in sorted(OUT.glob("*.png"))))