v1.10.0 "Aventurescence"
Panoramas are recognised as panoramas
A panorama's source frames were shot to be stitched, not to compete — but burst detection cannot tell: they arrive seconds apart from one camera at one focal length, so it grouped them as competing takes and hid all but one behind a lead chosen on a criterion that means nothing for a pan. On the 126k-photo library this was measured against, a confirmed 33-frame sweep was found shredded across seven burst groups.
Nothing in stored metadata identifies a pan — one confirmed set was shot with locked exposure and another on auto — so the evidence is geometric: SIFT features and a RANSAC homography between consecutive frames, measured on the stored 640px thumbnails. No original decode, no model, no new dependency.
The discriminator is cumulative drift, not per-frame shift. Real panoramas are shot at roughly 90 % overlap, so one step moves only 5–18 % of the frame; over a run a burst wobbles around zero (0.01 frame widths measured) while a sweep marches (0.56–2.83). Requiring every pair to shift would have rejected every HDR panorama, whose frames hold still while the bracket fires.
What you get
--detect-panoramaslabels sweeps aspanoramaorhdr_panorama(split on exposure spread), running at the end of every scan.- Two culling granularities — Panoramas and HDR panoramas — never merged into
All. Every frame starts marked keep, and confirming a set records no comparison pairs: preferring one frame of a pan describes how the set was shot, not the photograph. hide_panoramasgallery toggle, showing one representative frame per sweep, and a badge on that tile naming what it stands for.- Sticky per-set corrections that survive every later detection run.
- A Panoramas settings tab exposing the calibrated thresholds, with the re-run action beside them — detection is a batch pass, so a saved threshold reaches the gallery only after it runs again.
- Star ratings are now visible without hovering, on the same terms as the favourite heart.
Honest limits
Thresholds were fixed against 26 panoramas and 8 non-panoramas confirmed by eye, not reasoned about. Three plausible rules were falsified along the way and are recorded in the module so they are not retried: per-pair shift magnitude, pure-rotation focal recovery (ill-conditioned at these overlaps — one 33-frame sweep recovered focals from 22 to 2461 px), and exposure lock.
Measured ~96 % precision. Recall is deliberately incomplete: vertical low-drift sweeps and few-position panoramas fall below the drift floor, inside the confirmed-negative distribution, and no threshold recovers them without admitting reportage. Missing a panorama costs nothing; mislabelling reportage costs trust — and because geometry cannot recover intent, the sticky corrections fix both directions.
Fixed
- A bracket run no longer wipes another sequence kind's labels. The pass cleared every labelled row rather than its own, so once a second kind existed each run silently deleted the other's work — invisibly, because each then re-created its own labels on the way out.
hide_bracketsno longer hides frames that are not brackets. It matched on a null-vs-set exposure offset, true of every panorama frame, so with the toggle on by default an entire sweep would have vanished.- A detected sweep no longer disappears when
hide_burstsandhide_panoramasare both on (both ship on). They pick their representative on unrelated criteria and are ANDed, so for a sweep split across burst groups they could agree on no frame at all. - Auto-cull no longer shreds a panorama that reaches it through the burst feed, nor feeds comparison pairs from inside a sweep to the personal ranker.
Full changelog: https://github.com/ncoevoet/facet/blob/master/CHANGELOG.md