feat(tooling): X-Ray threshold optimizer, gallery utilities, artifact registry, docs build
Optimizer (scripts/optimizer/): replay.py runs the real C++ tracker/matcher/ scene_tracker chain over a dumped-embeddings HDF5 via sae_kpn, so a threshold sweep never re-decodes video or re-embeds faces. optimize.py drives scipy's differential_evolution over the knob space, with DE-level parallelism (multiple population candidates evaluated concurrently via a ThreadPoolExecutor) on top of per-film replay parallelism. second_score.py is the per-second X-Ray scoring metric (TPI/FPI/FN, out-of-cast misID weighted 10x, fair recall masked to gallery-known cast) that superseded an earlier scene-union metric. dump_error_frames.py / dump_scene_montage.py extract annotated video frames (bounding boxes, TPI/FPI/FN captions, onscreen-vs-offscreen split) for visual review of a replay against ground truth. Gallery utilities: cast_restrict.py, gallery_membership.py, fetch_missing_actors.py, reembed_gallery.py. scripts/validation/: X-Ray ground-truth loading and provider-agnostic identity matching (identity.py's keys_for — an actor is the union of every id we can derive, since pipeline output and ground truth don't share one id space). scripts/artifacts/: push/pull scripts for the Gitea generic package registry — galleries, montage frames, and experiment data (manifests/trajectories/results) are pushed there instead of committed, since none are needed to run the app, only benchmarks. Versioned by git short-SHA. scripts/docs/: MkDocs site build (build_site.sh) and the calibration-curve comparison chart (calibration_chart.py, matplotlib, reads each gallery's embedded calibration). Gallery-building scripts (make_jellyfin_gallery.py, make_gallery.py, filter_gallery.py, run_from_jellyfin.py, movienet_eval.py, movienet_prep.py, sae_gallery.py) updated to read/write HDF5 galleries exclusively, matching the engine-side format switch. run_from_jellyfin.py and the optimizer no longer carry movie source paths in shared manifests (some source filenames include scene-release tags) — resolved locally via a gitignored file-lut.json instead.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
reembed_gallery.py — re-embed an existing gallery's actors with a different model.
|
||||
|
||||
For the embedding-model bake-off: take a reference gallery (with all actor ids +
|
||||
source_images) and produce a new gallery where every actor's embeddings are computed
|
||||
by a DIFFERENT ArcFace/LVFace model from the SAME cached source images. All identity
|
||||
keys (imdb/tmdb/jellyfin/name) are preserved, so membership/matching is unchanged —
|
||||
only the embedding vectors (and hence the model's similarity space) differ.
|
||||
|
||||
Source images live in `--images <root>/<jellyfin_id>_<Name>/NN.jpg` (the gallery build
|
||||
cache). Actors are matched to their image dir by jellyfin_id first, then name.
|
||||
|
||||
Usage:
|
||||
python scripts/optimizer/reembed_gallery.py \
|
||||
--ref gallery_arcface_w600k_r50.h5 \
|
||||
--images images \
|
||||
--arcface models/arcface_r18.onnx \
|
||||
--out experiments/galleries/gallery_arcface_r18.h5 \
|
||||
[--build-dir build]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent.parent
|
||||
sys.path.insert(0, str(REPO / "scripts"))
|
||||
from sae_embed_loader import load_embedder # noqa: E402
|
||||
from sae_gallery import load_gallery_hdf5, save_gallery_hdf5 # noqa: E402
|
||||
|
||||
|
||||
def find_dir(images_root: Path, jellyfin_id: str, name: str) -> Path | None:
|
||||
if jellyfin_id:
|
||||
d = images_root / f"{jellyfin_id}_{name.replace(' ', '_')}"
|
||||
if d.is_dir():
|
||||
return d
|
||||
# jellyfin_id prefix match (name spelling may differ)
|
||||
hits = list(images_root.glob(f"{jellyfin_id}_*"))
|
||||
if hits:
|
||||
return hits[0]
|
||||
hits = list(images_root.glob(f"*_{name.replace(' ', '_')}"))
|
||||
return hits[0] if hits else None
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
p.add_argument("--ref", required=True, help="reference gallery.h5 (ids + source imgs)")
|
||||
p.add_argument("--images", required=True, help="image cache root")
|
||||
p.add_argument("--arcface", required=True, help="model ONNX to re-embed with")
|
||||
p.add_argument("--out", required=True)
|
||||
p.add_argument("--build-dir", default=str(REPO / "build"))
|
||||
p.add_argument("--models-dir", default=str(REPO / "models"))
|
||||
args = p.parse_args()
|
||||
|
||||
ref = load_gallery_hdf5(Path(args.ref))
|
||||
images_root = Path(args.images)
|
||||
embedder = load_embedder(args.build_dir, args.models_dir, args.arcface)
|
||||
|
||||
out_actors = []
|
||||
n_ok = n_nodir = n_noemb = 0
|
||||
total = len(ref["actors"])
|
||||
for i, a in enumerate(ref["actors"], 1):
|
||||
d = find_dir(images_root, a.get("jellyfin_id", ""), a["name"])
|
||||
if d is None:
|
||||
n_nodir += 1
|
||||
continue
|
||||
embeddings = []
|
||||
for img in sorted(d.glob("*.jpg")):
|
||||
res = embedder.embed(str(img))
|
||||
if res.ok:
|
||||
embeddings.append(list(res.embedding))
|
||||
if not embeddings:
|
||||
n_noemb += 1
|
||||
continue
|
||||
out_actors.append({"imdb_id": a.get("imdb_id", ""), "tmdb_id": a.get("tmdb_id", ""),
|
||||
"jellyfin_id": a.get("jellyfin_id", ""), "name": a["name"],
|
||||
"embeddings": embeddings,
|
||||
"source_images": [p.name for p in sorted(d.glob("*.jpg"))]})
|
||||
n_ok += 1
|
||||
if i % 200 == 0 or i == total:
|
||||
print(f" [{i}/{total}] ok={n_ok} no_dir={n_nodir} no_emb={n_noemb}",
|
||||
file=sys.stderr)
|
||||
|
||||
save_gallery_hdf5({"actors": out_actors}, Path(args.out))
|
||||
n_emb = sum(len(a["embeddings"]) for a in out_actors)
|
||||
print(f"[reembed] {Path(args.arcface).stem}: {n_ok}/{total} actors, {n_emb} embeddings "
|
||||
f"→ {args.out}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user