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:
@@ -7,7 +7,8 @@
|
||||
//! fetches the selected originals on a worker, hands them to the job, and
|
||||
//! opens the page; a timer drains the job's events into the page's
|
||||
//! properties. When the alignment arrives the page shows it and waits.
|
||||
//! "Merge" sends the decision; "Stop" or "Back" cancels. When the file is
|
||||
//! A frame's box leaves it out or brings it back, and the job aligns again
|
||||
//! over the rest. "Merge" sends the decision; "Stop" or "Back" cancels. When the file is
|
||||
//! staged, the outbox drains and the library rescans, and the composite
|
||||
//! appears in the grid beside its sources.
|
||||
|
||||
@@ -327,6 +328,37 @@ fn wire_decision(window: &AppWindow, ctl: &Rc<MergeController>) {
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
window.on_merge_frame_toggled(move |i, on| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// The set the alignment on screen was solved over, with this
|
||||
// frame flipped. The box is controlled: it moves when the job's
|
||||
// report comes back with the frame in or out.
|
||||
let Some(mut keep) = ctl.report.borrow().as_ref().map(|r| r.included.clone()) else {
|
||||
return;
|
||||
};
|
||||
let Some(slot) = keep.get_mut(i as usize) else {
|
||||
return;
|
||||
};
|
||||
*slot = on;
|
||||
if keep.iter().filter(|k| **k).count() < 2 {
|
||||
w.set_merge_status("A panorama needs at least two frames.".into());
|
||||
return;
|
||||
}
|
||||
if let Some(job) = ctl.job.borrow().as_ref() {
|
||||
let _ = job.decide.send(Decision::Frames {
|
||||
keep,
|
||||
projection: chip_projection(ctl.projection.get()),
|
||||
fill: ctl.fill_choice(),
|
||||
});
|
||||
}
|
||||
w.set_merge_running(true);
|
||||
w.set_merge_stage("Aligning".into());
|
||||
w.set_merge_status("".into());
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = ctl.clone();
|
||||
@@ -561,10 +593,11 @@ fn drain(window: &AppWindow, ctl: &Rc<MergeController>, on_done: &Rc<impl Fn(&Ap
|
||||
window.set_merge_running(false);
|
||||
window.set_merge_stage("".into());
|
||||
window.set_merge_fraction(0.0);
|
||||
window.set_merge_status(if report.frames.iter().all(Result::is_ok) {
|
||||
"Check the alignment, then press Merge.".into()
|
||||
window.set_merge_status(if report.is_complete() {
|
||||
"Check the alignment, then press Merge. Untick a frame to leave it out.".into()
|
||||
} else {
|
||||
"Not every frame could be placed. Remove the ones named and try again.".into()
|
||||
"Not every frame could be placed. Untick the ones named to merge without them."
|
||||
.into()
|
||||
});
|
||||
*ctl.report.borrow_mut() = Some(report);
|
||||
}
|
||||
@@ -632,28 +665,31 @@ fn show_report(window: &AppWindow, report: &AlignmentReport, names: &[String]) {
|
||||
.get(k)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("frame {k}"));
|
||||
let included = report.included.get(k).copied().unwrap_or(true);
|
||||
match f {
|
||||
Ok((yaw, pitch)) => MergeFrameRow {
|
||||
name: name.into(),
|
||||
detail: format!("yaw {yaw:.1}° · pitch {pitch:.1}°").into(),
|
||||
aligned: true,
|
||||
included,
|
||||
},
|
||||
Err(why) => MergeFrameRow {
|
||||
name: name.into(),
|
||||
detail: why.clone().into(),
|
||||
aligned: false,
|
||||
included,
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let complete = report.frames.iter().all(Result::is_ok);
|
||||
let complete = report.is_complete();
|
||||
window.set_merge_frames(slint::ModelRc::new(slint::VecModel::from(rows)));
|
||||
window.set_merge_aligned(true);
|
||||
window.set_merge_complete(complete);
|
||||
window.set_merge_summary(
|
||||
format!(
|
||||
"{} frames, {} overlaps · {:.0} mm equivalent · {:?} · {}×{} px · fit {:.1} px",
|
||||
report.frames.len(),
|
||||
report.included.iter().filter(|k| **k).count(),
|
||||
report.links,
|
||||
report.focal_mm,
|
||||
report.projection,
|
||||
@@ -663,9 +699,16 @@ fn show_report(window: &AppWindow, report: &AlignmentReport, names: &[String]) {
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
if let Some((w, h, rgba)) = &report.preview {
|
||||
let buffer = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(rgba, *w, *h);
|
||||
window.set_merge_preview(slint::Image::from_rgba8(buffer));
|
||||
// No preview is no picture, not the last one: after a frame is
|
||||
// unticked, the old composite would show a set that is not the one
|
||||
// being confirmed.
|
||||
match &report.preview {
|
||||
Some((w, h, rgba)) => {
|
||||
let buffer =
|
||||
slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(rgba, *w, *h);
|
||||
window.set_merge_preview(slint::Image::from_rgba8(buffer));
|
||||
}
|
||||
None => window.set_merge_preview(slint::Image::default()),
|
||||
}
|
||||
window.set_merge_preview_filled(report.filled);
|
||||
match &report.filler {
|
||||
|
||||
Reference in New Issue
Block a user