Files
DarkRoom/ui/dr-ui/examples/merge.rs
T
dtourolle 2e9a1eb0f0 The merge job and its page: a selection to a panorama DNG, confirmed first
dr_ui::merge is the orchestration with no interface in it: decode each
frame to sensor data and build its graph as a session would (orientation,
lens profile); render each through the camera-space tap at proxy size and
detect keypoints there, so the alignment is measured in the undistorted
frame the tiles are rendered in; align; solve one gain per frame from the
proxies' overlaps; draw the aligned set in colour for the page; then wait.
Nothing is written until a Decision arrives (FR-MRG-1). The merge writes
a linear DNG through the outbox with a destination record, so the drain
puts it beside its sources on a folder library and a server alike, and
the library rescans (FR-MRG-3).

merge.slint is the page, on the import page's model: the alignment
table with a failed frame named on its row and the button held off
(FR-MRG-5), the preview, the projection choice, Stop and Back. A
"Merge to panorama" button joins the grid's selection bar at two frames.

Headless, the example produces the fixture's 22 993 x 5 980 DNG in 45 s
on the reference desktop, exposures balanced across the stop of drift.
2026-09-19 15:24:20 +02:00

90 lines
3.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Merge a set of frames into a panorama DNG, headless.
//!
//! ```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 frame1.CR2 …
//! ```
//!
//! 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,
//! the chunked warp, the resolve to sixteen bits and the DNG — and that the
//! result opens in the application as one RAW.
use std::path::PathBuf;
use std::time::Instant;
fn main() {
env_logger::init();
let mut args: Vec<String> = std::env::args().skip(1).collect();
let mut out_dir = None;
if args.first().map(String::as_str) == Some("--out") {
args.remove(0);
out_dir = Some(PathBuf::from(args.remove(0)));
}
if args.len() < 2 {
eprintln!("usage: merge [--out DIR] <frame> <frame>...");
std::process::exit(2);
}
let Ok(gpu) = pollster::block_on(dr_gpu::GpuContext::new_headless()) else {
eprintln!("no GPU");
std::process::exit(1);
};
let frames: Vec<dr_ui::merge::MergeInput> = args
.iter()
.map(|a| dr_ui::merge::MergeInput::read(std::path::Path::new(a)).expect("read"))
.collect();
let dir = out_dir.unwrap_or_else(|| {
PathBuf::from(&args[0])
.parent()
.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 (tx, rx) = std::sync::mpsc::channel();
let (decide, decision) = std::sync::mpsc::channel();
let cancel = dr_ui::merge::Cancel::default();
let t = Instant::now();
let worker = std::thread::spawn(move || dr_ui::merge::run(gpu, request, tx, decision, cancel));
let mut last_stage = "";
for event in rx {
use dr_ui::merge::MergeEvent::*;
match event {
Progress { stage, done, total } => {
if stage != last_stage {
println!("{stage}…");
last_stage = stage;
}
if done == total || done % 4 == 0 {
println!(" {done}/{total}");
}
}
Aligned(r) => {
println!(
"aligned: {:.1} mm equivalent, rms {:.2} px, {} links, {:?} {}×{}",
r.focal_mm, r.rms_px, r.links, r.projection, r.width, r.height
);
for (k, f) in r.frames.iter().enumerate() {
match f {
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());
}
Failed(e) => {
eprintln!("failed: {e}");
std::process::exit(1);
}
Cancelled => println!("cancelled"),
}
}
let _ = worker.join();
}