Run the learned denoise in develop, and export with it

A Bayer photograph keeps its mosaic in the session and is offered the AI
Denoise switch. Asked for, the network runs on the decode executor from a
hot-pixel-repaired copy — the app's own pass — with the frame's noise from
its best source, and its progress in the activity bar; the classical
demosaic shows until the result lands, and the finished job says where the
noise figures came from. Keep grain is a GrainBlend of the two, made once
per value; the render draws it as its source and the adjust pass never
knows. demosaiced stays the classical result, so the raw histogram, the
white balance picker, masks and segmentation still read the sensor.

The develop view reconciles on a 250 ms poll rather than on each way an
edit can change (slider, undo, preset, version, a sidecar from another
device): two comparisons when nothing changed, and no path that can forget.
A failure is not retried until the switch is toggled. An export of a
photograph that asks for it waits for a running job or computes it.
This commit is contained in:
2026-10-03 11:51:00 -04:00
parent ad6bb892f3
commit dd43f498fb
13 changed files with 622 additions and 60 deletions
+11 -1
View File
@@ -118,6 +118,10 @@ pub struct DevelopSession {
/// history still stands on that crop. See [`super::framing::CropNotice`].
pub(super) crop_notice: Option<super::framing::CropNotice>,
pub(super) demosaiced: Arc<DemosaicedImage>,
/// TRACES: FR-DEV-3g
/// The learned demosaic: the kept mosaic, the job, the result. See
/// [`super::denoise`].
pub(super) denoise: super::denoise::DenoiseState,
/// TRACES: FR-DSP-2 | NFR-RES-2
/// The photograph at full resolution, when it is too large to hold in one
/// texture. `demosaiced` is then a reduced copy of it, which is all the
@@ -376,7 +380,12 @@ impl DevelopSession {
let (w, h) = (raw.crop.width.max(1), raw.crop.height.max(1));
let edge = PROXY_EDGE.min(DemosaicedImage::max_dimension(ctx));
if raw.samples_per_pixel != 3 || w.max(h) <= edge {
return Self::open(ctx, &raw, orientation);
let mut session = Self::open(ctx, &raw, orientation)?;
// TRACES: FR-DEV-3g
// The mosaic stays with the session when the learned demosaic
// could take it, so asking for it later needs no second decode.
session.keep_mosaic(raw);
return Ok(session);
}
let reduce = w.max(h).div_ceil(edge);
log::info!("{w}×{h} is larger than one texture; developing from a 1/{reduce} copy");
@@ -438,6 +447,7 @@ impl DevelopSession {
compared_snapshot: None,
crop_notice: None,
demosaiced: Arc::new(demosaiced),
denoise: Default::default(),
full: None,
window: None,
adjust: AdjustPass::new(ctx),