Leave a panorama frame out without leaving the page

A frame that did not fit ended the job with its name, and the only way
on was Back, a smaller selection and every frame read, demosaiced and
searched for keypoints again. Each row on the page now has a box. An
unticked frame is left out and the rest are solved again from what the
first pass measured.

dr_pano::align is split for it: match_pairs does the matching and the
pairwise RANSAC once over every frame (about 5 s for twelve), and solve
takes a subset of the frames and uses only the links among them (about
0.1 s). Solving a subset by re-aligning it also moved every RANSAC seed,
which are keyed on frame position, and on the fixture set that was
enough to lose a marginal link and strand a neighbour of the frame left
out.

A frame that cannot be placed no longer stops the job either. Its row
names it and Merge stays off until it is unticked; the headless example
leaves such frames out the same way, and takes --leave-out N to try it.
This commit is contained in:
2026-09-27 16:29:39 -04:00
parent e98b1def98
commit 621a6b8313
9 changed files with 616 additions and 202 deletions
+45 -1
View File
@@ -3,8 +3,12 @@
//! ```sh
//! cargo run -p dr-ui --example merge --release -- fixtures/pano/2025-08-05/*.CR2
//! cargo run -p dr-ui --example merge --release -- --out /var/tmp --fill frame1.CR2 …
//! cargo run -p dr-ui --example merge --release -- --leave-out 3 fixtures/pano/2025-08-05/*.CR2
//! ```
//!
//! `--leave-out N` unticks frame `N` once the first alignment is in, as the
//! page's box would, so the job aligns again without it.
//!
//! The job the interface runs (`dr_ui::merge`), driven from a loop instead
//! of a timer: the same code, the same events, a file at the end. What it
//! proves that the alignment example cannot is the second half — the tap,
@@ -20,6 +24,7 @@ fn main() {
let mut out_dir = None;
let mut fill = false;
let mut wait_engines = false;
let mut leave_out: Option<usize> = None;
loop {
match args.first().map(String::as_str) {
Some("--out") => {
@@ -30,6 +35,10 @@ fn main() {
args.remove(0);
fill = true;
}
Some("--leave-out") => {
args.remove(0);
leave_out = args.remove(0).parse().ok();
}
Some("--wait-engines") => {
args.remove(0);
wait_engines = true;
@@ -121,6 +130,7 @@ fn main() {
Ok((yaw, pitch)) => {
println!(" frame {k}: yaw {yaw:7.2}° pitch {pitch:6.2}°")
}
Err(why) if !r.included[k] => println!(" frame {k}: {why}"),
Err(why) => println!(" frame {k}: NOT ALIGNED — {why}"),
}
}
@@ -131,7 +141,41 @@ fn main() {
Err(why) => why.clone(),
}
);
// Headless: the confirmation the dialog would ask for.
if let Some(k) = leave_out.take() {
let mut keep = r.included.clone();
if let Some(slot) = keep.get_mut(k) {
*slot = false;
}
println!(" aligning again without frame {k}");
let _ = decide.send(dr_ui::merge::Decision::Frames {
keep,
projection: None,
fill: None,
});
continue;
}
// Headless: what a photographer would untick — the frames
// that could not be placed — then the confirmation.
if !r.is_complete() {
let keep: Vec<bool> = r
.frames
.iter()
.zip(&r.included)
.map(|(f, &kept)| kept && f.is_ok())
.collect();
if keep == r.included || keep.iter().filter(|k| **k).count() < 2 {
eprintln!("not enough frames can be placed to merge");
let _ = decide.send(dr_ui::merge::Decision::Abandon);
} else {
println!(" aligning again without the frames that could not be placed");
let _ = decide.send(dr_ui::merge::Decision::Frames {
keep,
projection: None,
fill: None,
});
}
continue;
}
let _ = decide.send(dr_ui::merge::Decision::Merge {
projection: None,
fill: fill.then(dr_ui::merge::FillSettings::default),