cargo fmt and clippy across the panorama work, and one lint master carried
The dr-face comparison is master's: a negated partial-order test on the eye box's width, rewritten as the two conditions it meant.
This commit is contained in:
@@ -41,7 +41,8 @@ fn main() {
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
});
|
||||
let request = dr_ui::merge::MergeRequest::new(frames, dr_ui::merge::MergeDestination::Local(dir));
|
||||
let request =
|
||||
dr_ui::merge::MergeRequest::new(frames, dr_ui::merge::MergeDestination::Local(dir));
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (decide, decision) = std::sync::mpsc::channel();
|
||||
let cancel = dr_ui::merge::Cancel::default();
|
||||
@@ -68,15 +69,26 @@ fn main() {
|
||||
);
|
||||
for (k, f) in r.frames.iter().enumerate() {
|
||||
match f {
|
||||
Ok((yaw, pitch)) => println!(" frame {k}: yaw {yaw:7.2}° pitch {pitch:6.2}°"),
|
||||
Ok((yaw, pitch)) => {
|
||||
println!(" frame {k}: yaw {yaw:7.2}° pitch {pitch:6.2}°")
|
||||
}
|
||||
Err(why) => println!(" frame {k}: NOT ALIGNED — {why}"),
|
||||
}
|
||||
}
|
||||
// Headless: the confirmation the dialog would ask for.
|
||||
let _ = decide.send(dr_ui::merge::Decision::Merge { projection: None });
|
||||
}
|
||||
Done { path, width, height, .. } => {
|
||||
println!("wrote {} ({width}×{height}) in {:?}", path.display(), t.elapsed());
|
||||
Done {
|
||||
path,
|
||||
width,
|
||||
height,
|
||||
..
|
||||
} => {
|
||||
println!(
|
||||
"wrote {} ({width}×{height}) in {:?}",
|
||||
path.display(),
|
||||
t.elapsed()
|
||||
);
|
||||
}
|
||||
Failed(e) => {
|
||||
eprintln!("failed: {e}");
|
||||
|
||||
@@ -8722,6 +8722,10 @@ mod tests {
|
||||
wb_coeffs: [1.0, 1.0, 1.0, 1.0],
|
||||
color_matrix: None,
|
||||
base_curve: dr_decode::BaseCurve::IDENTITY,
|
||||
samples_per_pixel: 1,
|
||||
profile: None,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
crop: dr_decode::CropRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
+72
-23
@@ -34,7 +34,9 @@ use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use dr_decode::RawImage;
|
||||
use dr_gpu::{AdjustPass, DemosaicedImage, Demosaicer, GpuContext, MergeFrame, MergeOutput, MergePass};
|
||||
use dr_gpu::{
|
||||
AdjustPass, DemosaicedImage, Demosaicer, GpuContext, MergeFrame, MergeOutput, MergePass,
|
||||
};
|
||||
use dr_pano::bundle::Cameras;
|
||||
use dr_pano::projection::{self, Projection};
|
||||
use dr_pano::{Alignment, Gray};
|
||||
@@ -109,11 +111,15 @@ impl MergeRequest {
|
||||
/// never automatic).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Decision {
|
||||
Merge { projection: Option<Projection> },
|
||||
Merge {
|
||||
projection: Option<Projection>,
|
||||
},
|
||||
/// Draw the alignment again on another surface, and report it again.
|
||||
/// The page's projection chips: what a choice looks like before it is
|
||||
/// confirmed, at proxy cost rather than the merge's.
|
||||
Preview { projection: Option<Projection> },
|
||||
Preview {
|
||||
projection: Option<Projection>,
|
||||
},
|
||||
Abandon,
|
||||
}
|
||||
|
||||
@@ -221,7 +227,11 @@ fn run_inner(
|
||||
graph.set_orientation(orientation);
|
||||
graph.set_lens_profile(crate::develop::DevelopSession::profile_for(&meta));
|
||||
let (w, h) = (raw.crop.width, raw.crop.height);
|
||||
let size = if orientation.quarter_turns % 2 == 1 { (h, w) } else { (w, h) };
|
||||
let size = if orientation.quarter_turns % 2 == 1 {
|
||||
(h, w)
|
||||
} else {
|
||||
(w, h)
|
||||
};
|
||||
frames.push(Loaded {
|
||||
raw,
|
||||
meta,
|
||||
@@ -255,8 +265,8 @@ fn run_inner(
|
||||
// 3. Alignment.
|
||||
let t = Instant::now();
|
||||
progress("Aligning", 0, 1);
|
||||
let alignment = dr_pano::align(&features, &dr_pano::AlignOptions::default())
|
||||
.map_err(|e| e.to_string())?;
|
||||
let alignment =
|
||||
dr_pano::align(&features, &dr_pano::AlignOptions::default()).map_err(|e| e.to_string())?;
|
||||
log::info!(
|
||||
"merge: aligned in {:?}, focal {:.1} px, rms {:.2} px, {} links",
|
||||
t.elapsed(),
|
||||
@@ -294,9 +304,16 @@ fn run_inner(
|
||||
let report = |projection: Projection| -> Result<AlignmentReport, String> {
|
||||
let bounds = projection::bounds(projection, focal_full, &cameras, frame_size)
|
||||
.ok_or("the frames project nowhere")?;
|
||||
let preview = alignment
|
||||
.is_complete()
|
||||
.then(|| preview(&colour, &proxies, &alignment, &gains, projection, &frames[0].raw));
|
||||
let preview = alignment.is_complete().then(|| {
|
||||
preview(
|
||||
&colour,
|
||||
&proxies,
|
||||
&alignment,
|
||||
&gains,
|
||||
projection,
|
||||
&frames[0].raw,
|
||||
)
|
||||
});
|
||||
Ok(AlignmentReport {
|
||||
focal_mm: focal_full * 36.0 / full_long,
|
||||
rms_px: alignment.rms_px,
|
||||
@@ -316,7 +333,10 @@ fn run_inner(
|
||||
.iter()
|
||||
.map(|(k, why)| format!("{}: {why}", request.frames[*k].name))
|
||||
.collect();
|
||||
return Err(format!("not every frame could be placed — {}", names.join("; ")));
|
||||
return Err(format!(
|
||||
"not every frame could be placed — {}",
|
||||
names.join("; ")
|
||||
));
|
||||
}
|
||||
|
||||
// Never automatic (FR-MRG-1): nothing is written until the alignment
|
||||
@@ -400,7 +420,8 @@ fn run_inner(
|
||||
// once the last strip is in, by which time every band has been seen.
|
||||
let inscribed = Arc::new(std::sync::Mutex::new(dr_export::Inscribed::new(out_w)));
|
||||
let inscribed_for_writer = inscribed.clone();
|
||||
let file = std::fs::File::create(&out_path).map_err(|e| format!("{}: {e}", out_path.display()))?;
|
||||
let file =
|
||||
std::fs::File::create(&out_path).map_err(|e| format!("{}: {e}", out_path.display()))?;
|
||||
let writer = std::thread::spawn(move || -> Result<(), String> {
|
||||
let mut file = std::io::BufWriter::new(file);
|
||||
dr_export::write_linear_dng(
|
||||
@@ -411,9 +432,9 @@ fn run_inner(
|
||||
&profile,
|
||||
Some(&carried),
|
||||
|_, buf| {
|
||||
let band = band_rx
|
||||
.recv()
|
||||
.map_err(|_| dr_export::ExportError::Encode("the merge stopped early".into()))?;
|
||||
let band = band_rx.recv().map_err(|_| {
|
||||
dr_export::ExportError::Encode("the merge stopped early".into())
|
||||
})?;
|
||||
buf.extend_from_slice(&band);
|
||||
Ok(())
|
||||
},
|
||||
@@ -458,7 +479,9 @@ fn run_inner(
|
||||
|| cancel.is_cancelled(),
|
||||
);
|
||||
drop(band_tx);
|
||||
let written = writer.join().unwrap_or_else(|_| Err("the writer panicked".into()));
|
||||
let written = writer
|
||||
.join()
|
||||
.unwrap_or_else(|_| Err("the writer panicked".into()));
|
||||
match merged {
|
||||
Ok(()) => {}
|
||||
Err(e) if cancel.is_cancelled() => {
|
||||
@@ -475,7 +498,13 @@ fn run_inner(
|
||||
cleanup(&out_path);
|
||||
return Err(e);
|
||||
}
|
||||
log::info!("merge: {}×{} written to {} in {:?}", out_w, out_h, out_path.display(), t.elapsed());
|
||||
log::info!(
|
||||
"merge: {}×{} written to {} in {:?}",
|
||||
out_w,
|
||||
out_h,
|
||||
out_path.display(),
|
||||
t.elapsed()
|
||||
);
|
||||
|
||||
// TRACES: FR-MRG-6
|
||||
// Provenance, beside the composite: what it was merged from, in order,
|
||||
@@ -547,7 +576,10 @@ fn camera_proxy(
|
||||
.chunks_exact(4)
|
||||
.map(|p| ((p[0] + p[1] + p[2]) / 3.0).clamp(0.0, 1.0).powf(1.0 / 2.2))
|
||||
.collect();
|
||||
let rgb: Vec<f32> = rgba.chunks_exact(4).flat_map(|p| [p[0], p[1], p[2]]).collect();
|
||||
let rgb: Vec<f32> = rgba
|
||||
.chunks_exact(4)
|
||||
.flat_map(|p| [p[0], p[1], p[2]])
|
||||
.collect();
|
||||
Ok((
|
||||
Gray {
|
||||
width: rw as usize,
|
||||
@@ -595,7 +627,9 @@ fn preview(
|
||||
let mut sum = [0.0f32; 3];
|
||||
let mut n = 0u32;
|
||||
for (k, g) in proxies.iter().enumerate() {
|
||||
let Some((x, y)) = cameras.project(k, d) else { continue };
|
||||
let Some((x, y)) = cameras.project(k, d) else {
|
||||
continue;
|
||||
};
|
||||
let (x, y) = (x + fw / 2.0, y + fh / 2.0);
|
||||
if x < 0.0 || y < 0.0 || x >= fw - 1.0 || y >= fh - 1.0 {
|
||||
continue;
|
||||
@@ -611,7 +645,11 @@ fn preview(
|
||||
out[o + 3] = 255;
|
||||
continue;
|
||||
}
|
||||
let cam = [sum[0] / n as f32 * wb[0], sum[1] / n as f32 * wb[1], sum[2] / n as f32 * wb[2]];
|
||||
let cam = [
|
||||
sum[0] / n as f32 * wb[0],
|
||||
sum[1] / n as f32 * wb[1],
|
||||
sum[2] / n as f32 * wb[2],
|
||||
];
|
||||
for c in 0..3 {
|
||||
let lin = m[c * 3] * cam[0] + m[c * 3 + 1] * cam[1] + m[c * 3 + 2] * cam[2];
|
||||
out[o + c] = (lin.clamp(0.0, 1.0).powf(1.0 / 2.2) * 255.0) as u8;
|
||||
@@ -637,7 +675,9 @@ fn preview(
|
||||
(fw / 2.0 - 1.0, -fh / 2.0 + fh * t),
|
||||
] {
|
||||
let d = cameras.bearing(k, p);
|
||||
let Some((u, v)) = projection.from_direction(scale, d) else { continue };
|
||||
let Some((u, v)) = projection.from_direction(scale, d) else {
|
||||
continue;
|
||||
};
|
||||
let x = ((u - bounds.min_u) / px) as i64;
|
||||
let y = ((v - bounds.min_v) / px) as i64;
|
||||
for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
|
||||
@@ -677,7 +717,10 @@ fn field_of_view(cameras: &Cameras, frame: (f64, f64)) -> (f64, f64) {
|
||||
}
|
||||
|
||||
fn report_size(b: &projection::Bounds) -> (u32, u32) {
|
||||
(b.width().ceil().max(1.0) as u32, b.height().ceil().max(1.0) as u32)
|
||||
(
|
||||
b.width().ceil().max(1.0) as u32,
|
||||
b.height().ceil().max(1.0) as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn describe(a: &Alignment) -> Vec<Result<(f64, f64), String>> {
|
||||
@@ -722,7 +765,9 @@ fn gains(proxies: &[Gray], a: &Alignment) -> Vec<f32> {
|
||||
for x in (0..proxies[l.i].width).step_by(step) {
|
||||
let p = (x as f64 - w / 2.0, y as f64 - h / 2.0);
|
||||
let d = cameras.bearing(l.i, p);
|
||||
let Some((qx, qy)) = cameras.project(l.j, d) else { continue };
|
||||
let Some((qx, qy)) = cameras.project(l.j, d) else {
|
||||
continue;
|
||||
};
|
||||
let (qx, qy) = (qx + w / 2.0, qy + h / 2.0);
|
||||
if qx < 0.0 || qy < 0.0 || qx >= w - 1.0 || qy >= h - 1.0 {
|
||||
continue;
|
||||
@@ -771,7 +816,11 @@ fn gains(proxies: &[Gray], a: &Alignment) -> Vec<f32> {
|
||||
|
||||
fn dng_profile(first: &Loaded, white_level: u32) -> dr_export::DngProfile {
|
||||
let wb = first.raw.wb_coeffs;
|
||||
let neutral_from_wb = [1.0 / wb[0].max(1e-3), 1.0 / wb[1].max(1e-3), 1.0 / wb[2].max(1e-3)];
|
||||
let neutral_from_wb = [
|
||||
1.0 / wb[0].max(1e-3),
|
||||
1.0 / wb[1].max(1e-3),
|
||||
1.0 / wb[2].max(1e-3),
|
||||
];
|
||||
let (calibrations, as_shot_neutral) = match &first.raw.profile {
|
||||
Some(p) => (p.dng_calibrations(), p.neutral().unwrap_or(neutral_from_wb)),
|
||||
None => (Vec::new(), neutral_from_wb),
|
||||
|
||||
@@ -146,7 +146,9 @@ pub fn wire<S, C, F>(
|
||||
// bytes, which keeps it free of the library.
|
||||
let conn = context.conn.clone();
|
||||
let fetch_names = names.clone();
|
||||
let fetch = move |tx: &Sender<MergeEvent>, cancel: &Cancel| -> Option<Vec<MergeInput>> {
|
||||
let fetch = move |tx: &Sender<MergeEvent>,
|
||||
cancel: &Cancel|
|
||||
-> Option<Vec<MergeInput>> {
|
||||
let mut frames = Vec::with_capacity(sources.len());
|
||||
for (i, (path, cache)) in sources.into_iter().enumerate() {
|
||||
if cancel.is_cancelled() {
|
||||
@@ -162,7 +164,10 @@ pub fn wire<S, C, F>(
|
||||
let bytes = match rx.recv() {
|
||||
Ok(Ok(bytes)) => bytes,
|
||||
Ok(Err(e)) => {
|
||||
let _ = tx.send(MergeEvent::Failed(format!("{}: {}", fetch_names[i], e.message)));
|
||||
let _ = tx.send(MergeEvent::Failed(format!(
|
||||
"{}: {}",
|
||||
fetch_names[i], e.message
|
||||
)));
|
||||
return None;
|
||||
}
|
||||
Err(_) => {
|
||||
@@ -197,9 +202,16 @@ pub fn wire<S, C, F>(
|
||||
if let (Some(gpu), Some(first)) = (gpu_for_start.clone(), paths.first()) {
|
||||
let names: Vec<String> = paths
|
||||
.iter()
|
||||
.map(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default())
|
||||
.map(|p| {
|
||||
p.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect();
|
||||
let dir = first.parent().map(std::path::Path::to_path_buf).unwrap_or_default();
|
||||
let dir = first
|
||||
.parent()
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.unwrap_or_default();
|
||||
let read_paths = paths.clone();
|
||||
let fetch = move |tx: &Sender<MergeEvent>, _: &Cancel| -> Option<Vec<MergeInput>> {
|
||||
let mut frames = Vec::new();
|
||||
@@ -214,7 +226,15 @@ pub fn wire<S, C, F>(
|
||||
}
|
||||
Some(frames)
|
||||
};
|
||||
start(window, &ctl, gpu, names, MergeDestination::Local(dir), fetch, &on_done);
|
||||
start(
|
||||
window,
|
||||
&ctl,
|
||||
gpu,
|
||||
names,
|
||||
MergeDestination::Local(dir),
|
||||
fetch,
|
||||
&on_done,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +329,9 @@ fn start<Fetch>(
|
||||
let cancel = cancel.clone();
|
||||
let tx = tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let Some(frames) = fetch(&tx, &cancel) else { return };
|
||||
let Some(frames) = fetch(&tx, &cancel) else {
|
||||
return;
|
||||
};
|
||||
let request = MergeRequest::new(frames, destination);
|
||||
merge::run(gpu, request, tx, decision, cancel);
|
||||
});
|
||||
@@ -334,7 +356,9 @@ fn start<Fetch>(
|
||||
window.set_merge_aligned(false);
|
||||
window.set_merge_complete(false);
|
||||
window.set_merge_summary("".into());
|
||||
window.set_merge_frames(slint::ModelRc::new(slint::VecModel::from(Vec::<MergeFrameRow>::new())));
|
||||
window.set_merge_frames(slint::ModelRc::new(slint::VecModel::from(Vec::<
|
||||
MergeFrameRow,
|
||||
>::new())));
|
||||
window.set_merge_preview(slint::Image::default());
|
||||
window.set_merge_projection_selected(0);
|
||||
window.set_merge_done(false);
|
||||
@@ -379,7 +403,11 @@ fn drain(window: &AppWindow, ctl: &Rc<MergeController>, on_done: &Rc<impl Fn(&Ap
|
||||
MergeEvent::Progress { stage, done, total } => {
|
||||
window.set_merge_stage(stage.into());
|
||||
window.set_merge_running(true);
|
||||
window.set_merge_fraction(if total > 0 { done as f32 / total as f32 } else { 0.0 });
|
||||
window.set_merge_fraction(if total > 0 {
|
||||
done as f32 / total as f32
|
||||
} else {
|
||||
0.0
|
||||
});
|
||||
if let Some(job) = ctl.job.borrow().as_ref() {
|
||||
job.activity.detail(stage.to_lowercase());
|
||||
job.activity.progress(done, total.max(1));
|
||||
@@ -463,7 +491,10 @@ fn show_report(window: &AppWindow, report: &AlignmentReport, names: &[String]) {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(k, f)| {
|
||||
let name = names.get(k).cloned().unwrap_or_else(|| format!("frame {k}"));
|
||||
let name = names
|
||||
.get(k)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("frame {k}"));
|
||||
match f {
|
||||
Ok((yaw, pitch)) => MergeFrameRow {
|
||||
name: name.into(),
|
||||
|
||||
Reference in New Issue
Block a user