diff --git a/.gitignore b/.gitignore index fa61ea8..d0015ad 100644 --- a/.gitignore +++ b/.gitignore @@ -85,9 +85,10 @@ docs_site/ # ignore docs/assets/ itself — only its contents, minus the one exception.) docs/assets/images/* !docs/assets/images/pipeline_topology.svg -# readme_example.jpg is referenced directly by README.md, which renders on the +# These frames are referenced directly by README.md, which renders on the # plain Gitea repo view — committed for the same reason as the SVG above. -!docs/assets/images/readme_example.jpg +!docs/assets/images/lovelace_perfect_second.jpg +!docs/assets/images/valerian_screen_call.jpg # Python __pycache__/ diff --git a/README.md b/README.md index a682b4b..c75ea81 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,18 @@ discussion in the [deep dive](https://pages.tourolle.paris/dtourolle/scene-actor Full benchmark write-up, model comparison, and failure-mode analysis: **https://pages.tourolle.paris/dtourolle/scene-actor-extraction/** -![Example: correctly identified actors in a held-out film](docs/assets/images/readme_example.jpg) -*A held-out film (never used for threshold tuning) — three actors correctly -identified with calibrated confidence scores.* +![A perfect X-Ray second on a held-out film](docs/assets/images/lovelace_perfect_second.jpg) +*A perfect X-Ray second on a held-out film (never used for threshold tuning): +every visible face named at 100%, the background extra honestly left unnamed, +and the two credited cast without a visible face correctly carried as present +off-screen. Bottom panels show the per-second verdict against Amazon X-Ray +(green = correct, orange = wrong, blue = missed).* + +It also doesn't care whether the face is in the room: + +![Herbie Hancock identified on an in-fiction video-call screen](docs/assets/images/valerian_screen_call.jpg) +*Herbie Hancock at 98% — as a face on a screen inside the movie, under a +sci-fi HUD overlay.* ## How it works diff --git a/docs/assets/images/lovelace_perfect_second.jpg b/docs/assets/images/lovelace_perfect_second.jpg new file mode 100644 index 0000000..2e0b16f Binary files /dev/null and b/docs/assets/images/lovelace_perfect_second.jpg differ diff --git a/docs/assets/images/readme_example.jpg b/docs/assets/images/readme_example.jpg deleted file mode 100644 index 1e2c23a..0000000 Binary files a/docs/assets/images/readme_example.jpg and /dev/null differ diff --git a/docs/assets/images/valerian_screen_call.jpg b/docs/assets/images/valerian_screen_call.jpg new file mode 100644 index 0000000..dca6167 Binary files /dev/null and b/docs/assets/images/valerian_screen_call.jpg differ diff --git a/docs/index.md b/docs/index.md index 37cf8b6..ef0c04a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,32 +5,60 @@ film or TV episode — built on [KPN++](https://gitea.tourolle.paris/dtourolle/K (a C++20 Kahn Process Network library) for the detect → track → match → scene pipeline, with a Jellyfin-integrated gallery and an X-Ray-validated optimizer. -This is what a good second looks like — one sampled frame from a held-out film, -19 faces named, all 19 correct, the rest honestly declared unknown: +This is a perfect X-Ray second, on a film the optimizer never saw: -![19 correct identifications in one wedding shot, Downton Abbey: A New Era](assets/images/downton_wedding_19_correct.jpg) +![A perfect X-Ray second: three faces named at 100%, two more correctly carried off-screen](assets/images/lovelace_perfect_second.jpg) -And this is why the work isn't done: on this same film the same config misses -6 in 10 of the actor-seconds X-Ray says are present, and on the worst held-out -film it reports ghost actors over empty walls — at 100% confidence. Both -stories, with the evidence, are in the pages below. +Every visible face named at 100% — Chris Noth, Hank Azaria, Bobby Cannavale — +the background extra honestly left unnamed, and the two credited cast without +a visible face correctly carried as present off-screen by the tracker's +presence windows. That's the pipeline exactly reproducing Amazon X-Ray's +record for this second. + +It doesn't always go like that: the hardest held-out film scores 46% F1, and +the report is honest about *why* — one tunable trade (extinction bridging at +hard cuts), one structural ceiling (X-Ray credits people whose faces never +appear), and a few cases where the pipeline is right and X-Ray is wrong. The +evidence for all of it is in the pages below. ## Start here — four questions this bake-off answers -- **[Which model is best?](best-model.md)** — calibration curves first - (discriminative power, independent of any threshold), then F1 on the actual - benchmark. LVFace-B Glint360K wins both. -- **[Whole gallery vs. limited (cast-restricted) gallery](gallery-scope.md)** — - restricting the matcher to a film's credited cast is a clean win on every - axis (+3.3pp F1, less than a third the misIDs), but isn't a shipped runtime - feature yet. -- **[Does pose expansion help?](pose-expansion.md)** — a real training-set - effect that didn't reproduce on 5 held-out films once two methodology bugs - were caught and fixed. An honest null result, not a forced narrative. -- **[Deep dive: LVFace-B Glint360K](lvface-deep-dive.md)** — the winning - model's held-out generalization gap, its two real failure modes (frozen-bbox - "ghost tracks"), and one case where it correctly identified an actor that - the X-Ray ground truth itself failed to credit. +
+ +- :material-trophy:{ .lg .middle } **[Which model is best?](best-model.md)** + + --- + + Calibration curves first (discriminative power, independent of any + threshold), then F1 on the actual benchmark. LVFace-B Glint360K wins + both. + +- :material-filter:{ .lg .middle } **[Whole vs. cast-restricted gallery](gallery-scope.md)** + + --- + + Restricting the matcher to a film's credited cast is a clean win on + every axis (+3.3pp F1, less than a third the misIDs) — but isn't a + shipped runtime feature yet. + +- :material-account-convert:{ .lg .middle } **[Does pose expansion help?](pose-expansion.md)** + + --- + + A convincing training-set effect that didn't reproduce on 5 held-out + films once two methodology bugs were caught and fixed. An honest null + result, not a forced narrative. + +- :material-magnify-expand:{ .lg .middle } **[Deep dive: LVFace-B Glint360K](lvface-deep-dive.md)** + + --- + + The held-out generalization gap, how the error budget decomposes + (extinction bridging at hard cuts, X-Ray's scene-membership vs. + on-screen-face ceiling), and the frames where the pipeline is right + and the ground truth is wrong. + +
## The full technical log @@ -43,7 +71,7 @@ stories, with the evidence, are in the pages below. earlier scene-union-metric tuning pass, superseded by the per-second metric used in the bake-off but kept for the ground-truth/architecture background. - **[Service conversion (proposal)](service-conversion.md)** — design sketch - for an idle-GPU Docker worker, not yet built. + for a native idle-GPU worker gated on screen lock, not yet built. ## Reproducing the benchmarks diff --git a/docs/lvface-deep-dive.md b/docs/lvface-deep-dive.md index e4fec6f..41ccf36 100644 --- a/docs/lvface-deep-dive.md +++ b/docs/lvface-deep-dive.md @@ -2,27 +2,45 @@ LVFace won the model bake-off (see [Which model is best?](best-model.md)) and is the shipped default embedder. This page is the honest accounting of how it -actually performs — what a good second looks like, where it's wrong and *why*, -and one case where the ground truth itself is wrong and LVFace is right. +actually performs — what a good second looks like, where the errors actually +come from, and two cases where the ground truth itself is wrong and LVFace is +right. + +!!! note "How to read the frames on this page" + + The top is the film frame, with a box and name on every face the pipeline + identified. The bottom panels are the per-second verdict against X-Ray: + **Onscreen** lists faces named in the frame, **Offscreen** lists cast + X-Ray marks present in the scene without a visible face — presence + carried by the tracker's windows, not by a detection. Colors are the + score: **green** = correct (TPI), + **orange** = wrong (FPI), + **blue** = missed (FN). ## What good looks like -Before the failure analysis, the ceiling. This is a single sampled second from -Downton Abbey's wedding scene — a packed, hat-heavy, period-costume group shot, -about as hostile as ensemble framing gets: +![Wedding couple correctly identified, Downton Abbey: A New Era](assets/images/downton_wedding_couple.jpg) -![19 correct identifications in one wedding shot, Downton Abbey: A New Era](assets/images/downton_wedding_19_correct.jpg) -*Frame `downton_abbey/best/best_t00127.jpg` from the `montage-frames` artifact -package (`scripts/artifacts/pull_artifacts.sh montage-frames -Downton_Abbey__A_New_Era`) — green = identified, blue = detected but unknown.* +Six faces on screen, all six named correctly — including Penelope Wilton at the +edge of the pews and a half-occluded Michelle Dockery — while thirteen more +cast members X-Ray marks present in the scene are correctly carried as +"Offscreen" by their presence windows. One miss in the whole frame: Maggie +Smith (blue). Score for this second: 0.86. -**Nineteen named faces in one frame, all nineteen correct** — Jim Carter half -behind a flower arrangement, Penelope Wilton at a three-quarter turn, Lesley -Nicol under a hat brim. The blue "unknown" boxes are the honest cases: faces the -detector found but the matcher declined to name rather than guess. The one miss -at this second is Maggie Smith — not on screen in this framing, but X-Ray marks -her present for the scene. That distinction (on-screen face vs. scene-level -ground truth) sets up everything below. +![19 of 20 correct in the funeral crowd](assets/images/downton_funeral_19of20.jpg) + +The same film's funeral gathering: mourning dress, hats, half the faces turned. +**Nineteen of the twenty cast X-Ray lists for this scene are scored correctly** +— seven named on screen at up to 100% confidence, twelve more correctly held +as present off-screen. + +And the pipeline doesn't need the face to be *real*: + +![Herbie Hancock identified on an in-fiction video call](assets/images/valerian_screen_call.jpg) + +That's Herbie Hancock at 98% — as a face on a *screen inside the movie*, over a +sci-fi HUD overlay, during a video call in Valerian. A face is a face, whether +it's in the room or on the bridge's comms display. ## Training vs. held-out: the generalization gap @@ -43,97 +61,134 @@ against the 5 films the optimizer never saw: **67.4% held-out vs. 75.3% on training** — an ~8pp drop, and a **37pp spread between the best and worst held-out film**. The config does not generalize -uniformly; two films are outright failure cases, for reasons that turn out to -be one mechanism. +uniformly, and the spread traces to two mechanisms, both visible frame by +frame below. -## The failure mode: frozen-bbox "ghost tracks" +## Mechanism 1: extinction bridging — usually right, wrong at hard cuts +The extinction window keeps an identity alive through seconds where no face is +detectable. **Most of the time this is exactly what you want**, and it's where +a lot of the TPI count comes from: + +![Two faces on screen, six more correctly bridged](assets/images/lovelace_polygraph_bridged.jpg) + +Lovelace's polygraph scene: only Eric Roberts and Amanda Seyfried have visible +faces, but X-Ray lists eight cast present — and all eight score green, the +other six correctly carried by presence windows through a scene where the +camera never shows them. A perfect second, and the extinction/anneal machinery +is *why*. + +The same mechanism has a failure case: a hard cut into long faceless footage. Both Many Saints of Newark (974 misIDs) and Downton Abbey (FN=80084, the worst -recall of the five) trace to the same root cause, verified directly against -the raw per-frame stream and the HDF5 dump's own detection counts — not -inferred from the score alone. +recall of the five) are dominated by it — verified directly against the raw +per-frame stream and the HDF5 dump's own detection counts, not inferred from +the score alone. **This is not a malfunction**: the tracker is doing exactly +what its window is for; the footage just stops cooperating. In the debug +overlay (which draws a bridged identity's last-known bbox, unlike the shipped +output, which emits presence windows and no boxes at all) the bridged state is +visible spatially: -![Frozen ghost boxes over background, The Many Saints of Newark](assets/images/many_saints_ghost_fpi.jpg) +![Debug overlay: bridged identities drawn at their last-known positions](assets/images/many_saints_ghost_fpi.jpg) +*Debug-overlay rendering (`dump_error_frames.py --raw`): "Jon Bernthal", "Joey +Diaz" and "Billy Magnussen" are extinction-bridged identities from the previous +shot, drawn frozen over the wall and the hanging plates. Frame +`many_saints/fpi/fpi_t03543.jpg`, `montage-frames` artifact package.* -At this second, three of the four labeled boxes ("Jon Bernthal", "Joey Diaz", -"Billy Magnussen") sit over empty background — a blurred wall, hanging -plates — with no face in them. The real face in frame carries a second, -colliding label from another frozen box. And it isn't an isolated second — the -same signature recurs throughout the film: - -![Ghost labels over a staircase while real faces stay honest unknowns](assets/images/many_saints_ghosts_vs_unknowns.jpg) -*Frame `many_saints_intervals/w002_worst_t01382.jpg`, same artifact package — -one frame, three distinct error classes.* - -This frame is worth reading closely, because it separates three things that a -single aggregate F1 number smears together. The two green labels ("Jon Bernthal -100%", "Michela De Rossi 100%") float over a staircase and a policeman's back — -frozen boxes from a previous shot, reported at full confidence. Meanwhile the -two *real* frontal faces in frame get honest blue "unknown 0%" boxes (they're -uncredited day-players with no gallery reference — the -[gallery coverage gap](gallery-scope.md)), and three more people simply face -away from camera, invisible to any face detector but still "present" in X-Ray's -scene-level ground truth. Precision failure, gallery-coverage failure, and the -face-vs-presence ceiling — one frame. - -### The mechanism, measured - -The starkest case is Downton Abbey's hard cut from a packed group shot into a -long blank credits sequence. Plotting the detector's per-second `face_count` -(from the dump HDF5, independent of the tracker) against what the tracker -reports makes the failure legible at a glance: +The cost is measurable, not just visible. Downton Abbey's hard cut into its +closing credits, plotting the dump's own per-second `face_count` (detector +output, independent of the tracker) against what the tracker reports: ![Detector vs. tracker through Downton Abbey's cut to credits](assets/images/downton_ghost_timeline.png) From the cut onward the detector sees **zero faces for nearly a minute** — and -the tracker keeps reporting the last group shot's 15 identities the entire -time, each with the *exact same bounding box, unchanged to the pixel* (verified -for Hugh Bonneville: `(1743.2, 0.0, 171.3, 317.8)` at every sampled second for -57+ seconds). The staircase decay at the right edge is the extinction window -finally expiring, actor by actor. - -This is `SceneTrackerFunc::active_[actor_idx].last_bbox` +the tracker keeps reporting the last shot's 15 identities the whole time +(verified for Hugh Bonneville: bbox `(1743.2, 0.0, 171.3, 317.8)`, unchanged to +the pixel, at every sampled second for 57+ seconds). The staircase at the right +edge is the extinction window expiring actor by actor. That plateau is +`SceneTrackerFunc::active_[actor_idx].last_bbox` ([`src/nodes/scene_tracker_node.hpp`](https://REPOLINK/src/nodes/scene_tracker_node.hpp)) -being re-emitted unchanged — the -extinction state machine working exactly as coded, not a bug. `extinction_sec` -was tuned to 57.4s specifically because long windows bridge real gaps -(occlusion, a turned face) in most footage; a hard cut into long faceless -footage is the one case where that same bridging manufactures ghosts, and the -training films never contained one long enough to punish it. The optimizer -"discovered" the plateau at the top of its search range for a reason that only -generalizes to films that never go faceless for a minute. +re-emitted as designed: `extinction_sec=57.4` was tuned long because bridging +wins on most footage (see the polygraph frame above) — the training films just +never contained a faceless stretch long enough to show the cost side, and the +held-out set did. + +The same track-continuation machinery has one milder spatial artifact, worth +knowing when reading these frames: + +![Two labels on one face after a shot/reverse-shot cut](assets/images/cafe_society_rapid_cut.jpg) +*Café Society (a training film), a shot/reverse-shot dialog: that is Steve +Carell wearing both his own label and Jesse Eisenberg's.* + +At a rapid cut, the previous shot's track can linger for a beat at nearly the +same screen position the new face occupies — here Jesse Eisenberg's box from +the counter-shot lands on Steve Carell. Note what the score panel says, +though: both actors are green, because both *are* present in this dialog +scene per X-Ray. The spatial label is briefly wrong; the per-second presence +claim — the thing the pipeline actually ships — is right. It's the same trade +as the extinction window: track continuation smooths over cuts, and 1 fps +sampling occasionally catches the seam. + +## Mechanism 2: the face-vs-presence ceiling + +Downton Abbey's recall didn't collapse because faces were misread — it +collapsed because for most of its 80084 FN-seconds there was **no face to +read**: + +![22 cast credited, nobody facing the camera](assets/images/downton_crew_fn.jpg) + +A newsreel crew hauls equipment through the hall: X-Ray credits 22 cast as +present in this scene; not one face looks at the camera. Eight are still +scored green (windows bridging from adjacent shots) — the other fourteen are +blue FNs that no face-recognition pipeline could ever recover. X-Ray encodes +*scene membership*; the pipeline measures *on-screen faces*. In ensemble films +those two definitions diverge massively, and that gap — not identification +error — is most of what the FN column counts. + +![Presence without a detectable face, The Many Saints of Newark](assets/images/many_saints_outofcast_fpi.jpg) + +Same ceiling from the other side: Michela De Rossi in frame but turned away, +five cast correctly bridged as offscreen (green), four blue FNs — and one +orange we'll come back to below. ## Where LVFace beat X-Ray -Not every flagged "misID" is actually wrong. +Not every orange in these frames is actually wrong. [`scripts/optimizer/second_score.py`](https://REPOLINK/scripts/optimizer/second_score.py) -counts a name as a true out-of-cast misID whenever the named actor isn't in -X-Ray's credited cast list for the film at all — but X-Ray's cast list is -itself incomplete. +scores strictly against X-Ray — but X-Ray itself has holes, and the pipeline +found two kinds. ![LVFace correctly identifies Germar Terrell Gardner, uncredited by X-Ray](assets/images/germar_beats_xray.jpg) Germar Terrell Gardner — a real, clean, high-confidence detection — is counted -as a misID here because he doesn't appear in X-Ray's `people.csv` for The Many -Saints of Newark at all. But Jellyfin's independent cast metadata *does* credit -him for this exact film (cross-checked via +as an out-of-cast misID because he doesn't appear in X-Ray's `people.csv` for +The Many Saints of Newark at all. But Jellyfin's independent cast metadata +*does* credit him for this exact film (cross-checked via `experiments/manifests/jellyfin_casts.json` from the `experiment-data` artifact -package, a completely separate data source from X-Ray). This isn't a lookalike error or a gallery mixup — it's the -pipeline correctly recognising a real cast member that one ground-truth source -happened to omit. +package, a completely separate data source from X-Ray). That's also him in +orange in the frame above — every one of those "errors" is the pipeline being +right about a person X-Ray forgot. + +![Robert Patrick, clearly on screen, scored wrong by a ground-truth gap](assets/images/lovelace_robert_patrick_fpi.jpg) + +And it isn't only uncredited bit-parts. That is **Robert Patrick** — top-billed +in Lovelace, unmistakably on screen, reading his newspaper, identified at +100% — scored orange because X-Ray's people-in-scene list for *this scene* +doesn't include him. The identification is flawless; the ground truth missed +an actor sitting in the middle of the frame. This doesn't mean every flagged misID is secretly correct — Many Saints' -974-count total is still overwhelmingly the frozen-bbox failure mode above, -not uncredited-but-real cameos. But it's a reminder that the X-Ray corpus is a -convenient, large-scale ground truth, not a perfect one, and the "misID" number -in any of these tables has some irreducible noise floor from ground-truth gaps -in the other direction too. +974-count total is still overwhelmingly extinction bridging at cuts, not +uncredited cameos. But the X-Ray corpus is a convenient, large-scale ground +truth, not a perfect one, and the misID/FPI numbers in these tables carry an +irreducible noise floor from ground-truth gaps in both directions. ## Summary -LVFace is the right default: it wins the model comparison outright, it can name -19 faces correctly in a single hostile group shot, and its failures are -traceable, understood, and mostly attributable to one tunable knob -(`extinction_sec`) rather than the embedder itself. The held-out +LVFace is the right default: it wins the model comparison outright, it names +19 of 20 correctly across a hat-heavy funeral crowd, and it recognises a face +on a screen inside the movie. Its error budget decomposes into two understood +mechanisms — extinction bridging at hard cuts (a tunable trade, not a bug) and +the face-vs-presence ceiling baked into X-Ray's semantics — plus a nonzero +slice where the pipeline is right and the ground truth is wrong. The held-out generalization gap (75.3% → 67.4%) is real and should be treated as the honest expected performance, not the training-set number. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..4601342 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,24 @@ +/* Frames and charts presented as cards */ +.md-typeset img { + border-radius: 6px; +} +.md-typeset p > img:only-child { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35); +} + +/* Captions: an italic-only paragraph immediately after an image reads as a + figure caption — centered, small, muted. Falls back to plain italics in + browsers without :has(). */ +.md-typeset p:has(> img:only-child) + p > em:only-child { + display: block; + text-align: center; + font-size: 0.72rem; + line-height: 1.5; + color: var(--md-default-fg-color--light); + margin-top: -0.4rem; +} + +/* Slightly tighter hero image spacing on the landing page */ +.md-typeset h1 + p + p > img:only-child { + margin-top: 0.2rem; +} diff --git a/mkdocs.yml b/mkdocs.yml index 6e76504..ae13c90 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,16 +7,31 @@ repo_name: dtourolle/scene-actor-extraction theme: name: material palette: - - scheme: slate - primary: indigo + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: amber + toggle: + icon: material/weather-sunny + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: black accent: indigo + toggle: + icon: material/weather-night + name: Switch to dark mode features: - navigation.tabs - navigation.sections - navigation.top + - navigation.footer - content.code.copy - content.code.annotate +extra_css: + - stylesheets/extra.css + nav: - Home: index.md - Findings: @@ -44,5 +59,8 @@ markdown_extensions: base_path: ['.'] check_paths: true - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - attr_list - md_in_html diff --git a/scripts/docs/build_site.sh b/scripts/docs/build_site.sh index a5e3f40..bbb0664 100755 --- a/scripts/docs/build_site.sh +++ b/scripts/docs/build_site.sh @@ -11,38 +11,65 @@ cd "$REPO_ROOT" ASSETS_DIR="docs/assets/images" mkdir -p "$ASSETS_DIR" -# Frames referenced by docs/model-bakeoff.md. Pull the film's montage -# frames from the registry if this machine doesn't already have them locally. -FRAMES_ROOT="experiments/results/holdout/frames" -if [ ! -d "$FRAMES_ROOT/many_saints" ] || [ ! -d "$FRAMES_ROOT/downton_abbey" ]; then - echo "==> pulling montage frames (not found locally)..." - scripts/artifacts/pull_artifacts.sh montage-frames Many_Saints_of_Newark || true - scripts/artifacts/pull_artifacts.sh montage-frames Downton_Abbey__A_New_Era || true -fi +# Frames referenced by the docs. The scene best/worst montages (proper +# renderer: Onscreen/Offscreen panel + TPI/FPI/FN legend) live in the +# montage-frames registry packages, one per film; pull any film that's missing. +MONTAGE_ROOT="experiments/results/holdout/montage_bestworst" +for film in Downton_Abbey__A_New_Era Lovelace Café_Society \ + Valerian_and_the_City_of_a_Thousand_Plan The_Many_Saints_of_Newark; do + if [ ! -d "${MONTAGE_ROOT}/${film}" ]; then + echo "==> pulling montage-frames/${film} (not found locally)..." + scripts/artifacts/pull_artifacts.sh montage-frames "$film" || true + fi +done + +# stage_frame : downscale to <=1920px wide for the site +stage_frame() { + if [ ! -f "$1" ]; then + echo "WARN: $1 not present; keeping existing ${ASSETS_DIR}/$2 (if any)" + return + fi + python3 - "$1" "${ASSETS_DIR}/$2" <<'PY' +import sys +from PIL import Image +src, dst = sys.argv[1], sys.argv[2] +im = Image.open(src) +if im.width > 1920: + im = im.resize((1920, round(im.height * 1920 / im.width)), Image.LANCZOS) +im.save(dst, quality=88) +print(f"staged {dst} ({im.width}x{im.height})") +PY +} echo "==> staging referenced frames into ${ASSETS_DIR}" -cp -v "${FRAMES_ROOT}/many_saints/fpi/fpi_t03543.jpg" \ - "${ASSETS_DIR}/many_saints_ghost_fpi.jpg" -cp -v "${FRAMES_ROOT}/downton_abbey/best/best_t00127.jpg" \ - "${ASSETS_DIR}/downton_wedding_19_correct.jpg" -if [ -f "${FRAMES_ROOT}/many_saints_intervals/w002_worst/w002_worst_t01382.jpg" ]; then - cp -v "${FRAMES_ROOT}/many_saints_intervals/w002_worst/w002_worst_t01382.jpg" \ - "${ASSETS_DIR}/many_saints_ghosts_vs_unknowns.jpg" -else - echo "WARN: many_saints_intervals frames not present; keeping existing" \ - "${ASSETS_DIR}/many_saints_ghosts_vs_unknowns.jpg (if any)" -fi +stage_frame "${MONTAGE_ROOT}/Downton_Abbey__A_New_Era/scene_4/4_best_t000128.jpg" \ + downton_wedding_couple.jpg +stage_frame "${MONTAGE_ROOT}/Downton_Abbey__A_New_Era/scene_16/16_best_t007100.jpg" \ + downton_funeral_19of20.jpg +stage_frame "${MONTAGE_ROOT}/Downton_Abbey__A_New_Era/scene_7/7_worst_t001754.jpg" \ + downton_crew_fn.jpg +stage_frame "${MONTAGE_ROOT}/Lovelace/scene_24/24_best_t003607.jpg" \ + lovelace_polygraph_bridged.jpg +stage_frame "${MONTAGE_ROOT}/Lovelace/scene_6/6_worst_t000421.jpg" \ + lovelace_robert_patrick_fpi.jpg +stage_frame "${MONTAGE_ROOT}/Lovelace/scene_15/15_best_t002005.jpg" \ + lovelace_perfect_second.jpg +stage_frame "${MONTAGE_ROOT}/Café_Society/scene_20/20_best_t002430.jpg" \ + cafe_society_rapid_cut.jpg +stage_frame "${MONTAGE_ROOT}/Valerian_and_the_City_of_a_Thousand_Plan/scene_4/4_best_t001027.jpg" \ + valerian_screen_call.jpg +stage_frame "${MONTAGE_ROOT}/The_Many_Saints_of_Newark/out_of_cast_fpi/4_worst_t000871.jpg" \ + many_saints_outofcast_fpi.jpg +# debug-overlay example (extinction state drawn as frozen boxes) — from the +# dump_error_frames output, not the montage package +stage_frame "experiments/results/holdout/frames/many_saints/fpi/fpi_t03543.jpg" \ + many_saints_ghost_fpi.jpg if [ ! -f "${ASSETS_DIR}/germar_beats_xray.jpg" ]; then echo "==> pulling report-highlights/germar_beats_xray.jpg..." scripts/artifacts/pull_artifacts.sh report-highlights germar_beats_xray.jpg fi -if [ ! -f "${ASSETS_DIR}/readme_example.jpg" ]; then - echo "==> pulling report-highlights/readme_example.jpg..." - scripts/artifacts/pull_artifacts.sh report-highlights readme_example.jpg -fi - # pipeline_topology.svg is small and hand-authored (not pulled from anywhere) — # committed directly at docs/assets/images/, not staged from the registry.