From 8e7b1350bf49397b8495c03456876532bd2b0bc2 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 5 Sep 2026 14:34:03 +0200 Subject: [PATCH 1/5] Name the frame's category for the decision, not the maths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Attribute::Geometry` becomes `Attribute::Compose`, and `film_sim` moves from `[tone, colour]` to `[effect]`. Two categories were doing the wrong job. "Geometry" describes what crop, straighten and the quarter turns do to coordinates — but it describes lens distortion correction exactly as well, and that is not a compositional choice at all. Naming the attribute for the photographer's decision is what separates it from `Optics`: one is what the lens did, the other is what they chose. The maths the two have in common is not the thing worth filing them under. A film stock declared both `tone` and `colour`, so "Kodachrome" appeared in the Light group beside exposure and again in Colour beside white balance — two places, neither of which is where anyone looks for it. It is neither: `Effect` is defined in this same file as "applied rather than corrected — a look, not a fix", which is what a stock is. That it moves tone and colour is true of every look, and is not what the attribute is for. `from_name` still accepts "geometry" on the way in. That string is persisted in `develop.copy_attributes`, and an entry it fails to parse is not an error — `presets::scope_for` logs it and drops it — so without the alias an existing settings file would have quietly narrowed what a paste carries. `name` writes the current spelling, so the file migrates itself the first time it is saved. Co-Authored-By: Claude Opus 5 (1M context) --- core/dr-pipeline/ops/film_sim.yaml | 8 +++++++- core/dr-pipeline/src/declared/decl.rs | 6 +++--- core/dr-pipeline/src/declared/mod.rs | 2 +- core/dr-pipeline/src/descriptor.rs | 27 +++++++++++++++++++++------ core/dr-pipeline/src/framing.rs | 2 +- core/dr-pipeline/src/preset.rs | 10 +++++----- core/dr-pipeline/tests/attributes.rs | 2 +- docs/traceability.md | 2 +- ui/dr-ui/src/develop.rs | 14 +++++++------- ui/dr-ui/src/labels.rs | 2 +- ui/dr-ui/src/presets.rs | 4 ++-- ui/dr-ui/ui/adjust.slint | 4 ++-- ui/dr-ui/ui/app.slint | 4 ++-- ui/dr-ui/ui/history.slint | 2 +- 14 files changed, 55 insertions(+), 34 deletions(-) diff --git a/core/dr-pipeline/ops/film_sim.yaml b/core/dr-pipeline/ops/film_sim.yaml index ce43cfd..f753f08 100644 --- a/core/dr-pipeline/ops/film_sim.yaml +++ b/core/dr-pipeline/ops/film_sim.yaml @@ -1,6 +1,12 @@ id: film_sim order: 25 -attributes: [tone, colour] +# Effect, not tone-and-colour. It declared both, which put "Kodachrome" in +# the Light group beside exposure and again in Colour beside white balance — +# two places, neither of which is where anyone looks for it, and both of which +# it crowded. A stock is `Effect`'s own definition: applied rather than +# corrected, a look and not a fix. That it moves tone and colour is true of +# every look and is not what the attribute is for. +attributes: [effect] rust: FilmSim why_rust: | diff --git a/core/dr-pipeline/src/declared/decl.rs b/core/dr-pipeline/src/declared/decl.rs index adb1039..7c327f8 100644 --- a/core/dr-pipeline/src/declared/decl.rs +++ b/core/dr-pipeline/src/declared/decl.rs @@ -120,7 +120,7 @@ pub enum Attr { Colour, Detail, Optics, - Geometry, + Compose, Effect, } @@ -132,7 +132,7 @@ impl Attr { Attr::Colour => "colour", Attr::Detail => "detail", Attr::Optics => "optics", - Attr::Geometry => "geometry", + Attr::Compose => "compose", Attr::Effect => "effect", } } @@ -144,7 +144,7 @@ impl Attr { Attr::Colour, Attr::Detail, Attr::Optics, - Attr::Geometry, + Attr::Compose, Attr::Effect, ]; } diff --git a/core/dr-pipeline/src/declared/mod.rs b/core/dr-pipeline/src/declared/mod.rs index f243b92..f7a9a72 100644 --- a/core/dr-pipeline/src/declared/mod.rs +++ b/core/dr-pipeline/src/declared/mod.rs @@ -350,7 +350,7 @@ fn attribute(a: decl::Attr) -> Attribute { decl::Attr::Colour => Attribute::Colour, decl::Attr::Detail => Attribute::Detail, decl::Attr::Optics => Attribute::Optics, - decl::Attr::Geometry => Attribute::Geometry, + decl::Attr::Compose => Attribute::Compose, decl::Attr::Effect => Attribute::Effect, } } diff --git a/core/dr-pipeline/src/descriptor.rs b/core/dr-pipeline/src/descriptor.rs index 0ce9848..af97c80 100644 --- a/core/dr-pipeline/src/descriptor.rs +++ b/core/dr-pipeline/src/descriptor.rs @@ -580,8 +580,15 @@ pub enum Attribute { /// Corrections for the lens that took the photograph: distortion, /// chromatic aberration, vignetting. Optics, - /// The shape of the frame: crop, straighten, rotation, flips. - Geometry, + /// How the frame is composed: crop, straighten, rotation, flips. + /// + /// Named for the decision rather than for the maths. The lens corrections + /// are geometry too — distortion moves pixels exactly as a straighten + /// does — and lumping the two together would file a correction the + /// photographer never asked for beside a choice that is the whole reason + /// they opened the photograph. [`Self::Optics`] is what the lens did; + /// this is what they decided. + Compose, /// Applied rather than corrected — a look, not a fix. Effect, } @@ -598,7 +605,7 @@ impl Attribute { Attribute::Colour, Attribute::Detail, Attribute::Optics, - Attribute::Geometry, + Attribute::Compose, Attribute::Effect, ]; @@ -613,7 +620,7 @@ impl Attribute { Self::Colour => "attr.colour", Self::Detail => "attr.detail", Self::Optics => "attr.optics", - Self::Geometry => "attr.geometry", + Self::Compose => "attr.compose", Self::Effect => "attr.effect", }) } @@ -630,7 +637,7 @@ impl Attribute { Self::Colour => "colour", Self::Detail => "detail", Self::Optics => "optics", - Self::Geometry => "geometry", + Self::Compose => "compose", Self::Effect => "effect", } } @@ -642,7 +649,15 @@ impl Attribute { "colour" => Self::Colour, "detail" => Self::Detail, "optics" => Self::Optics, - "geometry" => Self::Geometry, + "compose" => Self::Compose, + // The name this attribute was persisted under before it was + // called Compose. Settings written by an older build carry it in + // `develop.copy_attributes`, and `from_name` returning `None` + // there does not fail loudly — `presets::scope_for` logs and drops + // the entry, silently narrowing what a paste carries. Accepted on + // the way in only; `name` writes the current spelling, so a + // settings file rewrites itself the first time it is saved. + "geometry" => Self::Compose, "effect" => Self::Effect, _ => return None, }) diff --git a/core/dr-pipeline/src/framing.rs b/core/dr-pipeline/src/framing.rs index 0758dcd..a8737c4 100644 --- a/core/dr-pipeline/src/framing.rs +++ b/core/dr-pipeline/src/framing.rs @@ -78,7 +78,7 @@ static DESCRIPTOR: LazyLock> = LazyLock::new(|| { Arc::new(OpDescriptor { // The shape of the frame, and the only operation that changes the // output's dimensions. - attributes: vec![Attribute::Geometry], + attributes: vec![Attribute::Compose], id: ID, label: LocalizedKey("op.framing"), params: vec![ diff --git a/core/dr-pipeline/src/preset.rs b/core/dr-pipeline/src/preset.rs index 55c9a40..2b78e28 100644 --- a/core/dr-pipeline/src/preset.rs +++ b/core/dr-pipeline/src/preset.rs @@ -59,7 +59,7 @@ use crate::graph::EditGraph; /// /// It also dissolves a special case. Framing used to be excluded by an /// explicit test against one operation's id; it is now excluded because -/// [`Attribute::Geometry`] is not in the default set, and the argument below +/// [`Attribute::Compose`] is not in the default set, and the argument below /// is a statement about a kind of edit rather than about a particular node. /// /// # Why geometry is out by default @@ -116,7 +116,7 @@ impl Scope { /// its shape. The default. pub fn adjustments() -> Self { Self { - bits: Self::all_bits() & !Self::bit(Attribute::Geometry), + bits: Self::all_bits() & !Self::bit(Attribute::Compose), carries_unclassified: true, } } @@ -278,7 +278,7 @@ impl Preset { pub fn touches_framing(&self) -> bool { self.params .keys() - .any(|(op, _)| attributes_of(op).is_some_and(|a| a.contains(&Attribute::Geometry))) + .any(|(op, _)| attributes_of(op).is_some_and(|a| a.contains(&Attribute::Compose))) } /// How many operations this preset touches, in scope. @@ -1254,8 +1254,8 @@ mod tests { .into_iter() .filter(|a| !default_scope.has(*a)) .collect(); - assert_eq!(excluded, vec![Attribute::Geometry]); - assert!(Scope::everything().has(Attribute::Geometry)); + assert_eq!(excluded, vec![Attribute::Compose]); + assert!(Scope::everything().has(Attribute::Compose)); } #[test] diff --git a/core/dr-pipeline/tests/attributes.rs b/core/dr-pipeline/tests/attributes.rs index 05e1072..e3796e5 100644 --- a/core/dr-pipeline/tests/attributes.rs +++ b/core/dr-pipeline/tests/attributes.rs @@ -46,7 +46,7 @@ fn the_groups_are_derivable_from_the_chain() { ); assert!(present.contains(&Attribute::Colour)); assert!( - present.contains(&Attribute::Geometry), + present.contains(&Attribute::Compose), "framing is in the capability list and is geometry" ); diff --git a/docs/traceability.md b/docs/traceability.md index de84bf8..da76c7e 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -112,7 +112,7 @@ _None._ | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:388`](../apps/darkroom-android/src/lib.rs#L388) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:999`](../ui/dr-ui/src/lib.rs#L999), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | -| FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:652`](../core/dr-pipeline/src/descriptor.rs#L652), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:416`](../core/dr-pipeline/tests/declared_parity.rs#L416) | +| FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:667`](../core/dr-pipeline/src/descriptor.rs#L667), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:416`](../core/dr-pipeline/tests/declared_parity.rs#L416) | | FR-PLG-2d | [`core/dr-pipeline/src/declared/decl.rs:112`](../core/dr-pipeline/src/declared/decl.rs#L112), [`core/dr-pipeline/src/declared/decl.rs:152`](../core/dr-pipeline/src/declared/decl.rs#L152), [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:420`](../core/dr-pipeline/src/declared/decl.rs#L420), [`core/dr-pipeline/src/declared/decl.rs:67`](../core/dr-pipeline/src/declared/decl.rs#L67), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:384`](../core/dr-pipeline/src/declared/mod.rs#L384), [`core/dr-pipeline/src/declared/mod.rs:403`](../core/dr-pipeline/src/declared/mod.rs#L403) | | FR-PLG-8 | [`core/dr-pipeline/src/sidecar.rs:1807`](../core/dr-pipeline/src/sidecar.rs#L1807), [`core/dr-pipeline/src/sidecar.rs:1840`](../core/dr-pipeline/src/sidecar.rs#L1840) | | FR-RAW-1 | [`core/dr-decode/src/lib.rs:243`](../core/dr-decode/src/lib.rs#L243), [`core/dr-types/src/lib.rs:132`](../core/dr-types/src/lib.rs#L132), [`core/dr-types/src/lib.rs:203`](../core/dr-types/src/lib.rs#L203) | diff --git a/ui/dr-ui/src/develop.rs b/ui/dr-ui/src/develop.rs index c3652c6..a73dbb0 100644 --- a/ui/dr-ui/src/develop.rs +++ b/ui/dr-ui/src/develop.rs @@ -1021,15 +1021,15 @@ impl DevelopSession { /// (FR-DEV-3a). An attribute nothing carries is left out rather than /// offered as a tab that opens onto nothing. /// - /// Geometry is excluded: its one operation prefers an on-canvas widget and - /// is skipped by the row builder, so a Geometry tab would be empty of rows - /// while `GeometryPanel` holds the real controls. + /// Compose is excluded: its one operation prefers an on-canvas widget and + /// is skipped by the row builder, so a Compose tab would be empty of rows + /// while `ComposePanel` holds the real controls. pub fn tabs(&self) -> Vec<(dr_pipeline::Attribute, String)> { use dr_pipeline::Attribute; let caps = self.scoped_capabilities(); Attribute::ALL .into_iter() - .filter(|a| *a != Attribute::Geometry) + .filter(|a| *a != Attribute::Compose) .filter(|a| { caps.iter().any(|c| { c.attributes.contains(a) @@ -1171,7 +1171,7 @@ pub(crate) fn supported(widget: WidgetKind) -> bool { // Drawn in the panel. WidgetKind::ToneCurve => true, // Hosted on the canvas: the overlay is drawn over the photograph and - // the panel contributes `GeometryPanel`, the affordance that turns it + // the panel contributes `ComposePanel`, the affordance that turns it // on. WidgetKind::CropOverlay => true, // Not implemented. Listed rather than caught by a wildcard so the next @@ -1254,7 +1254,7 @@ pub(crate) fn rows_filtered( // terms, with nothing named. // // Skipped rather than rendered as an affordance row, because - // the affordance is `GeometryPanel` — a bespoke control for a + // the affordance is `ComposePanel` — a bespoke control for a // known stage, which is a thing the interface is entitled to // build (ARCH §4.3a draws the line at the *generated* panel // naming stages, not at the interface having hand-made @@ -5748,7 +5748,7 @@ mod tests { #[test] fn framing_is_not_generated_as_sliders() { - // `GeometryPanel` presents crop, rotation, flips and straightening as + // `ComposePanel` presents crop, rotation, flips and straightening as // the gestures they are. If the generic path emitted them too the // sidebar would carry both — including four "Crop Left/Top/Width/ // Height" sliders no one can compose a photograph with. diff --git a/ui/dr-ui/src/labels.rs b/ui/dr-ui/src/labels.rs index 49929e8..28954b2 100644 --- a/ui/dr-ui/src/labels.rs +++ b/ui/dr-ui/src/labels.rs @@ -127,7 +127,7 @@ fn catalogued(key: &str) -> Option<&'static str> { "attr.colour" => "Colour", "attr.detail" => "Detail", "attr.optics" => "Optics", - "attr.geometry" => "Geometry", + "attr.compose" => "Compose", "attr.effect" => "Effects", "op.white_balance" => "White Balance", diff --git a/ui/dr-ui/src/presets.rs b/ui/dr-ui/src/presets.rs index 05d3c80..7e995a1 100644 --- a/ui/dr-ui/src/presets.rs +++ b/ui/dr-ui/src/presets.rs @@ -210,7 +210,7 @@ fn label_for(attribute: dr_pipeline::Attribute) -> &'static str { Attribute::Colour => "Colour", Attribute::Detail => "Detail", Attribute::Optics => "Optics", - Attribute::Geometry => "Geometry", + Attribute::Compose => "Compose", Attribute::Effect => "Effect", } } @@ -548,7 +548,7 @@ pub fn render( // holding no crop loses nothing to the setting, and saying so anyway would // train the user to ignore the line. window.set_settings_framing_withheld( - clipboard.holds_framing() && !scope.has(dr_pipeline::Attribute::Geometry), + clipboard.holds_framing() && !scope.has(dr_pipeline::Attribute::Compose), ); } diff --git a/ui/dr-ui/ui/adjust.slint b/ui/dr-ui/ui/adjust.slint index ae2a438..417aa4c 100644 --- a/ui/dr-ui/ui/adjust.slint +++ b/ui/dr-ui/ui/adjust.slint @@ -535,7 +535,7 @@ component ParamControl inherits Rectangle { // This does not weaken ARCH §4.3: nothing here reads a parameter *value* out // of a descriptor or routes by index. It calls named session actions, which is // what a bespoke widget for a known stage is entitled to do. -export component GeometryPanel inherits Rectangle { +export component ComposePanel inherits Rectangle { in property enabled: true; in property angle: 0.0; in property max-straighten: 45.0; @@ -586,7 +586,7 @@ export component GeometryPanel inherits Rectangle { // naming the group, flagging that it holds an edit, and offering the // reset — without the hiding. GroupHeading { - title: "GEOMETRY"; + title: "COMPOSE"; modified: root.modified; has-reset: root.modified; reset => { root.reset(); } diff --git a/ui/dr-ui/ui/app.slint b/ui/dr-ui/ui/app.slint index 8997a70..658a3d0 100644 --- a/ui/dr-ui/ui/app.slint +++ b/ui/dr-ui/ui/app.slint @@ -1,5 +1,5 @@ import { Theme } from "theme.slint"; -import { AdjustPanel, GeometryPanel, GroupStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint"; +import { AdjustPanel, ComposePanel, GroupStrip, ParamRow, TransferPanel, ViewMode } from "adjust.slint"; import { CategoryRow, GradientHandle, GradientHandles, HandleRole, MaskPanel, MaskRow, SubjectRow } from "masks.slint"; import { SpotHandle, SpotHandles, SpotPanel, SpotRole } from "spots.slint"; import { CropOverlay } from "crop.slint"; @@ -2471,7 +2471,7 @@ in property panel-visible: true; // made rather than how it is applied: the frame is decided // by eye first and the pipeline runs it last (see // `dr_pipeline::framing` on the coordinate order). - if !root.local-mode && !root.repairing: GeometryPanel { + if !root.local-mode && !root.repairing: ComposePanel { enabled: root.adjust-enabled; angle: root.straighten; max-straighten: root.max-straighten; diff --git a/ui/dr-ui/ui/history.slint b/ui/dr-ui/ui/history.slint index 9acad69..9e5f27c 100644 --- a/ui/dr-ui/ui/history.slint +++ b/ui/dr-ui/ui/history.slint @@ -121,7 +121,7 @@ export component HistoryPanel inherits Rectangle { spacing: Theme.gap-sm; alignment: start; - // A heading, not a collapsible. `GeometryPanel` carries the argument + // A heading, not a collapsible. `ComposePanel` carries the argument // and it holds here: the list is last in the column, so what a lid // would save is scrolling past nothing. HorizontalLayout { From e17b909d41b758c47b028cc0df050a4468cf0f8f Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 5 Sep 2026 14:34:24 +0200 Subject: [PATCH 2/5] Connect the lens vignetting correction to the pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ops/vignetting.rs` has carried a complete descriptor, polynomial, helper and test suite without an entry in `ops/`, so it was never in `chain()`. It reached no photograph and no panel, and `Attribute::Optics` was an empty category in consequence — filtered out of the tab strip for having no rows, by a chain that had never been given its only member. Declaring it needs the one thing the operation was written against and which did not exist. `wgsl_body` reads `radius`, and the module claimed "the composer publishes `radius` in the shader prologue for exactly this reason". It did not. `sample_source` now does, in both sampling branches, beside the `source_px` it already published for the same class of caller. It is corner-normalised there, which is the part that is easy to leave out. `p` spans ±0.5·aspect, so its length at the corner is 0.5·length(aspect) — about 0.901 on a 3:2 frame, not 1. Lensfun's polynomials are fitted against a corner radius of 1, so passing `length(p)` straight in evaluates every one of them short of where it was measured, by a factor that changes with the aspect ratio. It would have read as a correction that is simply too weak, which is indistinguishable from a bad profile. Both `lens.rs` and `framing.rs` asserted the normalisation `p` does not have; corrected. `order: 5` puts the correction ahead of the tonal stages, and the ordering is load-bearing rather than tidy. Recovering a corner means dividing by an attenuation below one — about two stops for a fast prime wide open — so run after the highlights have been rolled off and clipped, the lift has nowhere to go and the corners posterise instead of brightening. `layer_chain` now drops `Optics` as well as the neighbourhood operations. A local vignetting slider would have worked, which is what makes it worth excluding: `radius` measures from the centre of the whole photograph and a mask cannot move the optical axis, so it would lay a frame-centred radial ramp across the picture and multiply it by the mask. The existing exclusion covers operations that move and do nothing; this one covers an operation that moves and does something its name does not promise. The rule both share is now written down. Co-Authored-By: Claude Opus 5 (1M context) --- core/dr-pipeline/ops/vignetting.yaml | 40 ++++++++++++++++ core/dr-pipeline/src/framing.rs | 13 ++++-- core/dr-pipeline/src/lens.rs | 6 +++ core/dr-pipeline/src/mask.rs | 56 ++++++++++++++++++----- core/dr-pipeline/src/operation.rs | 34 ++++++++++++++ core/dr-pipeline/src/ops/mod.rs | 8 ++++ core/dr-pipeline/tests/declared_parity.rs | 1 + docs/traceability.md | 16 +++---- ui/dr-ui/src/labels.rs | 8 ++++ 9 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 core/dr-pipeline/ops/vignetting.yaml diff --git a/core/dr-pipeline/ops/vignetting.yaml b/core/dr-pipeline/ops/vignetting.yaml new file mode 100644 index 0000000..879faa6 --- /dev/null +++ b/core/dr-pipeline/ops/vignetting.yaml @@ -0,0 +1,40 @@ +# A hand-written node, and the first thing in `Attribute::Optics`. +# +# Written, tested and unreferenced until now: `src/ops/vignetting.rs` has +# existed with a full descriptor and a working polynomial, and without an entry +# here it was never in `chain()` — so it reached no photograph and no panel. +# This file is the whole of what was missing, which is the point of `ops/` +# being the one place the pipeline's order is written down. +id: vignetting +order: 5 + +attributes: [optics] +rust: Vignetting + +why_rust: | + It carries a lens profile's `pa` coefficients, which are not parameters: they + come from the body and lens that took the photograph, not from the + photographer, and no `uniforms:` expression could produce them. The one + parameter that *is* theirs — the manual trim — is composed with `k1` in Rust, + because the profile and the trim have to reach the shader as a single + polynomial rather than as two the fragment would have to add up. + +placement: | + First in the chain, ahead of white balance and exposure. + + Vignetting is what the lens did to the light before the sensor measured it, + so undoing it belongs with reading the file rather than with editing the + picture — everything downstream is then working on the frame the lens would + have delivered had it been even. + + The ordering is load-bearing rather than tidy. Correcting a corner means + *dividing* by an attenuation below one, which pushes those pixels up: a fast + prime wide open needs about two stops there. Run after the tonal stages, that + recovery happens once the highlights have already been rolled off and + clipped, so it lifts values that no longer have anywhere to go and the + corners posterise instead of brightening. Run here, the headroom to hold them + still exists (ARCH §5.2). + + Before distortion and chromatic aberration in intent, though those are + `lens::Warp`s rather than nodes and compose ahead of the fetch, so no `order:` + relates the two. diff --git a/core/dr-pipeline/src/framing.rs b/core/dr-pipeline/src/framing.rs index a8737c4..646a960 100644 --- a/core/dr-pipeline/src/framing.rs +++ b/core/dr-pipeline/src/framing.rs @@ -893,9 +893,16 @@ impl Framing { /// The WGSL mapping an output pixel to a **normalised centred** source /// position, ready for the warp chain. /// - /// Leaves the result in `p`: centre `(0, 0)`, `r == 1` at the corner — - /// exactly the space [`crate::lens`] documents, so lens correction - /// composes on top of this without either stage naming the other. + /// Leaves the result in `p`: centre `(0, 0)`, spanning `±0.5 * aspect` — + /// the space [`crate::lens`] documents, so lens correction composes on top + /// of this without either stage naming the other. + /// + /// **`p` is centred but not corner-normalised**, and this used to claim it + /// was. Its length at the corner is `0.5 * length(aspect)`, not 1. The + /// corner-normalised radius the radial corrections need is published + /// separately as `radius` by `operation::sample_source`, which divides by + /// exactly that; anything reading `length(p)` as a lens radius is off by + /// an aspect-dependent factor. /// /// `aspect` is left in scope alongside it, since the warp chain and the /// sampler both need it to return to texture coordinates. diff --git a/core/dr-pipeline/src/lens.rs b/core/dr-pipeline/src/lens.rs index a1753f7..f545c1d 100644 --- a/core/dr-pipeline/src/lens.rs +++ b/core/dr-pipeline/src/lens.rs @@ -41,6 +41,12 @@ //! `(0, 0)`, and the radius is scaled so that `r == 1` at the corner. Both //! properties matter. //! +//! Note which variable carries which. The shader's `p` supplies the centring +//! and spans `±0.5 * aspect`; the corner normalisation is applied on top of it +//! by `operation::sample_source`, which publishes the result as `radius`. A +//! warp reading `length(p)` and calling it `r` would be evaluating its +//! polynomial short of where the profile was fitted. +//! //! Centring is what makes the polynomial meaningful — lens distortion is //! radially symmetric about the optical axis, so a formula written about any //! other origin would need cross terms to say the same thing. diff --git a/core/dr-pipeline/src/mask.rs b/core/dr-pipeline/src/mask.rs index 5d5cc53..86b2db2 100644 --- a/core/dr-pipeline/src/mask.rs +++ b/core/dr-pipeline/src/mask.rs @@ -55,7 +55,7 @@ use std::fmt::Write as _; use std::sync::Arc; use crate::coverage::Coverage; -use crate::descriptor::{OpDescriptor, ParamId}; +use crate::descriptor::{Attribute, OpDescriptor, ParamId}; use crate::operation::Operation; use crate::ops; @@ -777,8 +777,8 @@ pub struct MaskLayer { pub ops: Vec>, } -/// The chain a mask layer holds: every point operation, and none of the -/// neighbourhood ones. +/// The chain a mask layer holds: every point operation, and neither the +/// neighbourhood ones nor the optical corrections. /// /// A layer's adjustments are fused into the colour dispatch and multiplied by /// the mask afterwards, which is exactly why a layer needs no per-operation @@ -795,10 +795,26 @@ pub struct MaskLayer { /// Filtering here means a local sharpening or denoise control simply does not /// appear until there is a stage that can honour it, which is the honest /// state of affairs. +/// **The optical corrections are excluded on their own grounds**, which are +/// not the neighbourhood argument above: they would work perfectly and mean +/// nothing. +/// +/// `Attribute::Optics` describes what the *lens* did to the whole frame. Its +/// corrections are radial about the optical axis, and the `radius` they read +/// is the distance from the centre of the photograph — a layer's mask does not +/// and cannot move it. So a vignetting slider inside a mask would not correct +/// the falloff within the selected region; it would lay a frame-centred radial +/// ramp over the picture and then multiply it by the mask. That is a +/// well-defined operation nobody would ever want, and worse, it is one whose +/// name promises something else entirely. +/// +/// The general rule the two exclusions share: a layer holds an operation only +/// where restricting it to a region is a thing a photographer could mean. fn layer_chain() -> Vec> { ops::chain() .into_iter() .filter(|o| o.detail().is_none()) + .filter(|o| !o.descriptor().attributes.contains(&Attribute::Optics)) .collect() } @@ -1463,33 +1479,49 @@ mod tests { #[test] fn a_layer_offers_only_the_operations_it_can_actually_apply() { + // Two exclusions, for two different reasons, and the test states both + // because they fail in different ways. + // // A layer's adjustments are fused into the colour dispatch and then // multiplied by the mask. A neighbourhood operation cannot take that // route: it is a dispatch of its own, run after the fused pass and // after the masks are already applied, so there is nowhere to hand it - // one layer's mask. + // one layer's mask. Left in, it moves and nothing happens. + // + // An optical correction *can* take that route, and that is the + // problem. It is radial about the frame's optical axis, which a mask + // cannot move, so it would lay a frame-centred ramp over the whole + // picture and multiply it by the mask. Left in, it moves and the wrong + // thing happens — under a name that promises the right one. // // The panel builds itself from `capabilities()` and names no - // operation, so anything left in this chain becomes a control. One - // that cannot work is worse than one that is missing: it moves, the - // picture does not change, and nothing says why. + // operation, so anything left in this chain becomes a control. let layer = lit_layer("m1", 1.0); let ids: Vec<&str> = layer.capabilities().iter().map(|c| c.id.0).collect(); let global = crate::ops::chain(); + let local_means_something = |op: &Box| { + op.detail().is_none() + && !op + .descriptor() + .attributes + .contains(&crate::descriptor::Attribute::Optics) + }; + for op in &global { let id = op.descriptor().id.0; assert_eq!( ids.contains(&id), - op.detail().is_none(), - "{id} is offered as a local adjustment but cannot be one, \ - or is a point operation and has gone missing from a layer" + local_means_something(op), + "{id} is offered as a local adjustment but cannot meaningfully \ + be one, or is an ordinary point operation and has gone \ + missing from a layer" ); } assert!( - ids.len() < global.len() || global.iter().all(|o| o.detail().is_none()), + ids.len() < global.len() || global.iter().all(local_means_something), "the filter dropped nothing, so either it is not running or the \ - chain has no neighbourhood operation left to drop" + chain has nothing left that a layer cannot carry" ); // And a clone must rebuild the same chain: it copies parameters across diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index cebcf2f..0e58330 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -1074,6 +1074,23 @@ pub(crate) fn sample_source(interpolate: bool) -> &'static str { let source_px = uv_src * vec2(src_dims); // A free angle puts output pixels between source pixels. Nearest-neighbour // here is what makes a straightened horizon stair-step, so interpolate. + // Distance from the optical axis, normalised so the corner is exactly 1. + // + // Published beside `source_px` and for the same reason: a fragment is + // handed a colour with no way back to a coordinate, and the radial + // corrections need one. Derived from `p` after the whole coordinate stage, + // so it measures the *source* frame — which is what a lens profile is + // calibrated against, and why an off-centre crop still gets the falloff + // its corner actually had rather than one centred on the crop. + // + // **The division is the part that is easy to leave out.** `p` spans + // `+/-0.5 * aspect`, so at the corner its length is `0.5 * length(aspect)` + // -- about 0.901 on a 3:2 frame, not 1. Lensfun's polynomials are fitted + // against a corner radius of 1, so passing `length(p)` straight in + // evaluates every one of them short of where it was measured, and by an + // amount that changes with the aspect ratio. It reads as a correction that + // is simply too weak, which is indistinguishable from a bad profile. + let radius = length(p) / (0.5 * length(aspect)); var c = sample_bilinear(uv_src, src_dims); " } else { @@ -1101,6 +1118,23 @@ pub(crate) fn sample_source(interpolate: bool) -> &'static str { // put, and its *amount* is handled separately by how much film a pixel // covers -- see `dr_film::Grain`. let source_px = uv_src * vec2(src_dims); + // Distance from the optical axis, normalised so the corner is exactly 1. + // + // Published beside `source_px` and for the same reason: a fragment is + // handed a colour with no way back to a coordinate, and the radial + // corrections need one. Derived from `p` after the whole coordinate stage, + // so it measures the *source* frame — which is what a lens profile is + // calibrated against, and why an off-centre crop still gets the falloff + // its corner actually had rather than one centred on the crop. + // + // **The division is the part that is easy to leave out.** `p` spans + // `+/-0.5 * aspect`, so at the corner its length is `0.5 * length(aspect)` + // -- about 0.901 on a 3:2 frame, not 1. Lensfun's polynomials are fitted + // against a corner radius of 1, so passing `length(p)` straight in + // evaluates every one of them short of where it was measured, and by an + // amount that changes with the aspect ratio. It reads as a correction that + // is simply too weak, which is indistinguishable from a bad profile. + let radius = length(p) / (0.5 * length(aspect)); var c = textureLoad(source, coord, 0).rgb; " } diff --git a/core/dr-pipeline/src/ops/mod.rs b/core/dr-pipeline/src/ops/mod.rs index 15b169a..46caca1 100644 --- a/core/dr-pipeline/src/ops/mod.rs +++ b/core/dr-pipeline/src/ops/mod.rs @@ -54,6 +54,14 @@ //! than `Operation`, because they rewrite *coordinates* before the source is //! sampled rather than transforming a colour after it. They are not part of //! the develop chain and do not appear in `ops/`. +//! +//! [`vignetting`] is the exception, and the reason the split is drawn at +//! coordinates rather than at "lens correction": it applies a gain to the +//! pixel already fetched, so it is an ordinary node in `ops/` like any other. +//! What it needs that a colour fragment is not otherwise given is the pixel's +//! distance from the optical axis, which the sampler publishes as `radius` +//! — corner-normalised there, because the profile coefficients are fitted +//! against a corner radius of 1 and the prologue's `p` is not. // Hand-written nodes. Each is listed in `ops/` with `rust:`, which is what // places it in the chain; these are the implementations that entry points at. diff --git a/core/dr-pipeline/tests/declared_parity.rs b/core/dr-pipeline/tests/declared_parity.rs index 3048e00..bce9d46 100644 --- a/core/dr-pipeline/tests/declared_parity.rs +++ b/core/dr-pipeline/tests/declared_parity.rs @@ -402,6 +402,7 @@ fn every_declared_node_is_checked() { "noise_reduction", "texture", "tone_curve", + "vignetting", ], "the set of hand-written nodes changed; if that is deliberate, update \ this list and the module documentation above" diff --git a/docs/traceability.md b/docs/traceability.md index da76c7e..29df6eb 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -9,7 +9,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 334 | +| Source files scanned | 335 | | TRACES tags found | 1086 | | Requirements defined | 180 | | Requirements covered | 127 | @@ -60,15 +60,15 @@ _None._ | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../core/dr-pipeline/src/sidecar.rs#L1) | | FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389) | | FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:170`](../core/dr-pipeline/src/graph.rs#L170), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/mask.rs:151`](../core/dr-pipeline/src/mask.rs#L151), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:516`](../core/dr-pipeline/src/operation.rs#L516), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:1774`](../core/dr-pipeline/src/sidecar.rs#L1774), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:850`](../core/dr-segment/src/refine.rs#L850), [`ui/dr-ui/src/develop.rs:103`](../ui/dr-ui/src/develop.rs#L103), [`ui/dr-ui/src/develop.rs:1506`](../ui/dr-ui/src/develop.rs#L1506), [`ui/dr-ui/src/develop.rs:165`](../ui/dr-ui/src/develop.rs#L165), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:2136`](../ui/dr-ui/src/develop.rs#L2136), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:2168`](../ui/dr-ui/src/develop.rs#L2168), [`ui/dr-ui/src/develop.rs:2190`](../ui/dr-ui/src/develop.rs#L2190), [`ui/dr-ui/src/develop.rs:2336`](../ui/dr-ui/src/develop.rs#L2336), [`ui/dr-ui/src/develop.rs:2434`](../ui/dr-ui/src/develop.rs#L2434), [`ui/dr-ui/src/develop.rs:328`](../ui/dr-ui/src/develop.rs#L328), [`ui/dr-ui/src/develop.rs:365`](../ui/dr-ui/src/develop.rs#L365), [`ui/dr-ui/src/develop.rs:3855`](../ui/dr-ui/src/develop.rs#L3855), [`ui/dr-ui/src/develop.rs:3885`](../ui/dr-ui/src/develop.rs#L3885), [`ui/dr-ui/src/develop.rs:3951`](../ui/dr-ui/src/develop.rs#L3951), [`ui/dr-ui/src/develop.rs:3965`](../ui/dr-ui/src/develop.rs#L3965), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4817`](../ui/dr-ui/src/develop.rs#L4817), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/develop.rs:5274`](../ui/dr-ui/src/develop.rs#L5274), [`ui/dr-ui/src/develop.rs:5324`](../ui/dr-ui/src/develop.rs#L5324), [`ui/dr-ui/src/develop.rs:588`](../ui/dr-ui/src/develop.rs#L588), [`ui/dr-ui/src/develop.rs:649`](../ui/dr-ui/src/develop.rs#L649), [`ui/dr-ui/src/develop.rs:795`](../ui/dr-ui/src/develop.rs#L795), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/develop.rs:871`](../ui/dr-ui/src/develop.rs#L871), [`ui/dr-ui/src/lib.rs:1696`](../ui/dr-ui/src/lib.rs#L1696), [`ui/dr-ui/src/lib.rs:2498`](../ui/dr-ui/src/lib.rs#L2498), [`ui/dr-ui/src/lib.rs:2694`](../ui/dr-ui/src/lib.rs#L2694), [`ui/dr-ui/src/lib.rs:2866`](../ui/dr-ui/src/lib.rs#L2866), [`ui/dr-ui/src/lib.rs:2930`](../ui/dr-ui/src/lib.rs#L2930), [`ui/dr-ui/src/lib.rs:2984`](../ui/dr-ui/src/lib.rs#L2984), [`ui/dr-ui/src/lib.rs:389`](../ui/dr-ui/src/lib.rs#L389), [`ui/dr-ui/src/lib.rs:432`](../ui/dr-ui/src/lib.rs#L432), [`ui/dr-ui/src/lib.rs:455`](../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/presets.rs:394`](../ui/dr-ui/src/presets.rs#L394), [`ui/dr-ui/src/segmentation.rs:116`](../ui/dr-ui/src/segmentation.rs#L116), [`ui/dr-ui/src/segmentation.rs:330`](../ui/dr-ui/src/segmentation.rs#L330), [`ui/dr-ui/src/segmentation.rs:346`](../ui/dr-ui/src/segmentation.rs#L346), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476), [`ui/dr-ui/src/segmentation.rs:504`](../ui/dr-ui/src/segmentation.rs#L504), [`ui/dr-ui/ui/adjust.slint:547`](../ui/dr-ui/ui/adjust.slint#L547), [`ui/dr-ui/ui/adjust.slint:666`](../ui/dr-ui/ui/adjust.slint#L666), [`ui/dr-ui/ui/app.slint:150`](../ui/dr-ui/ui/app.slint#L150), [`ui/dr-ui/ui/app.slint:196`](../ui/dr-ui/ui/app.slint#L196), [`ui/dr-ui/ui/app.slint:2046`](../ui/dr-ui/ui/app.slint#L2046), [`ui/dr-ui/ui/app.slint:984`](../ui/dr-ui/ui/app.slint#L984), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | -| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:1092`](../core/dr-pipeline/src/mask.rs#L1092), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1403`](../ui/dr-ui/src/develop.rs#L1403), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | +| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1403`](../ui/dr-ui/src/develop.rs#L1403), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | | FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365) | -| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:1092`](../core/dr-pipeline/src/mask.rs#L1092), [`ui/dr-ui/src/develop.rs:5903`](../ui/dr-ui/src/develop.rs#L5903) | +| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`ui/dr-ui/src/develop.rs:5903`](../ui/dr-ui/src/develop.rs#L5903) | | FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:617`](../core/dr-pipeline/src/graph.rs#L617), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | -| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1495`](../core/dr-pipeline/src/operation.rs#L1495), [`core/dr-pipeline/src/operation.rs:1576`](../core/dr-pipeline/src/operation.rs#L1576), [`core/dr-pipeline/src/operation.rs:1601`](../core/dr-pipeline/src/operation.rs#L1601), [`core/dr-pipeline/src/operation.rs:1616`](../core/dr-pipeline/src/operation.rs#L1616), [`core/dr-pipeline/src/operation.rs:1640`](../core/dr-pipeline/src/operation.rs#L1640), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:440`](../core/dr-pipeline/src/operation.rs#L440), [`core/dr-pipeline/src/operation.rs:450`](../core/dr-pipeline/src/operation.rs#L450), [`core/dr-pipeline/src/operation.rs:600`](../core/dr-pipeline/src/operation.rs#L600) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:125`](../core/dr-pipeline/src/graph.rs#L125), [`core/dr-pipeline/src/graph.rs:325`](../core/dr-pipeline/src/graph.rs#L325), [`core/dr-pipeline/src/operation.rs:1065`](../core/dr-pipeline/src/operation.rs#L1065), [`core/dr-pipeline/src/operation.rs:1094`](../core/dr-pipeline/src/operation.rs#L1094), [`core/dr-pipeline/src/operation.rs:1495`](../core/dr-pipeline/src/operation.rs#L1495), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3602`](../ui/dr-ui/src/develop.rs#L3602), [`ui/dr-ui/src/develop.rs:3619`](../ui/dr-ui/src/develop.rs#L3619), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:3669`](../ui/dr-ui/src/develop.rs#L3669), [`ui/dr-ui/src/develop.rs:3678`](../ui/dr-ui/src/develop.rs#L3678), [`ui/dr-ui/src/develop.rs:3781`](../ui/dr-ui/src/develop.rs#L3781), [`ui/dr-ui/src/develop.rs:4194`](../ui/dr-ui/src/develop.rs#L4194), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/lib.rs:2472`](../ui/dr-ui/src/lib.rs#L2472), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2647`](../ui/dr-ui/ui/app.slint#L2647), [`ui/dr-ui/ui/app.slint:712`](../ui/dr-ui/ui/app.slint#L712) | -| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1060`](../core/dr-pipeline/src/framing.rs#L1060), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop.rs:140`](../ui/dr-ui/src/develop.rs#L140), [`ui/dr-ui/src/develop.rs:2353`](../ui/dr-ui/src/develop.rs#L2353), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476) | +| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1529`](../core/dr-pipeline/src/operation.rs#L1529), [`core/dr-pipeline/src/operation.rs:1610`](../core/dr-pipeline/src/operation.rs#L1610), [`core/dr-pipeline/src/operation.rs:1635`](../core/dr-pipeline/src/operation.rs#L1635), [`core/dr-pipeline/src/operation.rs:1650`](../core/dr-pipeline/src/operation.rs#L1650), [`core/dr-pipeline/src/operation.rs:1674`](../core/dr-pipeline/src/operation.rs#L1674), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:440`](../core/dr-pipeline/src/operation.rs#L440), [`core/dr-pipeline/src/operation.rs:450`](../core/dr-pipeline/src/operation.rs#L450), [`core/dr-pipeline/src/operation.rs:600`](../core/dr-pipeline/src/operation.rs#L600) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:125`](../core/dr-pipeline/src/graph.rs#L125), [`core/dr-pipeline/src/graph.rs:325`](../core/dr-pipeline/src/graph.rs#L325), [`core/dr-pipeline/src/operation.rs:1065`](../core/dr-pipeline/src/operation.rs#L1065), [`core/dr-pipeline/src/operation.rs:1111`](../core/dr-pipeline/src/operation.rs#L1111), [`core/dr-pipeline/src/operation.rs:1529`](../core/dr-pipeline/src/operation.rs#L1529), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3602`](../ui/dr-ui/src/develop.rs#L3602), [`ui/dr-ui/src/develop.rs:3619`](../ui/dr-ui/src/develop.rs#L3619), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:3669`](../ui/dr-ui/src/develop.rs#L3669), [`ui/dr-ui/src/develop.rs:3678`](../ui/dr-ui/src/develop.rs#L3678), [`ui/dr-ui/src/develop.rs:3781`](../ui/dr-ui/src/develop.rs#L3781), [`ui/dr-ui/src/develop.rs:4194`](../ui/dr-ui/src/develop.rs#L4194), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/lib.rs:2472`](../ui/dr-ui/src/lib.rs#L2472), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2647`](../ui/dr-ui/ui/app.slint#L2647), [`ui/dr-ui/ui/app.slint:712`](../ui/dr-ui/ui/app.slint#L712) | +| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1067`](../core/dr-pipeline/src/framing.rs#L1067), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop.rs:140`](../ui/dr-ui/src/develop.rs#L140), [`ui/dr-ui/src/develop.rs:2353`](../ui/dr-ui/src/develop.rs#L2353), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:351`](../core/dr-gpu/src/lib.rs#L351), [`ui/dr-ui/ui/crop.slint:1`](../ui/dr-ui/ui/crop.slint#L1) | -| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/develop.rs:4239`](../ui/dr-ui/src/develop.rs#L4239), [`ui/dr-ui/src/develop.rs:4252`](../ui/dr-ui/src/develop.rs#L4252), [`ui/dr-ui/src/develop.rs:4264`](../ui/dr-ui/src/develop.rs#L4264), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/develop.rs:4316`](../ui/dr-ui/src/develop.rs#L4316), [`ui/dr-ui/src/develop.rs:4335`](../ui/dr-ui/src/develop.rs#L4335), [`ui/dr-ui/src/develop.rs:4351`](../ui/dr-ui/src/develop.rs#L4351), [`ui/dr-ui/src/develop.rs:738`](../ui/dr-ui/src/develop.rs#L738), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:217`](../ui/dr-ui/src/labels.rs#L217), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/develop.rs:4239`](../ui/dr-ui/src/develop.rs#L4239), [`ui/dr-ui/src/develop.rs:4252`](../ui/dr-ui/src/develop.rs#L4252), [`ui/dr-ui/src/develop.rs:4264`](../ui/dr-ui/src/develop.rs#L4264), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/develop.rs:4316`](../ui/dr-ui/src/develop.rs#L4316), [`ui/dr-ui/src/develop.rs:4335`](../ui/dr-ui/src/develop.rs#L4335), [`ui/dr-ui/src/develop.rs:4351`](../ui/dr-ui/src/develop.rs#L4351), [`ui/dr-ui/src/develop.rs:738`](../ui/dr-ui/src/develop.rs#L738), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:225`](../ui/dr-ui/src/labels.rs#L225), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | | FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:4151`](../ui/dr-ui/src/develop.rs#L4151), [`ui/dr-ui/src/develop.rs:4172`](../ui/dr-ui/src/develop.rs#L4172), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2283`](../ui/dr-ui/src/lib.rs#L2283), [`ui/dr-ui/src/lib.rs:2312`](../ui/dr-ui/src/lib.rs#L2312), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:3185`](../ui/dr-ui/src/library_ui.rs#L3185), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:664`](../ui/dr-ui/src/presets.rs#L664), [`ui/dr-ui/src/presets.rs:698`](../ui/dr-ui/src/presets.rs#L698), [`ui/dr-ui/ui/adjust.slint:740`](../ui/dr-ui/ui/adjust.slint#L740), [`ui/dr-ui/ui/adjust.slint:767`](../ui/dr-ui/ui/adjust.slint#L767), [`ui/dr-ui/ui/adjust.slint:801`](../ui/dr-ui/ui/adjust.slint#L801), [`ui/dr-ui/ui/app.slint:1599`](../ui/dr-ui/ui/app.slint#L1599), [`ui/dr-ui/ui/app.slint:2736`](../ui/dr-ui/ui/app.slint#L2736), [`ui/dr-ui/ui/app.slint:784`](../ui/dr-ui/ui/app.slint#L784), [`ui/dr-ui/ui/app.slint:795`](../ui/dr-ui/ui/app.slint#L795), [`ui/dr-ui/ui/library.slint:1564`](../ui/dr-ui/ui/library.slint#L1564), [`ui/dr-ui/ui/library.slint:1570`](../ui/dr-ui/ui/library.slint#L1570), [`ui/dr-ui/ui/library.slint:4117`](../ui/dr-ui/ui/library.slint#L4117), [`ui/dr-ui/ui/library.slint:4132`](../ui/dr-ui/ui/library.slint#L4132), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | | FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | | FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:192`](../core/dr-pipeline/src/graph.rs#L192), [`core/dr-pipeline/src/graph.rs:686`](../core/dr-pipeline/src/graph.rs#L686), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:554`](../core/dr-pipeline/src/operation.rs#L554), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:674`](../core/dr-pipeline/src/sidecar.rs#L674), [`core/dr-pipeline/src/sidecar.rs:810`](../core/dr-pipeline/src/sidecar.rs#L810), [`core/dr-pipeline/src/sidecar.rs:864`](../core/dr-pipeline/src/sidecar.rs#L864), [`core/dr-pipeline/src/sidecar.rs:894`](../core/dr-pipeline/src/sidecar.rs#L894), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2543`](../ui/dr-ui/src/develop.rs#L2543), [`ui/dr-ui/src/develop.rs:2608`](../ui/dr-ui/src/develop.rs#L2608), [`ui/dr-ui/src/develop.rs:2691`](../ui/dr-ui/src/develop.rs#L2691), [`ui/dr-ui/src/develop.rs:2705`](../ui/dr-ui/src/develop.rs#L2705), [`ui/dr-ui/src/develop.rs:825`](../ui/dr-ui/src/develop.rs#L825), [`ui/dr-ui/src/labels.rs:53`](../ui/dr-ui/src/labels.rs#L53), [`ui/dr-ui/src/lib.rs:1703`](../ui/dr-ui/src/lib.rs#L1703), [`ui/dr-ui/src/lib.rs:2786`](../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:394`](../ui/dr-ui/src/lib.rs#L394), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:832`](../ui/dr-ui/ui/adjust.slint#L832), [`ui/dr-ui/ui/app.slint:140`](../ui/dr-ui/ui/app.slint#L140), [`ui/dr-ui/ui/app.slint:1949`](../ui/dr-ui/ui/app.slint#L1949), [`ui/dr-ui/ui/app.slint:2124`](../ui/dr-ui/ui/app.slint#L2124), [`ui/dr-ui/ui/app.slint:2548`](../ui/dr-ui/ui/app.slint#L2548), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | @@ -112,7 +112,7 @@ _None._ | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:388`](../apps/darkroom-android/src/lib.rs#L388) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:999`](../ui/dr-ui/src/lib.rs#L999), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | -| FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:667`](../core/dr-pipeline/src/descriptor.rs#L667), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:416`](../core/dr-pipeline/tests/declared_parity.rs#L416) | +| FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:667`](../core/dr-pipeline/src/descriptor.rs#L667), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:417`](../core/dr-pipeline/tests/declared_parity.rs#L417) | | FR-PLG-2d | [`core/dr-pipeline/src/declared/decl.rs:112`](../core/dr-pipeline/src/declared/decl.rs#L112), [`core/dr-pipeline/src/declared/decl.rs:152`](../core/dr-pipeline/src/declared/decl.rs#L152), [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:420`](../core/dr-pipeline/src/declared/decl.rs#L420), [`core/dr-pipeline/src/declared/decl.rs:67`](../core/dr-pipeline/src/declared/decl.rs#L67), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:384`](../core/dr-pipeline/src/declared/mod.rs#L384), [`core/dr-pipeline/src/declared/mod.rs:403`](../core/dr-pipeline/src/declared/mod.rs#L403) | | FR-PLG-8 | [`core/dr-pipeline/src/sidecar.rs:1807`](../core/dr-pipeline/src/sidecar.rs#L1807), [`core/dr-pipeline/src/sidecar.rs:1840`](../core/dr-pipeline/src/sidecar.rs#L1840) | | FR-RAW-1 | [`core/dr-decode/src/lib.rs:243`](../core/dr-decode/src/lib.rs#L243), [`core/dr-types/src/lib.rs:132`](../core/dr-types/src/lib.rs#L132), [`core/dr-types/src/lib.rs:203`](../core/dr-types/src/lib.rs#L203) | diff --git a/ui/dr-ui/src/labels.rs b/ui/dr-ui/src/labels.rs index 28954b2..1610c89 100644 --- a/ui/dr-ui/src/labels.rs +++ b/ui/dr-ui/src/labels.rs @@ -146,6 +146,14 @@ fn catalogued(key: &str) -> Option<&'static str> { // see from there. "op.capture_sharpen" => "Sharpening", "op.framing" => "Crop & Rotate", + // "Lens Vignetting", not the "Vignetting" `derive` would produce, and + // the qualifier is doing real work. This operation corrects the corner + // falloff a lens imposed; the creative vignette that darkens corners on + // purpose is a different operation, belongs to `Effect` rather than + // `Optics`, and will want the plain word when it exists. Naming this + // one for what it corrects means the two can sit in the same panel + // without either having to be renamed to make room. + "op.vignetting" => "Lens Vignetting", // Parameters "param.temperature" => "Temperature", From a1165ef182d70fd114e79313c4cb4482ea862915 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 5 Sep 2026 15:00:58 +0200 Subject: [PATCH 3/5] Put the coordinate-domain lens corrections into the graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `lens.rs` has held a `Warp` trait, a composer and two implementations — distortion and lateral chromatic aberration — since they were written, and `compose_warps` was called by nothing outside its own tests. The corrections existed, were correct, and never touched a photograph. `EditGraph` now holds them, and `compose_full` emits them between the framing prologue and the fetch. Distortion first, then CA: each warp receives the position the previous one produced, and lateral CA is a magnification about the optical axis of the *undistorted* frame, so measured on a barrel-distorted one it would be fitted to a radius no profile describes. They reach the panel the way framing already does — through `capabilities`. That was the one open question and existing practice answered it: framing is also not an `Operation`, also has parameters a photographer sets, and also arrives through that list. Because `Preset::capture` walks the same list, the sidecar, the clipboard and the undo stack carry a warp's parameters with nothing registered anywhere, and no file under `ui/` names one (FR-DEV-3a). `state()` destructures `EditGraph` field by field precisely so that a new field cannot be forgotten, and it was not. Chromatic aberration is the only thing that samples per channel, and `splits_channels` is what keeps everything else from paying for it. Red and blue are fetched from positions green is not — green is the reference and never moves, so a wrong correction still leaves one channel sharp rather than softening all three. With no CA in the chain the single-fetch path is emitted instead. The interpolating sampler is now chosen by framing *or* an active warp. Asking framing alone would have nearest-neighboured a distortion correction on an unstraightened frame, and that aliasing reads as a bad profile rather than as a missing filter. The warps go in the geometry invalidation key rather than the colour one: they decide which source pixel a colour is read from, so a tile cached across a distortion change would keep drawing the previous correction. The pipeline cache needs nothing new — `hash_source` already covers the generated body, and uniform values never enter it, so arming a warp recompiles and dragging it does not. Both are asserted. Co-Authored-By: Claude Opus 5 (1M context) --- core/dr-gpu/examples/local.rs | 1 + core/dr-gpu/src/adjust.rs | 18 +- core/dr-gpu/tests/local_adjustments.rs | 1 + core/dr-gpu/tests/masked_outputs.rs | 3 + core/dr-pipeline/src/detail.rs | 1 + core/dr-pipeline/src/framing.rs | 2 +- core/dr-pipeline/src/graph.rs | 215 ++++++++++++++++++++++- core/dr-pipeline/src/operation.rs | 232 ++++++++++++++++++++++++- docs/traceability.md | 28 +-- ui/dr-ui/src/labels.rs | 6 + 10 files changed, 477 insertions(+), 30 deletions(-) diff --git a/core/dr-gpu/examples/local.rs b/core/dr-gpu/examples/local.rs index 3385ca4..d47fba3 100644 --- a/core/dr-gpu/examples/local.rs +++ b/core/dr-gpu/examples/local.rs @@ -335,6 +335,7 @@ fn render_stack( ColourSpace::Srgb, stack, &SpotSet::new(), + &[], ); adjust .render_masked(source, &shader, ow, oh, Some(array)) diff --git a/core/dr-gpu/src/adjust.rs b/core/dr-gpu/src/adjust.rs index 0a2d2e7..f94c437 100644 --- a/core/dr-gpu/src/adjust.rs +++ b/core/dr-gpu/src/adjust.rs @@ -1845,7 +1845,21 @@ mod tests { fused_blocks += usize::from(point); } - // The count the loop above accumulated, plus framing — which emits a + // The lens corrections, which are the third kind of block. They are + // not in `descriptors` — they rewrite coordinates rather than + // transform a colour, so they are not operations — and they run ahead + // of the fetch rather than in either stage the loop above sorts into. + let mut warp_blocks = 0; + for desc in g.warp_descriptors() { + let id = desc.id.0; + assert!( + shader.source.contains(&format!("---- warp: {id} ----")), + "{id} was armed above and did not reach the shader" + ); + warp_blocks += 1; + } + + // The counts the loops above accumulated, plus framing — which emits a // stage of its own rather than an operation block and is not in // `descriptors`. Asserted as well as the per-operation exclusive-or // because the two catch different faults: the XOR catches an operation @@ -1853,7 +1867,7 @@ mod tests { // in the chain asked for. assert_eq!( shader.source.matches("---- ").count(), - fused_blocks + 1, + fused_blocks + warp_blocks + 1, "the fused shader carries a block nothing in the chain asked for" ); assert!( diff --git a/core/dr-gpu/tests/local_adjustments.rs b/core/dr-gpu/tests/local_adjustments.rs index 35aa88c..c7dfb08 100644 --- a/core/dr-gpu/tests/local_adjustments.rs +++ b/core/dr-gpu/tests/local_adjustments.rs @@ -72,6 +72,7 @@ fn render_at( ColourSpace::Srgb, stack, &SpotSet::new(), + &[], ); let mut masks = MaskPass::new(ctx).expect("mask pass"); diff --git a/core/dr-gpu/tests/masked_outputs.rs b/core/dr-gpu/tests/masked_outputs.rs index 28e6e5d..7e2b2aa 100644 --- a/core/dr-gpu/tests/masked_outputs.rs +++ b/core/dr-gpu/tests/masked_outputs.rs @@ -90,6 +90,7 @@ fn render_at(ctx: &GpuContext, stack: &MaskStack, out: u32) -> Vec { ColourSpace::Srgb, stack, &SpotSet::new(), + &[], ); let mut adjust = AdjustPass::new(ctx); adjust @@ -108,6 +109,7 @@ fn render_unmasked(ctx: &GpuContext, stack: &MaskStack, out: u32) -> Vec { ColourSpace::Srgb, stack, &SpotSet::new(), + &[], ); let mut adjust = AdjustPass::new(ctx); adjust.render(&source, &shader, out, out).expect("render"); @@ -230,6 +232,7 @@ fn render_gradient(ctx: &GpuContext, stack: &MaskStack, w: u32, h: u32) -> Vec(0.5)"), + crate::operation::sample_source(false, false).contains("p / aspect + vec2(0.5)"), "the sampler's return to texture coordinates moved" ); } diff --git a/core/dr-pipeline/src/graph.rs b/core/dr-pipeline/src/graph.rs index 3a9a2e6..344ded0 100644 --- a/core/dr-pipeline/src/graph.rs +++ b/core/dr-pipeline/src/graph.rs @@ -120,6 +120,20 @@ pub struct EditGraph { /// *where* an adjustment applies, and a spot says where a piece of the /// photograph comes from. spots: SpotSet, + /// The lens corrections that rewrite coordinates: distortion and lateral + /// chromatic aberration (`docs/architecture.md` §5.2). + /// + /// Apart from `ops` for the fourth time, and this one is not about shape + /// but about direction. Every [`Operation`] is a function from colour to + /// colour, and these run *before* a colour exists: they decide which + /// source pixel is read, and CA decides it three times over. See + /// [`crate::lens`] for why that cannot be expressed as an operation. + /// + /// Beside [`Self::framing`] in every way that matters — the two compose + /// into one coordinate map, and neither can be applied without the other's + /// result — but held separately because framing also changes the output's + /// dimensions, which a warp never does. + warps: Vec>, } /// TRACES: FR-DEV-3f @@ -164,6 +178,16 @@ impl EditGraph { masks: Arc::new(MaskStack::new()), film: None, spots: SpotSet::new(), + // Distortion first, then CA, and the order is the correction's + // rather than a preference. Each warp receives the position the + // previous one produced, and lateral CA is a magnification about + // the optical axis of the *undistorted* frame — measured on a + // barrel-distorted one it would be fitted to a radius the lens + // profile does not describe. + warps: vec![ + Box::new(crate::ops::Distortion::new()), + Box::new(crate::ops::Aberration::new()), + ], } } @@ -248,6 +272,18 @@ impl EditGraph { self.ops.iter().map(|o| o.descriptor()).collect() } + /// Descriptors for the coordinate-domain lens corrections, in order. + /// + /// The counterpart to [`Self::descriptors`] and split from it for the same + /// reason framing is absent there: a warp emits its own block in the + /// generated shader rather than a colour fragment, so the codegen tests + /// that count `---- ` markers have to know which kind they are counting. + /// A UI wanting everything still reads [`Self::capabilities`], where all + /// three kinds arrive together and indistinguishably (FR-DEV-3a). + pub fn warp_descriptors(&self) -> Vec> { + self.warps.iter().map(|w| w.descriptor()).collect() + } + /// TRACES: FR-DEV-3a | FR-DEV-3c /// Everything a UI needs to build its controls. /// @@ -286,6 +322,39 @@ impl EditGraph { } }); + // The lens corrections first, matching where they sit in the shader: + // they rewrite the coordinate before any colour is fetched, so nothing + // below them can be judged until they are right. It also puts the + // three optical corrections together in the panel — these two and the + // vignetting node, which is an ordinary operation and arrives above + // through `ops`. + let warps = self.warps.iter().map(|w| { + let desc = w.descriptor(); + OpCapability { + id: desc.id, + label: desc.label, + active: w.is_active(), + params: desc + .params + .iter() + .map(|p| ParamCapability { + id: p.id, + label: p.label, + kind: p.kind.clone(), + default: p.default, + value: w.param(p.id), + facet: p.facet, + }) + .collect(), + // No preferred widget. A distortion amount and the two CA + // scales are ordinary scalars, and plain sliders — the + // fallback every frontend implements — are the right control + // for them. + presentation: None, + attributes: desc.attributes.clone(), + } + }); + // Framing last, matching where it sits in the pipeline: the crop is // decided after the image looks right, not before. let desc = self.framing.descriptor(); @@ -314,7 +383,7 @@ impl EditGraph { attributes: desc.attributes.clone(), }; - ops.chain(std::iter::once(framing)).collect() + warps.chain(ops).chain(std::iter::once(framing)).collect() } /// Set a parameter, clamping to the descriptor's declared range. @@ -362,6 +431,11 @@ impl EditGraph { // nothing to register (FR-DEV-3c). ops: _, framing: _, + // Reached through `capabilities` with the other two. A warp's + // parameters are ordinary scalars once they are in that list, so + // the sidecar, the clipboard and the undo stack carry them with + // nothing registered anywhere (FR-DEV-3c). + warps: _, masks, film, spots, @@ -440,6 +514,20 @@ impl EditGraph { return; } + // The warps, before the operations. Their ids cannot collide with an + // operation's — `ops/` and the warp list are disjoint by construction, + // and `declared_parity` asserts the chain is exactly what `ops/` + // declares — so the order is for readability rather than precedence. + if let Some(warp) = self.warps.iter_mut().find(|w| w.descriptor().id == op) { + let descriptor = warp.descriptor(); + let Some(desc) = descriptor.param(param) else { + log::warn!("unknown parameter {param} on {op}; ignoring"); + return; + }; + warp.set_param(param, desc.clamp(value)); + return; + } + let Some(operation) = self.ops.iter_mut().find(|o| o.descriptor().id == op) else { // A sidecar naming an operation this build does not have. The // rest of the edit must still apply. @@ -465,6 +553,9 @@ impl EditGraph { .param(param) .map(|_| self.framing.param(param)); } + if let Some(warp) = self.warps.iter().find(|w| w.descriptor().id == op) { + return warp.descriptor().param(param).map(|_| warp.param(param)); + } self.ops .iter() .find(|o| o.descriptor().id == op) @@ -478,6 +569,11 @@ impl EditGraph { op.set_param(p.id, p.default); } } + for warp in &mut self.warps { + for p in &warp.descriptor().params { + warp.set_param(p.id, p.default); + } + } self.framing.reset(); // Masks go too, and this is why `apply` can be a replacement rather // than an overlay: a sidecar with no mask blocks means an edit with no @@ -542,7 +638,14 @@ impl EditGraph { /// graph renders to the screen and to a file in the same breath, and the /// two want different answers. pub fn compose_for(&self, output: dr_types::ColourSpace) -> ComposedShader { - compose_full(&self.ops, &self.framing, output, &self.masks, &self.spots) + compose_full( + &self.ops, + &self.framing, + output, + &self.masks, + &self.spots, + &self.warps, + ) } /// TRACES: FR-DSP-1 @@ -637,6 +740,22 @@ impl EditGraph { u64::from(crate::operation::canonical_bits(self.framing.param(p.id))), ); } + // The warps belong to the geometry key, not the colour one: they decide + // which source pixel a colour is read from, so a cached *result* of + // this stage is wrong the moment one moves. Hashed by id as well as by + // value, so two warps swapping their amounts is not the same edit. + for warp in &self.warps { + let descriptor = warp.descriptor(); + geometry = hash_bytes(geometry, descriptor.id.0.as_bytes()); + for p in &descriptor.params { + geometry = hash_bytes(geometry, p.id.0.as_bytes()); + geometry = mix( + geometry, + u64::from(crate::operation::canonical_bits(warp.param(p.id))), + ); + } + } + // The view rect is not a parameter and not in the structure key — it // is not an edit (see `Framing::view`). It is still an input to every // rendered pixel, so a cache that ignored it would show the wrong part @@ -823,20 +942,106 @@ mod tests { assert_ne!(first.uniforms, second.uniforms); } + /// The whole point of putting the warps in `capabilities`: everything that + /// walks that list carries them, with nothing registered anywhere. + #[test] + fn a_warp_is_carried_by_the_machinery_it_never_told_about_itself() { + use crate::ops::{aberration, distortion}; + + let mut g = EditGraph::default_chain(); + g.set_param(distortion::ID, distortion::AMOUNT, 40.0); + g.set_param(aberration::ID, aberration::RED, 25.0); + + assert_eq!(g.param(distortion::ID, distortion::AMOUNT), Some(40.0)); + assert_eq!(g.param(aberration::ID, aberration::RED), Some(25.0)); + + // Through the same capture/apply the sidecar, the clipboard and the + // undo stack all use. + let state = g.state(); + let mut restored = EditGraph::default_chain(); + // No film in this graph, so nothing is owed; the result is asserted + // rather than dropped because ignoring it elsewhere would leave a + // photograph rendering without its stock. + assert_eq!(restored.set_state(&state), FilmRebake::NotNeeded); + + assert_eq!( + restored.param(distortion::ID, distortion::AMOUNT), + Some(40.0), + "a distortion correction did not survive a state round trip, so \ + reopening the photograph would silently drop it" + ); + assert_eq!(restored.param(aberration::ID, aberration::RED), Some(25.0)); + } + + /// A warp is an edit, so `reset` has to reach it. It did not until the + /// loop was added: a reset that left the lens corrections standing would + /// mean "back to the file as it is" quietly did not mean that. + #[test] + fn resetting_the_graph_neutralises_the_warps() { + use crate::ops::distortion; + + let mut g = EditGraph::default_chain(); + g.set_param(distortion::ID, distortion::AMOUNT, 40.0); + g.reset(); + + assert_eq!(g.param(distortion::ID, distortion::AMOUNT), Some(0.0)); + } + + /// The warps belong to the geometry key, not the colour one. + /// + /// A tile cache keyed on geometry holds the *result* of the coordinate + /// stage. Moving a distortion slider changes which source pixel every + /// output pixel reads, so a cache that did not notice would keep drawing + /// the previous correction — visibly, and only where it had already + /// cached. + #[test] + fn a_warp_moves_the_geometry_key_and_leaves_the_others_alone() { + use crate::operation::Affects; + use crate::ops::distortion; + + let mut g = EditGraph::default_chain(); + let before = g.invalidation(); + g.set_param(distortion::ID, distortion::AMOUNT, 40.0); + let after = g.invalidation(); + + assert_ne!( + before.of(Affects::Geometry), + after.of(Affects::Geometry), + "a distortion change must invalidate the geometry stage" + ); + assert_eq!( + before.of(Affects::Colour), + after.of(Affects::Colour), + "and must not invalidate the colour stage, which it does not touch" + ); + } + #[test] fn capabilities_describe_every_operation_and_parameter() { // The UI builds its whole panel from this. Anything missing here is // something the UI would have to hardcode. let g = EditGraph::default_chain(); let caps = g.capabilities(); - // Every operation, plus framing — which is not an operation and so - // is absent from `descriptors`, but must still reach the panel. - assert_eq!(caps.len(), g.descriptors().len() + 1); + // Every operation, plus everything that is *not* an operation and so + // is absent from `descriptors`: the framing, and the coordinate-domain + // lens corrections. All of them have parameters a photographer sets, + // so all of them have to reach the panel — and the panel is forbidden + // from naming any of them (FR-DEV-3a), which leaves this list as the + // only way they can arrive. + assert_eq!(caps.len(), g.descriptors().len() + g.warps.len() + 1); assert!( caps.iter().any(|c| c.id == crate::framing::ID), "framing must appear in the capability list, or the UI cannot \ build a crop control without naming it" ); + for warp in &g.warps { + let id = warp.descriptor().id; + assert!( + caps.iter().any(|c| c.id == id), + "{id} must appear in the capability list, or its correction is \ + in the shader with no control anywhere that can reach it" + ); + } for cap in &caps { assert!(!cap.params.is_empty(), "{} exposes no parameters", cap.id); diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index 0e58330..ed0d837 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -510,6 +510,11 @@ pub fn compose_with_framing( output, &MaskStack::new(), &crate::spot::SpotSet::new(), + // No lens corrections. This entry point exists for callers that have + // an operation chain and nothing else — the codegen tests, and the + // export path before it grew a graph — and a warp is not something a + // caller can hold without one. + &[], ) } @@ -531,7 +536,13 @@ pub fn compose_full( output: ColourSpace, masks: &MaskStack, spots: &crate::spot::SpotSet, + warps: &[Box], ) -> ComposedShader { + // The lens corrections, composed into one coordinate transform. Beside + // `framing` because they are the other half of the same stage: framing + // says which part of the source this output pixel comes from, and a warp + // says where the lens put it once it got there. + let warp = crate::lens::compose_warps(warps); // Active *point* operations. A neighbourhood operation is filtered out // here rather than asked for a fragment it cannot write: it reads pixels // it is not writing, so it belongs to the detail stage that runs after @@ -636,6 +647,19 @@ pub fn compose_full( ); uniform_values.extend_from_slice(&framing.uniforms()); + // The warps, immediately after framing and before any operation, matching + // where they sit in the shader. Slot order is emission order and nothing + // addresses a slot by number, so this only has to be consistent with + // itself — but keeping it in pipeline order is what makes the generated + // struct readable next to the generated body. + uniform_fields.push_str(&warp.uniform_fields); + uniform_values.extend_from_slice(&warp.uniforms); + for h in &warp.helpers { + if !helpers.iter().any(|existing| existing.name == h.name) { + helpers.push(*h); + } + } + for op in &active { let id = op.descriptor().id.0; let prefix = sanitise(id); @@ -702,17 +726,35 @@ pub fn compose_full( // The coordinate stage: output pixel -> source position -> colour. Emitted // ahead of the operation fragments, which receive the sampled `c`. - let prologue = format!( - "{}\n{}", - framing.wgsl_prologue(), - sample_source(framing.needs_interpolation()) - ); - let sampler_helper = if framing.needs_interpolation() { - BILINEAR_HELPER + // A warp puts an output pixel between source pixels exactly as a free + // angle does, so either one forces the interpolating sampler. Asking + // framing alone — which is what this did before the warps existed — would + // have nearest-neighboured a distortion correction on an unstraightened + // frame, and the aliasing would have looked like a bad profile. + let interpolate = framing.needs_interpolation() || warp.is_active(); + + // Declared ahead of the warp block, which assigns to them. They enter + // equal to `p` so that a chain mixing a splitting warp with a + // non-splitting one still carries every earlier correction into the red + // and blue paths — a distortion correction must move all three channels, + // and only the CA that follows it may move them apart. + let channel_positions = if warp.splits_channels { + "\n // Per-channel source positions, for the lateral CA correction.\n\ + \x20 var p_r = p;\n\ + \x20 var p_b = p;\n" } else { "" }; + let prologue = format!( + "{}{}{}\n{}", + framing.wgsl_prologue(), + channel_positions, + warp.body, + sample_source(interpolate, warp.splits_channels) + ); + let sampler_helper = if interpolate { BILINEAR_HELPER } else { "" }; + // The tail, and it is the whole of the difference between the two output // modes. Everything above — the prologue, the fragments, the mask layers, // the camera matrix — is emitted identically either way, so an operation @@ -1049,7 +1091,51 @@ fn encode_output(c: vec3) -> vec3 {{ /// Split out because it is the join between the coordinate stage and the /// colour stage, and because the choice it makes — an exact integer load, or /// a filtered sample — is the one thing the free-angle case changes. -pub(crate) fn sample_source(interpolate: bool) -> &'static str { +pub(crate) fn sample_source(interpolate: bool, splits_channels: bool) -> &'static str { + if splits_channels { + // Lateral chromatic aberration is a per-channel radial magnification, + // so red and blue are fetched from positions green is not — which is + // the whole reason a warp is not an `Operation`. By the time a colour + // reaches an operation the three channels have been sampled together + // and the divergence is gone. + // + // Green never moves. It is the reference the other two are scaled + // about, so a correction that is wrong still leaves one channel sharp + // rather than softening all three. + return " // Back to texture coordinates, once per channel. + let uv_src = p / aspect + vec2(0.5); + let uv_r = p_r / aspect + vec2(0.5); + let uv_b = p_b / aspect + vec2(0.5); + + // Tested on green alone, not on all three. + // + // The three positions differ by a fraction of a pixel at any correction a + // real lens needs, so testing each would only let the outermost row of the + // frame disagree with itself about whether it exists — which draws a + // coloured fringe along the edge, the exact artefact this is here to + // remove. `sample_bilinear` clamps its own texel indices, so red and blue + // land on the edge pixel rather than out of bounds. + if (any(uv_src < vec2(0.0)) || any(uv_src >= vec2(1.0))) { + textureStore(output, vec2(gid.xy), vec4(0.0, 0.0, 0.0, 1.0)); + return; + } + + // TRACES: FR-DEV-3f + // Where this pixel sits on the *source*, in source pixels. Green's + // position, since that is the one that did not move. + let source_px = uv_src * vec2(src_dims); + let radius = length(p) / (0.5 * length(aspect)); + + // Three fetches, one channel kept from each. Two thirds of the work is + // discarded, which is why `Warp::splits_channels` exists: with no CA in + // the chain the single-sample path below is emitted instead. + var c = vec3( + sample_bilinear(uv_r, src_dims).r, + sample_bilinear(uv_src, src_dims).g, + sample_bilinear(uv_b, src_dims).b, + ); +"; + } if interpolate { " // Back to texture coordinates. let uv_src = p / aspect + vec2(0.5); @@ -1427,6 +1513,136 @@ mod tests { } } + use crate::lens::Warp as _; + + /// A neutral warp list must leave the shader exactly as it was. + /// + /// The property the whole `is_active` filter exists for: an unedited + /// photograph keeps the integer `textureLoad` path, and pays nothing — + /// not a bilinear fetch, not a uniform slot, not a recompile — for + /// corrections nobody has asked for. + #[test] + fn warps_at_neutral_change_nothing_at_all() { + let ops = crate::ops::chain(); + let framing = Framing::new(); + + let without = compose_full( + &ops, + &framing, + ColourSpace::Srgb, + &MaskStack::new(), + &crate::spot::SpotSet::new(), + &[], + ); + let with_neutral = compose_full( + &ops, + &framing, + ColourSpace::Srgb, + &MaskStack::new(), + &crate::spot::SpotSet::new(), + &[ + Box::new(crate::ops::Distortion::new()) as Box, + Box::new(crate::ops::Aberration::new()), + ], + ); + + assert_eq!(without.source, with_neutral.source); + assert_eq!(without.structure_hash, with_neutral.structure_hash); + assert_eq!(without.uniforms, with_neutral.uniforms); + assert!( + !without.source.contains("sample_bilinear"), + "an unwarped, unstraightened frame must keep the integer load path" + ); + } + + /// Distortion alone samples once; chromatic aberration samples three times. + /// + /// `splits_channels` is the whole reason for this test. Lateral CA fetches + /// red and blue from positions green is not, and paying that everywhere + /// would triple the texture bandwidth of the common case — a distortion + /// correction with no CA, which is most lens profiles. + #[test] + fn only_chromatic_aberration_splits_the_channels() { + let compose_with = |warps: Vec>| { + compose_full( + &crate::ops::chain(), + &Framing::new(), + ColourSpace::Srgb, + &MaskStack::new(), + &crate::spot::SpotSet::new(), + &warps, + ) + .source + }; + + let mut distortion = crate::ops::Distortion::new(); + distortion.set_param(crate::ops::distortion::AMOUNT, 40.0); + let only_distortion = compose_with(vec![Box::new(distortion)]); + + assert!( + only_distortion.contains("---- warp: distortion ----"), + "an active distortion must reach the shader" + ); + assert!( + only_distortion.contains("sample_bilinear"), + "a warp puts output pixels between source pixels, so it forces \ + the interpolating sampler even on an unstraightened frame" + ); + assert!( + !only_distortion.contains("var p_r"), + "distortion moves all three channels together and must not pay \ + for the per-channel path" + ); + + let mut ca = crate::ops::Aberration::new(); + ca.set_param(crate::ops::aberration::RED, 25.0); + let with_ca = compose_with(vec![Box::new(ca)]); + + assert!(with_ca.contains("var p_r"), "CA needs per-channel positions"); + assert_eq!( + with_ca.matches("sample_bilinear(").count(), + // Three fetches in the body, plus the helper's own definition. + 4, + "CA must fetch each channel from its own position" + ); + } + + /// An active warp is a different shader and must not reuse the cached one. + /// + /// Covered by `hash_source` rather than by anything warp-specific — the + /// body is written into the source — but asserted because the alternative + /// failure is silent: the correction would simply never appear, exactly as + /// a zoom did before `Framing::structure_key` gained its last bit. + #[test] + fn arming_a_warp_recompiles_but_moving_its_slider_does_not() { + let compose_at = |amount: f32| { + let mut d = crate::ops::Distortion::new(); + d.set_param(crate::ops::distortion::AMOUNT, amount); + compose_full( + &crate::ops::chain(), + &Framing::new(), + ColourSpace::Srgb, + &MaskStack::new(), + &crate::spot::SpotSet::new(), + &[Box::new(d) as Box], + ) + }; + + let neutral = compose_at(0.0); + let armed = compose_at(40.0); + let further = compose_at(70.0); + + assert_ne!( + neutral.structure_hash, armed.structure_hash, + "arming a warp adds a block to the shader and must recompile" + ); + assert_eq!( + armed.structure_hash, further.structure_hash, + "its magnitude is a uniform, so a drag must not recompile" + ); + assert_ne!(armed.uniforms, further.uniforms); + } + #[test] fn structure_hash_ignores_values_but_tracks_the_op_set() { // The property the shader cache depends on: moving a slider must not diff --git a/docs/traceability.md b/docs/traceability.md index 29df6eb..3bd7515 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 335 | -| TRACES tags found | 1086 | +| TRACES tags found | 1087 | | Requirements defined | 180 | | Requirements covered | 127 | | **Coverage** | **70.6%** (127/180) | @@ -45,7 +45,7 @@ _None._ | FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1171`](../core/dr-catalog/src/schema.rs#L1171), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:137`](../core/dr-pipeline/src/sidecar.rs#L137), [`ui/dr-ui/src/collections_ui.rs:1579`](../ui/dr-ui/src/collections_ui.rs#L1579), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1734`](../ui/dr-ui/src/collections_ui.rs#L1734), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4180`](../ui/dr-ui/src/library.rs#L4180), [`ui/dr-ui/src/library_ui.rs:713`](../ui/dr-ui/src/library_ui.rs#L713), [`ui/dr-ui/src/library_ui.rs:7677`](../ui/dr-ui/src/library_ui.rs#L7677), [`ui/dr-ui/src/library_ui.rs:7688`](../ui/dr-ui/src/library_ui.rs#L7688), [`ui/dr-ui/src/library_ui.rs:7701`](../ui/dr-ui/src/library_ui.rs#L7701), [`ui/dr-ui/src/library_ui.rs:7716`](../ui/dr-ui/src/library_ui.rs#L7716), [`ui/dr-ui/src/library_ui.rs:7725`](../ui/dr-ui/src/library_ui.rs#L7725), [`ui/dr-ui/ui/app.slint:349`](../ui/dr-ui/ui/app.slint#L349), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/library.slint:1350`](../ui/dr-ui/ui/library.slint#L1350), [`ui/dr-ui/ui/library.slint:1374`](../ui/dr-ui/ui/library.slint#L1374), [`ui/dr-ui/ui/library.slint:1427`](../ui/dr-ui/ui/library.slint#L1427), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101) | | FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4656`](../ui/dr-ui/src/library.rs#L4656), [`ui/dr-ui/src/library_ui.rs:389`](../ui/dr-ui/src/library_ui.rs#L389), [`ui/dr-ui/src/library_ui.rs:470`](../ui/dr-ui/src/library_ui.rs#L470), [`ui/dr-ui/src/library_ui.rs:6407`](../ui/dr-ui/src/library_ui.rs#L6407), [`ui/dr-ui/src/library_ui.rs:6460`](../ui/dr-ui/src/library_ui.rs#L6460), [`ui/dr-ui/src/library_ui.rs:7817`](../ui/dr-ui/src/library_ui.rs#L7817), [`ui/dr-ui/ui/app.slint:361`](../ui/dr-ui/ui/app.slint#L361), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/app.slint:874`](../ui/dr-ui/ui/app.slint#L874), [`ui/dr-ui/ui/library.slint:111`](../ui/dr-ui/ui/library.slint#L111), [`ui/dr-ui/ui/library.slint:1537`](../ui/dr-ui/ui/library.slint#L1537), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | | FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1073`](../ui/dr-ui/src/collections_ui.rs#L1073), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1598`](../ui/dr-ui/src/collections_ui.rs#L1598), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1913`](../ui/dr-ui/src/collections_ui.rs#L1913), [`ui/dr-ui/src/collections_ui.rs:1973`](../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:965`](../ui/dr-ui/src/collections_ui.rs#L965), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4461`](../ui/dr-ui/src/library.rs#L4461), [`ui/dr-ui/src/library.rs:4487`](../ui/dr-ui/src/library.rs#L4487), [`ui/dr-ui/src/library.rs:4542`](../ui/dr-ui/src/library.rs#L4542), [`ui/dr-ui/src/library_ui.rs:2686`](../ui/dr-ui/src/library_ui.rs#L2686), [`ui/dr-ui/src/library_ui.rs:4083`](../ui/dr-ui/src/library_ui.rs#L4083), [`ui/dr-ui/src/library_ui.rs:4834`](../ui/dr-ui/src/library_ui.rs#L4834), [`ui/dr-ui/ui/app.slint:352`](../ui/dr-ui/ui/app.slint#L352), [`ui/dr-ui/ui/app.slint:566`](../ui/dr-ui/ui/app.slint#L566), [`ui/dr-ui/ui/app.slint:595`](../ui/dr-ui/ui/app.slint#L595), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:1317`](../ui/dr-ui/ui/library.slint#L1317), [`ui/dr-ui/ui/library.slint:2886`](../ui/dr-ui/ui/library.slint#L2886), [`ui/dr-ui/ui/library.slint:4079`](../ui/dr-ui/ui/library.slint#L4079), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993) | -| FR-CAT-8 | [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4186`](../ui/dr-ui/src/develop.rs#L4186), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:4992`](../ui/dr-ui/src/develop.rs#L4992), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/export.rs:799`](../ui/dr-ui/src/export.rs#L799), [`ui/dr-ui/src/lib.rs:1082`](../ui/dr-ui/src/lib.rs#L1082), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2088`](../ui/dr-ui/src/lib.rs#L2088), [`ui/dr-ui/src/lib.rs:2223`](../ui/dr-ui/src/lib.rs#L2223), [`ui/dr-ui/src/lib.rs:634`](../ui/dr-ui/src/lib.rs#L634), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:6043`](../ui/dr-ui/src/library_ui.rs#L6043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-8 | [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:415`](../core/dr-pipeline/src/graph.rs#L415), [`core/dr-pipeline/src/graph.rs:459`](../core/dr-pipeline/src/graph.rs#L459), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4186`](../ui/dr-ui/src/develop.rs#L4186), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:4992`](../ui/dr-ui/src/develop.rs#L4992), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/export.rs:799`](../ui/dr-ui/src/export.rs#L799), [`ui/dr-ui/src/lib.rs:1082`](../ui/dr-ui/src/lib.rs#L1082), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2088`](../ui/dr-ui/src/lib.rs#L2088), [`ui/dr-ui/src/lib.rs:2223`](../ui/dr-ui/src/lib.rs#L2223), [`ui/dr-ui/src/lib.rs:634`](../ui/dr-ui/src/lib.rs#L634), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:6043`](../ui/dr-ui/src/library_ui.rs#L6043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop.rs:3544`](../ui/dr-ui/src/develop.rs#L3544), [`ui/dr-ui/src/develop.rs:3587`](../ui/dr-ui/src/develop.rs#L3587), [`ui/dr-ui/src/library.rs:1242`](../ui/dr-ui/src/library.rs#L1242), [`ui/dr-ui/src/library.rs:1295`](../ui/dr-ui/src/library.rs#L1295), [`ui/dr-ui/src/library.rs:1326`](../ui/dr-ui/src/library.rs#L1326), [`ui/dr-ui/src/library.rs:1431`](../ui/dr-ui/src/library.rs#L1431), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1883`](../ui/dr-ui/src/library.rs#L1883), [`ui/dr-ui/src/library.rs:1959`](../ui/dr-ui/src/library.rs#L1959), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4764`](../ui/dr-ui/src/library.rs#L4764), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1318`](../ui/dr-ui/src/library_ui.rs#L1318), [`ui/dr-ui/src/library_ui.rs:1909`](../ui/dr-ui/src/library_ui.rs#L1909), [`ui/dr-ui/src/library_ui.rs:1953`](../ui/dr-ui/src/library_ui.rs#L1953), [`ui/dr-ui/src/library_ui.rs:1969`](../ui/dr-ui/src/library_ui.rs#L1969), [`ui/dr-ui/src/library_ui.rs:2063`](../ui/dr-ui/src/library_ui.rs#L2063), [`ui/dr-ui/src/library_ui.rs:284`](../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:2925`](../ui/dr-ui/src/library_ui.rs#L2925), [`ui/dr-ui/src/library_ui.rs:317`](../ui/dr-ui/src/library_ui.rs#L317), [`ui/dr-ui/src/library_ui.rs:325`](../ui/dr-ui/src/library_ui.rs#L325), [`ui/dr-ui/src/library_ui.rs:3363`](../ui/dr-ui/src/library_ui.rs#L3363), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:3863`](../ui/dr-ui/src/library_ui.rs#L3863), [`ui/dr-ui/src/library_ui.rs:3951`](../ui/dr-ui/src/library_ui.rs#L3951), [`ui/dr-ui/src/library_ui.rs:4134`](../ui/dr-ui/src/library_ui.rs#L4134), [`ui/dr-ui/src/library_ui.rs:4248`](../ui/dr-ui/src/library_ui.rs#L4248), [`ui/dr-ui/src/library_ui.rs:523`](../ui/dr-ui/src/library_ui.rs#L523), [`ui/dr-ui/src/library_ui.rs:586`](../ui/dr-ui/src/library_ui.rs#L586), [`ui/dr-ui/src/library_ui.rs:6505`](../ui/dr-ui/src/library_ui.rs#L6505), [`ui/dr-ui/src/library_ui.rs:6620`](../ui/dr-ui/src/library_ui.rs#L6620), [`ui/dr-ui/src/presets.rs:453`](../ui/dr-ui/src/presets.rs#L453), [`ui/dr-ui/src/presets.rs:465`](../ui/dr-ui/src/presets.rs#L465), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | | FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:376`](../core/dr-catalog/src/schema.rs#L376), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:121`](../ui/dr-ui/src/develop.rs#L121), [`ui/dr-ui/src/develop.rs:130`](../ui/dr-ui/src/develop.rs#L130), [`ui/dr-ui/src/develop.rs:196`](../ui/dr-ui/src/develop.rs#L196), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:689`](../ui/dr-ui/src/develop.rs#L689), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2195`](../ui/dr-ui/src/lib.rs#L2195), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | @@ -58,28 +58,28 @@ _None._ | FR-CULL-8 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:487`](../core/dr-catalog/src/schema.rs#L487), [`core/dr-catalog/src/schema.rs:513`](../core/dr-catalog/src/schema.rs#L513), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/align.rs:291`](../core/dr-face/src/align.rs#L291), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:330`](../ui/dr-ui/src/faces.rs#L330), [`ui/dr-ui/src/faces.rs:346`](../ui/dr-ui/src/faces.rs#L346), [`ui/dr-ui/src/identity_ui.rs:540`](../ui/dr-ui/src/identity_ui.rs#L540), [`ui/dr-ui/src/lib.rs:1256`](../ui/dr-ui/src/lib.rs#L1256), [`ui/dr-ui/src/library.rs:2985`](../ui/dr-ui/src/library.rs#L2985), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077), [`ui/dr-ui/src/library.rs:3298`](../ui/dr-ui/src/library.rs#L3298), [`ui/dr-ui/src/library.rs:3434`](../ui/dr-ui/src/library.rs#L3434), [`ui/dr-ui/src/library.rs:3470`](../ui/dr-ui/src/library.rs#L3470), [`ui/dr-ui/ui/settings.slint:419`](../ui/dr-ui/ui/settings.slint#L419), [`ui/dr-ui/ui/settings.slint:82`](../ui/dr-ui/ui/settings.slint#L82) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/ui/identity.slint:283`](../ui/dr-ui/ui/identity.slint#L283) | | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../core/dr-pipeline/src/sidecar.rs#L1) | -| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389) | -| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:170`](../core/dr-pipeline/src/graph.rs#L170), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/mask.rs:151`](../core/dr-pipeline/src/mask.rs#L151), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:516`](../core/dr-pipeline/src/operation.rs#L516), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:1774`](../core/dr-pipeline/src/sidecar.rs#L1774), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:850`](../core/dr-segment/src/refine.rs#L850), [`ui/dr-ui/src/develop.rs:103`](../ui/dr-ui/src/develop.rs#L103), [`ui/dr-ui/src/develop.rs:1506`](../ui/dr-ui/src/develop.rs#L1506), [`ui/dr-ui/src/develop.rs:165`](../ui/dr-ui/src/develop.rs#L165), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:2136`](../ui/dr-ui/src/develop.rs#L2136), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:2168`](../ui/dr-ui/src/develop.rs#L2168), [`ui/dr-ui/src/develop.rs:2190`](../ui/dr-ui/src/develop.rs#L2190), [`ui/dr-ui/src/develop.rs:2336`](../ui/dr-ui/src/develop.rs#L2336), [`ui/dr-ui/src/develop.rs:2434`](../ui/dr-ui/src/develop.rs#L2434), [`ui/dr-ui/src/develop.rs:328`](../ui/dr-ui/src/develop.rs#L328), [`ui/dr-ui/src/develop.rs:365`](../ui/dr-ui/src/develop.rs#L365), [`ui/dr-ui/src/develop.rs:3855`](../ui/dr-ui/src/develop.rs#L3855), [`ui/dr-ui/src/develop.rs:3885`](../ui/dr-ui/src/develop.rs#L3885), [`ui/dr-ui/src/develop.rs:3951`](../ui/dr-ui/src/develop.rs#L3951), [`ui/dr-ui/src/develop.rs:3965`](../ui/dr-ui/src/develop.rs#L3965), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4817`](../ui/dr-ui/src/develop.rs#L4817), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/develop.rs:5274`](../ui/dr-ui/src/develop.rs#L5274), [`ui/dr-ui/src/develop.rs:5324`](../ui/dr-ui/src/develop.rs#L5324), [`ui/dr-ui/src/develop.rs:588`](../ui/dr-ui/src/develop.rs#L588), [`ui/dr-ui/src/develop.rs:649`](../ui/dr-ui/src/develop.rs#L649), [`ui/dr-ui/src/develop.rs:795`](../ui/dr-ui/src/develop.rs#L795), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/develop.rs:871`](../ui/dr-ui/src/develop.rs#L871), [`ui/dr-ui/src/lib.rs:1696`](../ui/dr-ui/src/lib.rs#L1696), [`ui/dr-ui/src/lib.rs:2498`](../ui/dr-ui/src/lib.rs#L2498), [`ui/dr-ui/src/lib.rs:2694`](../ui/dr-ui/src/lib.rs#L2694), [`ui/dr-ui/src/lib.rs:2866`](../ui/dr-ui/src/lib.rs#L2866), [`ui/dr-ui/src/lib.rs:2930`](../ui/dr-ui/src/lib.rs#L2930), [`ui/dr-ui/src/lib.rs:2984`](../ui/dr-ui/src/lib.rs#L2984), [`ui/dr-ui/src/lib.rs:389`](../ui/dr-ui/src/lib.rs#L389), [`ui/dr-ui/src/lib.rs:432`](../ui/dr-ui/src/lib.rs#L432), [`ui/dr-ui/src/lib.rs:455`](../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/presets.rs:394`](../ui/dr-ui/src/presets.rs#L394), [`ui/dr-ui/src/segmentation.rs:116`](../ui/dr-ui/src/segmentation.rs#L116), [`ui/dr-ui/src/segmentation.rs:330`](../ui/dr-ui/src/segmentation.rs#L330), [`ui/dr-ui/src/segmentation.rs:346`](../ui/dr-ui/src/segmentation.rs#L346), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476), [`ui/dr-ui/src/segmentation.rs:504`](../ui/dr-ui/src/segmentation.rs#L504), [`ui/dr-ui/ui/adjust.slint:547`](../ui/dr-ui/ui/adjust.slint#L547), [`ui/dr-ui/ui/adjust.slint:666`](../ui/dr-ui/ui/adjust.slint#L666), [`ui/dr-ui/ui/app.slint:150`](../ui/dr-ui/ui/app.slint#L150), [`ui/dr-ui/ui/app.slint:196`](../ui/dr-ui/ui/app.slint#L196), [`ui/dr-ui/ui/app.slint:2046`](../ui/dr-ui/ui/app.slint#L2046), [`ui/dr-ui/ui/app.slint:984`](../ui/dr-ui/ui/app.slint#L984), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | -| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1403`](../ui/dr-ui/src/develop.rs#L1403), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | +| FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403) | +| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:194`](../core/dr-pipeline/src/graph.rs#L194), [`core/dr-pipeline/src/graph.rs:681`](../core/dr-pipeline/src/graph.rs#L681), [`core/dr-pipeline/src/mask.rs:151`](../core/dr-pipeline/src/mask.rs#L151), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:535`](../core/dr-pipeline/src/operation.rs#L535), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:1774`](../core/dr-pipeline/src/sidecar.rs#L1774), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:850`](../core/dr-segment/src/refine.rs#L850), [`ui/dr-ui/src/develop.rs:103`](../ui/dr-ui/src/develop.rs#L103), [`ui/dr-ui/src/develop.rs:1506`](../ui/dr-ui/src/develop.rs#L1506), [`ui/dr-ui/src/develop.rs:165`](../ui/dr-ui/src/develop.rs#L165), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:2136`](../ui/dr-ui/src/develop.rs#L2136), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:2168`](../ui/dr-ui/src/develop.rs#L2168), [`ui/dr-ui/src/develop.rs:2190`](../ui/dr-ui/src/develop.rs#L2190), [`ui/dr-ui/src/develop.rs:2336`](../ui/dr-ui/src/develop.rs#L2336), [`ui/dr-ui/src/develop.rs:2434`](../ui/dr-ui/src/develop.rs#L2434), [`ui/dr-ui/src/develop.rs:328`](../ui/dr-ui/src/develop.rs#L328), [`ui/dr-ui/src/develop.rs:365`](../ui/dr-ui/src/develop.rs#L365), [`ui/dr-ui/src/develop.rs:3855`](../ui/dr-ui/src/develop.rs#L3855), [`ui/dr-ui/src/develop.rs:3885`](../ui/dr-ui/src/develop.rs#L3885), [`ui/dr-ui/src/develop.rs:3951`](../ui/dr-ui/src/develop.rs#L3951), [`ui/dr-ui/src/develop.rs:3965`](../ui/dr-ui/src/develop.rs#L3965), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4817`](../ui/dr-ui/src/develop.rs#L4817), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/develop.rs:5274`](../ui/dr-ui/src/develop.rs#L5274), [`ui/dr-ui/src/develop.rs:5324`](../ui/dr-ui/src/develop.rs#L5324), [`ui/dr-ui/src/develop.rs:588`](../ui/dr-ui/src/develop.rs#L588), [`ui/dr-ui/src/develop.rs:649`](../ui/dr-ui/src/develop.rs#L649), [`ui/dr-ui/src/develop.rs:795`](../ui/dr-ui/src/develop.rs#L795), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/develop.rs:871`](../ui/dr-ui/src/develop.rs#L871), [`ui/dr-ui/src/lib.rs:1696`](../ui/dr-ui/src/lib.rs#L1696), [`ui/dr-ui/src/lib.rs:2498`](../ui/dr-ui/src/lib.rs#L2498), [`ui/dr-ui/src/lib.rs:2694`](../ui/dr-ui/src/lib.rs#L2694), [`ui/dr-ui/src/lib.rs:2866`](../ui/dr-ui/src/lib.rs#L2866), [`ui/dr-ui/src/lib.rs:2930`](../ui/dr-ui/src/lib.rs#L2930), [`ui/dr-ui/src/lib.rs:2984`](../ui/dr-ui/src/lib.rs#L2984), [`ui/dr-ui/src/lib.rs:389`](../ui/dr-ui/src/lib.rs#L389), [`ui/dr-ui/src/lib.rs:432`](../ui/dr-ui/src/lib.rs#L432), [`ui/dr-ui/src/lib.rs:455`](../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/presets.rs:394`](../ui/dr-ui/src/presets.rs#L394), [`ui/dr-ui/src/segmentation.rs:116`](../ui/dr-ui/src/segmentation.rs#L116), [`ui/dr-ui/src/segmentation.rs:330`](../ui/dr-ui/src/segmentation.rs#L330), [`ui/dr-ui/src/segmentation.rs:346`](../ui/dr-ui/src/segmentation.rs#L346), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476), [`ui/dr-ui/src/segmentation.rs:504`](../ui/dr-ui/src/segmentation.rs#L504), [`ui/dr-ui/ui/adjust.slint:547`](../ui/dr-ui/ui/adjust.slint#L547), [`ui/dr-ui/ui/adjust.slint:666`](../ui/dr-ui/ui/adjust.slint#L666), [`ui/dr-ui/ui/app.slint:150`](../ui/dr-ui/ui/app.slint#L150), [`ui/dr-ui/ui/app.slint:196`](../ui/dr-ui/ui/app.slint#L196), [`ui/dr-ui/ui/app.slint:2046`](../ui/dr-ui/ui/app.slint#L2046), [`ui/dr-ui/ui/app.slint:984`](../ui/dr-ui/ui/app.slint#L984), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | +| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:287`](../core/dr-pipeline/src/graph.rs#L287), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1403`](../ui/dr-ui/src/develop.rs#L1403), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | | FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365) | -| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`ui/dr-ui/src/develop.rs:5903`](../ui/dr-ui/src/develop.rs#L5903) | -| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:617`](../core/dr-pipeline/src/graph.rs#L617), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | -| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1529`](../core/dr-pipeline/src/operation.rs#L1529), [`core/dr-pipeline/src/operation.rs:1610`](../core/dr-pipeline/src/operation.rs#L1610), [`core/dr-pipeline/src/operation.rs:1635`](../core/dr-pipeline/src/operation.rs#L1635), [`core/dr-pipeline/src/operation.rs:1650`](../core/dr-pipeline/src/operation.rs#L1650), [`core/dr-pipeline/src/operation.rs:1674`](../core/dr-pipeline/src/operation.rs#L1674), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:440`](../core/dr-pipeline/src/operation.rs#L440), [`core/dr-pipeline/src/operation.rs:450`](../core/dr-pipeline/src/operation.rs#L450), [`core/dr-pipeline/src/operation.rs:600`](../core/dr-pipeline/src/operation.rs#L600) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:125`](../core/dr-pipeline/src/graph.rs#L125), [`core/dr-pipeline/src/graph.rs:325`](../core/dr-pipeline/src/graph.rs#L325), [`core/dr-pipeline/src/operation.rs:1065`](../core/dr-pipeline/src/operation.rs#L1065), [`core/dr-pipeline/src/operation.rs:1111`](../core/dr-pipeline/src/operation.rs#L1111), [`core/dr-pipeline/src/operation.rs:1529`](../core/dr-pipeline/src/operation.rs#L1529), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3602`](../ui/dr-ui/src/develop.rs#L3602), [`ui/dr-ui/src/develop.rs:3619`](../ui/dr-ui/src/develop.rs#L3619), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:3669`](../ui/dr-ui/src/develop.rs#L3669), [`ui/dr-ui/src/develop.rs:3678`](../ui/dr-ui/src/develop.rs#L3678), [`ui/dr-ui/src/develop.rs:3781`](../ui/dr-ui/src/develop.rs#L3781), [`ui/dr-ui/src/develop.rs:4194`](../ui/dr-ui/src/develop.rs#L4194), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/lib.rs:2472`](../ui/dr-ui/src/lib.rs#L2472), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2647`](../ui/dr-ui/ui/app.slint#L2647), [`ui/dr-ui/ui/app.slint:712`](../ui/dr-ui/ui/app.slint#L712) | +| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:287`](../core/dr-pipeline/src/graph.rs#L287), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`ui/dr-ui/src/develop.rs:5903`](../ui/dr-ui/src/develop.rs#L5903) | +| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:720`](../core/dr-pipeline/src/graph.rs#L720), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | +| FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1762`](../core/dr-pipeline/src/operation.rs#L1762), [`core/dr-pipeline/src/operation.rs:1843`](../core/dr-pipeline/src/operation.rs#L1843), [`core/dr-pipeline/src/operation.rs:1868`](../core/dr-pipeline/src/operation.rs#L1868), [`core/dr-pipeline/src/operation.rs:1883`](../core/dr-pipeline/src/operation.rs#L1883), [`core/dr-pipeline/src/operation.rs:1907`](../core/dr-pipeline/src/operation.rs#L1907), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:454`](../core/dr-pipeline/src/operation.rs#L454), [`core/dr-pipeline/src/operation.rs:464`](../core/dr-pipeline/src/operation.rs#L464), [`core/dr-pipeline/src/operation.rs:625`](../core/dr-pipeline/src/operation.rs#L625) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:139`](../core/dr-pipeline/src/graph.rs#L139), [`core/dr-pipeline/src/graph.rs:394`](../core/dr-pipeline/src/graph.rs#L394), [`core/dr-pipeline/src/operation.rs:1137`](../core/dr-pipeline/src/operation.rs#L1137), [`core/dr-pipeline/src/operation.rs:1165`](../core/dr-pipeline/src/operation.rs#L1165), [`core/dr-pipeline/src/operation.rs:1211`](../core/dr-pipeline/src/operation.rs#L1211), [`core/dr-pipeline/src/operation.rs:1762`](../core/dr-pipeline/src/operation.rs#L1762), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3602`](../ui/dr-ui/src/develop.rs#L3602), [`ui/dr-ui/src/develop.rs:3619`](../ui/dr-ui/src/develop.rs#L3619), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:3669`](../ui/dr-ui/src/develop.rs#L3669), [`ui/dr-ui/src/develop.rs:3678`](../ui/dr-ui/src/develop.rs#L3678), [`ui/dr-ui/src/develop.rs:3781`](../ui/dr-ui/src/develop.rs#L3781), [`ui/dr-ui/src/develop.rs:4194`](../ui/dr-ui/src/develop.rs#L4194), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/lib.rs:2472`](../ui/dr-ui/src/lib.rs#L2472), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2647`](../ui/dr-ui/ui/app.slint#L2647), [`ui/dr-ui/ui/app.slint:712`](../ui/dr-ui/ui/app.slint#L712) | | FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1067`](../core/dr-pipeline/src/framing.rs#L1067), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop.rs:140`](../ui/dr-ui/src/develop.rs#L140), [`ui/dr-ui/src/develop.rs:2353`](../ui/dr-ui/src/develop.rs#L2353), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:351`](../core/dr-gpu/src/lib.rs#L351), [`ui/dr-ui/ui/crop.slint:1`](../ui/dr-ui/ui/crop.slint#L1) | -| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/develop.rs:4239`](../ui/dr-ui/src/develop.rs#L4239), [`ui/dr-ui/src/develop.rs:4252`](../ui/dr-ui/src/develop.rs#L4252), [`ui/dr-ui/src/develop.rs:4264`](../ui/dr-ui/src/develop.rs#L4264), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/develop.rs:4316`](../ui/dr-ui/src/develop.rs#L4316), [`ui/dr-ui/src/develop.rs:4335`](../ui/dr-ui/src/develop.rs#L4335), [`ui/dr-ui/src/develop.rs:4351`](../ui/dr-ui/src/develop.rs#L4351), [`ui/dr-ui/src/develop.rs:738`](../ui/dr-ui/src/develop.rs#L738), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:225`](../ui/dr-ui/src/labels.rs#L225), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:415`](../core/dr-pipeline/src/graph.rs#L415), [`core/dr-pipeline/src/graph.rs:459`](../core/dr-pipeline/src/graph.rs#L459), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/develop.rs:4239`](../ui/dr-ui/src/develop.rs#L4239), [`ui/dr-ui/src/develop.rs:4252`](../ui/dr-ui/src/develop.rs#L4252), [`ui/dr-ui/src/develop.rs:4264`](../ui/dr-ui/src/develop.rs#L4264), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/develop.rs:4316`](../ui/dr-ui/src/develop.rs#L4316), [`ui/dr-ui/src/develop.rs:4335`](../ui/dr-ui/src/develop.rs#L4335), [`ui/dr-ui/src/develop.rs:4351`](../ui/dr-ui/src/develop.rs#L4351), [`ui/dr-ui/src/develop.rs:738`](../ui/dr-ui/src/develop.rs#L738), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:231`](../ui/dr-ui/src/labels.rs#L231), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | | FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:4151`](../ui/dr-ui/src/develop.rs#L4151), [`ui/dr-ui/src/develop.rs:4172`](../ui/dr-ui/src/develop.rs#L4172), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2283`](../ui/dr-ui/src/lib.rs#L2283), [`ui/dr-ui/src/lib.rs:2312`](../ui/dr-ui/src/lib.rs#L2312), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:3185`](../ui/dr-ui/src/library_ui.rs#L3185), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:664`](../ui/dr-ui/src/presets.rs#L664), [`ui/dr-ui/src/presets.rs:698`](../ui/dr-ui/src/presets.rs#L698), [`ui/dr-ui/ui/adjust.slint:740`](../ui/dr-ui/ui/adjust.slint#L740), [`ui/dr-ui/ui/adjust.slint:767`](../ui/dr-ui/ui/adjust.slint#L767), [`ui/dr-ui/ui/adjust.slint:801`](../ui/dr-ui/ui/adjust.slint#L801), [`ui/dr-ui/ui/app.slint:1599`](../ui/dr-ui/ui/app.slint#L1599), [`ui/dr-ui/ui/app.slint:2736`](../ui/dr-ui/ui/app.slint#L2736), [`ui/dr-ui/ui/app.slint:784`](../ui/dr-ui/ui/app.slint#L784), [`ui/dr-ui/ui/app.slint:795`](../ui/dr-ui/ui/app.slint#L795), [`ui/dr-ui/ui/library.slint:1564`](../ui/dr-ui/ui/library.slint#L1564), [`ui/dr-ui/ui/library.slint:1570`](../ui/dr-ui/ui/library.slint#L1570), [`ui/dr-ui/ui/library.slint:4117`](../ui/dr-ui/ui/library.slint#L4117), [`ui/dr-ui/ui/library.slint:4132`](../ui/dr-ui/ui/library.slint#L4132), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | | FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:192`](../core/dr-pipeline/src/graph.rs#L192), [`core/dr-pipeline/src/graph.rs:686`](../core/dr-pipeline/src/graph.rs#L686), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:554`](../core/dr-pipeline/src/operation.rs#L554), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:674`](../core/dr-pipeline/src/sidecar.rs#L674), [`core/dr-pipeline/src/sidecar.rs:810`](../core/dr-pipeline/src/sidecar.rs#L810), [`core/dr-pipeline/src/sidecar.rs:864`](../core/dr-pipeline/src/sidecar.rs#L864), [`core/dr-pipeline/src/sidecar.rs:894`](../core/dr-pipeline/src/sidecar.rs#L894), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2543`](../ui/dr-ui/src/develop.rs#L2543), [`ui/dr-ui/src/develop.rs:2608`](../ui/dr-ui/src/develop.rs#L2608), [`ui/dr-ui/src/develop.rs:2691`](../ui/dr-ui/src/develop.rs#L2691), [`ui/dr-ui/src/develop.rs:2705`](../ui/dr-ui/src/develop.rs#L2705), [`ui/dr-ui/src/develop.rs:825`](../ui/dr-ui/src/develop.rs#L825), [`ui/dr-ui/src/labels.rs:53`](../ui/dr-ui/src/labels.rs#L53), [`ui/dr-ui/src/lib.rs:1703`](../ui/dr-ui/src/lib.rs#L1703), [`ui/dr-ui/src/lib.rs:2786`](../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:394`](../ui/dr-ui/src/lib.rs#L394), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:832`](../ui/dr-ui/ui/adjust.slint#L832), [`ui/dr-ui/ui/app.slint:140`](../ui/dr-ui/ui/app.slint#L140), [`ui/dr-ui/ui/app.slint:1949`](../ui/dr-ui/ui/app.slint#L1949), [`ui/dr-ui/ui/app.slint:2124`](../ui/dr-ui/ui/app.slint#L2124), [`ui/dr-ui/ui/app.slint:2548`](../ui/dr-ui/ui/app.slint#L2548), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | -| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2126`](../core/dr-gpu/src/adjust.rs#L2126), [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:2288`](../core/dr-gpu/src/adjust.rs#L2288), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:548`](../core/dr-pipeline/src/graph.rs#L548), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:3139`](../ui/dr-ui/src/develop.rs#L3139), [`ui/dr-ui/src/develop.rs:4606`](../ui/dr-ui/src/develop.rs#L4606), [`ui/dr-ui/src/develop.rs:5448`](../ui/dr-ui/src/develop.rs#L5448), [`ui/dr-ui/src/develop.rs:5482`](../ui/dr-ui/src/develop.rs#L5482), [`ui/dr-ui/src/lib.rs:890`](../ui/dr-ui/src/lib.rs#L890), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/lib.rs:949`](../ui/dr-ui/src/lib.rs#L949) | +| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:216`](../core/dr-pipeline/src/graph.rs#L216), [`core/dr-pipeline/src/graph.rs:805`](../core/dr-pipeline/src/graph.rs#L805), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:579`](../core/dr-pipeline/src/operation.rs#L579), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:674`](../core/dr-pipeline/src/sidecar.rs#L674), [`core/dr-pipeline/src/sidecar.rs:810`](../core/dr-pipeline/src/sidecar.rs#L810), [`core/dr-pipeline/src/sidecar.rs:864`](../core/dr-pipeline/src/sidecar.rs#L864), [`core/dr-pipeline/src/sidecar.rs:894`](../core/dr-pipeline/src/sidecar.rs#L894), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2543`](../ui/dr-ui/src/develop.rs#L2543), [`ui/dr-ui/src/develop.rs:2608`](../ui/dr-ui/src/develop.rs#L2608), [`ui/dr-ui/src/develop.rs:2691`](../ui/dr-ui/src/develop.rs#L2691), [`ui/dr-ui/src/develop.rs:2705`](../ui/dr-ui/src/develop.rs#L2705), [`ui/dr-ui/src/develop.rs:825`](../ui/dr-ui/src/develop.rs#L825), [`ui/dr-ui/src/labels.rs:53`](../ui/dr-ui/src/labels.rs#L53), [`ui/dr-ui/src/lib.rs:1703`](../ui/dr-ui/src/lib.rs#L1703), [`ui/dr-ui/src/lib.rs:2786`](../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:394`](../ui/dr-ui/src/lib.rs#L394), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:832`](../ui/dr-ui/ui/adjust.slint#L832), [`ui/dr-ui/ui/app.slint:140`](../ui/dr-ui/ui/app.slint#L140), [`ui/dr-ui/ui/app.slint:1949`](../ui/dr-ui/ui/app.slint#L1949), [`ui/dr-ui/ui/app.slint:2124`](../ui/dr-ui/ui/app.slint#L2124), [`ui/dr-ui/ui/app.slint:2548`](../ui/dr-ui/ui/app.slint#L2548), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | +| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2140`](../core/dr-gpu/src/adjust.rs#L2140), [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:2302`](../core/dr-gpu/src/adjust.rs#L2302), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:651`](../core/dr-pipeline/src/graph.rs#L651), [`core/dr-pipeline/src/graph.rs:681`](../core/dr-pipeline/src/graph.rs#L681), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:3139`](../ui/dr-ui/src/develop.rs#L3139), [`ui/dr-ui/src/develop.rs:4606`](../ui/dr-ui/src/develop.rs#L4606), [`ui/dr-ui/src/develop.rs:5448`](../ui/dr-ui/src/develop.rs#L5448), [`ui/dr-ui/src/develop.rs:5482`](../ui/dr-ui/src/develop.rs#L5482), [`ui/dr-ui/src/lib.rs:890`](../ui/dr-ui/src/lib.rs#L890), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/lib.rs:949`](../ui/dr-ui/src/lib.rs#L949) | | FR-DSP-3 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/local_contrast.rs:329`](../core/dr-gpu/tests/local_contrast.rs#L329), [`core/dr-pipeline/src/ops/local_contrast.rs:447`](../core/dr-pipeline/src/ops/local_contrast.rs#L447) | | FR-DSP-5 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/zoom_resolution.rs:135`](../core/dr-gpu/tests/zoom_resolution.rs#L135), [`core/dr-gpu/tests/zoom_resolution.rs:166`](../core/dr-gpu/tests/zoom_resolution.rs#L166), [`core/dr-gpu/tests/zoom_resolution.rs:1`](../core/dr-gpu/tests/zoom_resolution.rs#L1), [`core/dr-gpu/tests/zoom_resolution.rs:216`](../core/dr-gpu/tests/zoom_resolution.rs#L216) | -| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:483`](../core/dr-pipeline/src/operation.rs#L483), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/develop.rs:6876`](../ui/dr-ui/src/develop.rs#L6876), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3169`](../ui/dr-ui/src/lib.rs#L3169) | +| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/develop.rs:6876`](../ui/dr-ui/src/develop.rs#L6876), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3169`](../ui/dr-ui/src/lib.rs#L3169) | | FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3218`](../ui/dr-ui/src/develop.rs#L3218), [`ui/dr-ui/src/develop.rs:6550`](../ui/dr-ui/src/develop.rs#L6550), [`ui/dr-ui/src/develop.rs:6582`](../ui/dr-ui/src/develop.rs#L6582), [`ui/dr-ui/src/develop.rs:749`](../ui/dr-ui/src/develop.rs#L749), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1769`](../ui/dr-ui/src/lib.rs#L1769), [`ui/dr-ui/src/lib.rs:373`](../ui/dr-ui/src/lib.rs#L373), [`ui/dr-ui/ui/app.slint:74`](../ui/dr-ui/ui/app.slint#L74), [`ui/dr-ui/ui/histogram.slint:168`](../ui/dr-ui/ui/histogram.slint#L168), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | | FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:3115`](../ui/dr-ui/src/develop.rs#L3115), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/develop.rs:6876`](../ui/dr-ui/src/develop.rs#L6876), [`ui/dr-ui/src/develop.rs:6922`](../ui/dr-ui/src/develop.rs#L6922), [`ui/dr-ui/src/develop.rs:6941`](../ui/dr-ui/src/develop.rs#L6941), [`ui/dr-ui/src/develop.rs:856`](../ui/dr-ui/src/develop.rs#L856), [`ui/dr-ui/src/display_ui.rs:192`](../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1620`](../ui/dr-ui/src/lib.rs#L1620), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3082`](../ui/dr-ui/src/lib.rs#L3082), [`ui/dr-ui/src/lib.rs:3169`](../ui/dr-ui/src/lib.rs#L3169), [`ui/dr-ui/ui/app.slint:1797`](../ui/dr-ui/ui/app.slint#L1797), [`ui/dr-ui/ui/app.slint:53`](../ui/dr-ui/ui/app.slint#L53), [`ui/dr-ui/ui/settings.slint:124`](../ui/dr-ui/ui/settings.slint#L124), [`ui/dr-ui/ui/settings.slint:794`](../ui/dr-ui/ui/settings.slint#L794) | | FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2436`](../core/dr-gpu/src/adjust.rs#L2436), [`core/dr-pipeline/src/graph.rs:538`](../core/dr-pipeline/src/graph.rs#L538), [`core/dr-pipeline/src/graph.rs:592`](../core/dr-pipeline/src/graph.rs#L592), [`core/dr-pipeline/src/operation.rs:483`](../core/dr-pipeline/src/operation.rs#L483), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:713`](../core/dr-types/src/settings.rs#L713), [`ui/dr-ui/src/develop.rs:6941`](../ui/dr-ui/src/develop.rs#L6941), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | +| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2450`](../core/dr-gpu/src/adjust.rs#L2450), [`core/dr-pipeline/src/graph.rs:634`](../core/dr-pipeline/src/graph.rs#L634), [`core/dr-pipeline/src/graph.rs:695`](../core/dr-pipeline/src/graph.rs#L695), [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:713`](../core/dr-types/src/settings.rs#L713), [`ui/dr-ui/src/develop.rs:6941`](../ui/dr-ui/src/develop.rs#L6941), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-3 | [`core/dr-export/src/lib.rs:182`](../core/dr-export/src/lib.rs#L182), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:758`](../core/dr-types/src/settings.rs#L758), [`core/dr-types/src/settings.rs:766`](../core/dr-types/src/settings.rs#L766), [`core/dr-types/src/settings.rs:782`](../core/dr-types/src/settings.rs#L782), [`core/dr-types/src/settings.rs:851`](../core/dr-types/src/settings.rs#L851), [`core/dr-types/src/settings.rs:879`](../core/dr-types/src/settings.rs#L879), [`core/dr-types/src/settings.rs:890`](../core/dr-types/src/settings.rs#L890), [`core/dr-types/src/settings.rs:896`](../core/dr-types/src/settings.rs#L896), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:207`](../ui/dr-ui/src/settings_ui.rs#L207), [`ui/dr-ui/src/settings_ui.rs:220`](../ui/dr-ui/src/settings_ui.rs#L220), [`ui/dr-ui/src/settings_ui.rs:235`](../ui/dr-ui/src/settings_ui.rs#L235), [`ui/dr-ui/src/settings_ui.rs:565`](../ui/dr-ui/src/settings_ui.rs#L565), [`ui/dr-ui/ui/settings.slint:697`](../ui/dr-ui/ui/settings.slint#L697), [`ui/dr-ui/ui/settings.slint:707`](../ui/dr-ui/ui/settings.slint#L707) | | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | diff --git a/ui/dr-ui/src/labels.rs b/ui/dr-ui/src/labels.rs index 1610c89..5121fef 100644 --- a/ui/dr-ui/src/labels.rs +++ b/ui/dr-ui/src/labels.rs @@ -154,6 +154,12 @@ fn catalogued(key: &str) -> Option<&'static str> { // one for what it corrects means the two can sit in the same panel // without either having to be renamed to make room. "op.vignetting" => "Lens Vignetting", + // "Chromatic Aberration", not the "Aberration" `derive` would give. + // The bare word names a whole family of lens defects — coma, spherical, + // astigmatism — and this operation corrects exactly one of them. Under + // it sit sliders called "Red" and "Blue", which only make sense once + // the heading has said what is being separated. + "op.aberration" => "Chromatic Aberration", // Parameters "param.temperature" => "Temperature", From c4ddcbe0f7ab5faf571be5fd0993c8a4b00c50e9 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 5 Sep 2026 15:11:32 +0200 Subject: [PATCH 4/5] Look the lens up and say plainly whether one was found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dr-lens` has held a complete Lensfun lookup — distortion, TCA and vignetting coefficients from a lens name, a focal length and an aperture — with no dependents anywhere in the workspace. The three corrections it feeds now exist in the graph, so this connects the two and finishes the chain. The coefficient structs stay duplicated. `dr-pipeline` is organised around having no dependencies so its codegen is testable without a device or a database (ARCH §6.5a), and `dr-lens` carries an XML parser and 5.5 MB of profile data. Neither crate can convert to the other, so the conversion goes above both, in `develop.rs`, which is the only place that sees them together. Both traits grow the same defaulted door. The optical corrections do not sit on the same side of the fetch — distortion and CA rewrite coordinates and are `Warp`s, vignetting applies a gain to the pixel already there and is an ordinary node — and fanning a profile out by which trait each happens to implement would make the caller reason about that distinction. Each correction takes its own share of the whole profile instead, and `set_lens_profile` walks both lists identically. The lookup happens in `set_source_metadata` rather than in its caller, because that is the one place a session is told which file it came from. Doing it there makes it unforgettable, in the shape `FilmRebake` already uses for the other derived thing — and, more to the point, makes *clearing* unforgettable: a session that opened a second photograph while still holding the first one's profile would correct it for the wrong optics, invisibly, in a way that looks exactly like the lens. It needs the whole shot and not just a name. Distortion is interpolated across a zoom's focal range and vignetting depends strongly on aperture — a fast prime can be two stops down in the corners wide open and clean by f/8 — so a lookup missing either returns coefficients measured for a shot nobody took. Missing any of the three refuses rather than guesses. A profile is derived, not persisted: it comes from the file's EXIF and a database, so it is not a parameter, not in the sidecar and not undoable. What is an edit is the manual trim beside it, which each correction composes with the measurement — so a photographer can lean on it, override it, or work without one. `InfoPanel` gains a lens line, and it distinguishes three cases rather than two. `dr-lens` states the rule it exists for: an automatic correction that silently did nothing is worse than one the user can see is unavailable. A session with no header draws nothing, a header naming no lens reads "Lens not recorded", and a lens the database has never heard of reads "· no profile". Collapsing the last two would send somebody hunting for a profile that was never missing — which, for third-party and adapted glass, is the ordinary case. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 1 + core/dr-pipeline/src/graph.rs | 114 ++++++++++++ core/dr-pipeline/src/lens.rs | 40 +++++ core/dr-pipeline/src/lib.rs | 2 +- core/dr-pipeline/src/operation.rs | 19 +- core/dr-pipeline/src/ops/aberration.rs | 11 ++ core/dr-pipeline/src/ops/distortion.rs | 7 + core/dr-pipeline/src/ops/vignetting.rs | 7 + docs/traceability.md | 80 ++++----- ui/dr-ui/Cargo.toml | 6 + ui/dr-ui/src/develop.rs | 238 +++++++++++++++++++++++++ ui/dr-ui/src/lib.rs | 14 ++ ui/dr-ui/ui/app.slint | 7 + ui/dr-ui/ui/develop.slint | 21 +++ 14 files changed, 525 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f47e41c..eb8af4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,6 +1658,7 @@ dependencies = [ "dr-film", "dr-gpu", "dr-ingest", + "dr-lens", "dr-pipeline", "dr-plat", "dr-preset-xmp", diff --git a/core/dr-pipeline/src/graph.rs b/core/dr-pipeline/src/graph.rs index 344ded0..5ad986c 100644 --- a/core/dr-pipeline/src/graph.rs +++ b/core/dr-pipeline/src/graph.rs @@ -14,6 +14,7 @@ use crate::descriptor::{ Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamId, ParamKind, Presentation, }; use crate::framing::{CropRect, Framing}; +use crate::lens::LensProfile; use crate::mask::MaskStack; use crate::operation::{compose_full, ComposedShader, Operation}; use crate::ops; @@ -134,6 +135,19 @@ pub struct EditGraph { /// result — but held separately because framing also changes the output's /// dimensions, which a warp never does. warps: Vec>, + /// The lens profile the corrections above were given, if any. + /// + /// Kept as well as fanned out, because the corrections hold it in a form + /// nothing can read back: each has folded its own share of the profile + /// into private coefficients. Something has to be able to answer "is this + /// photograph corrected from a measurement, or by hand?" — the interface + /// is required to say so plainly rather than let an automatic correction + /// silently do nothing — and this is the only place that can. + /// + /// Not in [`EditState`], not in the sidecar, and not undoable: it is + /// derived from the file's EXIF and a database, exactly as the film's + /// baked tables are derived from a stock's id. + lens_profile: Option, } /// TRACES: FR-DEV-3f @@ -188,6 +202,7 @@ impl EditGraph { Box::new(crate::ops::Distortion::new()), Box::new(crate::ops::Aberration::new()), ], + lens_profile: None, } } @@ -272,6 +287,41 @@ impl EditGraph { self.ops.iter().map(|o| o.descriptor()).collect() } + /// TRACES: FR-DEV-3 + /// Apply a lens profile's measured coefficients to every correction that + /// wants some, or clear them all with `None`. + /// + /// **Derived state, not an edit.** A profile comes from the file's EXIF + /// plus a database this crate does not link, so it is not a parameter, is + /// not in the sidecar, and is not undoable. What *is* an edit is the manual + /// trim beside it: each correction composes the profile with its own + /// slider, so a photographer can lean on the measurement, override it, or + /// work without one. + /// + /// **Clearing matters as much as setting.** Opening a photograph from an + /// unrecognised lens must pass `None` rather than simply not calling this: + /// a graph reused across images would otherwise correct this frame for the + /// optics of the last one, which is both wrong and invisible. + /// + /// Fans out over the warps and the operations alike. Which trait a + /// correction implements is a fact about where it sits relative to the + /// fetch, and no business of the caller's — see + /// [`crate::Operation::set_lens_profile`]. + pub fn set_lens_profile(&mut self, profile: Option) { + self.lens_profile = profile; + for warp in &mut self.warps { + warp.set_profile(profile.as_ref()); + } + for op in &mut self.ops { + op.set_lens_profile(profile.as_ref()); + } + } + + /// The profile currently applied, if any. + pub fn lens_profile(&self) -> Option<&LensProfile> { + self.lens_profile.as_ref() + } + /// Descriptors for the coordinate-domain lens corrections, in order. /// /// The counterpart to [`Self::descriptors`] and split from it for the same @@ -436,6 +486,10 @@ impl EditGraph { // the sidecar, the clipboard and the undo stack carry them with // nothing registered anywhere (FR-DEV-3c). warps: _, + // Derived from the file and a database, so it is rebuilt on open + // rather than restored — the same reason the film's tables travel + // as an id and not as numbers. + lens_profile: _, masks, film, spots, @@ -973,6 +1027,66 @@ mod tests { assert_eq!(restored.param(aberration::ID, aberration::RED), Some(25.0)); } + /// A profile has to reach all three corrections, across both traits. + /// + /// The failure this guards is the quiet one: a profile that reached the + /// warps and not the vignetting node would correct the geometry and leave + /// the corners dark, which looks like an under-corrected lens rather than + /// like a wiring fault. + #[test] + fn a_lens_profile_reaches_every_correction_that_wants_one() { + use crate::lens::{LensProfile, Tca}; + use crate::ops::{aberration, distortion, vignetting}; + + let mut g = EditGraph::default_chain(); + for id in [distortion::ID, aberration::ID, vignetting::ID] { + assert_eq!( + g.param(id, ParamId("amount")).unwrap_or(0.0), + 0.0, + "{id} should start neutral" + ); + } + + g.set_lens_profile(Some(LensProfile { + distortion: Some(distortion::PtLens { + a: 0.0, + b: -0.012, + c: 0.0, + }), + tca: Some(Tca { + red_scale: 1.000_32, + blue_scale: 0.999_93, + }), + vignetting: Some(vignetting::Pa { + k1: -0.42, + k2: 0.05, + k3: 0.0, + }), + })); + + // Every correction is now doing something, with every slider still at + // its default — which is the whole point of a profile. + let source = g.compose().source; + for marker in [ + "---- warp: distortion ----", + "---- warp: aberration ----", + "---- vignetting ----", + ] { + assert!( + source.contains(marker), + "a profile did not reach {marker}: {source}" + ); + } + + // And clearing it puts the photograph back, which is what opening an + // image from an unrecognised lens has to do. + g.set_lens_profile(None); + let cleared = g.compose().source; + assert!(!cleared.contains("---- warp: ")); + assert!(!cleared.contains("---- vignetting ----")); + assert!(g.lens_profile().is_none()); + } + /// A warp is an edit, so `reset` has to reach it. It did not until the /// loop was added: a reset that left the lens corrections standing would /// mean "back to the file as it is" quietly did not mean that. diff --git a/core/dr-pipeline/src/lens.rs b/core/dr-pipeline/src/lens.rs index f545c1d..0c468e7 100644 --- a/core/dr-pipeline/src/lens.rs +++ b/core/dr-pipeline/src/lens.rs @@ -64,6 +64,32 @@ use std::sync::Arc; use crate::descriptor::{OpDescriptor, ParamId}; use crate::operation::{Helper, Uniform}; +/// Lateral chromatic aberration, as a per-channel radial scale. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Tca { + pub red_scale: f32, + pub blue_scale: f32, +} + +/// What a lens profile says about one shot, in the pipeline's own types. +/// +/// **Deliberately a mirror of `dr_lens::LensProfile` rather than that type +/// itself.** The dependency would have to run the wrong way: `dr-lens` carries +/// an XML parser and 5.5 MB of Lensfun data, and this crate is organised around +/// having no dependencies so that its codegen is testable without a GPU or a +/// database (ARCH §6.5a). Whatever sits above both does the conversion; it is +/// nine fields and a `match`. +/// +/// Every field is independently optional because the database is: it commonly +/// carries distortion for a lens and no vignetting, or covers only part of a +/// zoom. A partial profile is useful and must not be discarded wholesale. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct LensProfile { + pub distortion: Option, + pub tca: Option, + pub vignetting: Option, +} + /// A coordinate-domain operation, applied before the source is sampled. /// /// Object-safe for the same reason [`crate::operation::Operation`] is: the @@ -118,6 +144,20 @@ pub trait Warp: Send + Sync { fn helpers(&self) -> &'static [Helper] { &[] } + + /// Take whatever this warp needs from a lens profile. + /// + /// Handed the *whole* profile rather than its own slice of it, so that the + /// graph fanning one out does not have to know which correction wants + /// which coefficients — the same reason an operation is handed a + /// [`ParamId`] rather than a field. `None` clears any profile in place, + /// which is what opening a photograph from an unrecognised lens must do: + /// leaving the previous one standing would correct this frame for the + /// optics of the last one. + /// + /// Defaulted, because a warp need not be profile-driven. Nothing about the + /// coordinate stage requires a database behind it. + fn set_profile(&mut self, _profile: Option<&LensProfile>) {} } /// The composed geometry stage: WGSL, uniforms, and what it needs from the diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs index 68e2379..f3567fb 100644 --- a/core/dr-pipeline/src/lib.rs +++ b/core/dr-pipeline/src/lib.rs @@ -61,7 +61,7 @@ pub use detail::{ pub use framing::{CropRect, Framing}; pub use graph::{EditGraph, OpCapability, ParamCapability}; pub use history::{Edit, Entry as HistoryEntry, History, Step}; -pub use lens::{compose_warps, ComposedWarp, Warp}; +pub use lens::{compose_warps, ComposedWarp, LensProfile, Tca, Warp}; pub use operation::{ compose, compose_with_framing, Affects, ComposedShader, Helper, Invalidation, Operation, OutputMode, Uniform, BASE_CURVE_POINTS, BASE_CURVE_UNIFORM_OFFSET, RESERVED_UNIFORM_FIELDS, diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index ed0d837..03d2fc0 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -377,6 +377,20 @@ pub trait Operation: Send + Sync { fn presentation(&self) -> Option { None } + + /// Take whatever this operation needs from a lens profile. + /// + /// The counterpart of [`crate::lens::Warp::set_profile`], and it exists on + /// this trait as well because the optical corrections do not all live on + /// the same side of the fetch. Distortion and CA rewrite coordinates and + /// are warps; vignetting applies a gain to the pixel already there and is + /// an ordinary node. Splitting the fan-out by which trait a correction + /// happens to implement would make the caller reason about that, so both + /// traits carry the same door and `EditGraph::set_lens_profile` walks + /// both lists the same way. + /// + /// Defaulted: fourteen of the fifteen operations have nothing to take. + fn set_lens_profile(&mut self, _profile: Option<&crate::lens::LensProfile>) {} } /// A named WGSL helper function, deduplicated across operations. @@ -1598,7 +1612,10 @@ mod tests { ca.set_param(crate::ops::aberration::RED, 25.0); let with_ca = compose_with(vec![Box::new(ca)]); - assert!(with_ca.contains("var p_r"), "CA needs per-channel positions"); + assert!( + with_ca.contains("var p_r"), + "CA needs per-channel positions" + ); assert_eq!( with_ca.matches("sample_bilinear(").count(), // Three fetches in the body, plus the helper's own definition. diff --git a/core/dr-pipeline/src/ops/aberration.rs b/core/dr-pipeline/src/ops/aberration.rs index 0d6117e..eb9de6f 100644 --- a/core/dr-pipeline/src/ops/aberration.rs +++ b/core/dr-pipeline/src/ops/aberration.rs @@ -141,6 +141,17 @@ impl Warp for Aberration { r != 1.0 || b != 1.0 } + fn set_profile(&mut self, profile: Option<&crate::lens::LensProfile>) { + // The inherent `set_profile` taking just this correction's own + // coefficients, not this trait method: an inherent method wins over a + // trait one of the same name, so this is a narrowing and not a loop. + self.set_profile( + profile + .and_then(|p| p.tca) + .map(|t| (t.red_scale, t.blue_scale)), + ); + } + fn wgsl_body(&self) -> String { // `p_r` and `p_b` enter equal to `p` and are carried out of the block. // Green is deliberately absent: it is the reference and never moves. diff --git a/core/dr-pipeline/src/ops/distortion.rs b/core/dr-pipeline/src/ops/distortion.rs index c327e1c..6838f8a 100644 --- a/core/dr-pipeline/src/ops/distortion.rs +++ b/core/dr-pipeline/src/ops/distortion.rs @@ -136,6 +136,13 @@ impl Warp for Distortion { c.a != 0.0 || c.b != 0.0 || c.c != 0.0 } + fn set_profile(&mut self, profile: Option<&crate::lens::LensProfile>) { + // The inherent `set_profile` taking just this correction's own + // coefficients, not this trait method: an inherent method wins over a + // trait one of the same name, so this is a narrowing and not a loop. + self.set_profile(profile.and_then(|p| p.distortion)); + } + fn wgsl_body(&self) -> String { // Written against `p`, which is already normalised and centred. "\ diff --git a/core/dr-pipeline/src/ops/vignetting.rs b/core/dr-pipeline/src/ops/vignetting.rs index 7c6ffd7..821e6dd 100644 --- a/core/dr-pipeline/src/ops/vignetting.rs +++ b/core/dr-pipeline/src/ops/vignetting.rs @@ -133,6 +133,13 @@ impl Operation for Vignetting { c.k1 != 0.0 || c.k2 != 0.0 || c.k3 != 0.0 } + fn set_lens_profile(&mut self, profile: Option<&crate::lens::LensProfile>) { + // The inherent `set_profile` taking just this correction's own + // coefficients, not this trait method: an inherent method wins over a + // trait one of the same name, so this is a narrowing and not a loop. + self.set_profile(profile.and_then(|p| p.vignetting)); + } + fn wgsl_body(&self) -> String { // `radius` comes from the prologue: the pixel's distance from the // optical axis, normalised so the corner is 1. diff --git a/docs/traceability.md b/docs/traceability.md index 3bd7515..08b376a 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -10,7 +10,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 335 | -| TRACES tags found | 1087 | +| TRACES tags found | 1092 | | Requirements defined | 180 | | Requirements covered | 127 | | **Coverage** | **70.6%** (127/180) | @@ -40,75 +40,75 @@ _None._ | FR-CAT-15 | [`core/dr-catalog/src/schema.rs:753`](../core/dr-catalog/src/schema.rs#L753), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:457`](../core/dr-sync-nextcloud/src/lib.rs#L457), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:563`](../core/dr-sync/src/scan.rs#L563), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`core/dr-types/src/place.rs:91`](../core/dr-types/src/place.rs#L91), [`ui/dr-ui/src/collections_ui.rs:1283`](../ui/dr-ui/src/collections_ui.rs#L1283), [`ui/dr-ui/src/collections_ui.rs:2189`](../ui/dr-ui/src/collections_ui.rs#L2189), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:192`](../ui/dr-ui/src/library.rs#L192), [`ui/dr-ui/src/library.rs:241`](../ui/dr-ui/src/library.rs#L241), [`ui/dr-ui/src/library.rs:4132`](../ui/dr-ui/src/library.rs#L4132), [`ui/dr-ui/src/library.rs:4166`](../ui/dr-ui/src/library.rs#L4166), [`ui/dr-ui/src/library_ui.rs:238`](../ui/dr-ui/src/library_ui.rs#L238), [`ui/dr-ui/src/library_ui.rs:838`](../ui/dr-ui/src/library_ui.rs#L838), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:621`](../ui/dr-ui/ui/collections.slint#L621) | | FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:56`](../core/dr-types/src/lib.rs#L56), [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1), [`platform/dr-plat/src/storage.rs:216`](../platform/dr-plat/src/storage.rs#L216), [`platform/dr-plat/src/storage.rs:46`](../platform/dr-plat/src/storage.rs#L46) | | FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) | -| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488), [`ui/dr-ui/src/library.rs:2984`](../ui/dr-ui/src/library.rs#L2984), [`ui/dr-ui/src/library.rs:3575`](../ui/dr-ui/src/library.rs#L3575), [`ui/dr-ui/src/library_ui.rs:177`](../ui/dr-ui/src/library_ui.rs#L177), [`ui/dr-ui/src/library_ui.rs:4221`](../ui/dr-ui/src/library_ui.rs#L4221), [`ui/dr-ui/src/library_ui.rs:5720`](../ui/dr-ui/src/library_ui.rs#L5720), [`ui/dr-ui/ui/app.slint:428`](../ui/dr-ui/ui/app.slint#L428), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | +| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488), [`ui/dr-ui/src/library.rs:2984`](../ui/dr-ui/src/library.rs#L2984), [`ui/dr-ui/src/library.rs:3575`](../ui/dr-ui/src/library.rs#L3575), [`ui/dr-ui/src/library_ui.rs:177`](../ui/dr-ui/src/library_ui.rs#L177), [`ui/dr-ui/src/library_ui.rs:4221`](../ui/dr-ui/src/library_ui.rs#L4221), [`ui/dr-ui/src/library_ui.rs:5720`](../ui/dr-ui/src/library_ui.rs#L5720), [`ui/dr-ui/ui/app.slint:434`](../ui/dr-ui/ui/app.slint#L434), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | | FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:222`](../ui/dr-ui/src/library.rs#L222), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1) | -| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1171`](../core/dr-catalog/src/schema.rs#L1171), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:137`](../core/dr-pipeline/src/sidecar.rs#L137), [`ui/dr-ui/src/collections_ui.rs:1579`](../ui/dr-ui/src/collections_ui.rs#L1579), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1734`](../ui/dr-ui/src/collections_ui.rs#L1734), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4180`](../ui/dr-ui/src/library.rs#L4180), [`ui/dr-ui/src/library_ui.rs:713`](../ui/dr-ui/src/library_ui.rs#L713), [`ui/dr-ui/src/library_ui.rs:7677`](../ui/dr-ui/src/library_ui.rs#L7677), [`ui/dr-ui/src/library_ui.rs:7688`](../ui/dr-ui/src/library_ui.rs#L7688), [`ui/dr-ui/src/library_ui.rs:7701`](../ui/dr-ui/src/library_ui.rs#L7701), [`ui/dr-ui/src/library_ui.rs:7716`](../ui/dr-ui/src/library_ui.rs#L7716), [`ui/dr-ui/src/library_ui.rs:7725`](../ui/dr-ui/src/library_ui.rs#L7725), [`ui/dr-ui/ui/app.slint:349`](../ui/dr-ui/ui/app.slint#L349), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/library.slint:1350`](../ui/dr-ui/ui/library.slint#L1350), [`ui/dr-ui/ui/library.slint:1374`](../ui/dr-ui/ui/library.slint#L1374), [`ui/dr-ui/ui/library.slint:1427`](../ui/dr-ui/ui/library.slint#L1427), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101) | -| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4656`](../ui/dr-ui/src/library.rs#L4656), [`ui/dr-ui/src/library_ui.rs:389`](../ui/dr-ui/src/library_ui.rs#L389), [`ui/dr-ui/src/library_ui.rs:470`](../ui/dr-ui/src/library_ui.rs#L470), [`ui/dr-ui/src/library_ui.rs:6407`](../ui/dr-ui/src/library_ui.rs#L6407), [`ui/dr-ui/src/library_ui.rs:6460`](../ui/dr-ui/src/library_ui.rs#L6460), [`ui/dr-ui/src/library_ui.rs:7817`](../ui/dr-ui/src/library_ui.rs#L7817), [`ui/dr-ui/ui/app.slint:361`](../ui/dr-ui/ui/app.slint#L361), [`ui/dr-ui/ui/app.slint:571`](../ui/dr-ui/ui/app.slint#L571), [`ui/dr-ui/ui/app.slint:874`](../ui/dr-ui/ui/app.slint#L874), [`ui/dr-ui/ui/library.slint:111`](../ui/dr-ui/ui/library.slint#L111), [`ui/dr-ui/ui/library.slint:1537`](../ui/dr-ui/ui/library.slint#L1537), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | -| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1073`](../ui/dr-ui/src/collections_ui.rs#L1073), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1598`](../ui/dr-ui/src/collections_ui.rs#L1598), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1913`](../ui/dr-ui/src/collections_ui.rs#L1913), [`ui/dr-ui/src/collections_ui.rs:1973`](../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:965`](../ui/dr-ui/src/collections_ui.rs#L965), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4461`](../ui/dr-ui/src/library.rs#L4461), [`ui/dr-ui/src/library.rs:4487`](../ui/dr-ui/src/library.rs#L4487), [`ui/dr-ui/src/library.rs:4542`](../ui/dr-ui/src/library.rs#L4542), [`ui/dr-ui/src/library_ui.rs:2686`](../ui/dr-ui/src/library_ui.rs#L2686), [`ui/dr-ui/src/library_ui.rs:4083`](../ui/dr-ui/src/library_ui.rs#L4083), [`ui/dr-ui/src/library_ui.rs:4834`](../ui/dr-ui/src/library_ui.rs#L4834), [`ui/dr-ui/ui/app.slint:352`](../ui/dr-ui/ui/app.slint#L352), [`ui/dr-ui/ui/app.slint:566`](../ui/dr-ui/ui/app.slint#L566), [`ui/dr-ui/ui/app.slint:595`](../ui/dr-ui/ui/app.slint#L595), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:1317`](../ui/dr-ui/ui/library.slint#L1317), [`ui/dr-ui/ui/library.slint:2886`](../ui/dr-ui/ui/library.slint#L2886), [`ui/dr-ui/ui/library.slint:4079`](../ui/dr-ui/ui/library.slint#L4079), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993) | -| FR-CAT-8 | [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:415`](../core/dr-pipeline/src/graph.rs#L415), [`core/dr-pipeline/src/graph.rs:459`](../core/dr-pipeline/src/graph.rs#L459), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4186`](../ui/dr-ui/src/develop.rs#L4186), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:4992`](../ui/dr-ui/src/develop.rs#L4992), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/export.rs:799`](../ui/dr-ui/src/export.rs#L799), [`ui/dr-ui/src/lib.rs:1082`](../ui/dr-ui/src/lib.rs#L1082), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2088`](../ui/dr-ui/src/lib.rs#L2088), [`ui/dr-ui/src/lib.rs:2223`](../ui/dr-ui/src/lib.rs#L2223), [`ui/dr-ui/src/lib.rs:634`](../ui/dr-ui/src/lib.rs#L634), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:6043`](../ui/dr-ui/src/library_ui.rs#L6043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | -| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop.rs:3544`](../ui/dr-ui/src/develop.rs#L3544), [`ui/dr-ui/src/develop.rs:3587`](../ui/dr-ui/src/develop.rs#L3587), [`ui/dr-ui/src/library.rs:1242`](../ui/dr-ui/src/library.rs#L1242), [`ui/dr-ui/src/library.rs:1295`](../ui/dr-ui/src/library.rs#L1295), [`ui/dr-ui/src/library.rs:1326`](../ui/dr-ui/src/library.rs#L1326), [`ui/dr-ui/src/library.rs:1431`](../ui/dr-ui/src/library.rs#L1431), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1883`](../ui/dr-ui/src/library.rs#L1883), [`ui/dr-ui/src/library.rs:1959`](../ui/dr-ui/src/library.rs#L1959), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4764`](../ui/dr-ui/src/library.rs#L4764), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1318`](../ui/dr-ui/src/library_ui.rs#L1318), [`ui/dr-ui/src/library_ui.rs:1909`](../ui/dr-ui/src/library_ui.rs#L1909), [`ui/dr-ui/src/library_ui.rs:1953`](../ui/dr-ui/src/library_ui.rs#L1953), [`ui/dr-ui/src/library_ui.rs:1969`](../ui/dr-ui/src/library_ui.rs#L1969), [`ui/dr-ui/src/library_ui.rs:2063`](../ui/dr-ui/src/library_ui.rs#L2063), [`ui/dr-ui/src/library_ui.rs:284`](../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:2925`](../ui/dr-ui/src/library_ui.rs#L2925), [`ui/dr-ui/src/library_ui.rs:317`](../ui/dr-ui/src/library_ui.rs#L317), [`ui/dr-ui/src/library_ui.rs:325`](../ui/dr-ui/src/library_ui.rs#L325), [`ui/dr-ui/src/library_ui.rs:3363`](../ui/dr-ui/src/library_ui.rs#L3363), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:3863`](../ui/dr-ui/src/library_ui.rs#L3863), [`ui/dr-ui/src/library_ui.rs:3951`](../ui/dr-ui/src/library_ui.rs#L3951), [`ui/dr-ui/src/library_ui.rs:4134`](../ui/dr-ui/src/library_ui.rs#L4134), [`ui/dr-ui/src/library_ui.rs:4248`](../ui/dr-ui/src/library_ui.rs#L4248), [`ui/dr-ui/src/library_ui.rs:523`](../ui/dr-ui/src/library_ui.rs#L523), [`ui/dr-ui/src/library_ui.rs:586`](../ui/dr-ui/src/library_ui.rs#L586), [`ui/dr-ui/src/library_ui.rs:6505`](../ui/dr-ui/src/library_ui.rs#L6505), [`ui/dr-ui/src/library_ui.rs:6620`](../ui/dr-ui/src/library_ui.rs#L6620), [`ui/dr-ui/src/presets.rs:453`](../ui/dr-ui/src/presets.rs#L453), [`ui/dr-ui/src/presets.rs:465`](../ui/dr-ui/src/presets.rs#L465), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | +| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1171`](../core/dr-catalog/src/schema.rs#L1171), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:137`](../core/dr-pipeline/src/sidecar.rs#L137), [`ui/dr-ui/src/collections_ui.rs:1579`](../ui/dr-ui/src/collections_ui.rs#L1579), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1734`](../ui/dr-ui/src/collections_ui.rs#L1734), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4180`](../ui/dr-ui/src/library.rs#L4180), [`ui/dr-ui/src/library_ui.rs:713`](../ui/dr-ui/src/library_ui.rs#L713), [`ui/dr-ui/src/library_ui.rs:7677`](../ui/dr-ui/src/library_ui.rs#L7677), [`ui/dr-ui/src/library_ui.rs:7688`](../ui/dr-ui/src/library_ui.rs#L7688), [`ui/dr-ui/src/library_ui.rs:7701`](../ui/dr-ui/src/library_ui.rs#L7701), [`ui/dr-ui/src/library_ui.rs:7716`](../ui/dr-ui/src/library_ui.rs#L7716), [`ui/dr-ui/src/library_ui.rs:7725`](../ui/dr-ui/src/library_ui.rs#L7725), [`ui/dr-ui/ui/app.slint:355`](../ui/dr-ui/ui/app.slint#L355), [`ui/dr-ui/ui/app.slint:577`](../ui/dr-ui/ui/app.slint#L577), [`ui/dr-ui/ui/library.slint:1350`](../ui/dr-ui/ui/library.slint#L1350), [`ui/dr-ui/ui/library.slint:1374`](../ui/dr-ui/ui/library.slint#L1374), [`ui/dr-ui/ui/library.slint:1427`](../ui/dr-ui/ui/library.slint#L1427), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101) | +| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4656`](../ui/dr-ui/src/library.rs#L4656), [`ui/dr-ui/src/library_ui.rs:389`](../ui/dr-ui/src/library_ui.rs#L389), [`ui/dr-ui/src/library_ui.rs:470`](../ui/dr-ui/src/library_ui.rs#L470), [`ui/dr-ui/src/library_ui.rs:6407`](../ui/dr-ui/src/library_ui.rs#L6407), [`ui/dr-ui/src/library_ui.rs:6460`](../ui/dr-ui/src/library_ui.rs#L6460), [`ui/dr-ui/src/library_ui.rs:7817`](../ui/dr-ui/src/library_ui.rs#L7817), [`ui/dr-ui/ui/app.slint:367`](../ui/dr-ui/ui/app.slint#L367), [`ui/dr-ui/ui/app.slint:577`](../ui/dr-ui/ui/app.slint#L577), [`ui/dr-ui/ui/app.slint:880`](../ui/dr-ui/ui/app.slint#L880), [`ui/dr-ui/ui/library.slint:111`](../ui/dr-ui/ui/library.slint#L111), [`ui/dr-ui/ui/library.slint:1537`](../ui/dr-ui/ui/library.slint#L1537), [`ui/dr-ui/ui/library.slint:4101`](../ui/dr-ui/ui/library.slint#L4101), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | +| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1073`](../ui/dr-ui/src/collections_ui.rs#L1073), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1598`](../ui/dr-ui/src/collections_ui.rs#L1598), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1913`](../ui/dr-ui/src/collections_ui.rs#L1913), [`ui/dr-ui/src/collections_ui.rs:1973`](../ui/dr-ui/src/collections_ui.rs#L1973), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:965`](../ui/dr-ui/src/collections_ui.rs#L965), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4461`](../ui/dr-ui/src/library.rs#L4461), [`ui/dr-ui/src/library.rs:4487`](../ui/dr-ui/src/library.rs#L4487), [`ui/dr-ui/src/library.rs:4542`](../ui/dr-ui/src/library.rs#L4542), [`ui/dr-ui/src/library_ui.rs:2686`](../ui/dr-ui/src/library_ui.rs#L2686), [`ui/dr-ui/src/library_ui.rs:4083`](../ui/dr-ui/src/library_ui.rs#L4083), [`ui/dr-ui/src/library_ui.rs:4834`](../ui/dr-ui/src/library_ui.rs#L4834), [`ui/dr-ui/ui/app.slint:358`](../ui/dr-ui/ui/app.slint#L358), [`ui/dr-ui/ui/app.slint:572`](../ui/dr-ui/ui/app.slint#L572), [`ui/dr-ui/ui/app.slint:601`](../ui/dr-ui/ui/app.slint#L601), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:1317`](../ui/dr-ui/ui/library.slint#L1317), [`ui/dr-ui/ui/library.slint:2886`](../ui/dr-ui/ui/library.slint#L2886), [`ui/dr-ui/ui/library.slint:4079`](../ui/dr-ui/ui/library.slint#L4079), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993) | +| FR-CAT-8 | [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:465`](../core/dr-pipeline/src/graph.rs#L465), [`core/dr-pipeline/src/graph.rs:513`](../core/dr-pipeline/src/graph.rs#L513), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:1933`](../ui/dr-ui/src/develop.rs#L1933), [`ui/dr-ui/src/develop.rs:4265`](../ui/dr-ui/src/develop.rs#L4265), [`ui/dr-ui/src/develop.rs:4294`](../ui/dr-ui/src/develop.rs#L4294), [`ui/dr-ui/src/develop.rs:5176`](../ui/dr-ui/src/develop.rs#L5176), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/develop.rs:5332`](../ui/dr-ui/src/develop.rs#L5332), [`ui/dr-ui/src/export.rs:799`](../ui/dr-ui/src/export.rs#L799), [`ui/dr-ui/src/lib.rs:1082`](../ui/dr-ui/src/lib.rs#L1082), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2102`](../ui/dr-ui/src/lib.rs#L2102), [`ui/dr-ui/src/lib.rs:2237`](../ui/dr-ui/src/lib.rs#L2237), [`ui/dr-ui/src/lib.rs:634`](../ui/dr-ui/src/lib.rs#L634), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:6043`](../ui/dr-ui/src/library_ui.rs#L6043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`ui/dr-ui/src/develop.rs:3652`](../ui/dr-ui/src/develop.rs#L3652), [`ui/dr-ui/src/develop.rs:3695`](../ui/dr-ui/src/develop.rs#L3695), [`ui/dr-ui/src/library.rs:1242`](../ui/dr-ui/src/library.rs#L1242), [`ui/dr-ui/src/library.rs:1295`](../ui/dr-ui/src/library.rs#L1295), [`ui/dr-ui/src/library.rs:1326`](../ui/dr-ui/src/library.rs#L1326), [`ui/dr-ui/src/library.rs:1431`](../ui/dr-ui/src/library.rs#L1431), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1883`](../ui/dr-ui/src/library.rs#L1883), [`ui/dr-ui/src/library.rs:1959`](../ui/dr-ui/src/library.rs#L1959), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4764`](../ui/dr-ui/src/library.rs#L4764), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1318`](../ui/dr-ui/src/library_ui.rs#L1318), [`ui/dr-ui/src/library_ui.rs:1909`](../ui/dr-ui/src/library_ui.rs#L1909), [`ui/dr-ui/src/library_ui.rs:1953`](../ui/dr-ui/src/library_ui.rs#L1953), [`ui/dr-ui/src/library_ui.rs:1969`](../ui/dr-ui/src/library_ui.rs#L1969), [`ui/dr-ui/src/library_ui.rs:2063`](../ui/dr-ui/src/library_ui.rs#L2063), [`ui/dr-ui/src/library_ui.rs:284`](../ui/dr-ui/src/library_ui.rs#L284), [`ui/dr-ui/src/library_ui.rs:2925`](../ui/dr-ui/src/library_ui.rs#L2925), [`ui/dr-ui/src/library_ui.rs:317`](../ui/dr-ui/src/library_ui.rs#L317), [`ui/dr-ui/src/library_ui.rs:325`](../ui/dr-ui/src/library_ui.rs#L325), [`ui/dr-ui/src/library_ui.rs:3363`](../ui/dr-ui/src/library_ui.rs#L3363), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:3863`](../ui/dr-ui/src/library_ui.rs#L3863), [`ui/dr-ui/src/library_ui.rs:3951`](../ui/dr-ui/src/library_ui.rs#L3951), [`ui/dr-ui/src/library_ui.rs:4134`](../ui/dr-ui/src/library_ui.rs#L4134), [`ui/dr-ui/src/library_ui.rs:4248`](../ui/dr-ui/src/library_ui.rs#L4248), [`ui/dr-ui/src/library_ui.rs:523`](../ui/dr-ui/src/library_ui.rs#L523), [`ui/dr-ui/src/library_ui.rs:586`](../ui/dr-ui/src/library_ui.rs#L586), [`ui/dr-ui/src/library_ui.rs:6505`](../ui/dr-ui/src/library_ui.rs#L6505), [`ui/dr-ui/src/library_ui.rs:6620`](../ui/dr-ui/src/library_ui.rs#L6620), [`ui/dr-ui/src/presets.rs:453`](../ui/dr-ui/src/presets.rs#L453), [`ui/dr-ui/src/presets.rs:465`](../ui/dr-ui/src/presets.rs#L465), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | -| FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:376`](../core/dr-catalog/src/schema.rs#L376), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:121`](../ui/dr-ui/src/develop.rs#L121), [`ui/dr-ui/src/develop.rs:130`](../ui/dr-ui/src/develop.rs#L130), [`ui/dr-ui/src/develop.rs:196`](../ui/dr-ui/src/develop.rs#L196), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:689`](../ui/dr-ui/src/develop.rs#L689), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2195`](../ui/dr-ui/src/lib.rs#L2195), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | +| FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:376`](../core/dr-catalog/src/schema.rs#L376), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:121`](../ui/dr-ui/src/develop.rs#L121), [`ui/dr-ui/src/develop.rs:130`](../ui/dr-ui/src/develop.rs#L130), [`ui/dr-ui/src/develop.rs:196`](../ui/dr-ui/src/develop.rs#L196), [`ui/dr-ui/src/develop.rs:2165`](../ui/dr-ui/src/develop.rs#L2165), [`ui/dr-ui/src/develop.rs:689`](../ui/dr-ui/src/develop.rs#L689), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2209`](../ui/dr-ui/src/lib.rs#L2209), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-11 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/library.rs:287`](../ui/dr-ui/src/library.rs#L287), [`ui/dr-ui/src/library.rs:317`](../ui/dr-ui/src/library.rs#L317), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-12 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:376`](../core/dr-catalog/src/schema.rs#L376), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463) | -| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3250`](../ui/dr-ui/src/develop.rs#L3250), [`ui/dr-ui/src/develop.rs:3263`](../ui/dr-ui/src/develop.rs#L3263), [`ui/dr-ui/src/develop.rs:3308`](../ui/dr-ui/src/develop.rs#L3308), [`ui/dr-ui/src/develop.rs:3319`](../ui/dr-ui/src/develop.rs#L3319), [`ui/dr-ui/src/develop.rs:3325`](../ui/dr-ui/src/develop.rs#L3325), [`ui/dr-ui/src/develop.rs:3342`](../ui/dr-ui/src/develop.rs#L3342), [`ui/dr-ui/src/develop.rs:6643`](../ui/dr-ui/src/develop.rs#L6643), [`ui/dr-ui/src/develop.rs:6707`](../ui/dr-ui/src/develop.rs#L6707), [`ui/dr-ui/src/develop.rs:6730`](../ui/dr-ui/src/develop.rs#L6730), [`ui/dr-ui/src/develop.rs:755`](../ui/dr-ui/src/develop.rs#L755), [`ui/dr-ui/src/develop.rs:775`](../ui/dr-ui/src/develop.rs#L775), [`ui/dr-ui/src/develop.rs:781`](../ui/dr-ui/src/develop.rs#L781), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1637`](../ui/dr-ui/src/lib.rs#L1637), [`ui/dr-ui/src/lib.rs:1715`](../ui/dr-ui/src/lib.rs#L1715), [`ui/dr-ui/src/lib.rs:1814`](../ui/dr-ui/src/lib.rs#L1814), [`ui/dr-ui/src/lib.rs:1855`](../ui/dr-ui/src/lib.rs#L1855), [`ui/dr-ui/src/lib.rs:3105`](../ui/dr-ui/src/lib.rs#L3105), [`ui/dr-ui/src/lib.rs:383`](../ui/dr-ui/src/lib.rs#L383), [`ui/dr-ui/src/peaking.rs:1`](../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:1844`](../ui/dr-ui/ui/app.slint#L1844), [`ui/dr-ui/ui/app.slint:2450`](../ui/dr-ui/ui/app.slint#L2450), [`ui/dr-ui/ui/app.slint:89`](../ui/dr-ui/ui/app.slint#L89), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../ui/dr-ui/ui/peaking.slint#L56) | +| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3261`](../ui/dr-ui/src/develop.rs#L3261), [`ui/dr-ui/src/develop.rs:3274`](../ui/dr-ui/src/develop.rs#L3274), [`ui/dr-ui/src/develop.rs:3319`](../ui/dr-ui/src/develop.rs#L3319), [`ui/dr-ui/src/develop.rs:3330`](../ui/dr-ui/src/develop.rs#L3330), [`ui/dr-ui/src/develop.rs:3336`](../ui/dr-ui/src/develop.rs#L3336), [`ui/dr-ui/src/develop.rs:3353`](../ui/dr-ui/src/develop.rs#L3353), [`ui/dr-ui/src/develop.rs:6881`](../ui/dr-ui/src/develop.rs#L6881), [`ui/dr-ui/src/develop.rs:6945`](../ui/dr-ui/src/develop.rs#L6945), [`ui/dr-ui/src/develop.rs:6968`](../ui/dr-ui/src/develop.rs#L6968), [`ui/dr-ui/src/develop.rs:763`](../ui/dr-ui/src/develop.rs#L763), [`ui/dr-ui/src/develop.rs:783`](../ui/dr-ui/src/develop.rs#L783), [`ui/dr-ui/src/develop.rs:789`](../ui/dr-ui/src/develop.rs#L789), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/histogram.rs:208`](../ui/dr-ui/src/histogram.rs#L208), [`ui/dr-ui/src/histogram.rs:228`](../ui/dr-ui/src/histogram.rs#L228), [`ui/dr-ui/src/histogram.rs:272`](../ui/dr-ui/src/histogram.rs#L272), [`ui/dr-ui/src/histogram.rs:544`](../ui/dr-ui/src/histogram.rs#L544), [`ui/dr-ui/src/histogram.rs:565`](../ui/dr-ui/src/histogram.rs#L565), [`ui/dr-ui/src/histogram.rs:593`](../ui/dr-ui/src/histogram.rs#L593), [`ui/dr-ui/src/histogram.rs:621`](../ui/dr-ui/src/histogram.rs#L621), [`ui/dr-ui/src/histogram.rs:658`](../ui/dr-ui/src/histogram.rs#L658), [`ui/dr-ui/src/lib.rs:1637`](../ui/dr-ui/src/lib.rs#L1637), [`ui/dr-ui/src/lib.rs:1715`](../ui/dr-ui/src/lib.rs#L1715), [`ui/dr-ui/src/lib.rs:1814`](../ui/dr-ui/src/lib.rs#L1814), [`ui/dr-ui/src/lib.rs:1855`](../ui/dr-ui/src/lib.rs#L1855), [`ui/dr-ui/src/lib.rs:3119`](../ui/dr-ui/src/lib.rs#L3119), [`ui/dr-ui/src/lib.rs:383`](../ui/dr-ui/src/lib.rs#L383), [`ui/dr-ui/src/peaking.rs:1`](../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:1850`](../ui/dr-ui/ui/app.slint#L1850), [`ui/dr-ui/ui/app.slint:2457`](../ui/dr-ui/ui/app.slint#L2457), [`ui/dr-ui/ui/app.slint:95`](../ui/dr-ui/ui/app.slint#L95), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../ui/dr-ui/ui/peaking.slint#L56) | | FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:137`](../core/dr-pipeline/src/sidecar.rs#L137), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492) | | FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:419`](../core/dr-catalog/src/schema.rs#L419), [`ui/dr-ui/src/bursts.rs:1`](../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library.rs:202`](../ui/dr-ui/src/library.rs#L202), [`ui/dr-ui/src/library.rs:5536`](../ui/dr-ui/src/library.rs#L5536) | | FR-CULL-8 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:487`](../core/dr-catalog/src/schema.rs#L487), [`core/dr-catalog/src/schema.rs:513`](../core/dr-catalog/src/schema.rs#L513), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/align.rs:291`](../core/dr-face/src/align.rs#L291), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:330`](../ui/dr-ui/src/faces.rs#L330), [`ui/dr-ui/src/faces.rs:346`](../ui/dr-ui/src/faces.rs#L346), [`ui/dr-ui/src/identity_ui.rs:540`](../ui/dr-ui/src/identity_ui.rs#L540), [`ui/dr-ui/src/lib.rs:1256`](../ui/dr-ui/src/lib.rs#L1256), [`ui/dr-ui/src/library.rs:2985`](../ui/dr-ui/src/library.rs#L2985), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077), [`ui/dr-ui/src/library.rs:3298`](../ui/dr-ui/src/library.rs#L3298), [`ui/dr-ui/src/library.rs:3434`](../ui/dr-ui/src/library.rs#L3434), [`ui/dr-ui/src/library.rs:3470`](../ui/dr-ui/src/library.rs#L3470), [`ui/dr-ui/ui/settings.slint:419`](../ui/dr-ui/ui/settings.slint#L419), [`ui/dr-ui/ui/settings.slint:82`](../ui/dr-ui/ui/settings.slint#L82) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:554`](../core/dr-catalog/src/schema.rs#L554), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/ui/identity.slint:283`](../ui/dr-ui/ui/identity.slint#L283) | | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../core/dr-pipeline/src/sidecar.rs#L1) | | FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403) | -| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:194`](../core/dr-pipeline/src/graph.rs#L194), [`core/dr-pipeline/src/graph.rs:681`](../core/dr-pipeline/src/graph.rs#L681), [`core/dr-pipeline/src/mask.rs:151`](../core/dr-pipeline/src/mask.rs#L151), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:535`](../core/dr-pipeline/src/operation.rs#L535), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:1774`](../core/dr-pipeline/src/sidecar.rs#L1774), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:850`](../core/dr-segment/src/refine.rs#L850), [`ui/dr-ui/src/develop.rs:103`](../ui/dr-ui/src/develop.rs#L103), [`ui/dr-ui/src/develop.rs:1506`](../ui/dr-ui/src/develop.rs#L1506), [`ui/dr-ui/src/develop.rs:165`](../ui/dr-ui/src/develop.rs#L165), [`ui/dr-ui/src/develop.rs:1922`](../ui/dr-ui/src/develop.rs#L1922), [`ui/dr-ui/src/develop.rs:2136`](../ui/dr-ui/src/develop.rs#L2136), [`ui/dr-ui/src/develop.rs:2154`](../ui/dr-ui/src/develop.rs#L2154), [`ui/dr-ui/src/develop.rs:2168`](../ui/dr-ui/src/develop.rs#L2168), [`ui/dr-ui/src/develop.rs:2190`](../ui/dr-ui/src/develop.rs#L2190), [`ui/dr-ui/src/develop.rs:2336`](../ui/dr-ui/src/develop.rs#L2336), [`ui/dr-ui/src/develop.rs:2434`](../ui/dr-ui/src/develop.rs#L2434), [`ui/dr-ui/src/develop.rs:328`](../ui/dr-ui/src/develop.rs#L328), [`ui/dr-ui/src/develop.rs:365`](../ui/dr-ui/src/develop.rs#L365), [`ui/dr-ui/src/develop.rs:3855`](../ui/dr-ui/src/develop.rs#L3855), [`ui/dr-ui/src/develop.rs:3885`](../ui/dr-ui/src/develop.rs#L3885), [`ui/dr-ui/src/develop.rs:3951`](../ui/dr-ui/src/develop.rs#L3951), [`ui/dr-ui/src/develop.rs:3965`](../ui/dr-ui/src/develop.rs#L3965), [`ui/dr-ui/src/develop.rs:4157`](../ui/dr-ui/src/develop.rs#L4157), [`ui/dr-ui/src/develop.rs:4817`](../ui/dr-ui/src/develop.rs#L4817), [`ui/dr-ui/src/develop.rs:4938`](../ui/dr-ui/src/develop.rs#L4938), [`ui/dr-ui/src/develop.rs:5094`](../ui/dr-ui/src/develop.rs#L5094), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/develop.rs:5274`](../ui/dr-ui/src/develop.rs#L5274), [`ui/dr-ui/src/develop.rs:5324`](../ui/dr-ui/src/develop.rs#L5324), [`ui/dr-ui/src/develop.rs:588`](../ui/dr-ui/src/develop.rs#L588), [`ui/dr-ui/src/develop.rs:649`](../ui/dr-ui/src/develop.rs#L649), [`ui/dr-ui/src/develop.rs:795`](../ui/dr-ui/src/develop.rs#L795), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/develop.rs:871`](../ui/dr-ui/src/develop.rs#L871), [`ui/dr-ui/src/lib.rs:1696`](../ui/dr-ui/src/lib.rs#L1696), [`ui/dr-ui/src/lib.rs:2498`](../ui/dr-ui/src/lib.rs#L2498), [`ui/dr-ui/src/lib.rs:2694`](../ui/dr-ui/src/lib.rs#L2694), [`ui/dr-ui/src/lib.rs:2866`](../ui/dr-ui/src/lib.rs#L2866), [`ui/dr-ui/src/lib.rs:2930`](../ui/dr-ui/src/lib.rs#L2930), [`ui/dr-ui/src/lib.rs:2984`](../ui/dr-ui/src/lib.rs#L2984), [`ui/dr-ui/src/lib.rs:389`](../ui/dr-ui/src/lib.rs#L389), [`ui/dr-ui/src/lib.rs:432`](../ui/dr-ui/src/lib.rs#L432), [`ui/dr-ui/src/lib.rs:455`](../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/presets.rs:394`](../ui/dr-ui/src/presets.rs#L394), [`ui/dr-ui/src/segmentation.rs:116`](../ui/dr-ui/src/segmentation.rs#L116), [`ui/dr-ui/src/segmentation.rs:330`](../ui/dr-ui/src/segmentation.rs#L330), [`ui/dr-ui/src/segmentation.rs:346`](../ui/dr-ui/src/segmentation.rs#L346), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476), [`ui/dr-ui/src/segmentation.rs:504`](../ui/dr-ui/src/segmentation.rs#L504), [`ui/dr-ui/ui/adjust.slint:547`](../ui/dr-ui/ui/adjust.slint#L547), [`ui/dr-ui/ui/adjust.slint:666`](../ui/dr-ui/ui/adjust.slint#L666), [`ui/dr-ui/ui/app.slint:150`](../ui/dr-ui/ui/app.slint#L150), [`ui/dr-ui/ui/app.slint:196`](../ui/dr-ui/ui/app.slint#L196), [`ui/dr-ui/ui/app.slint:2046`](../ui/dr-ui/ui/app.slint#L2046), [`ui/dr-ui/ui/app.slint:984`](../ui/dr-ui/ui/app.slint#L984), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | -| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:287`](../core/dr-pipeline/src/graph.rs#L287), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1403`](../ui/dr-ui/src/develop.rs#L1403), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | -| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365) | -| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:287`](../core/dr-pipeline/src/graph.rs#L287), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`ui/dr-ui/src/develop.rs:5903`](../ui/dr-ui/src/develop.rs#L5903) | -| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:720`](../core/dr-pipeline/src/graph.rs#L720), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | +| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/coverage.rs:122`](../core/dr-pipeline/src/coverage.rs#L122), [`core/dr-pipeline/src/coverage.rs:1`](../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-pipeline/src/framing.rs:753`](../core/dr-pipeline/src/framing.rs#L753), [`core/dr-pipeline/src/graph.rs:209`](../core/dr-pipeline/src/graph.rs#L209), [`core/dr-pipeline/src/graph.rs:290`](../core/dr-pipeline/src/graph.rs#L290), [`core/dr-pipeline/src/graph.rs:735`](../core/dr-pipeline/src/graph.rs#L735), [`core/dr-pipeline/src/mask.rs:151`](../core/dr-pipeline/src/mask.rs#L151), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:535`](../core/dr-pipeline/src/operation.rs#L535), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:1048`](../core/dr-pipeline/src/sidecar.rs#L1048), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:1714`](../core/dr-pipeline/src/sidecar.rs#L1714), [`core/dr-pipeline/src/sidecar.rs:1774`](../core/dr-pipeline/src/sidecar.rs#L1774), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:850`](../core/dr-segment/src/refine.rs#L850), [`ui/dr-ui/src/develop.rs:103`](../ui/dr-ui/src/develop.rs#L103), [`ui/dr-ui/src/develop.rs:1517`](../ui/dr-ui/src/develop.rs#L1517), [`ui/dr-ui/src/develop.rs:165`](../ui/dr-ui/src/develop.rs#L165), [`ui/dr-ui/src/develop.rs:1933`](../ui/dr-ui/src/develop.rs#L1933), [`ui/dr-ui/src/develop.rs:2147`](../ui/dr-ui/src/develop.rs#L2147), [`ui/dr-ui/src/develop.rs:2165`](../ui/dr-ui/src/develop.rs#L2165), [`ui/dr-ui/src/develop.rs:2179`](../ui/dr-ui/src/develop.rs#L2179), [`ui/dr-ui/src/develop.rs:2201`](../ui/dr-ui/src/develop.rs#L2201), [`ui/dr-ui/src/develop.rs:2347`](../ui/dr-ui/src/develop.rs#L2347), [`ui/dr-ui/src/develop.rs:2445`](../ui/dr-ui/src/develop.rs#L2445), [`ui/dr-ui/src/develop.rs:328`](../ui/dr-ui/src/develop.rs#L328), [`ui/dr-ui/src/develop.rs:3502`](../ui/dr-ui/src/develop.rs#L3502), [`ui/dr-ui/src/develop.rs:3562`](../ui/dr-ui/src/develop.rs#L3562), [`ui/dr-ui/src/develop.rs:365`](../ui/dr-ui/src/develop.rs#L365), [`ui/dr-ui/src/develop.rs:3963`](../ui/dr-ui/src/develop.rs#L3963), [`ui/dr-ui/src/develop.rs:3993`](../ui/dr-ui/src/develop.rs#L3993), [`ui/dr-ui/src/develop.rs:4059`](../ui/dr-ui/src/develop.rs#L4059), [`ui/dr-ui/src/develop.rs:4073`](../ui/dr-ui/src/develop.rs#L4073), [`ui/dr-ui/src/develop.rs:4265`](../ui/dr-ui/src/develop.rs#L4265), [`ui/dr-ui/src/develop.rs:5055`](../ui/dr-ui/src/develop.rs#L5055), [`ui/dr-ui/src/develop.rs:5176`](../ui/dr-ui/src/develop.rs#L5176), [`ui/dr-ui/src/develop.rs:5332`](../ui/dr-ui/src/develop.rs#L5332), [`ui/dr-ui/src/develop.rs:5468`](../ui/dr-ui/src/develop.rs#L5468), [`ui/dr-ui/src/develop.rs:5512`](../ui/dr-ui/src/develop.rs#L5512), [`ui/dr-ui/src/develop.rs:5562`](../ui/dr-ui/src/develop.rs#L5562), [`ui/dr-ui/src/develop.rs:588`](../ui/dr-ui/src/develop.rs#L588), [`ui/dr-ui/src/develop.rs:649`](../ui/dr-ui/src/develop.rs#L649), [`ui/dr-ui/src/develop.rs:803`](../ui/dr-ui/src/develop.rs#L803), [`ui/dr-ui/src/develop.rs:850`](../ui/dr-ui/src/develop.rs#L850), [`ui/dr-ui/src/develop.rs:879`](../ui/dr-ui/src/develop.rs#L879), [`ui/dr-ui/src/lib.rs:1696`](../ui/dr-ui/src/lib.rs#L1696), [`ui/dr-ui/src/lib.rs:2512`](../ui/dr-ui/src/lib.rs#L2512), [`ui/dr-ui/src/lib.rs:2708`](../ui/dr-ui/src/lib.rs#L2708), [`ui/dr-ui/src/lib.rs:2880`](../ui/dr-ui/src/lib.rs#L2880), [`ui/dr-ui/src/lib.rs:2944`](../ui/dr-ui/src/lib.rs#L2944), [`ui/dr-ui/src/lib.rs:2998`](../ui/dr-ui/src/lib.rs#L2998), [`ui/dr-ui/src/lib.rs:389`](../ui/dr-ui/src/lib.rs#L389), [`ui/dr-ui/src/lib.rs:432`](../ui/dr-ui/src/lib.rs#L432), [`ui/dr-ui/src/lib.rs:455`](../ui/dr-ui/src/lib.rs#L455), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/presets.rs:394`](../ui/dr-ui/src/presets.rs#L394), [`ui/dr-ui/src/segmentation.rs:116`](../ui/dr-ui/src/segmentation.rs#L116), [`ui/dr-ui/src/segmentation.rs:330`](../ui/dr-ui/src/segmentation.rs#L330), [`ui/dr-ui/src/segmentation.rs:346`](../ui/dr-ui/src/segmentation.rs#L346), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476), [`ui/dr-ui/src/segmentation.rs:504`](../ui/dr-ui/src/segmentation.rs#L504), [`ui/dr-ui/ui/adjust.slint:547`](../ui/dr-ui/ui/adjust.slint#L547), [`ui/dr-ui/ui/adjust.slint:666`](../ui/dr-ui/ui/adjust.slint#L666), [`ui/dr-ui/ui/app.slint:156`](../ui/dr-ui/ui/app.slint#L156), [`ui/dr-ui/ui/app.slint:202`](../ui/dr-ui/ui/app.slint#L202), [`ui/dr-ui/ui/app.slint:2052`](../ui/dr-ui/ui/app.slint#L2052), [`ui/dr-ui/ui/app.slint:68`](../ui/dr-ui/ui/app.slint#L68), [`ui/dr-ui/ui/app.slint:990`](../ui/dr-ui/ui/app.slint#L990), [`ui/dr-ui/ui/develop.slint:220`](../ui/dr-ui/ui/develop.slint#L220), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | +| FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:25`](../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:337`](../core/dr-pipeline/src/graph.rs#L337), [`core/dr-pipeline/src/graph.rs:47`](../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:60`](../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1414`](../ui/dr-ui/src/develop.rs#L1414), [`ui/dr-ui/src/lib.rs:749`](../ui/dr-ui/src/lib.rs#L749), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | +| FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395), [`core/dr-pipeline/src/graph.rs:60`](../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365) | +| FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:337`](../core/dr-pipeline/src/graph.rs#L337), [`core/dr-pipeline/src/graph.rs:47`](../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/mask.rs:1108`](../core/dr-pipeline/src/mask.rs#L1108), [`ui/dr-ui/src/develop.rs:6141`](../ui/dr-ui/src/develop.rs#L6141) | +| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:774`](../core/dr-pipeline/src/graph.rs#L774), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:403`](../core/dr-pipeline/src/operation.rs#L403), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | | FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1762`](../core/dr-pipeline/src/operation.rs#L1762), [`core/dr-pipeline/src/operation.rs:1843`](../core/dr-pipeline/src/operation.rs#L1843), [`core/dr-pipeline/src/operation.rs:1868`](../core/dr-pipeline/src/operation.rs#L1868), [`core/dr-pipeline/src/operation.rs:1883`](../core/dr-pipeline/src/operation.rs#L1883), [`core/dr-pipeline/src/operation.rs:1907`](../core/dr-pipeline/src/operation.rs#L1907), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:454`](../core/dr-pipeline/src/operation.rs#L454), [`core/dr-pipeline/src/operation.rs:464`](../core/dr-pipeline/src/operation.rs#L464), [`core/dr-pipeline/src/operation.rs:625`](../core/dr-pipeline/src/operation.rs#L625) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:139`](../core/dr-pipeline/src/graph.rs#L139), [`core/dr-pipeline/src/graph.rs:394`](../core/dr-pipeline/src/graph.rs#L394), [`core/dr-pipeline/src/operation.rs:1137`](../core/dr-pipeline/src/operation.rs#L1137), [`core/dr-pipeline/src/operation.rs:1165`](../core/dr-pipeline/src/operation.rs#L1165), [`core/dr-pipeline/src/operation.rs:1211`](../core/dr-pipeline/src/operation.rs#L1211), [`core/dr-pipeline/src/operation.rs:1762`](../core/dr-pipeline/src/operation.rs#L1762), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3602`](../ui/dr-ui/src/develop.rs#L3602), [`ui/dr-ui/src/develop.rs:3619`](../ui/dr-ui/src/develop.rs#L3619), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:3669`](../ui/dr-ui/src/develop.rs#L3669), [`ui/dr-ui/src/develop.rs:3678`](../ui/dr-ui/src/develop.rs#L3678), [`ui/dr-ui/src/develop.rs:3781`](../ui/dr-ui/src/develop.rs#L3781), [`ui/dr-ui/src/develop.rs:4194`](../ui/dr-ui/src/develop.rs#L4194), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/lib.rs:2472`](../ui/dr-ui/src/lib.rs#L2472), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2647`](../ui/dr-ui/ui/app.slint#L2647), [`ui/dr-ui/ui/app.slint:712`](../ui/dr-ui/ui/app.slint#L712) | -| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1067`](../core/dr-pipeline/src/framing.rs#L1067), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop.rs:140`](../ui/dr-ui/src/develop.rs#L140), [`ui/dr-ui/src/develop.rs:2353`](../ui/dr-ui/src/develop.rs#L2353), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:103`](../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:153`](../core/dr-pipeline/src/graph.rs#L153), [`core/dr-pipeline/src/graph.rs:444`](../core/dr-pipeline/src/graph.rs#L444), [`core/dr-pipeline/src/operation.rs:1137`](../core/dr-pipeline/src/operation.rs#L1137), [`core/dr-pipeline/src/operation.rs:1165`](../core/dr-pipeline/src/operation.rs#L1165), [`core/dr-pipeline/src/operation.rs:1211`](../core/dr-pipeline/src/operation.rs#L1211), [`core/dr-pipeline/src/operation.rs:1762`](../core/dr-pipeline/src/operation.rs#L1762), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:169`](../core/dr-pipeline/src/sidecar.rs#L169), [`core/dr-pipeline/src/sidecar.rs:2060`](../core/dr-pipeline/src/sidecar.rs#L2060), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:535`](../core/dr-pipeline/src/sidecar.rs#L535), [`core/dr-pipeline/src/sidecar.rs:662`](../core/dr-pipeline/src/sidecar.rs#L662), [`core/dr-pipeline/src/sidecar.rs:794`](../core/dr-pipeline/src/sidecar.rs#L794), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3710`](../ui/dr-ui/src/develop.rs#L3710), [`ui/dr-ui/src/develop.rs:3727`](../ui/dr-ui/src/develop.rs#L3727), [`ui/dr-ui/src/develop.rs:3739`](../ui/dr-ui/src/develop.rs#L3739), [`ui/dr-ui/src/develop.rs:3777`](../ui/dr-ui/src/develop.rs#L3777), [`ui/dr-ui/src/develop.rs:3786`](../ui/dr-ui/src/develop.rs#L3786), [`ui/dr-ui/src/develop.rs:3889`](../ui/dr-ui/src/develop.rs#L3889), [`ui/dr-ui/src/develop.rs:4302`](../ui/dr-ui/src/develop.rs#L4302), [`ui/dr-ui/src/develop.rs:4317`](../ui/dr-ui/src/develop.rs#L4317), [`ui/dr-ui/src/lib.rs:2486`](../ui/dr-ui/src/lib.rs#L2486), [`ui/dr-ui/src/lib.rs:682`](../ui/dr-ui/src/lib.rs#L682), [`ui/dr-ui/src/lib.rs:740`](../ui/dr-ui/src/lib.rs#L740), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:402`](../ui/dr-ui/src/presets.rs#L402), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:996`](../ui/dr-ui/ui/adjust.slint#L996), [`ui/dr-ui/ui/app.slint:2654`](../ui/dr-ui/ui/app.slint#L2654), [`ui/dr-ui/ui/app.slint:718`](../ui/dr-ui/ui/app.slint#L718) | +| FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1067`](../core/dr-pipeline/src/framing.rs#L1067), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:498`](../core/dr-pipeline/src/framing.rs#L498), [`core/dr-types/src/lib.rs:339`](../core/dr-types/src/lib.rs#L339), [`core/dr-types/src/lib.rs:447`](../core/dr-types/src/lib.rs#L447), [`core/dr-types/src/lib.rs:459`](../core/dr-types/src/lib.rs#L459), [`core/dr-types/src/lib.rs:475`](../core/dr-types/src/lib.rs#L475), [`ui/dr-ui/src/develop.rs:140`](../ui/dr-ui/src/develop.rs#L140), [`ui/dr-ui/src/develop.rs:2364`](../ui/dr-ui/src/develop.rs#L2364), [`ui/dr-ui/src/segmentation.rs:476`](../ui/dr-ui/src/segmentation.rs#L476) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:351`](../core/dr-gpu/src/lib.rs#L351), [`ui/dr-ui/ui/crop.slint:1`](../ui/dr-ui/ui/crop.slint#L1) | -| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:415`](../core/dr-pipeline/src/graph.rs#L415), [`core/dr-pipeline/src/graph.rs:459`](../core/dr-pipeline/src/graph.rs#L459), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3631`](../ui/dr-ui/src/develop.rs#L3631), [`ui/dr-ui/src/develop.rs:4209`](../ui/dr-ui/src/develop.rs#L4209), [`ui/dr-ui/src/develop.rs:4239`](../ui/dr-ui/src/develop.rs#L4239), [`ui/dr-ui/src/develop.rs:4252`](../ui/dr-ui/src/develop.rs#L4252), [`ui/dr-ui/src/develop.rs:4264`](../ui/dr-ui/src/develop.rs#L4264), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/develop.rs:4316`](../ui/dr-ui/src/develop.rs#L4316), [`ui/dr-ui/src/develop.rs:4335`](../ui/dr-ui/src/develop.rs#L4335), [`ui/dr-ui/src/develop.rs:4351`](../ui/dr-ui/src/develop.rs#L4351), [`ui/dr-ui/src/develop.rs:738`](../ui/dr-ui/src/develop.rs#L738), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:231`](../ui/dr-ui/src/labels.rs#L231), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:4151`](../ui/dr-ui/src/develop.rs#L4151), [`ui/dr-ui/src/develop.rs:4172`](../ui/dr-ui/src/develop.rs#L4172), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2283`](../ui/dr-ui/src/lib.rs#L2283), [`ui/dr-ui/src/lib.rs:2312`](../ui/dr-ui/src/lib.rs#L2312), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:3185`](../ui/dr-ui/src/library_ui.rs#L3185), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:664`](../ui/dr-ui/src/presets.rs#L664), [`ui/dr-ui/src/presets.rs:698`](../ui/dr-ui/src/presets.rs#L698), [`ui/dr-ui/ui/adjust.slint:740`](../ui/dr-ui/ui/adjust.slint#L740), [`ui/dr-ui/ui/adjust.slint:767`](../ui/dr-ui/ui/adjust.slint#L767), [`ui/dr-ui/ui/adjust.slint:801`](../ui/dr-ui/ui/adjust.slint#L801), [`ui/dr-ui/ui/app.slint:1599`](../ui/dr-ui/ui/app.slint#L1599), [`ui/dr-ui/ui/app.slint:2736`](../ui/dr-ui/ui/app.slint#L2736), [`ui/dr-ui/ui/app.slint:784`](../ui/dr-ui/ui/app.slint#L784), [`ui/dr-ui/ui/app.slint:795`](../ui/dr-ui/ui/app.slint#L795), [`ui/dr-ui/ui/library.slint:1564`](../ui/dr-ui/ui/library.slint#L1564), [`ui/dr-ui/ui/library.slint:1570`](../ui/dr-ui/ui/library.slint#L1570), [`ui/dr-ui/ui/library.slint:4117`](../ui/dr-ui/ui/library.slint#L4117), [`ui/dr-ui/ui/library.slint:4132`](../ui/dr-ui/ui/library.slint#L4132), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | -| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/develop.rs:4312`](../ui/dr-ui/src/develop.rs#L4312), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2668`](../ui/dr-ui/src/lib.rs#L2668), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:216`](../core/dr-pipeline/src/graph.rs#L216), [`core/dr-pipeline/src/graph.rs:805`](../core/dr-pipeline/src/graph.rs#L805), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:579`](../core/dr-pipeline/src/operation.rs#L579), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:674`](../core/dr-pipeline/src/sidecar.rs#L674), [`core/dr-pipeline/src/sidecar.rs:810`](../core/dr-pipeline/src/sidecar.rs#L810), [`core/dr-pipeline/src/sidecar.rs:864`](../core/dr-pipeline/src/sidecar.rs#L864), [`core/dr-pipeline/src/sidecar.rs:894`](../core/dr-pipeline/src/sidecar.rs#L894), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2543`](../ui/dr-ui/src/develop.rs#L2543), [`ui/dr-ui/src/develop.rs:2608`](../ui/dr-ui/src/develop.rs#L2608), [`ui/dr-ui/src/develop.rs:2691`](../ui/dr-ui/src/develop.rs#L2691), [`ui/dr-ui/src/develop.rs:2705`](../ui/dr-ui/src/develop.rs#L2705), [`ui/dr-ui/src/develop.rs:825`](../ui/dr-ui/src/develop.rs#L825), [`ui/dr-ui/src/labels.rs:53`](../ui/dr-ui/src/labels.rs#L53), [`ui/dr-ui/src/lib.rs:1703`](../ui/dr-ui/src/lib.rs#L1703), [`ui/dr-ui/src/lib.rs:2786`](../ui/dr-ui/src/lib.rs#L2786), [`ui/dr-ui/src/lib.rs:394`](../ui/dr-ui/src/lib.rs#L394), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:832`](../ui/dr-ui/ui/adjust.slint#L832), [`ui/dr-ui/ui/app.slint:140`](../ui/dr-ui/ui/app.slint#L140), [`ui/dr-ui/ui/app.slint:1949`](../ui/dr-ui/ui/app.slint#L1949), [`ui/dr-ui/ui/app.slint:2124`](../ui/dr-ui/ui/app.slint#L2124), [`ui/dr-ui/ui/app.slint:2548`](../ui/dr-ui/ui/app.slint#L2548), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | -| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2140`](../core/dr-gpu/src/adjust.rs#L2140), [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:2302`](../core/dr-gpu/src/adjust.rs#L2302), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:651`](../core/dr-pipeline/src/graph.rs#L651), [`core/dr-pipeline/src/graph.rs:681`](../core/dr-pipeline/src/graph.rs#L681), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:3139`](../ui/dr-ui/src/develop.rs#L3139), [`ui/dr-ui/src/develop.rs:4606`](../ui/dr-ui/src/develop.rs#L4606), [`ui/dr-ui/src/develop.rs:5448`](../ui/dr-ui/src/develop.rs#L5448), [`ui/dr-ui/src/develop.rs:5482`](../ui/dr-ui/src/develop.rs#L5482), [`ui/dr-ui/src/lib.rs:890`](../ui/dr-ui/src/lib.rs#L890), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/lib.rs:949`](../ui/dr-ui/src/lib.rs#L949) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:465`](../core/dr-pipeline/src/graph.rs#L465), [`core/dr-pipeline/src/graph.rs:513`](../core/dr-pipeline/src/graph.rs#L513), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3739`](../ui/dr-ui/src/develop.rs#L3739), [`ui/dr-ui/src/develop.rs:4317`](../ui/dr-ui/src/develop.rs#L4317), [`ui/dr-ui/src/develop.rs:4347`](../ui/dr-ui/src/develop.rs#L4347), [`ui/dr-ui/src/develop.rs:4360`](../ui/dr-ui/src/develop.rs#L4360), [`ui/dr-ui/src/develop.rs:4372`](../ui/dr-ui/src/develop.rs#L4372), [`ui/dr-ui/src/develop.rs:4388`](../ui/dr-ui/src/develop.rs#L4388), [`ui/dr-ui/src/develop.rs:4420`](../ui/dr-ui/src/develop.rs#L4420), [`ui/dr-ui/src/develop.rs:4424`](../ui/dr-ui/src/develop.rs#L4424), [`ui/dr-ui/src/develop.rs:4443`](../ui/dr-ui/src/develop.rs#L4443), [`ui/dr-ui/src/develop.rs:4459`](../ui/dr-ui/src/develop.rs#L4459), [`ui/dr-ui/src/develop.rs:746`](../ui/dr-ui/src/develop.rs#L746), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:231`](../ui/dr-ui/src/labels.rs#L231), [`ui/dr-ui/src/lib.rs:1631`](../ui/dr-ui/src/lib.rs#L1631), [`ui/dr-ui/src/lib.rs:1674`](../ui/dr-ui/src/lib.rs#L1674), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2682`](../ui/dr-ui/src/lib.rs#L2682), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:4259`](../ui/dr-ui/src/develop.rs#L4259), [`ui/dr-ui/src/develop.rs:4280`](../ui/dr-ui/src/develop.rs#L4280), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2297`](../ui/dr-ui/src/lib.rs#L2297), [`ui/dr-ui/src/lib.rs:2326`](../ui/dr-ui/src/lib.rs#L2326), [`ui/dr-ui/src/library.rs:1923`](../ui/dr-ui/src/library.rs#L1923), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:3185`](../ui/dr-ui/src/library_ui.rs#L3185), [`ui/dr-ui/src/library_ui.rs:3585`](../ui/dr-ui/src/library_ui.rs#L3585), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:664`](../ui/dr-ui/src/presets.rs#L664), [`ui/dr-ui/src/presets.rs:698`](../ui/dr-ui/src/presets.rs#L698), [`ui/dr-ui/ui/adjust.slint:740`](../ui/dr-ui/ui/adjust.slint#L740), [`ui/dr-ui/ui/adjust.slint:767`](../ui/dr-ui/ui/adjust.slint#L767), [`ui/dr-ui/ui/adjust.slint:801`](../ui/dr-ui/ui/adjust.slint#L801), [`ui/dr-ui/ui/app.slint:1605`](../ui/dr-ui/ui/app.slint#L1605), [`ui/dr-ui/ui/app.slint:2743`](../ui/dr-ui/ui/app.slint#L2743), [`ui/dr-ui/ui/app.slint:790`](../ui/dr-ui/ui/app.slint#L790), [`ui/dr-ui/ui/app.slint:801`](../ui/dr-ui/ui/app.slint#L801), [`ui/dr-ui/ui/library.slint:1564`](../ui/dr-ui/ui/library.slint#L1564), [`ui/dr-ui/ui/library.slint:1570`](../ui/dr-ui/ui/library.slint#L1570), [`ui/dr-ui/ui/library.slint:4117`](../ui/dr-ui/ui/library.slint#L4117), [`ui/dr-ui/ui/library.slint:4132`](../ui/dr-ui/ui/library.slint#L4132), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | +| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:4388`](../ui/dr-ui/src/develop.rs#L4388), [`ui/dr-ui/src/develop.rs:4420`](../ui/dr-ui/src/develop.rs#L4420), [`ui/dr-ui/src/lib.rs:1682`](../ui/dr-ui/src/lib.rs#L1682), [`ui/dr-ui/src/lib.rs:2682`](../ui/dr-ui/src/lib.rs#L2682), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:115`](../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:231`](../core/dr-pipeline/src/graph.rs#L231), [`core/dr-pipeline/src/graph.rs:859`](../core/dr-pipeline/src/graph.rs#L859), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:579`](../core/dr-pipeline/src/operation.rs#L579), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:674`](../core/dr-pipeline/src/sidecar.rs#L674), [`core/dr-pipeline/src/sidecar.rs:810`](../core/dr-pipeline/src/sidecar.rs#L810), [`core/dr-pipeline/src/sidecar.rs:864`](../core/dr-pipeline/src/sidecar.rs#L864), [`core/dr-pipeline/src/sidecar.rs:894`](../core/dr-pipeline/src/sidecar.rs#L894), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2516`](../ui/dr-ui/src/develop.rs#L2516), [`ui/dr-ui/src/develop.rs:2554`](../ui/dr-ui/src/develop.rs#L2554), [`ui/dr-ui/src/develop.rs:2619`](../ui/dr-ui/src/develop.rs#L2619), [`ui/dr-ui/src/develop.rs:2702`](../ui/dr-ui/src/develop.rs#L2702), [`ui/dr-ui/src/develop.rs:2716`](../ui/dr-ui/src/develop.rs#L2716), [`ui/dr-ui/src/develop.rs:833`](../ui/dr-ui/src/develop.rs#L833), [`ui/dr-ui/src/labels.rs:53`](../ui/dr-ui/src/labels.rs#L53), [`ui/dr-ui/src/lib.rs:1703`](../ui/dr-ui/src/lib.rs#L1703), [`ui/dr-ui/src/lib.rs:2800`](../ui/dr-ui/src/lib.rs#L2800), [`ui/dr-ui/src/lib.rs:394`](../ui/dr-ui/src/lib.rs#L394), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:832`](../ui/dr-ui/ui/adjust.slint#L832), [`ui/dr-ui/ui/app.slint:146`](../ui/dr-ui/ui/app.slint#L146), [`ui/dr-ui/ui/app.slint:1955`](../ui/dr-ui/ui/app.slint#L1955), [`ui/dr-ui/ui/app.slint:2130`](../ui/dr-ui/ui/app.slint#L2130), [`ui/dr-ui/ui/app.slint:2555`](../ui/dr-ui/ui/app.slint#L2555), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | +| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2140`](../core/dr-gpu/src/adjust.rs#L2140), [`core/dr-gpu/src/adjust.rs:2217`](../core/dr-gpu/src/adjust.rs#L2217), [`core/dr-gpu/src/adjust.rs:2302`](../core/dr-gpu/src/adjust.rs#L2302), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:705`](../core/dr-pipeline/src/graph.rs#L705), [`core/dr-pipeline/src/graph.rs:735`](../core/dr-pipeline/src/graph.rs#L735), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:3150`](../ui/dr-ui/src/develop.rs#L3150), [`ui/dr-ui/src/develop.rs:4714`](../ui/dr-ui/src/develop.rs#L4714), [`ui/dr-ui/src/develop.rs:5686`](../ui/dr-ui/src/develop.rs#L5686), [`ui/dr-ui/src/develop.rs:5720`](../ui/dr-ui/src/develop.rs#L5720), [`ui/dr-ui/src/lib.rs:890`](../ui/dr-ui/src/lib.rs#L890), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/lib.rs:949`](../ui/dr-ui/src/lib.rs#L949) | | FR-DSP-3 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/local_contrast.rs:329`](../core/dr-gpu/tests/local_contrast.rs#L329), [`core/dr-pipeline/src/ops/local_contrast.rs:447`](../core/dr-pipeline/src/ops/local_contrast.rs#L447) | | FR-DSP-5 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/zoom_resolution.rs:135`](../core/dr-gpu/tests/zoom_resolution.rs#L135), [`core/dr-gpu/tests/zoom_resolution.rs:166`](../core/dr-gpu/tests/zoom_resolution.rs#L166), [`core/dr-gpu/tests/zoom_resolution.rs:1`](../core/dr-gpu/tests/zoom_resolution.rs#L1), [`core/dr-gpu/tests/zoom_resolution.rs:216`](../core/dr-gpu/tests/zoom_resolution.rs#L216) | -| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/develop.rs:6876`](../ui/dr-ui/src/develop.rs#L6876), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3169`](../ui/dr-ui/src/lib.rs#L3169) | -| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3218`](../ui/dr-ui/src/develop.rs#L3218), [`ui/dr-ui/src/develop.rs:6550`](../ui/dr-ui/src/develop.rs#L6550), [`ui/dr-ui/src/develop.rs:6582`](../ui/dr-ui/src/develop.rs#L6582), [`ui/dr-ui/src/develop.rs:749`](../ui/dr-ui/src/develop.rs#L749), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1769`](../ui/dr-ui/src/lib.rs#L1769), [`ui/dr-ui/src/lib.rs:373`](../ui/dr-ui/src/lib.rs#L373), [`ui/dr-ui/ui/app.slint:74`](../ui/dr-ui/ui/app.slint#L74), [`ui/dr-ui/ui/histogram.slint:168`](../ui/dr-ui/ui/histogram.slint#L168), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | -| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:3115`](../ui/dr-ui/src/develop.rs#L3115), [`ui/dr-ui/src/develop.rs:3169`](../ui/dr-ui/src/develop.rs#L3169), [`ui/dr-ui/src/develop.rs:6876`](../ui/dr-ui/src/develop.rs#L6876), [`ui/dr-ui/src/develop.rs:6922`](../ui/dr-ui/src/develop.rs#L6922), [`ui/dr-ui/src/develop.rs:6941`](../ui/dr-ui/src/develop.rs#L6941), [`ui/dr-ui/src/develop.rs:856`](../ui/dr-ui/src/develop.rs#L856), [`ui/dr-ui/src/display_ui.rs:192`](../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1620`](../ui/dr-ui/src/lib.rs#L1620), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3082`](../ui/dr-ui/src/lib.rs#L3082), [`ui/dr-ui/src/lib.rs:3169`](../ui/dr-ui/src/lib.rs#L3169), [`ui/dr-ui/ui/app.slint:1797`](../ui/dr-ui/ui/app.slint#L1797), [`ui/dr-ui/ui/app.slint:53`](../ui/dr-ui/ui/app.slint#L53), [`ui/dr-ui/ui/settings.slint:124`](../ui/dr-ui/ui/settings.slint#L124), [`ui/dr-ui/ui/settings.slint:794`](../ui/dr-ui/ui/settings.slint#L794) | +| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:3180`](../ui/dr-ui/src/develop.rs#L3180), [`ui/dr-ui/src/develop.rs:7114`](../ui/dr-ui/src/develop.rs#L7114), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3183`](../ui/dr-ui/src/lib.rs#L3183) | +| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:3229`](../ui/dr-ui/src/develop.rs#L3229), [`ui/dr-ui/src/develop.rs:6788`](../ui/dr-ui/src/develop.rs#L6788), [`ui/dr-ui/src/develop.rs:6820`](../ui/dr-ui/src/develop.rs#L6820), [`ui/dr-ui/src/develop.rs:757`](../ui/dr-ui/src/develop.rs#L757), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1769`](../ui/dr-ui/src/lib.rs#L1769), [`ui/dr-ui/src/lib.rs:373`](../ui/dr-ui/src/lib.rs#L373), [`ui/dr-ui/ui/app.slint:80`](../ui/dr-ui/ui/app.slint#L80), [`ui/dr-ui/ui/histogram.slint:168`](../ui/dr-ui/ui/histogram.slint#L168), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | +| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:3126`](../ui/dr-ui/src/develop.rs#L3126), [`ui/dr-ui/src/develop.rs:3180`](../ui/dr-ui/src/develop.rs#L3180), [`ui/dr-ui/src/develop.rs:7114`](../ui/dr-ui/src/develop.rs#L7114), [`ui/dr-ui/src/develop.rs:7160`](../ui/dr-ui/src/develop.rs#L7160), [`ui/dr-ui/src/develop.rs:7179`](../ui/dr-ui/src/develop.rs#L7179), [`ui/dr-ui/src/develop.rs:864`](../ui/dr-ui/src/develop.rs#L864), [`ui/dr-ui/src/display_ui.rs:192`](../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1620`](../ui/dr-ui/src/lib.rs#L1620), [`ui/dr-ui/src/lib.rs:1659`](../ui/dr-ui/src/lib.rs#L1659), [`ui/dr-ui/src/lib.rs:3096`](../ui/dr-ui/src/lib.rs#L3096), [`ui/dr-ui/src/lib.rs:3183`](../ui/dr-ui/src/lib.rs#L3183), [`ui/dr-ui/ui/app.slint:1803`](../ui/dr-ui/ui/app.slint#L1803), [`ui/dr-ui/ui/app.slint:53`](../ui/dr-ui/ui/app.slint#L53), [`ui/dr-ui/ui/settings.slint:124`](../ui/dr-ui/ui/settings.slint#L124), [`ui/dr-ui/ui/settings.slint:794`](../ui/dr-ui/ui/settings.slint#L794) | | FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2450`](../core/dr-gpu/src/adjust.rs#L2450), [`core/dr-pipeline/src/graph.rs:634`](../core/dr-pipeline/src/graph.rs#L634), [`core/dr-pipeline/src/graph.rs:695`](../core/dr-pipeline/src/graph.rs#L695), [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:713`](../core/dr-types/src/settings.rs#L713), [`ui/dr-ui/src/develop.rs:6941`](../ui/dr-ui/src/develop.rs#L6941), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | +| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2450`](../core/dr-gpu/src/adjust.rs#L2450), [`core/dr-pipeline/src/graph.rs:688`](../core/dr-pipeline/src/graph.rs#L688), [`core/dr-pipeline/src/graph.rs:749`](../core/dr-pipeline/src/graph.rs#L749), [`core/dr-pipeline/src/operation.rs:497`](../core/dr-pipeline/src/operation.rs#L497), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:713`](../core/dr-types/src/settings.rs#L713), [`ui/dr-ui/src/develop.rs:7179`](../ui/dr-ui/src/develop.rs#L7179), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-3 | [`core/dr-export/src/lib.rs:182`](../core/dr-export/src/lib.rs#L182), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:758`](../core/dr-types/src/settings.rs#L758), [`core/dr-types/src/settings.rs:766`](../core/dr-types/src/settings.rs#L766), [`core/dr-types/src/settings.rs:782`](../core/dr-types/src/settings.rs#L782), [`core/dr-types/src/settings.rs:851`](../core/dr-types/src/settings.rs#L851), [`core/dr-types/src/settings.rs:879`](../core/dr-types/src/settings.rs#L879), [`core/dr-types/src/settings.rs:890`](../core/dr-types/src/settings.rs#L890), [`core/dr-types/src/settings.rs:896`](../core/dr-types/src/settings.rs#L896), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:207`](../ui/dr-ui/src/settings_ui.rs#L207), [`ui/dr-ui/src/settings_ui.rs:220`](../ui/dr-ui/src/settings_ui.rs#L220), [`ui/dr-ui/src/settings_ui.rs:235`](../ui/dr-ui/src/settings_ui.rs#L235), [`ui/dr-ui/src/settings_ui.rs:565`](../ui/dr-ui/src/settings_ui.rs#L565), [`ui/dr-ui/ui/settings.slint:697`](../ui/dr-ui/ui/settings.slint#L697), [`ui/dr-ui/ui/settings.slint:707`](../ui/dr-ui/ui/settings.slint#L707) | | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:679`](../ui/dr-ui/src/settings_ui.rs#L679) | -| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:992`](../ui/dr-ui/src/export.rs#L992), [`ui/dr-ui/src/lib.rs:232`](../ui/dr-ui/src/lib.rs#L232), [`ui/dr-ui/src/lib.rs:2414`](../ui/dr-ui/src/lib.rs#L2414), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/lib.rs:530`](../ui/dr-ui/src/lib.rs#L530), [`ui/dr-ui/src/lib.rs:557`](../ui/dr-ui/src/lib.rs#L557), [`ui/dr-ui/src/library_ui.rs:3968`](../ui/dr-ui/src/library_ui.rs#L3968), [`ui/dr-ui/src/library_ui.rs:645`](../ui/dr-ui/src/library_ui.rs#L645), [`ui/dr-ui/src/library_ui.rs:745`](../ui/dr-ui/src/library_ui.rs#L745), [`ui/dr-ui/src/library_ui.rs:7659`](../ui/dr-ui/src/library_ui.rs#L7659), [`ui/dr-ui/src/library_ui.rs:7736`](../ui/dr-ui/src/library_ui.rs#L7736), [`ui/dr-ui/src/library_ui.rs:7748`](../ui/dr-ui/src/library_ui.rs#L7748), [`ui/dr-ui/src/library_ui.rs:802`](../ui/dr-ui/src/library_ui.rs#L802), [`ui/dr-ui/ui/app.slint:1123`](../ui/dr-ui/ui/app.slint#L1123), [`ui/dr-ui/ui/app.slint:1609`](../ui/dr-ui/ui/app.slint#L1609), [`ui/dr-ui/ui/library.slint:1573`](../ui/dr-ui/ui/library.slint#L1573), [`ui/dr-ui/ui/library.slint:4157`](../ui/dr-ui/ui/library.slint#L4157) | -| FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:426`](../core/dr-types/src/settings.rs#L426), [`ui/dr-ui/src/develop.rs:3474`](../ui/dr-ui/src/develop.rs#L3474), [`ui/dr-ui/src/develop.rs:3485`](../ui/dr-ui/src/develop.rs#L3485), [`ui/dr-ui/src/develop.rs:703`](../ui/dr-ui/src/develop.rs#L703), [`ui/dr-ui/src/export.rs:467`](../ui/dr-ui/src/export.rs#L467), [`ui/dr-ui/src/export.rs:634`](../ui/dr-ui/src/export.rs#L634), [`ui/dr-ui/src/export.rs:680`](../ui/dr-ui/src/export.rs#L680), [`ui/dr-ui/src/export.rs:702`](../ui/dr-ui/src/export.rs#L702), [`ui/dr-ui/src/export.rs:826`](../ui/dr-ui/src/export.rs#L826), [`ui/dr-ui/src/export.rs:844`](../ui/dr-ui/src/export.rs#L844), [`ui/dr-ui/src/lib.rs:232`](../ui/dr-ui/src/lib.rs#L232), [`ui/dr-ui/src/lib.rs:255`](../ui/dr-ui/src/lib.rs#L255), [`ui/dr-ui/src/lib.rs:516`](../ui/dr-ui/src/lib.rs#L516), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1106`](../core/dr-gpu/src/adjust.rs#L1106), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop.rs:3506`](../ui/dr-ui/src/develop.rs#L3506), [`ui/dr-ui/src/develop.rs:3544`](../ui/dr-ui/src/develop.rs#L3544), [`ui/dr-ui/src/develop.rs:4992`](../ui/dr-ui/src/develop.rs#L4992), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077), [`ui/dr-ui/src/library.rs:3434`](../ui/dr-ui/src/library.rs#L3434), [`ui/dr-ui/src/library.rs:3470`](../ui/dr-ui/src/library.rs#L3470), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | +| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:992`](../ui/dr-ui/src/export.rs#L992), [`ui/dr-ui/src/lib.rs:232`](../ui/dr-ui/src/lib.rs#L232), [`ui/dr-ui/src/lib.rs:2428`](../ui/dr-ui/src/lib.rs#L2428), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/lib.rs:530`](../ui/dr-ui/src/lib.rs#L530), [`ui/dr-ui/src/lib.rs:557`](../ui/dr-ui/src/lib.rs#L557), [`ui/dr-ui/src/library_ui.rs:3968`](../ui/dr-ui/src/library_ui.rs#L3968), [`ui/dr-ui/src/library_ui.rs:645`](../ui/dr-ui/src/library_ui.rs#L645), [`ui/dr-ui/src/library_ui.rs:745`](../ui/dr-ui/src/library_ui.rs#L745), [`ui/dr-ui/src/library_ui.rs:7659`](../ui/dr-ui/src/library_ui.rs#L7659), [`ui/dr-ui/src/library_ui.rs:7736`](../ui/dr-ui/src/library_ui.rs#L7736), [`ui/dr-ui/src/library_ui.rs:7748`](../ui/dr-ui/src/library_ui.rs#L7748), [`ui/dr-ui/src/library_ui.rs:802`](../ui/dr-ui/src/library_ui.rs#L802), [`ui/dr-ui/ui/app.slint:1129`](../ui/dr-ui/ui/app.slint#L1129), [`ui/dr-ui/ui/app.slint:1615`](../ui/dr-ui/ui/app.slint#L1615), [`ui/dr-ui/ui/library.slint:1573`](../ui/dr-ui/ui/library.slint#L1573), [`ui/dr-ui/ui/library.slint:4157`](../ui/dr-ui/ui/library.slint#L4157) | +| FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:426`](../core/dr-types/src/settings.rs#L426), [`ui/dr-ui/src/develop.rs:3485`](../ui/dr-ui/src/develop.rs#L3485), [`ui/dr-ui/src/develop.rs:3593`](../ui/dr-ui/src/develop.rs#L3593), [`ui/dr-ui/src/develop.rs:703`](../ui/dr-ui/src/develop.rs#L703), [`ui/dr-ui/src/export.rs:467`](../ui/dr-ui/src/export.rs#L467), [`ui/dr-ui/src/export.rs:634`](../ui/dr-ui/src/export.rs#L634), [`ui/dr-ui/src/export.rs:680`](../ui/dr-ui/src/export.rs#L680), [`ui/dr-ui/src/export.rs:702`](../ui/dr-ui/src/export.rs#L702), [`ui/dr-ui/src/export.rs:826`](../ui/dr-ui/src/export.rs#L826), [`ui/dr-ui/src/export.rs:844`](../ui/dr-ui/src/export.rs#L844), [`ui/dr-ui/src/lib.rs:232`](../ui/dr-ui/src/lib.rs#L232), [`ui/dr-ui/src/lib.rs:255`](../ui/dr-ui/src/lib.rs#L255), [`ui/dr-ui/src/lib.rs:516`](../ui/dr-ui/src/lib.rs#L516), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | +| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1106`](../core/dr-gpu/src/adjust.rs#L1106), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop.rs:3614`](../ui/dr-ui/src/develop.rs#L3614), [`ui/dr-ui/src/develop.rs:3652`](../ui/dr-ui/src/develop.rs#L3652), [`ui/dr-ui/src/develop.rs:5230`](../ui/dr-ui/src/develop.rs#L5230), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077), [`ui/dr-ui/src/library.rs:3434`](../ui/dr-ui/src/library.rs#L3434), [`ui/dr-ui/src/library.rs:3470`](../ui/dr-ui/src/library.rs#L3470), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`ui/dr-ui/src/launch.rs:277`](../ui/dr-ui/src/launch.rs#L277), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:417`](../ui/dr-ui/src/launch_ui.rs#L417) | | FR-NC-10 | [`core/dr-sync/src/account.rs:220`](../core/dr-sync/src/account.rs#L220), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:557`](../ui/dr-ui/src/lib.rs#L557), [`ui/dr-ui/src/library.rs:1121`](../ui/dr-ui/src/library.rs#L1121), [`ui/dr-ui/src/library.rs:1959`](../ui/dr-ui/src/library.rs#L1959), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1969`](../ui/dr-ui/src/library_ui.rs#L1969), [`ui/dr-ui/src/library_ui.rs:3968`](../ui/dr-ui/src/library_ui.rs#L3968), [`ui/dr-ui/src/library_ui.rs:586`](../ui/dr-ui/src/library_ui.rs#L586), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-NC-12 | [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:40`](../core/dr-sync-nextcloud/src/lib.rs#L40), [`core/dr-sync-nextcloud/src/lib.rs:914`](../core/dr-sync-nextcloud/src/lib.rs#L914), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:81`](../core/dr-sync/src/account.rs#L81), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/lib.rs:51`](../core/dr-sync/src/lib.rs#L51), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`core/dr-sync/src/provider.rs:1`](../core/dr-sync/src/provider.rs#L1), [`core/dr-sync/src/provider.rs:53`](../core/dr-sync/src/provider.rs#L53), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-13 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`ui/dr-ui/src/launch_ui.rs:316`](../ui/dr-ui/src/launch_ui.rs#L316), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-2 | [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:298`](../core/dr-sync/src/account.rs#L298), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`core/dr-sync/src/account.rs:59`](../core/dr-sync/src/account.rs#L59), [`platform/dr-plat/src/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | -| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`core/dr-sync/src/capability.rs:85`](../core/dr-sync/src/capability.rs#L85), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2984`](../ui/dr-ui/src/library.rs#L2984), [`ui/dr-ui/src/library.rs:3575`](../ui/dr-ui/src/library.rs#L3575), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:4221`](../ui/dr-ui/src/library_ui.rs#L4221), [`ui/dr-ui/src/library_ui.rs:5720`](../ui/dr-ui/src/library_ui.rs#L5720), [`ui/dr-ui/ui/app.slint:428`](../ui/dr-ui/ui/app.slint#L428), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | +| FR-NC-3 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`core/dr-sync/src/capability.rs:85`](../core/dr-sync/src/capability.rs#L85), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:2984`](../ui/dr-ui/src/library.rs#L2984), [`ui/dr-ui/src/library.rs:3575`](../ui/dr-ui/src/library.rs#L3575), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/library_ui.rs:4221`](../ui/dr-ui/src/library_ui.rs#L4221), [`ui/dr-ui/src/library_ui.rs:5720`](../ui/dr-ui/src/library_ui.rs#L5720), [`ui/dr-ui/ui/app.slint:434`](../ui/dr-ui/ui/app.slint#L434), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | | FR-NC-4 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-nextcloud/src/propfind.rs:103`](../core/dr-sync-nextcloud/src/propfind.rs#L103), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61) | | FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488) | | FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) | -| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1126`](../core/dr-catalog/src/schema.rs#L1126), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/lib.rs:2118`](../ui/dr-ui/src/lib.rs#L2118), [`ui/dr-ui/src/lib.rs:3241`](../ui/dr-ui/src/lib.rs#L3241), [`ui/dr-ui/src/library.rs:1585`](../ui/dr-ui/src/library.rs#L1585), [`ui/dr-ui/src/library.rs:1608`](../ui/dr-ui/src/library.rs#L1608), [`ui/dr-ui/src/library.rs:1883`](../ui/dr-ui/src/library.rs#L1883), [`ui/dr-ui/src/library_ui.rs:1394`](../ui/dr-ui/src/library_ui.rs#L1394), [`ui/dr-ui/src/library_ui.rs:1467`](../ui/dr-ui/src/library_ui.rs#L1467), [`ui/dr-ui/src/library_ui.rs:1568`](../ui/dr-ui/src/library_ui.rs#L1568), [`ui/dr-ui/src/library_ui.rs:1623`](../ui/dr-ui/src/library_ui.rs#L1623), [`ui/dr-ui/src/library_ui.rs:1758`](../ui/dr-ui/src/library_ui.rs#L1758), [`ui/dr-ui/src/library_ui.rs:1876`](../ui/dr-ui/src/library_ui.rs#L1876), [`ui/dr-ui/src/library_ui.rs:2643`](../ui/dr-ui/src/library_ui.rs#L2643), [`ui/dr-ui/src/library_ui.rs:341`](../ui/dr-ui/src/library_ui.rs#L341), [`ui/dr-ui/src/library_ui.rs:352`](../ui/dr-ui/src/library_ui.rs#L352), [`ui/dr-ui/src/library_ui.rs:360`](../ui/dr-ui/src/library_ui.rs#L360), [`ui/dr-ui/src/library_ui.rs:372`](../ui/dr-ui/src/library_ui.rs#L372), [`ui/dr-ui/src/library_ui.rs:381`](../ui/dr-ui/src/library_ui.rs#L381), [`ui/dr-ui/src/library_ui.rs:482`](../ui/dr-ui/src/library_ui.rs#L482), [`ui/dr-ui/src/library_ui.rs:492`](../ui/dr-ui/src/library_ui.rs#L492), [`ui/dr-ui/src/library_ui.rs:539`](../ui/dr-ui/src/library_ui.rs#L539), [`ui/dr-ui/src/library_ui.rs:602`](../ui/dr-ui/src/library_ui.rs#L602), [`ui/dr-ui/src/library_ui.rs:633`](../ui/dr-ui/src/library_ui.rs#L633), [`ui/dr-ui/src/library_ui.rs:645`](../ui/dr-ui/src/library_ui.rs#L645), [`ui/dr-ui/src/library_ui.rs:6522`](../ui/dr-ui/src/library_ui.rs#L6522), [`ui/dr-ui/src/library_ui.rs:6540`](../ui/dr-ui/src/library_ui.rs#L6540), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1507`](../ui/dr-ui/ui/app.slint#L1507), [`ui/dr-ui/ui/app.slint:2726`](../ui/dr-ui/ui/app.slint#L2726), [`ui/dr-ui/ui/app.slint:560`](../ui/dr-ui/ui/app.slint#L560), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:360`](../ui/dr-ui/ui/collections.slint#L360), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:372`](../ui/dr-ui/ui/collections.slint#L372), [`ui/dr-ui/ui/collections.slint:395`](../ui/dr-ui/ui/collections.slint#L395), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:596`](../ui/dr-ui/ui/collections.slint#L596), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | +| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1126`](../core/dr-catalog/src/schema.rs#L1126), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/lib.rs:2132`](../ui/dr-ui/src/lib.rs#L2132), [`ui/dr-ui/src/lib.rs:3255`](../ui/dr-ui/src/lib.rs#L3255), [`ui/dr-ui/src/library.rs:1585`](../ui/dr-ui/src/library.rs#L1585), [`ui/dr-ui/src/library.rs:1608`](../ui/dr-ui/src/library.rs#L1608), [`ui/dr-ui/src/library.rs:1883`](../ui/dr-ui/src/library.rs#L1883), [`ui/dr-ui/src/library_ui.rs:1394`](../ui/dr-ui/src/library_ui.rs#L1394), [`ui/dr-ui/src/library_ui.rs:1467`](../ui/dr-ui/src/library_ui.rs#L1467), [`ui/dr-ui/src/library_ui.rs:1568`](../ui/dr-ui/src/library_ui.rs#L1568), [`ui/dr-ui/src/library_ui.rs:1623`](../ui/dr-ui/src/library_ui.rs#L1623), [`ui/dr-ui/src/library_ui.rs:1758`](../ui/dr-ui/src/library_ui.rs#L1758), [`ui/dr-ui/src/library_ui.rs:1876`](../ui/dr-ui/src/library_ui.rs#L1876), [`ui/dr-ui/src/library_ui.rs:2643`](../ui/dr-ui/src/library_ui.rs#L2643), [`ui/dr-ui/src/library_ui.rs:341`](../ui/dr-ui/src/library_ui.rs#L341), [`ui/dr-ui/src/library_ui.rs:352`](../ui/dr-ui/src/library_ui.rs#L352), [`ui/dr-ui/src/library_ui.rs:360`](../ui/dr-ui/src/library_ui.rs#L360), [`ui/dr-ui/src/library_ui.rs:372`](../ui/dr-ui/src/library_ui.rs#L372), [`ui/dr-ui/src/library_ui.rs:381`](../ui/dr-ui/src/library_ui.rs#L381), [`ui/dr-ui/src/library_ui.rs:482`](../ui/dr-ui/src/library_ui.rs#L482), [`ui/dr-ui/src/library_ui.rs:492`](../ui/dr-ui/src/library_ui.rs#L492), [`ui/dr-ui/src/library_ui.rs:539`](../ui/dr-ui/src/library_ui.rs#L539), [`ui/dr-ui/src/library_ui.rs:602`](../ui/dr-ui/src/library_ui.rs#L602), [`ui/dr-ui/src/library_ui.rs:633`](../ui/dr-ui/src/library_ui.rs#L633), [`ui/dr-ui/src/library_ui.rs:645`](../ui/dr-ui/src/library_ui.rs#L645), [`ui/dr-ui/src/library_ui.rs:6522`](../ui/dr-ui/src/library_ui.rs#L6522), [`ui/dr-ui/src/library_ui.rs:6540`](../ui/dr-ui/src/library_ui.rs#L6540), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1513`](../ui/dr-ui/ui/app.slint#L1513), [`ui/dr-ui/ui/app.slint:2733`](../ui/dr-ui/ui/app.slint#L2733), [`ui/dr-ui/ui/app.slint:566`](../ui/dr-ui/ui/app.slint#L566), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:360`](../ui/dr-ui/ui/collections.slint#L360), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:372`](../ui/dr-ui/ui/collections.slint#L372), [`ui/dr-ui/ui/collections.slint:395`](../ui/dr-ui/ui/collections.slint#L395), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:596`](../ui/dr-ui/ui/collections.slint#L596), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | | FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1623`](../ui/dr-ui/src/library_ui.rs#L1623), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:167`](../core/dr-sync-nextcloud/src/desktop_client.rs#L167), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:122`](../core/dr-types/src/lib.rs#L122), [`core/dr-types/src/lib.rs:204`](../core/dr-types/src/lib.rs#L204), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3413`](../ui/dr-ui/src/collections_ui.rs#L3413), [`ui/dr-ui/src/collections_ui.rs:636`](../ui/dr-ui/src/collections_ui.rs#L636), [`ui/dr-ui/src/collections_ui.rs:704`](../ui/dr-ui/src/collections_ui.rs#L704), [`ui/dr-ui/src/derived_sync.rs:585`](../ui/dr-ui/src/derived_sync.rs#L585), [`ui/dr-ui/src/derived_sync.rs:805`](../ui/dr-ui/src/derived_sync.rs#L805), [`ui/dr-ui/src/library.rs:1705`](../ui/dr-ui/src/library.rs#L1705), [`ui/dr-ui/src/library.rs:1795`](../ui/dr-ui/src/library.rs#L1795), [`ui/dr-ui/src/library.rs:3261`](../ui/dr-ui/src/library.rs#L3261), [`ui/dr-ui/src/library.rs:3695`](../ui/dr-ui/src/library.rs#L3695), [`ui/dr-ui/src/library.rs:981`](../ui/dr-ui/src/library.rs#L981), [`ui/dr-ui/src/library_ui.rs:1394`](../ui/dr-ui/src/library_ui.rs#L1394), [`ui/dr-ui/src/library_ui.rs:1467`](../ui/dr-ui/src/library_ui.rs#L1467), [`ui/dr-ui/src/library_ui.rs:1666`](../ui/dr-ui/src/library_ui.rs#L1666), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | | FR-NC-6d | [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1) | | FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:105`](../core/dr-sync-nextcloud/src/lib.rs#L105), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3575`](../ui/dr-ui/src/library.rs#L3575), [`ui/dr-ui/src/library_ui.rs:4221`](../ui/dr-ui/src/library_ui.rs#L4221), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387) | | FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1304`](../ui/dr-ui/src/lib.rs#L1304), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | | FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:122`](../ui/dr-ui/src/import.rs#L122), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import.rs:584`](../ui/dr-ui/src/import.rs#L584), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1304`](../ui/dr-ui/src/lib.rs#L1304) | -| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:120`](../core/dr-pipeline/src/sidecar.rs#L120), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`ui/dr-ui/src/lib.rs:2088`](../ui/dr-ui/src/lib.rs#L2088), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554) | +| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:120`](../core/dr-pipeline/src/sidecar.rs#L120), [`core/dr-pipeline/src/sidecar.rs:94`](../core/dr-pipeline/src/sidecar.rs#L94), [`ui/dr-ui/src/lib.rs:2102`](../ui/dr-ui/src/lib.rs#L2102), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library_ui.rs:554`](../ui/dr-ui/src/library_ui.rs#L554) | | FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/schema.rs:668`](../core/dr-catalog/src/schema.rs#L668), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:158`](../core/dr-pipeline/src/sidecar.rs#L158), [`core/dr-pipeline/src/sidecar.rs:185`](../core/dr-pipeline/src/sidecar.rs#L185), [`core/dr-pipeline/src/sidecar.rs:2136`](../core/dr-pipeline/src/sidecar.rs#L2136), [`core/dr-pipeline/src/sidecar.rs:354`](../core/dr-pipeline/src/sidecar.rs#L354), [`core/dr-pipeline/src/sidecar.rs:452`](../core/dr-pipeline/src/sidecar.rs#L452), [`core/dr-pipeline/src/spot.rs:245`](../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/mask_sidecar.rs:1055`](../core/dr-pipeline/tests/mask_sidecar.rs#L1055), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`ui/dr-ui/src/derived_sync.rs:585`](../ui/dr-ui/src/derived_sync.rs#L585), [`ui/dr-ui/src/library.rs:1031`](../ui/dr-ui/src/library.rs#L1031), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877) | | FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-sync-folder/src/lib.rs:242`](../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-sync/src/scan.rs:490`](../core/dr-sync/src/scan.rs#L490), [`core/dr-sync/src/scan.rs:519`](../core/dr-sync/src/scan.rs#L519), [`ui/dr-ui/src/library.rs:1242`](../ui/dr-ui/src/library.rs#L1242), [`ui/dr-ui/src/library.rs:1295`](../ui/dr-ui/src/library.rs#L1295), [`ui/dr-ui/src/library.rs:1326`](../ui/dr-ui/src/library.rs#L1326), [`ui/dr-ui/src/library.rs:1431`](../ui/dr-ui/src/library.rs#L1431), [`ui/dr-ui/src/library_ui.rs:1239`](../ui/dr-ui/src/library_ui.rs#L1239), [`ui/dr-ui/src/library_ui.rs:1318`](../ui/dr-ui/src/library_ui.rs#L1318), [`ui/dr-ui/src/library_ui.rs:1917`](../ui/dr-ui/src/library_ui.rs#L1917), [`ui/dr-ui/src/library_ui.rs:325`](../ui/dr-ui/src/library_ui.rs#L325), [`ui/dr-ui/src/library_ui.rs:523`](../ui/dr-ui/src/library_ui.rs#L523) | | FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | | FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | -| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3447`](../ui/dr-ui/src/develop.rs#L3447), [`ui/dr-ui/src/identity_ui.rs:121`](../ui/dr-ui/src/identity_ui.rs#L121), [`ui/dr-ui/src/lib.rs:1123`](../ui/dr-ui/src/lib.rs#L1123), [`ui/dr-ui/src/lib.rs:1572`](../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/memory.rs:163`](../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | +| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:166`](../apps/darkroom-android/src/lib.rs#L166), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3458`](../ui/dr-ui/src/develop.rs#L3458), [`ui/dr-ui/src/identity_ui.rs:121`](../ui/dr-ui/src/identity_ui.rs#L121), [`ui/dr-ui/src/lib.rs:1123`](../ui/dr-ui/src/lib.rs#L1123), [`ui/dr-ui/src/lib.rs:1572`](../ui/dr-ui/src/lib.rs#L1572), [`ui/dr-ui/src/memory.rs:163`](../ui/dr-ui/src/memory.rs#L163), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:388`](../apps/darkroom-android/src/lib.rs#L388) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:999`](../ui/dr-ui/src/lib.rs#L999), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | @@ -119,25 +119,25 @@ _None._ | FR-RAW-3 | [`core/dr-decode/src/lib.rs:139`](../core/dr-decode/src/lib.rs#L139), [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-decode/src/locate.rs:1366`](../core/dr-decode/src/locate.rs#L1366) | | FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:232`](../ui/dr-ui/src/lib.rs#L232) | | FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) | -| FR-UI-1 | [`ui/dr-ui/src/lib.rs:101`](../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/lib.rs:108`](../ui/dr-ui/src/lib.rs#L108), [`ui/dr-ui/src/lib.rs:3323`](../ui/dr-ui/src/lib.rs#L3323), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/ui/app.slint:2265`](../ui/dr-ui/ui/app.slint#L2265), [`ui/dr-ui/ui/identity.slint:188`](../ui/dr-ui/ui/identity.slint#L188), [`ui/dr-ui/ui/library.slint:1071`](../ui/dr-ui/ui/library.slint#L1071), [`ui/dr-ui/ui/toolrail.slint:88`](../ui/dr-ui/ui/toolrail.slint#L88) | -| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1045`](../ui/dr-ui/src/collections_ui.rs#L1045), [`ui/dr-ui/src/collections_ui.rs:1055`](../ui/dr-ui/src/collections_ui.rs#L1055), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1569`](../ui/dr-ui/src/collections_ui.rs#L1569), [`ui/dr-ui/src/collections_ui.rs:1821`](../ui/dr-ui/src/collections_ui.rs#L1821), [`ui/dr-ui/src/collections_ui.rs:2926`](../ui/dr-ui/src/collections_ui.rs#L2926), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:101`](../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/library_ui.rs:360`](../ui/dr-ui/src/library_ui.rs#L360), [`ui/dr-ui/src/library_ui.rs:6407`](../ui/dr-ui/src/library_ui.rs#L6407), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/src/library_ui.rs:7767`](../ui/dr-ui/src/library_ui.rs#L7767), [`ui/dr-ui/ui/app.slint:590`](../ui/dr-ui/ui/app.slint#L590), [`ui/dr-ui/ui/app.slint:604`](../ui/dr-ui/ui/app.slint#L604), [`ui/dr-ui/ui/app.slint:60`](../ui/dr-ui/ui/app.slint#L60), [`ui/dr-ui/ui/app.slint:955`](../ui/dr-ui/ui/app.slint#L955), [`ui/dr-ui/ui/controls.slint:593`](../ui/dr-ui/ui/controls.slint#L593), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1277`](../ui/dr-ui/ui/library.slint#L1277), [`ui/dr-ui/ui/library.slint:1284`](../ui/dr-ui/ui/library.slint#L1284), [`ui/dr-ui/ui/library.slint:1381`](../ui/dr-ui/ui/library.slint#L1381), [`ui/dr-ui/ui/library.slint:2565`](../ui/dr-ui/ui/library.slint#L2565), [`ui/dr-ui/ui/library.slint:4195`](../ui/dr-ui/ui/library.slint#L4195), [`ui/dr-ui/ui/library.slint:960`](../ui/dr-ui/ui/library.slint#L960), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:110`](../ui/dr-ui/ui/settings.slint#L110) | -| FR-UI-3 | [`ui/dr-ui/src/develop.rs:2434`](../ui/dr-ui/src/develop.rs#L2434), [`ui/dr-ui/src/develop.rs:2543`](../ui/dr-ui/src/develop.rs#L2543), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:976`](../ui/dr-ui/src/masks_ui.rs#L976), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2046`](../ui/dr-ui/ui/app.slint#L2046), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | -| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1045`](../ui/dr-ui/src/collections_ui.rs#L1045), [`ui/dr-ui/src/collections_ui.rs:1055`](../ui/dr-ui/src/collections_ui.rs#L1055), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:126`](../ui/dr-ui/src/collections_ui.rs#L126), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1569`](../ui/dr-ui/src/collections_ui.rs#L1569), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1821`](../ui/dr-ui/src/collections_ui.rs#L1821), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1964`](../ui/dr-ui/src/collections_ui.rs#L1964), [`ui/dr-ui/src/collections_ui.rs:2926`](../ui/dr-ui/src/collections_ui.rs#L2926), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:589`](../ui/dr-ui/src/collections_ui.rs#L589), [`ui/dr-ui/src/gesture_book.rs:3`](../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/library_ui.rs:5483`](../ui/dr-ui/src/library_ui.rs#L5483), [`ui/dr-ui/src/library_ui.rs:5553`](../ui/dr-ui/src/library_ui.rs#L5553), [`ui/dr-ui/src/library_ui.rs:5663`](../ui/dr-ui/src/library_ui.rs#L5663), [`ui/dr-ui/src/library_ui.rs:5691`](../ui/dr-ui/src/library_ui.rs#L5691), [`ui/dr-ui/src/library_ui.rs:6540`](../ui/dr-ui/src/library_ui.rs#L6540), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/ui/app.slint:1886`](../ui/dr-ui/ui/app.slint#L1886), [`ui/dr-ui/ui/app.slint:566`](../ui/dr-ui/ui/app.slint#L566), [`ui/dr-ui/ui/app.slint:595`](../ui/dr-ui/ui/app.slint#L595), [`ui/dr-ui/ui/app.slint:604`](../ui/dr-ui/ui/app.slint#L604), [`ui/dr-ui/ui/app.slint:689`](../ui/dr-ui/ui/app.slint#L689), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1048`](../ui/dr-ui/ui/library.slint#L1048), [`ui/dr-ui/ui/library.slint:1252`](../ui/dr-ui/ui/library.slint#L1252), [`ui/dr-ui/ui/library.slint:1277`](../ui/dr-ui/ui/library.slint#L1277), [`ui/dr-ui/ui/library.slint:1284`](../ui/dr-ui/ui/library.slint#L1284), [`ui/dr-ui/ui/library.slint:1317`](../ui/dr-ui/ui/library.slint#L1317), [`ui/dr-ui/ui/library.slint:1374`](../ui/dr-ui/ui/library.slint#L1374), [`ui/dr-ui/ui/library.slint:1381`](../ui/dr-ui/ui/library.slint#L1381), [`ui/dr-ui/ui/library.slint:2565`](../ui/dr-ui/ui/library.slint#L2565), [`ui/dr-ui/ui/library.slint:4079`](../ui/dr-ui/ui/library.slint#L4079), [`ui/dr-ui/ui/library.slint:4195`](../ui/dr-ui/ui/library.slint#L4195), [`ui/dr-ui/ui/library.slint:960`](../ui/dr-ui/ui/library.slint#L960), [`ui/dr-ui/ui/library.slint:987`](../ui/dr-ui/ui/library.slint#L987), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993) | -| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2751`](../ui/dr-ui/src/lib.rs#L2751), [`ui/dr-ui/src/lib.rs:3362`](../ui/dr-ui/src/lib.rs#L3362), [`ui/dr-ui/src/lib.rs:3547`](../ui/dr-ui/src/lib.rs#L3547), [`ui/dr-ui/src/masks_ui.rs:931`](../ui/dr-ui/src/masks_ui.rs#L931), [`ui/dr-ui/ui/app.slint:121`](../ui/dr-ui/ui/app.slint#L121), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) | +| FR-UI-1 | [`ui/dr-ui/src/lib.rs:101`](../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/lib.rs:108`](../ui/dr-ui/src/lib.rs#L108), [`ui/dr-ui/src/lib.rs:3337`](../ui/dr-ui/src/lib.rs#L3337), [`ui/dr-ui/src/masks_ui.rs:884`](../ui/dr-ui/src/masks_ui.rs#L884), [`ui/dr-ui/ui/app.slint:2271`](../ui/dr-ui/ui/app.slint#L2271), [`ui/dr-ui/ui/identity.slint:188`](../ui/dr-ui/ui/identity.slint#L188), [`ui/dr-ui/ui/library.slint:1071`](../ui/dr-ui/ui/library.slint#L1071), [`ui/dr-ui/ui/toolrail.slint:88`](../ui/dr-ui/ui/toolrail.slint#L88) | +| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1045`](../ui/dr-ui/src/collections_ui.rs#L1045), [`ui/dr-ui/src/collections_ui.rs:1055`](../ui/dr-ui/src/collections_ui.rs#L1055), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1569`](../ui/dr-ui/src/collections_ui.rs#L1569), [`ui/dr-ui/src/collections_ui.rs:1821`](../ui/dr-ui/src/collections_ui.rs#L1821), [`ui/dr-ui/src/collections_ui.rs:2926`](../ui/dr-ui/src/collections_ui.rs#L2926), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:101`](../ui/dr-ui/src/lib.rs#L101), [`ui/dr-ui/src/library_ui.rs:360`](../ui/dr-ui/src/library_ui.rs#L360), [`ui/dr-ui/src/library_ui.rs:6407`](../ui/dr-ui/src/library_ui.rs#L6407), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/src/library_ui.rs:7767`](../ui/dr-ui/src/library_ui.rs#L7767), [`ui/dr-ui/ui/app.slint:596`](../ui/dr-ui/ui/app.slint#L596), [`ui/dr-ui/ui/app.slint:60`](../ui/dr-ui/ui/app.slint#L60), [`ui/dr-ui/ui/app.slint:610`](../ui/dr-ui/ui/app.slint#L610), [`ui/dr-ui/ui/app.slint:961`](../ui/dr-ui/ui/app.slint#L961), [`ui/dr-ui/ui/controls.slint:593`](../ui/dr-ui/ui/controls.slint#L593), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1277`](../ui/dr-ui/ui/library.slint#L1277), [`ui/dr-ui/ui/library.slint:1284`](../ui/dr-ui/ui/library.slint#L1284), [`ui/dr-ui/ui/library.slint:1381`](../ui/dr-ui/ui/library.slint#L1381), [`ui/dr-ui/ui/library.slint:2565`](../ui/dr-ui/ui/library.slint#L2565), [`ui/dr-ui/ui/library.slint:4195`](../ui/dr-ui/ui/library.slint#L4195), [`ui/dr-ui/ui/library.slint:960`](../ui/dr-ui/ui/library.slint#L960), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:110`](../ui/dr-ui/ui/settings.slint#L110) | +| FR-UI-3 | [`ui/dr-ui/src/develop.rs:2445`](../ui/dr-ui/src/develop.rs#L2445), [`ui/dr-ui/src/develop.rs:2554`](../ui/dr-ui/src/develop.rs#L2554), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/masks_ui.rs:249`](../ui/dr-ui/src/masks_ui.rs#L249), [`ui/dr-ui/src/masks_ui.rs:976`](../ui/dr-ui/src/masks_ui.rs#L976), [`ui/dr-ui/src/masks_ui.rs:998`](../ui/dr-ui/src/masks_ui.rs#L998), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2052`](../ui/dr-ui/ui/app.slint#L2052), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:747`](../ui/dr-ui/ui/collections.slint#L747), [`ui/dr-ui/ui/masks.slint:612`](../ui/dr-ui/ui/masks.slint#L612) | +| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`ui/dr-ui/src/collections_ui.rs:1045`](../ui/dr-ui/src/collections_ui.rs#L1045), [`ui/dr-ui/src/collections_ui.rs:1055`](../ui/dr-ui/src/collections_ui.rs#L1055), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:126`](../ui/dr-ui/src/collections_ui.rs#L126), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1552`](../ui/dr-ui/src/collections_ui.rs#L1552), [`ui/dr-ui/src/collections_ui.rs:1569`](../ui/dr-ui/src/collections_ui.rs#L1569), [`ui/dr-ui/src/collections_ui.rs:1685`](../ui/dr-ui/src/collections_ui.rs#L1685), [`ui/dr-ui/src/collections_ui.rs:1821`](../ui/dr-ui/src/collections_ui.rs#L1821), [`ui/dr-ui/src/collections_ui.rs:1848`](../ui/dr-ui/src/collections_ui.rs#L1848), [`ui/dr-ui/src/collections_ui.rs:1964`](../ui/dr-ui/src/collections_ui.rs#L1964), [`ui/dr-ui/src/collections_ui.rs:2926`](../ui/dr-ui/src/collections_ui.rs#L2926), [`ui/dr-ui/src/collections_ui.rs:2963`](../ui/dr-ui/src/collections_ui.rs#L2963), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:589`](../ui/dr-ui/src/collections_ui.rs#L589), [`ui/dr-ui/src/gesture_book.rs:3`](../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/library_ui.rs:5483`](../ui/dr-ui/src/library_ui.rs#L5483), [`ui/dr-ui/src/library_ui.rs:5553`](../ui/dr-ui/src/library_ui.rs#L5553), [`ui/dr-ui/src/library_ui.rs:5663`](../ui/dr-ui/src/library_ui.rs#L5663), [`ui/dr-ui/src/library_ui.rs:5691`](../ui/dr-ui/src/library_ui.rs#L5691), [`ui/dr-ui/src/library_ui.rs:6540`](../ui/dr-ui/src/library_ui.rs#L6540), [`ui/dr-ui/src/library_ui.rs:6552`](../ui/dr-ui/src/library_ui.rs#L6552), [`ui/dr-ui/ui/app.slint:1892`](../ui/dr-ui/ui/app.slint#L1892), [`ui/dr-ui/ui/app.slint:572`](../ui/dr-ui/ui/app.slint#L572), [`ui/dr-ui/ui/app.slint:601`](../ui/dr-ui/ui/app.slint#L601), [`ui/dr-ui/ui/app.slint:610`](../ui/dr-ui/ui/app.slint#L610), [`ui/dr-ui/ui/app.slint:695`](../ui/dr-ui/ui/app.slint#L695), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1048`](../ui/dr-ui/ui/library.slint#L1048), [`ui/dr-ui/ui/library.slint:1252`](../ui/dr-ui/ui/library.slint#L1252), [`ui/dr-ui/ui/library.slint:1277`](../ui/dr-ui/ui/library.slint#L1277), [`ui/dr-ui/ui/library.slint:1284`](../ui/dr-ui/ui/library.slint#L1284), [`ui/dr-ui/ui/library.slint:1317`](../ui/dr-ui/ui/library.slint#L1317), [`ui/dr-ui/ui/library.slint:1374`](../ui/dr-ui/ui/library.slint#L1374), [`ui/dr-ui/ui/library.slint:1381`](../ui/dr-ui/ui/library.slint#L1381), [`ui/dr-ui/ui/library.slint:2565`](../ui/dr-ui/ui/library.slint#L2565), [`ui/dr-ui/ui/library.slint:4079`](../ui/dr-ui/ui/library.slint#L4079), [`ui/dr-ui/ui/library.slint:4195`](../ui/dr-ui/ui/library.slint#L4195), [`ui/dr-ui/ui/library.slint:960`](../ui/dr-ui/ui/library.slint#L960), [`ui/dr-ui/ui/library.slint:987`](../ui/dr-ui/ui/library.slint#L987), [`ui/dr-ui/ui/library.slint:993`](../ui/dr-ui/ui/library.slint#L993) | +| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2765`](../ui/dr-ui/src/lib.rs#L2765), [`ui/dr-ui/src/lib.rs:3376`](../ui/dr-ui/src/lib.rs#L3376), [`ui/dr-ui/src/lib.rs:3561`](../ui/dr-ui/src/lib.rs#L3561), [`ui/dr-ui/src/masks_ui.rs:931`](../ui/dr-ui/src/masks_ui.rs#L931), [`ui/dr-ui/ui/app.slint:127`](../ui/dr-ui/ui/app.slint#L127), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) | | FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../ui/dr-ui/ui/widgets.slint#L1) | | FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:395`](../core/dr-pipeline/src/framing.rs#L395) | | FR-UI-8 | [`core/dr-catalog/src/collections.rs:555`](../core/dr-catalog/src/collections.rs#L555), [`core/dr-catalog/src/collections.rs:576`](../core/dr-catalog/src/collections.rs#L576), [`core/dr-types/src/place.rs:1`](../core/dr-types/src/place.rs#L1), [`ui/dr-ui/src/derived_sync.rs:188`](../ui/dr-ui/src/derived_sync.rs#L188), [`ui/dr-ui/src/derived_sync.rs:657`](../ui/dr-ui/src/derived_sync.rs#L657), [`ui/dr-ui/src/derived_sync.rs:667`](../ui/dr-ui/src/derived_sync.rs#L667), [`ui/dr-ui/src/derived_sync.rs:749`](../ui/dr-ui/src/derived_sync.rs#L749), [`ui/dr-ui/src/derived_sync.rs:74`](../ui/dr-ui/src/derived_sync.rs#L74), [`ui/dr-ui/src/library.rs:1072`](../ui/dr-ui/src/library.rs#L1072), [`ui/dr-ui/src/library.rs:4248`](../ui/dr-ui/src/library.rs#L4248), [`ui/dr-ui/src/library.rs:4966`](../ui/dr-ui/src/library.rs#L4966), [`ui/dr-ui/src/library.rs:5032`](../ui/dr-ui/src/library.rs#L5032), [`ui/dr-ui/src/library_ui.rs:195`](../ui/dr-ui/src/library_ui.rs#L195), [`ui/dr-ui/src/library_ui.rs:2104`](../ui/dr-ui/src/library_ui.rs#L2104), [`ui/dr-ui/src/library_ui.rs:2293`](../ui/dr-ui/src/library_ui.rs#L2293), [`ui/dr-ui/src/library_ui.rs:2310`](../ui/dr-ui/src/library_ui.rs#L2310), [`ui/dr-ui/src/library_ui.rs:2408`](../ui/dr-ui/src/library_ui.rs#L2408), [`ui/dr-ui/src/library_ui.rs:5504`](../ui/dr-ui/src/library_ui.rs#L5504), [`ui/dr-ui/src/library_ui.rs:5544`](../ui/dr-ui/src/library_ui.rs#L5544), [`ui/dr-ui/src/library_ui.rs:5573`](../ui/dr-ui/src/library_ui.rs#L5573), [`ui/dr-ui/src/library_ui.rs:5836`](../ui/dr-ui/src/library_ui.rs#L5836), [`ui/dr-ui/src/library_ui.rs:6059`](../ui/dr-ui/src/library_ui.rs#L6059), [`ui/dr-ui/src/library_ui.rs:6781`](../ui/dr-ui/src/library_ui.rs#L6781), [`ui/dr-ui/src/library_ui.rs:859`](../ui/dr-ui/src/library_ui.rs#L859), [`ui/dr-ui/src/library_ui.rs:892`](../ui/dr-ui/src/library_ui.rs#L892), [`ui/dr-ui/src/library_ui.rs:938`](../ui/dr-ui/src/library_ui.rs#L938), [`ui/dr-ui/src/place.rs:1`](../ui/dr-ui/src/place.rs#L1) | | NFR-A11Y-2 | [`ui/dr-ui/tests/ui_controls_are_accessible.rs:1`](../ui/dr-ui/tests/ui_controls_are_accessible.rs#L1) | | NFR-A11Y-3 | [`ui/dr-ui/src/histogram.rs:356`](../ui/dr-ui/src/histogram.rs#L356), [`ui/dr-ui/ui/histogram.slint:137`](../ui/dr-ui/ui/histogram.slint#L137), [`ui/dr-ui/ui/library.slint:756`](../ui/dr-ui/ui/library.slint#L756), [`ui/dr-ui/ui/library.slint:904`](../ui/dr-ui/ui/library.slint#L904), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1) | | NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077) | -| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1809`](../ui/dr-ui/src/export.rs#L1809), [`ui/dr-ui/src/export.rs:1836`](../ui/dr-ui/src/export.rs#L1836), [`ui/dr-ui/src/export.rs:412`](../ui/dr-ui/src/export.rs#L412), [`ui/dr-ui/src/export.rs:438`](../ui/dr-ui/src/export.rs#L438), [`ui/dr-ui/src/lib.rs:2448`](../ui/dr-ui/src/lib.rs#L2448), [`ui/dr-ui/ui/app.slint:1134`](../ui/dr-ui/ui/app.slint#L1134), [`ui/dr-ui/ui/library.slint:4157`](../ui/dr-ui/ui/library.slint#L4157) | +| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1809`](../ui/dr-ui/src/export.rs#L1809), [`ui/dr-ui/src/export.rs:1836`](../ui/dr-ui/src/export.rs#L1836), [`ui/dr-ui/src/export.rs:412`](../ui/dr-ui/src/export.rs#L412), [`ui/dr-ui/src/export.rs:438`](../ui/dr-ui/src/export.rs#L438), [`ui/dr-ui/src/lib.rs:2462`](../ui/dr-ui/src/lib.rs#L2462), [`ui/dr-ui/ui/app.slint:1140`](../ui/dr-ui/ui/app.slint#L1140), [`ui/dr-ui/ui/library.slint:4157`](../ui/dr-ui/ui/library.slint#L4157) | | NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../core/dr-thumbs/src/error.rs#L1), [`platform/dr-plat/src/storage.rs:148`](../platform/dr-plat/src/storage.rs#L148), [`ui/dr-ui/src/export.rs:514`](../ui/dr-ui/src/export.rs#L514) | | NFR-OPS-1 | [`platform/dr-plat/src/diagnostics.rs:1`](../platform/dr-plat/src/diagnostics.rs#L1), [`platform/dr-plat/src/state.rs:1`](../platform/dr-plat/src/state.rs#L1) | | NFR-OPS-2 | [`platform/dr-plat/src/crash.rs:1`](../platform/dr-plat/src/crash.rs#L1) | | NFR-OPS-3 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`tools/bench/src/catalog_open.rs:1`](../tools/bench/src/catalog_open.rs#L1) | | NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | -| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop.rs:3342`](../ui/dr-ui/src/develop.rs#L3342), [`ui/dr-ui/src/lib.rs:1814`](../ui/dr-ui/src/lib.rs#L1814) | +| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop.rs:3353`](../ui/dr-ui/src/develop.rs#L3353), [`ui/dr-ui/src/lib.rs:1814`](../ui/dr-ui/src/lib.rs#L1814) | | NFR-P3 | [`tools/bench/src/thumbnails.rs:1`](../tools/bench/src/thumbnails.rs#L1) | | NFR-P5 | [`core/dr-catalog/src/schema.rs:337`](../core/dr-catalog/src/schema.rs#L337), [`ui/dr-ui/src/library.rs:4355`](../ui/dr-ui/src/library.rs#L4355), [`ui/dr-ui/src/library.rs:5460`](../ui/dr-ui/src/library.rs#L5460), [`ui/dr-ui/src/library_ui.rs:4711`](../ui/dr-ui/src/library_ui.rs#L4711), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | | NFR-P9 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/export.rs:992`](../ui/dr-ui/src/export.rs#L992), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1) | @@ -150,7 +150,7 @@ _None._ | NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:255`](../core/dr-catalog/src/lib.rs#L255), [`core/dr-catalog/src/recovery.rs:1`](../core/dr-catalog/src/recovery.rs#L1) | | NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | | NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | -| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3447`](../ui/dr-ui/src/develop.rs#L3447), [`ui/dr-ui/src/identity_ui.rs:121`](../ui/dr-ui/src/identity_ui.rs#L121), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | +| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3458`](../ui/dr-ui/src/develop.rs#L3458), [`ui/dr-ui/src/identity_ui.rs:121`](../ui/dr-ui/src/identity_ui.rs#L121), [`ui/dr-ui/src/lib.rs:93`](../ui/dr-ui/src/lib.rs#L93), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | NFR-RES-2 | [`ui/dr-ui/src/faces.rs:346`](../ui/dr-ui/src/faces.rs#L346), [`ui/dr-ui/src/library.rs:3077`](../ui/dr-ui/src/library.rs#L3077), [`ui/dr-ui/src/library.rs:3268`](../ui/dr-ui/src/library.rs#L3268) | | NFR-RES-4 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-catalog/src/schema.rs:725`](../core/dr-catalog/src/schema.rs#L725), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/library.rs:2984`](../ui/dr-ui/src/library.rs#L2984) | | NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) | diff --git a/ui/dr-ui/Cargo.toml b/ui/dr-ui/Cargo.toml index 8d2aded..64b0ad9 100644 --- a/ui/dr-ui/Cargo.toml +++ b/ui/dr-ui/Cargo.toml @@ -34,6 +34,12 @@ dr-sync-nextcloud.workspace = true dr-export.workspace = true dr-ingest.workspace = true dr-film.workspace = true +# The lens profile database, here for the same reason dr-film is: dr-pipeline +# knows the maths of lens correction and deliberately has no dependency with +# which to find out which coefficients belong to which lens. The conversion +# between the two crates' mirrored coefficient types happens in `develop.rs`, +# because it is the only place that can see both. +dr-lens.workspace = true dr-pipeline.workspace = true dr-catalog.workspace = true # The face pipeline, with the ONNX runtime: this is the layer that actually diff --git a/ui/dr-ui/src/develop.rs b/ui/dr-ui/src/develop.rs index a73dbb0..1943ad9 100644 --- a/ui/dr-ui/src/develop.rs +++ b/ui/dr-ui/src/develop.rs @@ -728,6 +728,14 @@ pub struct DevelopSession { /// the memory of where the pixels came from; the allowlist that turns it /// into something writable stays the one function in `export.rs`. source_meta: Option, + /// Whether the lens in the header matched a profile in the database. + /// + /// A separate flag rather than `graph.lens_profile().is_some()`, because + /// the two answer different questions once the photographer starts work: + /// the graph says what is *applied*, which a manual correction also + /// satisfies, and this says whether a *measurement* was found. Only the + /// second can honestly caption "no profile". + lens_profile_found: bool, /// Kept so the session can build GPU resources after construction. /// /// The distance fields behind a subject mask are made when a layer is @@ -955,6 +963,9 @@ impl DevelopSession { // built straight from pixels — a test, `masks_ui`'s fixture — // honestly has no header, and says so. source_meta: None, + // Nothing has been looked up, which is not the same as "looked up + // and not found" — `lens_summary` distinguishes them. + lens_profile_found: false, ctx: ctx.clone(), graph, history, @@ -3479,9 +3490,106 @@ impl DevelopSession { /// its header together — every other way of making a session starts from /// pixels that never had a file behind them. pub fn set_source_metadata(&mut self, meta: dr_decode::Metadata) { + // The lens profile is applied *here* rather than by the caller, and + // that is the point of putting it in this method. This is the one + // place a session is told which file it came from, so it is the one + // place the lookup can be made unforgettable — the same shape + // `FilmRebake` uses to stop a derived thing being quietly skipped. + self.apply_lens_profile(&meta); self.source_meta = Some(meta); } + /// TRACES: FR-DEV-3 + /// Look this shot's lens up and hand the coefficients to the corrections. + /// + /// Called with **every** header, including ones naming no lens: the + /// clearing case matters as much as the setting one, because a session + /// reused for a second photograph would otherwise correct it for the + /// optics of the first. + fn apply_lens_profile(&mut self, meta: &dr_decode::Metadata) { + let found = Self::profile_for(meta); + self.lens_profile_found = found.is_some(); + self.graph.set_lens_profile(found); + } + + /// The profile for one shot, converted into the pipeline's own types. + /// + /// **The conversion lives here because nowhere else can see both sides.** + /// `dr-lens` carries the Lensfun database and `dr-pipeline` carries the + /// maths, and the coefficient structs are deliberately duplicated so that + /// the dependency between them does not exist (ARCH §6.5a). This function + /// is the seam, and it is a `match` on three optionals. + /// + /// Every field is taken independently. The database routinely knows a + /// lens's distortion and not its vignetting, or covers only part of a + /// zoom's range, and a partial profile is worth applying — discarding it + /// because one field is missing would turn a good correction into none. + fn profile_for(meta: &dr_decode::Metadata) -> Option { + // All three are needed to ask the question at all. A lens name alone + // does not identify a correction: distortion is interpolated across a + // zoom's focal range and vignetting depends strongly on aperture — a + // fast prime can be two stops down in the corners wide open and clean + // by f/8 — so a lookup missing either would return a profile measured + // for a shot nobody took. + let (lens, focal, aperture) = (meta.lens.as_deref()?, meta.focal_length?, meta.aperture?); + + let shot = dr_lens::ShotInfo::new(lens, focal, aperture); + let found = dr_lens::lookup(&shot)?; + if found.is_empty() { + return None; + } + + Some(dr_pipeline::LensProfile { + distortion: found + .distortion + .map(|d| dr_pipeline::ops::distortion::PtLens { + a: d.a, + b: d.b, + c: d.c, + }), + tca: found.tca.map(|t| dr_pipeline::Tca { + red_scale: t.red_scale, + blue_scale: t.blue_scale, + }), + vignetting: found.vignetting.map(|v| dr_pipeline::ops::vignetting::Pa { + k1: v.k1, + k2: v.k2, + k3: v.k3, + }), + }) + } + + /// TRACES: FR-DEV-3 + /// What to tell the photographer about the automatic lens correction. + /// + /// `dr-lens` states the rule this exists to satisfy: an automatic + /// correction that silently did nothing is worse than one the user can see + /// is unavailable. Most lenses in most photographs will not be in the + /// database — third-party glass often reports nothing, adapted manual + /// lenses report nothing at all — so "no profile" is the ordinary case and + /// has to read as a fact rather than as a failure. + pub fn lens_summary(&self) -> String { + let Some(meta) = self.source_meta.as_ref() else { + return String::new(); + }; + let Some(lens) = meta + .lens + .as_deref() + .map(str::trim) + .filter(|l| !l.is_empty()) + else { + // Not "no profile found": nothing was looked up, because the file + // does not say what it was taken with. Naming the wrong reason + // would send someone hunting for a profile that was never missing. + return "Lens not recorded".into(); + }; + if self.lens_profile_found { + format!("{lens} · corrected") + } else { + format!("{lens} · no profile") + } + } + /// TRACES: FR-EXP-8 /// The header this session was opened from, where there was one. /// @@ -4814,6 +4922,136 @@ mod tests { .clone() } + /// A lookup needs all three of lens, focal length and aperture. + /// + /// Not pedantry about missing fields: distortion is interpolated across a + /// zoom's focal range and vignetting depends strongly on aperture, so a + /// lookup done without them would return coefficients measured for a shot + /// nobody took and apply them with full confidence. Refusing is the honest + /// answer, and the panel says so. + #[test] + fn a_lookup_needs_the_whole_shot_and_not_just_the_lens() { + let complete = dr_decode::Metadata { + lens: Some("Nikon AF-S 50mm f/1.8G".into()), + focal_length: Some(50.0), + aperture: Some(1.8), + ..Default::default() + }; + + for (name, meta) in [ + ( + "no lens", + dr_decode::Metadata { + lens: None, + ..complete.clone() + }, + ), + ( + "no focal length", + dr_decode::Metadata { + focal_length: None, + ..complete.clone() + }, + ), + ( + "no aperture", + dr_decode::Metadata { + aperture: None, + ..complete.clone() + }, + ), + ] { + assert!( + DevelopSession::profile_for(&meta).is_none(), + "{name}: a partial header must not produce a confident profile" + ); + } + } + + /// "Not recorded" and "no profile" are different facts. + /// + /// Collapsing them would send someone hunting for a missing profile when + /// the file simply never said what took the photograph — and `dr-lens`'s + /// own rule is that the interface must be plain about which it is, because + /// a correction that silently did nothing is worse than one visibly + /// unavailable. + #[test] + fn the_lens_line_says_which_kind_of_nothing_it_found() { + let Some(ctx) = headless() else { return }; + let rgba: Vec = (0..8 * 8).flat_map(|_| [128u8, 128, 128, 255]).collect(); + + let session = |meta: dr_decode::Metadata| { + let mut s = DevelopSession::open_rgb(&ctx, &rgba, 8, 8, dr_types::Orientation::NORMAL) + .expect("session"); + s.set_source_metadata(meta); + s + }; + + // A session that was never given a header at all. + let bare = DevelopSession::open_rgb(&ctx, &rgba, 8, 8, dr_types::Orientation::NORMAL) + .expect("session"); + assert_eq!(bare.lens_summary(), ""); + + let unrecorded = session(dr_decode::Metadata::default()); + assert_eq!(unrecorded.lens_summary(), "Lens not recorded"); + + // A name no database will match. Deliberately absurd rather than a real + // obscure lens, so the test cannot start passing for the wrong reason + // if the bundled database grows. + let unmatched = session(dr_decode::Metadata { + lens: Some("Nonexistent 999mm f/0.5".into()), + focal_length: Some(999.0), + aperture: Some(0.5), + ..Default::default() + }); + assert_eq!( + unmatched.lens_summary(), + "Nonexistent 999mm f/0.5 · no profile" + ); + assert!( + unmatched.graph.lens_profile().is_none(), + "an unmatched lens must leave the corrections alone" + ); + } + + /// Opening a second photograph must not correct it for the first one's lens. + /// + /// The clearing case, and the reason `apply_lens_profile` runs on every + /// header rather than only on the ones that match something. A stale + /// profile is invisible: the picture is simply wrong in a way that looks + /// like the lens. + #[test] + fn a_second_photograph_does_not_inherit_the_first_lens_profile() { + let Some(ctx) = headless() else { return }; + let rgba: Vec = (0..8 * 8).flat_map(|_| [128u8, 128, 128, 255]).collect(); + let mut session = + DevelopSession::open_rgb(&ctx, &rgba, 8, 8, dr_types::Orientation::NORMAL) + .expect("session"); + + // Stand in for a matched lens by applying a profile directly, so the + // test does not depend on what the bundled database happens to hold. + session + .graph + .set_lens_profile(Some(dr_pipeline::LensProfile { + distortion: Some(dr_pipeline::ops::distortion::PtLens { + a: 0.0, + b: -0.02, + c: 0.0, + }), + tca: None, + vignetting: None, + })); + assert!(session.graph.lens_profile().is_some()); + + session.set_source_metadata(dr_decode::Metadata::default()); + + assert!( + session.graph.lens_profile().is_none(), + "a header naming no lens must clear the previous photograph's \ + correction, not leave it standing" + ); + } + /// TRACES: FR-DEV-3 /// A gradient needs no segmentation, and until now it silently got no mask. /// diff --git a/ui/dr-ui/src/lib.rs b/ui/dr-ui/src/lib.rs index 3aac316..9b3c8f3 100644 --- a/ui/dr-ui/src/lib.rs +++ b/ui/dr-ui/src/lib.rs @@ -1995,6 +1995,19 @@ pub fn run(paths: Vec) -> Result<()> { window.set_exposure(describe_exposure(&l.meta).into()); window.set_dimensions(format!("{} × {}", l.width, l.height).into()); + // Composed by the session rather than here, because only it + // knows whether the lookup found anything — the header can + // name a lens the database has never heard of, which is the + // ordinary case for third-party and adapted glass and must + // read as a fact rather than as a failure. + window.set_lens( + l.session + .as_ref() + .map(|s| s.lens_summary()) + .unwrap_or_default() + .into(), + ); + // The panel is built from what the pipeline reports, so // this code names no operation (FR-DEV-3a). match l.session { @@ -2041,6 +2054,7 @@ pub fn run(paths: Vec) -> Result<()> { window.set_camera("".into()); window.set_exposure("".into()); window.set_dimensions("".into()); + window.set_lens("".into()); } } }) diff --git a/ui/dr-ui/ui/app.slint b/ui/dr-ui/ui/app.slint index 658a3d0..1595120 100644 --- a/ui/dr-ui/ui/app.slint +++ b/ui/dr-ui/ui/app.slint @@ -65,6 +65,12 @@ export component AppWindow inherits Window { // Current image, for the status strip and empty state. in property filename: ""; in property camera: ""; + /// TRACES: FR-DEV-3 + /// The lens the file names, and whether a correction profile was found for + /// it. Composed in Rust because the sentence depends on a database lookup + /// this file cannot see, and because "not recorded" and "no profile" are + /// different facts that must not be collapsed into one. + in property lens: ""; in property exposure: ""; in property dimensions: ""; in property index: 0; @@ -2416,6 +2422,7 @@ in property panel-visible: true; column := VerticalLayout { if !root.local-mode && !root.repairing: InfoPanel { camera: root.camera; + lens: root.lens; exposure: root.exposure; dimensions: root.dimensions; } diff --git a/ui/dr-ui/ui/develop.slint b/ui/dr-ui/ui/develop.slint index cbb3643..ef73f34 100644 --- a/ui/dr-ui/ui/develop.slint +++ b/ui/dr-ui/ui/develop.slint @@ -217,6 +217,17 @@ export component StatusBar inherits Rectangle { // wanted, and the per-group lids only stood between the user and the controls. export component InfoPanel inherits Rectangle { in property camera; + /// TRACES: FR-DEV-3 + /// The lens, and whether it matched a correction profile. + /// + /// Here rather than beside the optical sliders, and the reason is what the + /// line is *for*. `dr-lens` states the rule — an automatic correction that + /// silently did nothing is worse than one the user can see is unavailable + /// — and the fact it reports is a fact about the file: which lens took + /// this photograph, and whether the database has heard of it. That is the + /// same kind of thing as the body and the exposure, and it wants reading + /// once on opening rather than hunting for under a group filter. + in property lens; in property exposure; in property dimensions; @@ -239,6 +250,16 @@ export component InfoPanel inherits Rectangle { Label { text: root.exposure; } + // Dim, like the dimensions below it: this is something the file says, + // not something the photographer chose. An empty string draws nothing + // — a session built from pixels with no header has no lens to report, + // and an empty row is honest where "Unknown" would be noise. + Caption { + text: root.lens; + visible: root.lens != ""; + wrap: word-wrap; + } + Caption { text: root.dimensions; } } } From 2841eaf9a1e5c2e0bbb058816d8e739618a13801 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 5 Sep 2026 15:14:15 +0200 Subject: [PATCH 5/5] Take the layer-chain test's closure down to &dyn Operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `clippy::borrowed_box` is denied by the workspace lint set, and the closure added with the optics exclusion took `&Box` — a borrow of the box rather than of the thing in it, which says nothing the plain trait object does not. Caught by `cargo clippy --workspace --all-targets -- -D warnings`, which is what CI runs and what the workspace tests do not: a lint on test code only appears when the tests are compiled as a clippy target. Co-Authored-By: Claude Opus 5 (1M context) --- core/dr-pipeline/src/mask.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/dr-pipeline/src/mask.rs b/core/dr-pipeline/src/mask.rs index 86b2db2..29f752b 100644 --- a/core/dr-pipeline/src/mask.rs +++ b/core/dr-pipeline/src/mask.rs @@ -1500,7 +1500,7 @@ mod tests { let ids: Vec<&str> = layer.capabilities().iter().map(|c| c.id.0).collect(); let global = crate::ops::chain(); - let local_means_something = |op: &Box| { + let local_means_something = |op: &dyn Operation| { op.detail().is_none() && !op .descriptor() @@ -1512,14 +1512,15 @@ mod tests { let id = op.descriptor().id.0; assert_eq!( ids.contains(&id), - local_means_something(op), + local_means_something(op.as_ref()), "{id} is offered as a local adjustment but cannot meaningfully \ be one, or is an ordinary point operation and has gone \ missing from a layer" ); } assert!( - ids.len() < global.len() || global.iter().all(local_means_something), + ids.len() < global.len() + || global.iter().all(|op| local_means_something(op.as_ref())), "the filter dropped nothing, so either it is not running or the \ chain has nothing left that a layer cannot carry" );