mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 04:27:30 -06:00
LR-MERGE-HDR: decode full-size merge frames one at a time; AHD writes its tiles straight into the picture
A 7-frame bracket of 67 MP raws ran out of memory: the merge decoded the frames four at a time, and the AHD demosaic collected every tile before copying them into a second full-size picture. Full-size frames now decode one after the other (the demosaic is parallel inside; previews stay parallel), and AHD fills the picture by rows of tiles. Peak memory of 67 MP HDR merges: 3 frames 4.8 → 3.8 GB, 4 frames 6.3 → 4.7 GB, about the same time; AHD output is pixel-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a142179f20
commit
6fb0d22326
+12
-14
@@ -105,20 +105,18 @@ impl MergeJob {
|
||||
use rayon::prelude::*;
|
||||
let done = std::sync::atomic::AtomicUsize::new(0);
|
||||
let n = self.sources.len();
|
||||
let frames: std::result::Result<Vec<Frame>, String> = self
|
||||
.sources
|
||||
.par_iter()
|
||||
.map(|(_, path)| {
|
||||
let bytes = (self.read)(path)?;
|
||||
// a raw without colour matrices of its own starts from the colour it has when opened alone (#965)
|
||||
let f = lightcraft_merge::load_frame_with(&bytes, self.preview, orient, &crate::camera_preview::starting_colour_tags)
|
||||
.map_err(|e| format!("{path}: {e}"));
|
||||
let k = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
|
||||
progress(span.0 + (span.1 - span.0) * k as f32 / n as f32, "Reading photos");
|
||||
f
|
||||
})
|
||||
.collect();
|
||||
frames
|
||||
let one = |(_, path): &(PhotoId, String)| {
|
||||
let bytes = (self.read)(path)?;
|
||||
// a raw without colour matrices of its own starts from the colour it has when opened alone (#965)
|
||||
let f = lightcraft_merge::load_frame_with(&bytes, self.preview, orient, &crate::camera_preview::starting_colour_tags)
|
||||
.map_err(|e| format!("{path}: {e}"));
|
||||
let k = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
|
||||
progress(span.0 + (span.1 - span.0) * k as f32 / n as f32, "Reading photos");
|
||||
f
|
||||
};
|
||||
// Full-size photos are decoded one at a time (the demosaic is parallel inside): decoding several 60 MP raws at
|
||||
// once multiplied the decoders' scratch memory, and a 7-frame bracket ran out of memory.
|
||||
if self.preview.is_some() { self.sources.par_iter().map(one).collect() } else { self.sources.iter().map(one).collect() }
|
||||
}
|
||||
|
||||
/// Run the merge. `progress(fraction, stage)` returns false to cancel.
|
||||
|
||||
@@ -19,20 +19,21 @@ const MARGIN: usize = 6;
|
||||
|
||||
pub(crate) fn ahd(m: &Mosaic) -> Rgb32f {
|
||||
let (w, h) = (m.w, m.h);
|
||||
let tiles: Vec<(usize, usize)> = (0..h.div_ceil(TILE)).flat_map(|ty| (0..w.div_ceil(TILE)).map(move |tx| (tx * TILE, ty * TILE))).collect();
|
||||
let results: Vec<(usize, usize, usize, usize, Vec<[f32; 3]>)> = tiles
|
||||
.par_iter()
|
||||
.map(|&(x0, y0)| {
|
||||
let (tw, th) = (TILE.min(w - x0), TILE.min(h - y0));
|
||||
(x0, y0, tw, th, tile(m, x0, y0, tw, th))
|
||||
})
|
||||
.collect();
|
||||
let mut out = Rgb32f::new(w, h);
|
||||
for (x0, y0, tw, th, px) in results {
|
||||
for y in 0..th {
|
||||
out.data[(y0 + y) * w + x0..(y0 + y) * w + x0 + tw].copy_from_slice(&px[y * tw..(y + 1) * tw]);
|
||||
}
|
||||
if w == 0 || h == 0 {
|
||||
return out;
|
||||
}
|
||||
// one row of tiles per task, written straight into the picture (collecting the tiles first held a second copy)
|
||||
out.data.par_chunks_mut(w * TILE).enumerate().for_each(|(ty, band)| {
|
||||
let (y0, th) = (ty * TILE, band.len() / w);
|
||||
for x0 in (0..w).step_by(TILE) {
|
||||
let tw = TILE.min(w - x0);
|
||||
let px = tile(m, x0, y0, tw, th);
|
||||
for y in 0..th {
|
||||
band[y * w + x0..y * w + x0 + tw].copy_from_slice(&px[y * tw..(y + 1) * tw]);
|
||||
}
|
||||
}
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@@ -74,4 +74,7 @@ scale in `BaselineExposure`; raw sources keep their DNG colour tags (camera RGB)
|
||||
- *Create Stack*: the result goes on top of a collapsed stack of its sources (`Catalog::stack_with_ops`); its
|
||||
caption also lists them.
|
||||
- Panorama output is limited to `maxMegapixels` (default 40) — blending memory is ~25 bytes/pixel.
|
||||
- HDR memory: every frame is held as 32-bit float RGB (~0.86 GB per 67 MP frame at the peak, plus ~1.2 GB); full-size
|
||||
photos are decoded one at a time. Measured on 67 MP raws: 3 frames 3.8 GB, 4 frames 4.7 GB, 5 frames 5.5 GB (was
|
||||
4.8 / 6.3 GB for 3 / 4). Half-float frames would halve the per-frame share.
|
||||
- The browser build has no file access for merges yet.
|
||||
|
||||
Reference in New Issue
Block a user