hero: admit portrait sources, the crop was built for them

The aspect floor is really an ALBUM-ART guard -- its own comment says so: a
square sleeve passes every other gate, and Camping in Alaska's square "lost and
found" artwork once reached a live hero slot that way. But it was written as a
blanket "wide only" rule, so it also discarded portrait press photography one
crate before the machinery built to handle it.

The app already crops tall sources to 16:9 around detected faces: hero_crop.rs
keeps full source width, slides the window vertically, and applies headroom
rules above the topmost face (recipe vertical-yoloxnano-...-faces-first).
maximal_hero_rect exists specifically for sources taller than 16:9. Meanwhile
the selection gate in the metadata crate rejected everything below 1.15, so
those images never arrived.

Sleeves are square; press portraits are not. Admitting ratio <= 0.92 with at
least 1000px of width keeps the guard exactly where release artwork lives while
opening the one route to a hero for artists whose entire press corpus is
portrait. Posters and flyers cannot enter this way either: the text, flat and
collage gates all run before this one.

Hana Vu goes from BANNER NONE to 2 -- the LA Times 2000x2500 portrait and an
Our Culture press shot -- and renders correctly, face placed with headroom.
Camping in Alaska is unchanged, same three banners. Square 1:1 press crops
(Nikki Nair's Rinse FM and Corsica shots) stay rejected, which is the guard
still doing its job.
This commit is contained in:
KeelrLeek committed 2026-07-25 16:35:39 -05:00
1 parent 9536ff23a9
commit d79fe2f28a
1 file changed
+24 -3
+24 -3
View File
@@ -565,6 +565,15 @@ const HERO_SCORE_CONCURRENCY: usize = 4;
/// Minimum width:height for a hero candidate. Album sleeves are exactly 1.0; /// Minimum width:height for a hero candidate. Album sleeves are exactly 1.0;
/// every press photo across the probe fixtures measures 1.33 or wider. /// every press photo across the probe fixtures measures 1.33 or wider.
const HERO_MIN_ASPECT: f64 = 1.15; const HERO_MIN_ASPECT: f64 = 1.15;
/// Portrait sources are admitted below this ratio, well clear of the square
/// band where release artwork lives. The app crops tall sources to 16:9 around
/// the detected faces (`hero_crop::maximal_hero_rect` keeps full width and
/// slides the window vertically), so a portrait press photo is usable hero
/// material while a 1:1 sleeve is not.
const HERO_PORTRAIT_MAX_ASPECT: f64 = 0.92;
/// A portrait source is cropped to full-width x (width*9/16), so its width IS
/// the resulting hero width. Keep enough pixels that the crop is still a hero.
const HERO_PORTRAIT_MIN_WIDTH: u32 = 1000;
const HERO_SEARCH_WAVE_CONCURRENCY: usize = 3; const HERO_SEARCH_WAVE_CONCURRENCY: usize = 3;
/// A Bing image page with at most this many cards is a degraded answer, not a /// A Bing image page with at most this many cards is a degraded answer, not a
/// small result set. Bing's genuine pages run 12-100 cards; the degraded shape /// small result set. Bing's genuine pages run 12-100 cards; the degraded shape
@@ -8902,10 +8911,22 @@ fn score_bing_hero_image(
// collage detector only runs at `text_penalty <= 0.70`). Camping in // collage detector only runs at `text_penalty <= 0.70`). Camping in
// Alaska's square "lost and found" sleeve reached a live hero slot that // Alaska's square "lost and found" sleeve reached a live hero slot that
// way. Real press photos measured across the fixtures run 1.33-1.9, so // way. Real press photos measured across the fixtures run 1.33-1.9, so
// the floor sits below every one of them; portrait crops fail it too, // the floor sits below every one of them. Trusted ticketing profiles
// which is correct for a full-bleed banner. Trusted ticketing profiles
// keep their existing exemption — that tier is identity-verified art. // keep their existing exemption — that tier is identity-verified art.
if ratio < HERO_MIN_ASPECT && !trusted_ticketing_profile { //
// PORTRAIT is admitted, though, because the guard is really an ALBUM-ART
// guard and sleeves are square: a 0.75-0.85 press portrait is not a sleeve.
// The app already crops tall sources to 16:9 around the detected faces
// (`hero_crop.rs`, recipe `vertical-yoloxnano-...-faces-first`), so these
// were being discarded one crate before the machinery built to use them.
// This is the only route to a hero for artists whose entire press corpus is
// portrait: Hana Vu (2000x2500, 1705x2129) and Nikki Nair (1080x1350,
// 1024x1365) both pass identity and died here. Text-heavy posters and
// flyers cannot sneak in this way — the text, flat-graphic and collage
// gates all run BEFORE this one.
let usable_portrait =
ratio <= HERO_PORTRAIT_MAX_ASPECT && w >= HERO_PORTRAIT_MIN_WIDTH;
if ratio < HERO_MIN_ASPECT && !usable_portrait && !trusted_ticketing_profile {
if diagnostics { if diagnostics {
eprintln!( eprintln!(
"HERO_REJECT\taspect\t{}x{}\tratio={:.2}\t{}", "HERO_REJECT\taspect\t{}x{}\tratio={:.2}\t{}",