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:
2026-07-19 19:06:48 +02:00
parent 26139ffe8a
commit 6f0ad83a55
31 changed files with 3411 additions and 47 deletions
+6 -4
View File
@@ -5,7 +5,7 @@ movienet_prep.py — extract probe crops from MovieNet-PS for actors in our gall
Usage:
python scripts/movienet_prep.py \
--movienet <movienet_root> \
--gallery gallery.json \
--gallery gallery.h5 \
--output eval/ \
[--split Train_app10] \
[--margin 0.2] \
@@ -28,6 +28,9 @@ import zipfile
from io import BytesIO
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from sae_gallery import load_gallery_hdf5 # noqa: E402
try:
import cv2
import numpy as np
@@ -74,8 +77,7 @@ def load_movienet_annotations(movienet_root: Path, split: str) -> list[dict]:
def load_gallery_ids(gallery_path: str) -> dict[str, str]:
"""Return {imdb_id: actor_name} for all actors in the gallery."""
with open(gallery_path) as f:
data = json.load(f)
data = load_gallery_hdf5(Path(gallery_path))
return {a["imdb_id"]: a["name"] for a in data["actors"]}
@@ -100,7 +102,7 @@ def crop_face(img: "np.ndarray", bbox: list[float], margin: float) -> "np.ndarra
def main():
p = argparse.ArgumentParser()
p.add_argument("--movienet", required=True, help="MovieNet-PS root directory")
p.add_argument("--gallery", required=True, help="gallery.json (for actor list)")
p.add_argument("--gallery", required=True, help="gallery.h5 (for actor list)")
p.add_argument("--output", default="eval", help="output directory")
p.add_argument("--split", default="Train_app10",
help="annotation split to use (default: Train_app10)")