diff --git a/core/dr-decode/profiles/base_curves.yaml b/core/dr-decode/profiles/base_curves.yaml deleted file mode 100644 index 4e21f33..0000000 --- a/core/dr-decode/profiles/base_curves.yaml +++ /dev/null @@ -1,160 +0,0 @@ -# DarkRoom camera base curves (FR-DEV-3e). -# -# --------------------------------------------------------------------------- -# Adding a body is editing this file. It is not a code change. -# --------------------------------------------------------------------------- -# -# The copy you are reading is compiled into the binary as a floor. At startup -# `dr_decode::base_curve::load` also looks for `base_curves.yaml` in: -# -# 1. $DARKROOM_PROFILES/ (set it while you are tuning) -# 2. $XDG_DATA_HOME/darkroom/profiles/ -# or $HOME/.local/share/darkroom/profiles/ -# -# and uses the first one it finds *whose `version:` is higher than this one's*. -# So: bump `version`, drop the file in that directory, restart. A body added -# this afternoon renders correctly this afternoon, with no release and no -# rebuild — which is what the requirement asks for, and what makes these -# contributable under the GPL. -# -# The version check runs both ways on purpose. A file older than the built-in -# copy is ignored with a log line, so upgrading DarkRoom cannot silently lose -# curves to a pack somebody downloaded a year ago. -# -# --------------------------------------------------------------------------- -# What the numbers mean -# --------------------------------------------------------------------------- -# -# Five `[x, y]` control points on a monotone spline (Fritsch-Carlson, the same -# one the tone curve widget draws). Both axes are **linear**: -# -# x scene-referred camera RGB after white balance, 1.0 = sensor saturation -# y display-referred linear; the sRGB transfer function is applied later, -# at the end of the shader, so do not pre-apply a gamma here -# -# The identity is y = x, and it is what an unrecognised body gets if `default:` -# is removed. It is also the wrong answer for almost every photograph: linear -# scene data has middle grey at about 13% and a camera JPEG puts it near 18%, -# so an uncurved render is roughly half a stop dark through the midtones and -# has no highlight rolloff at all. -# -# A curve that works has three parts, and it is worth naming them because they -# are what you are actually tuning: -# -# the toe the first span, slope near or below 1. Deep shadows stay -# deep. Lift it and blacks go milky; crush it and shadow -# detail the sensor recorded disappears. -# the midtones the middle spans, slope well above 1. This is the contrast -# and the brightness people read as "the camera's look". -# the shoulder the last span, slope well below 1. Highlights compress -# toward white instead of arriving there and clipping. It is -# the difference between a rolled-off sky and a white hole. -# -# Two invariants are enforced in code and tested, so a mistake here fails the -# build rather than the photograph: x must strictly increase, y must not -# decrease, and everything must lie inside the unit square. -# -# --------------------------------------------------------------------------- -# Honesty about these values -# --------------------------------------------------------------------------- -# -# These are hand-tuned shapes, not measurements. They encode what every camera -# JPEG rendering has in common — the toe/midtone/shoulder structure above — -# plus each maker's well-known house differences: Canon's gentler shoulder and -# warmer-reading midtones, Nikon's slightly higher midtone contrast, Sony's -# flatter and more conservative default, Fujifilm's markedly contrastier -# Provia-derived rendering. -# -# FR-DEV-3e's acceptance criterion is subjective comparison against each body's -# own JPEG, and meeting it properly needs a frame from that body in front of -# you. Where that has not been done, the entry is still much closer to right -# than the identity — which is the bar these have to clear, and do. - -version: 1 - -# The rendering for a body with no entry of its own. -# -# **Deliberately not the identity.** The failure this requirement exists to fix -# is the flat render, and a conservative curve is far closer to right for every -# body than no curve is for any of them. It is gentler than the per-body -# entries below — a shallower midtone and an earlier, softer shoulder — because -# it has to be safe on a sensor nobody has looked at, and the cost of being too -# tame is a photograph that wants a little contrast rather than one that has -# lost its highlights. -default: - points: - - [0.00, 0.000] - - [0.04, 0.043] - - [0.13, 0.175] - - [0.45, 0.690] - - [1.00, 1.000] - -bodies: - # Canon. A soft toe and a long, gradual shoulder — the reason Canon files - # are described as forgiving in highlights and a little low in contrast - # straight out of camera. - - make: Canon - model: EOS 6D - points: - - [0.00, 0.000] - - [0.04, 0.045] - - [0.13, 0.190] - - [0.45, 0.720] - - [1.00, 1.000] - - - make: Canon - model: EOS R6 - points: - - [0.00, 0.000] - - [0.04, 0.044] - - [0.13, 0.195] - - [0.45, 0.730] - - [1.00, 1.000] - - # Nikon. A slightly deeper toe and more midtone slope than Canon, which is - # the "punchier out of camera" difference people describe between the two. - - make: Nikon - model: Z 6 - points: - - [0.00, 0.000] - - [0.04, 0.038] - - [0.13, 0.200] - - [0.46, 0.750] - - [1.00, 1.000] - - - make: Nikon - model: D750 - points: - - [0.00, 0.000] - - [0.04, 0.039] - - [0.13, 0.198] - - [0.46, 0.745] - - [1.00, 1.000] - - # Sony. The flattest default of the four, and intentionally so — Sony's own - # rendering leaves more headroom than it uses, which is why Sony files are - # the ones people describe as needing the most work. - - make: Sony - model: ILCE-7M3 - points: - - [0.00, 0.000] - - [0.04, 0.048] - - [0.13, 0.185] - - [0.44, 0.700] - - [1.00, 1.000] - - # Fujifilm. Provia, the default film simulation: a firm toe, the steepest - # midtones here, and a hard shoulder. It is the most distinctive rendering of - # the four and the one where a flat render looks most obviously wrong. - # - # This entry does *not* read the in-RAF film simulation tag — that is - # FR-DEV-3f, and until it lands every Fujifilm file gets the Provia shape - # whatever the camera was set to. - - make: Fujifilm - model: X-T3 - points: - - [0.00, 0.000] - - [0.045, 0.040] - - [0.14, 0.215] - - [0.47, 0.775] - - [1.00, 1.000] diff --git a/core/dr-decode/src/base_curve.rs b/core/dr-decode/src/base_curve.rs deleted file mode 100644 index 5cc19fb..0000000 --- a/core/dr-decode/src/base_curve.rs +++ /dev/null @@ -1,752 +0,0 @@ -//! TRACES: FR-DEV-3e -//! Base curves — the per-body rendering that turns a correct exposure into a -//! photograph. -//! -//! # What this is for -//! -//! A camera matrix gets the *colours* right and leaves the picture flat. Sensor -//! data is scene-referred and very nearly linear; a print, a screen and a -//! camera's own JPEG are none of those things. Rendering linear data straight -//! out is the dcraw default, and FR-DEV-3e names it precisely: "the flat, -//! poor-skin-tone rendering characteristic of dcraw defaults, which is the -//! documented reason people abandon darktable in the first hour." -//! -//! The fix is a tone curve applied as part of *reading* the file rather than as -//! an edit — a toe, a steep midtone, and a shoulder that rolls highlights off -//! instead of clipping them. Every raw converter has one. Adobe calls it the -//! camera profile's tone curve, darktable calls it the base curve, and the name -//! here follows darktable's because the placement does too: it runs in camera -//! RGB, after white balance and the user's adjustments, immediately before the -//! conversion out to a working space. -//! -//! # Why it is not an edit -//! -//! It never reaches the sidecar and there is no slider for it, for the same -//! reason the EXIF orientation is not an edit (FR-DEV-3h): it is a property of -//! the body that took the frame, not of what anyone decided about the frame. -//! Sidecars are shared between devices and bodies (FR-NC-9), and one camera's -//! rendering must not follow an edit onto another camera's file. -//! -//! # Why it is data -//! -//! FR-DEV-3e requires the profile database to be "versioned independently of -//! the app binary so bodies and curves can be added without a release — and, -//! under D8's GPLv3, contributed by users". So the curves live in -//! `profiles/base_curves.yaml`, a file that is compiled in as a floor and -//! *overridden* by a copy on disk carrying a higher `version:`. Adding a body -//! is adding ten numbers to a YAML file; shipping that body to users is -//! publishing the file. Neither is a code change and neither needs a release. -//! -//! See [`load`] for the search path and [`Curves::body`] for the matching. - -use std::path::{Path, PathBuf}; -use std::sync::OnceLock; - -/// How many control points a base curve has. -/// -/// Five, which is not a coincidence: it is what the tone curve widget uses -/// (`dr_pipeline::ops::curve::POINTS`), so the shader evaluates a profile's -/// curve and a photographer's curve through exactly the same spline. A profile -/// author and a photographer dragging a point mean the same thing by it, and -/// the generated shader carries one implementation rather than two that could -/// disagree. -pub const POINTS: usize = 5; - -/// TRACES: FR-DEV-3e -/// A base curve: five points on a monotone spline through the unit square. -/// -/// `xs` is scene-linear camera RGB, normalised so that 1.0 is the sensor's -/// saturation point. `ys` is display-referred linear — *not* gamma-encoded, -/// because the sRGB transfer function is applied at the very end of the -/// generated shader and applying it twice would wash the image out. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct BaseCurve { - pub xs: [f32; POINTS], - pub ys: [f32; POINTS], -} - -impl BaseCurve { - /// The curve that does nothing — the identity diagonal. - /// - /// What an unrecognised body gets if the database carries no default, and - /// what a JPEG gets always: an already-rendered image must not be rendered - /// a second time. - pub const IDENTITY: Self = Self { - xs: [0.0, 0.25, 0.5, 0.75, 1.0], - ys: [0.0, 0.25, 0.5, 0.75, 1.0], - }; - - /// Whether this curve would leave the image alone. - /// - /// The shader is told to skip the stage entirely when it would, so an - /// unprofiled body costs a branch that is uniform across the dispatch - /// rather than a spline evaluation per channel per pixel. - pub fn is_identity(&self) -> bool { - self.xs - .iter() - .zip(self.ys.iter()) - .all(|(x, y)| (x - y).abs() < 1e-6) - } - - /// Build from raw pairs, rejecting anything that is not a curve. - /// - /// A profile file is data a user may have edited, so this is the boundary - /// where "ten numbers" becomes "a curve": the x coordinates must increase, - /// the y coordinates must not decrease, and both must lie in the unit - /// square. A non-monotone x sends the spline's span search backwards and - /// divides by a negative width; a decreasing y inverts tones locally, - /// which reads as a dark halo through smooth gradients rather than as a - /// bad profile. - /// - /// Endpoints are not forced to (0,0) and (1,1). A curve that lifts black - /// slightly, or that places the shoulder below white, is a legitimate - /// rendering choice and several bodies make it. - pub fn from_points(points: &[[f32; 2]]) -> Option { - if points.len() != POINTS { - return None; - } - let mut xs = [0.0f32; POINTS]; - let mut ys = [0.0f32; POINTS]; - for (i, p) in points.iter().enumerate() { - if !p[0].is_finite() || !p[1].is_finite() { - return None; - } - if !(0.0..=1.0).contains(&p[0]) || !(0.0..=1.0).contains(&p[1]) { - return None; - } - xs[i] = p[0]; - ys[i] = p[1]; - } - for i in 1..POINTS { - // Strictly increasing in x — the spline divides by the span width. - if xs[i] <= xs[i - 1] { - return None; - } - // Non-decreasing in y. Flat is allowed: a curve that holds a - // highlight range at white is clipping deliberately. - if ys[i] < ys[i - 1] { - return None; - } - } - Some(Self { xs, ys }) - } -} - -/// One body's entry in the database. -#[derive(Debug, Clone, PartialEq)] -pub struct BodyCurve { - /// The manufacturer, as the file writes it — "Canon", "NIKON CORPORATION". - pub make: String, - /// The model, as the file writes it — "EOS 6D", "ILCE-7M3". - pub model: String, - pub curve: BaseCurve, -} - -/// TRACES: FR-DEV-3e -/// The base curve database. -/// -/// Versioned as a whole rather than per body, because that is the unit a user -/// downloads and the unit that has to beat the built-in copy. See [`load`]. -#[derive(Debug, Clone, PartialEq)] -pub struct Curves { - version: u32, - default: Option, - bodies: Vec, -} - -impl Curves { - /// TRACES: FR-DEV-3e - /// The curve to render a frame from this body with. - /// - /// Falls back, in order, to the database's `default:` and then to the - /// identity. **The default is deliberately not the identity**: an - /// unrecognised body rendered flat is the failure this requirement exists - /// to prevent, and a gentle, conservative curve is much closer to right for - /// every body than no curve is for any of them. A body with its own entry - /// gets that instead. - /// - /// # What "this body" has to survive - /// - /// The same camera names itself three ways depending on which program last - /// touched the file. A native NEF says make "NIKON CORPORATION", model - /// "NIKON Z 6"; rawler's own database cleans that to "Nikon" and "Z 6"; an - /// Adobe-converted DNG keeps the uncleaned pair. A database that had to - /// spell every variant would go stale the first time a maker changed its - /// mind about its own name, so the matching does the folding instead: - /// - /// - Case, punctuation and runs of whitespace are flattened, so - /// "ILCE-7M3", "ILCE 7M3" and "ilce-7m3" are one body. - /// - The make is compared on its **first word only**. Every maker's - /// trailing corporate boilerplate — "CORPORATION", "IMAGING CORP" — is - /// noise, and no two camera manufacturers share a first word. - /// - The model is tried both as written and with a leading copy of the - /// make removed, which is what lets one "Canon"/"EOS 6D" entry cover - /// "Canon EOS 6D" as well. - pub fn body(&self, make: &str, model: &str) -> BaseCurve { - let (make, model) = (make_key(make), normalise(model)); - // The model with a leading copy of the maker's name removed. - let bare = model.strip_prefix(&format!("{make} ")).unwrap_or(&model); - - self.bodies - .iter() - .find(|b| { - let entry_model = normalise(&b.model); - make_key(&b.make) == make && (entry_model == model || entry_model == bare) - }) - .map(|b| b.curve) - .or(self.default) - .unwrap_or(BaseCurve::IDENTITY) - } - - /// The database version. Higher wins; see [`load`]. - pub fn version(&self) -> u32 { - self.version - } - - /// How many bodies have their own curve, excluding the default. - pub fn len(&self) -> usize { - self.bodies.len() - } - - pub fn is_empty(&self) -> bool { - self.bodies.is_empty() - } - - /// Parse a database from YAML. - /// - /// Entries that are not curves are dropped with a warning rather than - /// failing the parse. A user-contributed file with one bad body should - /// cost that body's rendering, not every body's — and the alternative is an - /// application that will not open a photograph because somebody typed a - /// comma. - pub fn parse(yaml: &str) -> Result { - let file: File = serde_norway::from_str(yaml).map_err(|e| e.to_string())?; - - let default = file.default.and_then(|d| { - BaseCurve::from_points(&d.points).or_else(|| { - log::warn!("base curves: the default entry is not a monotone curve; ignoring it"); - None - }) - }); - - let bodies = file - .bodies - .into_iter() - .filter_map(|b| match BaseCurve::from_points(&b.points) { - Some(curve) => Some(BodyCurve { - make: b.make, - model: b.model, - curve, - }), - None => { - log::warn!( - "base curves: {} {} is not a monotone curve; ignoring it", - b.make, - b.model - ); - None - } - }) - .collect(); - - Ok(Self { - version: file.version, - default, - bodies, - }) - } -} - -/// The copy that ships inside the binary. -/// -/// A floor, not the answer: [`load`] prefers a newer file on disk. Compiled in -/// so that a fresh install with no profile directory — and every Android build, -/// where there is no such directory to speak of — still renders properly. -const BUILT_IN: &str = include_str!("../profiles/base_curves.yaml"); - -/// TRACES: FR-DEV-3e -/// The base curve database, loaded once. -/// -/// # The search path, and why it is a version comparison -/// -/// 1. `$DARKROOM_PROFILES`, a directory, when set. The escape hatch: a profile -/// author iterating on a curve points this at their working copy and does -/// not have to install anything. -/// 2. `$XDG_DATA_HOME/darkroom/profiles/`, else `$HOME/.local/share/darkroom/profiles/`. -/// The same base directory the catalog uses, chosen there for the same -/// reason — it is data, not cache, and must survive a storage sweep. -/// 3. The copy compiled into the binary. -/// -/// The first file that parses *and carries a higher `version:` than the -/// built-in copy* wins. The version check is the whole mechanism the -/// requirement asks for, and it runs in both directions: -/// -/// - A downloaded pack at version 7 supersedes a binary shipping version 3, so -/// a body added after the release renders correctly with no release. -/// - A stale pack at version 2 does **not** supersede a binary shipping version -/// 3, so upgrading the application cannot silently lose curves to a file -/// somebody downloaded a year ago and forgot. -/// -/// Failures are warnings, never errors. A malformed profile file must cost the -/// user their curves, not their photographs. -pub fn load() -> &'static Curves { - static LOADED: OnceLock = OnceLock::new(); - LOADED.get_or_init(|| { - let built_in = Curves::parse(BUILT_IN).unwrap_or_else(|e| { - // Unreachable in a build that ran its tests — `the_shipped_database_parses` - // asserts exactly this — but a panic here would mean an - // application that cannot open a photograph because of a typo in a - // data file, which is never the right trade. - log::error!("base curves: the built-in database does not parse: {e}"); - Curves { - version: 0, - default: None, - bodies: Vec::new(), - } - }); - - choose(built_in, &search_path()) - }) -} - -/// The version comparison, separated from where the directories come from. -/// -/// Split out so it can be tested against real files in a real directory -/// without the process-wide `OnceLock` and the environment `load` reads. The -/// rule this implements is the whole of what FR-DEV-3e asks for, so it is -/// worth being able to state it as a test rather than as a comment. -fn choose(built_in: Curves, dirs: &[PathBuf]) -> Curves { - for dir in dirs { - let path = dir.join("base_curves.yaml"); - let Ok(text) = std::fs::read_to_string(&path) else { - continue; - }; - match Curves::parse(&text) { - Ok(external) if external.version > built_in.version => { - log::info!( - "base curves: using {} (version {}, {} bodies) over the built-in version {}", - path.display(), - external.version, - external.len(), - built_in.version - ); - return external; - } - Ok(external) => log::info!( - "base curves: ignoring {} at version {}; the built-in database is version {}", - path.display(), - external.version, - built_in.version - ), - Err(e) => log::warn!("base curves: {} does not parse: {e}", path.display()), - } - } - built_in -} - -/// TRACES: FR-DEV-3e -/// The curve for a body, from the loaded database. -/// -/// The one call site the decoder needs; everything above is reachable for -/// tests and for a future profile editor. -pub fn for_body(make: &str, model: &str) -> BaseCurve { - load().body(make, model) -} - -/// Directories that may hold a `base_curves.yaml`, most specific first. -fn search_path() -> Vec { - let mut dirs = Vec::new(); - if let Some(explicit) = std::env::var_os("DARKROOM_PROFILES") { - dirs.push(PathBuf::from(explicit)); - } - // The same resolution `dr_ui::library::catalog_path` uses, and for the - // same reason: this is data a user may have installed, not a cache. It is - // duplicated rather than shared because `dr-decode` sits far below the UI - // and must not acquire a dependency on it to find a directory. - let base = std::env::var_os("XDG_DATA_HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("HOME").map(|h| Path::new(&h).join(".local/share"))); - if let Some(base) = base { - dirs.push(base.join("darkroom").join("profiles")); - } - dirs -} - -/// A manufacturer's first word, folded. -/// -/// "NIKON CORPORATION", "Nikon" and "nikon" all become `NIKON`. The corporate -/// suffixes are not information — they appear or not depending on whether the -/// file went through a DNG converter — and no two camera manufacturers share a -/// first word, so nothing is lost by dropping them. -fn make_key(s: &str) -> String { - normalise(s) - .split(' ') - .next() - .unwrap_or_default() - .to_string() -} - -/// Fold a make or model into something two files can agree on. -/// -/// Upper-cased, with every run of non-alphanumeric characters collapsed to one -/// space and the ends trimmed, so that "ILCE-7M3", "ILCE 7M3" and "ilce-7m3" -/// become one. -fn normalise(s: &str) -> String { - let mut out = String::with_capacity(s.len()); - let mut pending_space = false; - for c in s.chars() { - if c.is_ascii_alphanumeric() { - if pending_space && !out.is_empty() { - out.push(' '); - } - pending_space = false; - out.push(c.to_ascii_uppercase()); - } else { - pending_space = true; - } - } - out -} - -// ---- The on-disk shape, kept apart from the in-memory one ---------------- -// -// Deliberately separate types. The file is data a user edits and is allowed to -// be wrong; `Curves` is a parsed database whose every entry is known to be a -// monotone curve. Deriving `Deserialize` on `BaseCurve` directly would delete -// that boundary and let an unchecked five-point array reach the shader. -// -// Unknown fields are **accepted**, which is not laziness. The database is -// versioned independently of the binary and moves in both directions: a pack -// published after this release may carry keys this build has never heard of — -// a hue twist, a look table (FR-DEV-3f) — and it must still deliver its curves -// to an older DarkRoom rather than failing to parse and leaving every body -// flat. `deny_unknown_fields` would trade that for a diagnostic nobody needs. - -#[derive(serde::Deserialize)] -struct File { - version: u32, - #[serde(default)] - default: Option, - #[serde(default)] - bodies: Vec, -} - -#[derive(serde::Deserialize)] -struct Entry { - points: Vec<[f32; 2]>, -} - -#[derive(serde::Deserialize)] -struct BodyEntry { - make: String, - model: String, - points: Vec<[f32; 2]>, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn the_shipped_database_parses_and_carries_a_default() { - // The one test that must never be allowed to fail quietly: `load` - // degrades to an empty database rather than panicking, so without this - // a typo in the YAML would ship as "every photograph renders flat" - // rather than as a build failure. - let curves = Curves::parse(BUILT_IN).expect("the shipped database parses"); - assert!(curves.version() >= 1); - assert!(!curves.is_empty(), "the database ships bodies"); - assert!( - !curves.body("Nobody", "Nothing").is_identity(), - "an unknown body must still get the default rendering" - ); - } - - #[test] - fn every_shipped_curve_lifts_the_midtones_and_rolls_the_highlights() { - // What makes a base curve a base curve rather than a decoration. If a - // shipped curve failed either half it would be a worse rendering than - // the flat one it replaced, which is the one outcome forbidden. - let curves = Curves::parse(BUILT_IN).expect("parses"); - let all = curves - .bodies - .iter() - .map(|b| (format!("{} {}", b.make, b.model), b.curve)) - .chain(curves.default.map(|c| ("default".to_string(), c))); - - for (name, curve) in all { - // The midtone point sits above the diagonal: a linear midtone is - // roughly a stop and a half darker than any camera renders it. - let mid = 2; - assert!( - curve.ys[mid] > curve.xs[mid], - "{name} does not lift its midtones ({} -> {})", - curve.xs[mid], - curve.ys[mid] - ); - // And the last span is shallower than the one before it, which is - // what a shoulder *is*. Without one the curve clips highlights - // harder than the linear rendering did. - let slope = - |i: usize| (curve.ys[i + 1] - curve.ys[i]) / (curve.xs[i + 1] - curve.xs[i]); - assert!( - slope(POINTS - 2) < slope(POINTS - 3), - "{name} has no highlight shoulder" - ); - } - } - - #[test] - fn a_curve_that_is_not_monotone_is_refused() { - // The profile file is user-editable, so this is a real boundary and - // not a formality. A decreasing y inverts tones locally and shows up - // as a dark halo in a gradient, which reads as a rendering fault - // rather than as a bad profile. - assert_eq!( - BaseCurve::from_points(&[[0.0, 0.0], [0.25, 0.4], [0.5, 0.3], [0.75, 0.8], [1.0, 1.0]]), - None - ); - } - - #[test] - fn a_curve_whose_x_does_not_advance_is_refused() { - // The spline divides by the span width; a repeated x is a division by - // zero in the shader, which is a NaN pixel rather than an error. - assert_eq!( - BaseCurve::from_points(&[ - [0.0, 0.0], - [0.25, 0.3], - [0.25, 0.5], - [0.75, 0.8], - [1.0, 1.0] - ]), - None - ); - } - - #[test] - fn a_curve_of_the_wrong_length_is_refused() { - assert_eq!(BaseCurve::from_points(&[[0.0, 0.0], [1.0, 1.0]]), None); - } - - #[test] - fn values_outside_the_unit_square_are_refused() { - // The shader clamps its output at the very end anyway, but a control - // point above 1.0 would put the shoulder outside the range the curve - // is defined over and silently flatten everything below it. - assert_eq!( - BaseCurve::from_points(&[[0.0, 0.0], [0.25, 0.3], [0.5, 1.4], [0.75, 1.5], [1.0, 1.6]]), - None - ); - } - - #[test] - fn a_body_with_its_own_entry_beats_the_default() { - let curves = Curves::parse( - "version: 2 -default: - points: [[0.0, 0.0], [0.25, 0.3], [0.5, 0.6], [0.75, 0.85], [1.0, 1.0]] -bodies: - - make: Canon - model: EOS 6D - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("parses"); - - assert_eq!(curves.body("Canon", "EOS 6D").ys[1], 0.35); - assert_eq!(curves.body("Canon", "EOS 5D").ys[1], 0.30); - } - - #[test] - fn the_make_may_be_repeated_in_the_model() { - // Canon writes "Canon" as the make and "Canon EOS 6D" as the model; - // rawler's cleaned strings drop the repetition and both reach here. - // One entry has to cover both or half the files on a card miss. - let curves = Curves::parse( - "version: 1 -bodies: - - make: Canon - model: EOS 6D - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("parses"); - - assert_eq!(curves.body("Canon", "Canon EOS 6D").ys[1], 0.35); - assert_eq!(curves.body("Canon", "EOS 6D").ys[1], 0.35); - assert_eq!(curves.body("CANON", "eos 6d").ys[1], 0.35); - } - - #[test] - fn a_corporate_suffix_does_not_hide_a_body() { - // The same Z 6 arrives as "Nikon"/"Z 6" from rawler's camera database - // and as "NIKON CORPORATION"/"NIKON Z 6" from a DNG converted out of - // the same file. Both must find the entry, or converting a file to - // DNG would silently change how it renders. - let curves = Curves::parse( - "version: 1 -bodies: - - make: Nikon - model: Z 6 - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("parses"); - - assert_eq!(curves.body("Nikon", "Z 6").ys[1], 0.35); - assert_eq!(curves.body("NIKON CORPORATION", "NIKON Z 6").ys[1], 0.35); - } - - #[test] - fn punctuation_and_spacing_do_not_decide_whether_a_body_is_known() { - let curves = Curves::parse( - "version: 1 -bodies: - - make: Sony - model: ILCE-7M3 - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("parses"); - - assert_eq!(curves.body("SONY", "ILCE 7M3").ys[1], 0.35); - assert_eq!(curves.body("sony", "ilce-7m3").ys[1], 0.35); - } - - #[test] - fn one_bad_entry_does_not_cost_the_rest() { - // A user-contributed file with one typo should cost that body's - // rendering, not every body's. - let curves = Curves::parse( - "version: 1 -bodies: - - make: Broken - model: Body - points: [[0.0, 0.0], [0.25, 0.9], [0.5, 0.1], [0.75, 0.9], [1.0, 1.0]] - - make: Canon - model: EOS 6D - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("parses"); - - assert_eq!(curves.len(), 1); - assert_eq!(curves.body("Canon", "EOS 6D").ys[1], 0.35); - assert!(curves.body("Broken", "Body").is_identity()); - } - - #[test] - fn a_pack_from_the_future_still_delivers_its_curves() { - // The database is versioned independently of the binary, so a pack - // published after this build may carry keys this build has never heard - // of. It must still hand over the curves it does understand — failing - // the parse would leave every body flat, which is the exact failure - // FR-DEV-3e exists to prevent, delivered by the mechanism meant to - // prevent it. - let curves = Curves::parse( - "version: 9 -look_table: ambitious -bodies: - - make: Canon - model: EOS 6D - hue_twist: [1, 2, 3] - points: [[0.0, 0.0], [0.25, 0.35], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -", - ) - .expect("an unfamiliar key must not fail the parse"); - - assert_eq!(curves.version(), 9); - assert_eq!(curves.body("Canon", "EOS 6D").ys[1], 0.35); - } - - #[test] - fn an_unknown_body_with_no_default_gets_the_identity() { - // Graceful fallback, stated as a property: never worse than a flat - // render, and never a curve tuned for somebody else's sensor when the - // database declines to offer one. - let curves = Curves::parse("version: 1\nbodies: []\n").expect("parses"); - assert!(curves.body("Nobody", "Nothing").is_identity()); - } - - /// A directory holding one `base_curves.yaml`, unique to the caller. - fn a_pack_dir(name: &str, yaml: &str) -> PathBuf { - let dir = std::env::temp_dir().join(format!("darkroom-base-curves-{name}")); - let _ = std::fs::remove_dir_all(&dir); - std::fs::create_dir_all(&dir).expect("a writable temp directory"); - std::fs::write(dir.join("base_curves.yaml"), yaml).expect("write"); - dir - } - - const A_CANON_ENTRY: &str = "bodies: - - make: Canon - model: EOS 6D - points: [[0.0, 0.0], [0.25, 0.42], [0.5, 0.7], [0.75, 0.9], [1.0, 1.0]] -"; - - #[test] - fn a_newer_pack_on_disk_supersedes_the_built_in_database() { - // **This is the requirement.** FR-DEV-3e asks for a profile database - // versioned independently of the app binary "so bodies and curves can - // be added without a release". A file with a higher version, dropped - // in the profile directory, is what that means in practice. - let built_in = Curves::parse(BUILT_IN).expect("parses"); - let newer = format!("version: {}\n{A_CANON_ENTRY}", built_in.version() + 1); - let dir = a_pack_dir("newer", &newer); - - let chosen = choose(built_in.clone(), &[dir]); - assert_eq!(chosen.version(), built_in.version() + 1); - assert_eq!(chosen.body("Canon", "EOS 6D").ys[1], 0.42); - } - - #[test] - fn a_stale_pack_does_not_survive_an_upgrade() { - // The other direction, and the one that protects the user. Somebody - // downloads a pack, a release later ships better curves for the same - // bodies, and the forgotten file must not quietly hold the application - // back at last year's rendering. - let built_in = Curves::parse(BUILT_IN).expect("parses"); - let stale = format!("version: {}\n{A_CANON_ENTRY}", built_in.version()); - let dir = a_pack_dir("stale", &stale); - - let chosen = choose(built_in.clone(), &[dir]); - assert_eq!(chosen.version(), built_in.version()); - assert_ne!( - chosen.body("Canon", "EOS 6D").ys[1], - 0.42, - "an equal version must not displace the built-in database" - ); - } - - #[test] - fn a_broken_pack_costs_the_curves_and_not_the_photographs() { - // A malformed profile file must degrade to the built-in database, not - // to an error. The user came here to look at a photograph. - let built_in = Curves::parse(BUILT_IN).expect("parses"); - let dir = a_pack_dir("broken", "version: [this is not a number\n"); - - let chosen = choose(built_in.clone(), &[dir]); - assert_eq!(chosen.version(), built_in.version()); - assert_eq!(chosen.len(), built_in.len()); - } - - #[test] - fn a_directory_with_no_pack_in_it_is_simply_skipped() { - // The ordinary case on every machine: the search path exists, the file - // does not. It must not be a warning, an error, or a slow path. - let built_in = Curves::parse(BUILT_IN).expect("parses"); - let missing = std::env::temp_dir().join("darkroom-base-curves-nothing-here"); - let _ = std::fs::remove_dir_all(&missing); - - assert_eq!(choose(built_in.clone(), &[missing]), built_in); - } - - #[test] - fn the_identity_is_recognised_as_doing_nothing() { - assert!(BaseCurve::IDENTITY.is_identity()); - assert!(!Curves::parse(BUILT_IN) - .expect("parses") - .body("Canon", "EOS 6D") - .is_identity()); - } -} diff --git a/core/dr-decode/src/lib.rs b/core/dr-decode/src/lib.rs index 2334f84..d20adaf 100644 --- a/core/dr-decode/src/lib.rs +++ b/core/dr-decode/src/lib.rs @@ -16,14 +16,12 @@ //! second decoder can be put behind them without changing any of them //! (FR-RAW-2). [`Rawler`] is the one that ships; [`default`] hands it out. -pub mod base_curve; mod decoder; mod error; mod locate; mod preview; pub mod profile; -pub use base_curve::BaseCurve; pub use decoder::{default, Decoder, Rawler}; pub use error::DecodeError; pub use locate::{ @@ -125,19 +123,6 @@ pub struct RawImage { /// for the light the frame was shot under; see [`profile::CameraProfile`]. pub color_matrix: Option<[f32; 9]>, /// TRACES: FR-DEV-3e - /// The per-body rendering curve, the other half of the camera profile. - /// - /// The matrix above decides what the colours *are*; this decides what the - /// picture looks like. Carried on the decoded image rather than looked up - /// downstream because this is the only point in the system that knows - /// which body took the frame, and because it is not an edit: it belongs to - /// the file in the same way the masked-photosite crop does, and must never - /// reach a sidecar (FR-NC-9). - /// - /// [`BaseCurve::IDENTITY`] for an unknown body with no default in the - /// database, which renders exactly as this decoder did before profiles - /// existed. - pub base_curve: BaseCurve, /// The usable region of `data`, excluding masked and border photosites. pub crop: CropRect, /// TRACES: FR-MRG-3 @@ -592,15 +577,6 @@ fn decode_unguarded(bytes: &[u8]) -> Result { profile.as_ref().map(|p| p.xyz_to_cam()).as_ref(), ); - // The rendering half of the profile (FR-DEV-3e). rawler's cleaned strings - // are preferred where it has them — they are what the shipped database is - // written against — and the matching folds the variants either way, so a - // DNG naming the same body differently still finds its curve. - let base_curve = base_curve::for_body( - image.camera.clean_make.as_str(), - image.camera.clean_model.as_str(), - ); - // TRACES: FR-MRG-3 // A linear DNG — three samples per pixel, no colour filter array — is a // composite this application wrote (or any other demosaiced DNG). It @@ -683,7 +659,6 @@ fn decode_unguarded(bytes: &[u8]) -> Result { .unwrap_or(u16::MAX), wb_coeffs, color_matrix, - base_curve, samples_per_pixel, profile, make: image.camera.clean_make.clone(), diff --git a/core/dr-decode/src/profile.rs b/core/dr-decode/src/profile.rs index bf45372..06b38ac 100644 --- a/core/dr-decode/src/profile.rs +++ b/core/dr-decode/src/profile.rs @@ -6,8 +6,10 @@ //! colour needs two things the file cannot supply on its own: a **matrix** //! saying how this sensor's three responses relate to the CIE observer, and a //! **rendering** saying what to do with the resulting scene-referred values so -//! that a photograph looks like a photograph. This module supplies the first -//! and looks up the second ([`crate::base_curve`]). +//! that a photograph looks like a photograph. This module supplies the first. +//! The second is not the body's: since D19 it is the pipeline's view +//! transform (FR-DEV-3j), one for every camera, and the per-body base curves +//! that used to be looked up here are retired. //! //! # What is extracted, and from where //! diff --git a/core/dr-film/src/profile.rs b/core/dr-film/src/profile.rs index 20f0281..1979d56 100644 --- a/core/dr-film/src/profile.rs +++ b/core/dr-film/src/profile.rs @@ -2,9 +2,9 @@ //! //! # Why it is data //! -//! The same argument `dr_decode::base_curve` makes for camera bodies, and for -//! the same requirement: under the GPLv3 a stock should be contributable -//! without a release. A profile is three tables and a handful of facts, all of +//! Under the GPLv3 a stock should be contributable without a release (the +//! argument the retired per-body base curves made for camera bodies, before +//! D19). A profile is three tables and a handful of facts, all of //! them published in the manufacturer's datasheet, so adding a stock is adding //! a file — not a code change, not a shader, and not a new operation. //! diff --git a/core/dr-gpu/src/adjust.rs b/core/dr-gpu/src/adjust.rs index ebe5941..8b4c417 100644 --- a/core/dr-gpu/src/adjust.rs +++ b/core/dr-gpu/src/adjust.rs @@ -34,16 +34,6 @@ use crate::{DemosaicedImage, GpuContext, GpuError}; /// reads them. const RESERVED_FIELDS: usize = dr_pipeline::RESERVED_UNIFORM_FIELDS; -/// TRACES: FR-DEV-3e -/// The two crates must agree on how many points a base curve has. -/// -/// `dr-decode` reads them from the profile database and `dr-pipeline` declares -/// the uniform slots; this file is the only place the two meet, and it packs -/// them by index. A disagreement would not fail to compile — it would upload a -/// curve with a point missing or a stale float in it, which renders as a -/// plausible-looking wrong tone response. Cheaper to catch here, at build time. -const _: () = assert!(dr_decode::base_curve::POINTS == dr_pipeline::BASE_CURVE_POINTS); - /// Runs composed operation chains against demosaiced images. pub struct AdjustPass { ctx: GpuContext, @@ -1275,22 +1265,6 @@ impl AdjustPass { // runs. See `DemosaicedImage::is_non_linear`. let non_linear = if source.is_non_linear() { 1.0 } else { 0.0 }; uniforms[12..16].copy_from_slice(&[wb[0], wb[1], wb[2], non_linear]); - // TRACES: FR-DEV-3e - // The camera profile's base curve, packed the way the generated block - // declares it: four x, four y, then the fifth point and the flag. The - // flag is what lets one compiled shader serve a profiled body and an - // unprofiled one, so the pipeline cache is not split in two by which - // camera took the frame. - // - // Written here rather than at the call site so that *both* callers — - // the plain render and the masked one — carry the profile. Filling it - // at one of them was how the two halves of this merge each had it. - let curve = source.base_curve(); - let on = if curve.is_identity() { 0.0 } else { 1.0 }; - let b = dr_pipeline::BASE_CURVE_UNIFORM_OFFSET; - uniforms[b..b + 4].copy_from_slice(&curve.xs[0..4]); - uniforms[b + 4..b + 8].copy_from_slice(&curve.ys[0..4]); - uniforms[b + 8..b + 12].copy_from_slice(&[curve.xs[4], curve.ys[4], on, 0.0]); uniforms } @@ -1478,8 +1452,6 @@ impl AdjustPass { uniforms[4..8].copy_from_slice(&[0.0, 1.0, 0.0, 0.0]); uniforms[8..12].copy_from_slice(&[0.0, 0.0, 1.0, 0.0]); uniforms[12..16].copy_from_slice(&[1.0, 1.0, 1.0, non_linear]); - let b = dr_pipeline::BASE_CURVE_UNIFORM_OFFSET; - uniforms[b + 10] = 0.0; let params_buf = self .ctx @@ -1707,7 +1679,7 @@ pub(crate) fn numbered(src: &str) -> String { #[cfg(test)] mod tests { use super::*; - use dr_decode::{BaseCurve, CfaPattern, CropRect, RawImage}; + use dr_decode::{CfaPattern, CropRect, RawImage}; use dr_pipeline::ops::{colour_mixer, exposure, saturation}; use dr_pipeline::EditGraph; // For `Operation::detail`, which is how `the_whole_chain_at_once_compiles` @@ -1745,7 +1717,6 @@ mod tests { // Identity, so the test reasons about the operations alone // rather than about a camera's colour response. color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -1949,7 +1920,6 @@ mod tests { white_level: 16383, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -2538,7 +2508,6 @@ mod tests { white_level: 16383, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -2642,7 +2611,6 @@ mod tests { white_level: 16383, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -3227,11 +3195,16 @@ mod tests { } #[test] - fn a_jpeg_and_sensor_data_agree_on_the_same_scene_value() { - // The two producers must be interchangeable. A mid-grey that is - // linearly 0.216 (sRGB 128) arriving as sensor data and as a JPEG - // must render the same, or an edit would mean different things - // depending on which decoder opened the file. + fn a_jpeg_and_sensor_data_differ_by_exactly_the_view_transform() { + // TRACES: FR-DEV-3j + // The two producers must be interchangeable up to the rendering. A + // mid-grey that is linearly 0.216 (sRGB 128) arriving as sensor data + // is scene-referred and goes through the view transform; arriving as + // a JPEG it is already a rendering and must come out as it went in. + // Before D19 the fixture's identity base curve made both unrendered + // and this asserted they matched; what it guards is unchanged — the + // linearisation of each agrees — but the rendering between them is + // now always there for sensor data. let Some(ctx) = ctx() else { return }; let mut pass = AdjustPass::new(&ctx); let shader = EditGraph::default_chain().compose(); @@ -3250,11 +3223,18 @@ mod tests { read_centre(&ctx, t) }; - let delta = (i32::from(from_sensor[0]) - i32::from(from_jpeg[0])).abs(); + let scene = 3537.0 / 16383.0; + let viewed = dr_pipeline::view::Sigmoid::default_curve().channel(scene); + let expected = (dr_types::Transfer::Srgb.encode(viewed) * 255.0).round() as i32; + let delta = (i32::from(from_sensor[0]) - expected).abs(); assert!( delta <= 3, - "the same scene value rendered {from_sensor:?} from sensor data \ - and {from_jpeg:?} from a JPEG" + "sensor data rendered {from_sensor:?}, expected about {expected}" + ); + let delta = (i32::from(from_jpeg[0]) - 128).abs(); + assert!( + delta <= 3, + "a JPEG was rendered again: {from_jpeg:?} from sRGB 128" ); } } diff --git a/core/dr-gpu/src/demosaic.rs b/core/dr-gpu/src/demosaic.rs index 26ab581..5fdab39 100644 --- a/core/dr-gpu/src/demosaic.rs +++ b/core/dr-gpu/src/demosaic.rs @@ -10,7 +10,7 @@ //! pass over this texture; it does not re-demosaic, which is what keeps the //! interaction budget (NFR-P9) reachable on a 24 MP file. -use dr_decode::{BaseCurve, CfaPattern, RawImage}; +use dr_decode::{CfaPattern, RawImage}; use wgpu::util::DeviceExt; use crate::{GpuContext, GpuError}; @@ -109,16 +109,6 @@ pub struct DemosaicedImage { color_matrix: [f32; 9], /// As-shot white balance, the neutral starting point for the WB control. as_shot_wb: [f32; 3], - /// TRACES: FR-DEV-3e - /// The camera profile's rendering curve, carried through for the adjust - /// pass exactly as `color_matrix` is. - /// - /// It rides on the image rather than on the edit graph because it is not - /// an edit: it belongs to the body that took the frame, the way the - /// masked-photosite crop and the EXIF orientation do, and a sidecar shared - /// between two bodies must never carry one body's rendering onto the - /// other's file (FR-NC-9). - base_curve: BaseCurve, /// Whether the texture holds gamma-encoded rather than linear values. non_linear: bool, /// Which upload this is, unique for the life of the process. See @@ -165,15 +155,6 @@ impl DemosaicedImage { self.color_matrix } - /// TRACES: FR-DEV-3e - /// The camera profile's base curve, as five `(x, y)` points. - /// - /// [`BaseCurve::IDENTITY`] where the body is unprofiled or the source was - /// never raw, in which case the adjust pass skips the stage entirely. - pub fn base_curve(&self) -> BaseCurve { - self.base_curve - } - /// As-shot white balance multipliers, green-normalised. /// /// The white balance control is expressed *relative* to these, so its @@ -291,7 +272,6 @@ impl DemosaicedImage { // camera's base curve baked in by the camera; applying one again // would render the rendering, crushing the shadows and flattening // the highlights of an image that was already finished. - base_curve: BaseCurve::IDENTITY, non_linear: true, id: next_image_id(), }) @@ -368,7 +348,6 @@ impl DemosaicedImage { height, color_matrix: raw.color_matrix.unwrap_or(IDENTITY_3X3), as_shot_wb: [raw.wb_coeffs[0], raw.wb_coeffs[1], raw.wb_coeffs[2]], - base_curve: raw.base_curve, non_linear: false, id: next_image_id(), }) @@ -784,7 +763,6 @@ impl Demosaicer { // Whatever the profile database had for this body (FR-DEV-3e), // resolved at decode because that is the only place the make and // model are known. - base_curve: raw.base_curve, // Sensor data is linear by construction — the demosaic shader // normalises against black and white levels and applies no // transfer function. @@ -1284,7 +1262,6 @@ mod tests { white_level: white, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: None, - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -1400,7 +1377,6 @@ mod tests { white_level: white, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: None, - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -1697,7 +1673,6 @@ mod tests { white_level: 16383, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: None, - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), @@ -1782,7 +1757,6 @@ mod tests { 1.0, ], color_matrix: None, - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), diff --git a/core/dr-gpu/tests/base_curve.rs b/core/dr-gpu/tests/base_curve.rs deleted file mode 100644 index a77379b..0000000 --- a/core/dr-gpu/tests/base_curve.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! TRACES: FR-DEV-3e -//! The camera profile's base curve, end to end on a device. -//! -//! The unit tests either side of this one check halves. `dr-decode` asserts -//! that the shipped database parses and that every curve in it lifts its -//! midtones; `dr-pipeline` asserts that the generated WGSL evaluates a curve -//! in the right place. Neither would notice if the two agreed with each other -//! and both were wrong — a curve packed into the wrong uniform slots, or a -//! flag read from the wrong component, satisfies both and renders nothing. -//! -//! So this renders real pixels twice, once with a profiled body's curve and -//! once with the identity, and asserts the difference is the one a base curve -//! is for: midtones lifted, black still black, white still white. - -use dr_decode::{BaseCurve, CfaPattern, CropRect, RawImage}; -use dr_gpu::{AdjustPass, Demosaicer, GpuContext}; -use dr_pipeline::EditGraph; - -const SIZE: u32 = 16; - -fn ctx() -> Option { - pollster::block_on(GpuContext::new_headless()).ok() -} - -/// A flat RGGB frame at `level` out of 65535, carrying `curve`. -/// -/// Every photosite the same value, so the demosaic result is a uniform grey -/// and the only thing that can move a pixel is the curve. The colour matrix is -/// the identity and the balance is neutral for the same reason: this test is -/// about one stage, and a real body's matrix would make every assertion below -/// a statement about that body instead. -fn flat_raw(level: u16, curve: BaseCurve) -> RawImage { - RawImage { - width: SIZE, - height: SIZE, - data: vec![level; (SIZE * SIZE) as usize], - cfa_pattern: CfaPattern::Rggb, - black_level: [0; 4], - white_level: u16::MAX, - wb_coeffs: [1.0, 1.0, 1.0, 1.0], - color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: curve, - samples_per_pixel: 1, - profile: None, - make: String::new(), - model: String::new(), - crop: CropRect { - x: 0, - y: 0, - width: SIZE, - height: SIZE, - }, - } -} - -/// Render a neutral edit over a flat frame and return the centre pixel's red. -/// -/// The centre rather than a corner: a demosaic has to invent its edges, and -/// the interpolated border of a 16×16 frame is not where anyone should be -/// reading a tone off. -fn rendered_level(ctx: &GpuContext, level: u16, curve: BaseCurve) -> u8 { - let raw = flat_raw(level, curve); - let source = Demosaicer::new(ctx) - .expect("demosaicer") - .run(&raw) - .expect("demosaic"); - let shader = EditGraph::default_chain().compose(); - let mut adjust = AdjustPass::new(ctx); - adjust.render(&source, &shader, SIZE, SIZE).expect("render"); - let (pixels, _, _) = adjust.export_pixels().expect("readback"); - let centre = ((SIZE / 2) * SIZE + SIZE / 2) * 4; - pixels[centre as usize] -} - -/// The Canon EOS 6D's curve, from the shipped profile database. -/// -/// Looked up by name rather than written out, so this also asserts the thing -/// no other test can: that a curve travels from the YAML, through the body -/// match, onto the decoded image and into the uniform block that the shader -/// actually reads. -fn six_d() -> BaseCurve { - let curve = dr_decode::base_curve::for_body("Canon", "EOS 6D"); - assert!( - !curve.is_identity(), - "the shipped database must have a curve for the EOS 6D" - ); - curve -} - -#[test] -fn a_profiled_body_renders_brighter_midtones_than_a_flat_one() { - // **The whole requirement, in one assertion.** A linear midtone renders - // roughly half a stop dark, which is the flat, lifeless look FR-DEV-3e - // exists to get away from. If the curve did not reach the shader — wrong - // slot, wrong flag, wrong stage — this is the only test that would fail. - let Some(ctx) = ctx() else { - eprintln!("skipping: no GPU adapter"); - return; - }; - - // 13% of full scale: roughly where a camera places middle grey, leaving - // about two and a half stops of highlight headroom above it. - let level = (0.13 * 65535.0) as u16; - let flat = rendered_level(&ctx, level, BaseCurve::IDENTITY); - let profiled = rendered_level(&ctx, level, six_d()); - - assert!( - profiled > flat + 8, - "the profile lifted middle grey from {flat} only to {profiled}" - ); -} - -#[test] -fn the_curve_leaves_black_black_and_white_white() { - // A base curve renders the range between the endpoints; it must not move - // the endpoints themselves. A curve that lifted black would put a grey - // veil over every night photograph, and one that pulled white down would - // make a correctly exposed frame look underexposed. - let Some(ctx) = ctx() else { - eprintln!("skipping: no GPU adapter"); - return; - }; - - let curve = six_d(); - assert_eq!(rendered_level(&ctx, 0, curve), 0, "black moved"); - assert_eq!(rendered_level(&ctx, u16::MAX, curve), 255, "white moved"); -} - -#[test] -fn an_unprofiled_body_renders_exactly_as_it_did_before_profiles_existed() { - // The graceful fallback, asserted as a number rather than as a promise. - // With no curve the pipeline must still be a pass-through: black level - // out, white level in, sRGB encoding on the way to the screen and nothing - // else. "Never worse than today" is the one property this change was not - // allowed to trade away, and the way it would break is silently — a flag - // read from the wrong component would apply a curve nobody asked for. - let Some(ctx) = ctx() else { - eprintln!("skipping: no GPU adapter"); - return; - }; - - for level in [0u16, 4_000, 8_520, 32_768, 60_000, u16::MAX] { - let scene = f32::from(level) / f32::from(u16::MAX); - let expected = (dr_types::Transfer::Srgb.encode(scene) * 255.0).round() as i32; - let got = i32::from(rendered_level(&ctx, level, BaseCurve::IDENTITY)); - // Two 8-bit steps: the texture holding the demosaiced frame is - // `Rgba16Float`, so a value round-trips through eleven mantissa bits - // before it is encoded. That is well under one step at any level, and - // the tolerance is for the rounding either side of it rather than for - // the transform being approximate. - assert!( - (got - expected).abs() <= 2, - "raw {level} rendered as {got}, expected about {expected}" - ); - } -} - -#[test] -fn the_curve_is_monotone_through_the_whole_range() { - // The property the spline's tangent limiting exists to guarantee, checked - // where it actually matters: on the device, through the real uniform - // packing. A curve that dipped anywhere would put a dark band across a - // smooth gradient — a sky, most visibly — and it would read as a - // rendering fault rather than as a bad profile. - let Some(ctx) = ctx() else { - eprintln!("skipping: no GPU adapter"); - return; - }; - - let curve = six_d(); - let mut previous = 0u8; - for step in 0..=16u32 { - let level = (step * 65535 / 16) as u16; - let value = rendered_level(&ctx, level, curve); - assert!( - value >= previous, - "the curve fell from {previous} to {value} at raw level {level}" - ); - previous = value; - } -} diff --git a/core/dr-gpu/tests/film_sim.rs b/core/dr-gpu/tests/film_sim.rs index 4b2688c..d801ac9 100644 --- a/core/dr-gpu/tests/film_sim.rs +++ b/core/dr-gpu/tests/film_sim.rs @@ -15,7 +15,7 @@ //! model is checked against the reference, and the shader is checked against //! the CPU model. -use dr_decode::{BaseCurve, CfaPattern, CropRect, RawImage}; +use dr_decode::{CfaPattern, CropRect, RawImage}; use dr_film::bake::{bake, Recipe, Settings}; use dr_gpu::{AdjustPass, Demosaicer, GpuContext, LabelField, MaskPass}; use dr_pipeline::mask::{MaskLayer, MaskSource}; @@ -48,7 +48,6 @@ fn flat_raw(level: u16) -> RawImage { // Off deliberately: a film replaces the camera's rendering, and // leaving a curve here would test the suppression rather than the // film. `dr-pipeline` asserts the suppression on the generated source. - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), diff --git a/core/dr-gpu/tests/hot_pixels.rs b/core/dr-gpu/tests/hot_pixels.rs index 0262ee5..126e20e 100644 --- a/core/dr-gpu/tests/hot_pixels.rs +++ b/core/dr-gpu/tests/hot_pixels.rs @@ -6,7 +6,7 @@ //! anything: the repair happens on the mosaic, and what a photographer would //! see of a defect it missed is the coloured cross the demosaic makes of it. -use dr_decode::{BaseCurve, CfaPattern, CropRect, RawImage}; +use dr_decode::{CfaPattern, CropRect, RawImage}; use dr_gpu::{AdjustPass, Demosaicer, GpuContext}; use dr_pipeline::EditGraph; @@ -32,7 +32,6 @@ fn frame(pattern: CfaPattern, level: u16, set: &[(u32, u32, u16)]) -> RawImage { white_level: WHITE, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), - base_curve: BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), diff --git a/core/dr-gpu/tests/view_transform.rs b/core/dr-gpu/tests/view_transform.rs new file mode 100644 index 0000000..af26321 --- /dev/null +++ b/core/dr-gpu/tests/view_transform.rs @@ -0,0 +1,129 @@ +//! TRACES: FR-DEV-3j | FR-DEV-2 +//! The view transform, end to end on a device. +//! +//! `dr-pipeline` checks the curve on the CPU and that the composer emits it in +//! the right place. Neither would notice a shader that disagreed with the CPU +//! reference, or a clamp somewhere upstream that made two highlights the same +//! number before the curve ever saw them — which is exactly what the retired +//! base curve did, and why D19 exists. So this renders real pixels. + +use dr_decode::{CfaPattern, CropRect, RawImage}; +use dr_gpu::{AdjustPass, Demosaicer, GpuContext}; +use dr_pipeline::view::Sigmoid; +use dr_pipeline::EditGraph; + +const SIZE: u32 = 16; + +fn ctx() -> Option { + pollster::block_on(GpuContext::new_headless()).ok() +} + +/// A flat RGGB frame at `level` out of 65535, with an identity matrix and a +/// neutral balance, so the only things that move a pixel are the edit and the +/// view transform. +fn flat_raw(level: u16) -> RawImage { + RawImage { + width: SIZE, + height: SIZE, + data: vec![level; (SIZE * SIZE) as usize], + cfa_pattern: CfaPattern::Rggb, + black_level: [0; 4], + white_level: u16::MAX, + wb_coeffs: [1.0, 1.0, 1.0, 1.0], + color_matrix: Some([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]), + samples_per_pixel: 1, + profile: None, + make: String::new(), + model: String::new(), + crop: CropRect { + x: 0, + y: 0, + width: SIZE, + height: SIZE, + }, + } +} + +/// Render `graph` over a flat frame and return the centre pixel's red. +/// +/// The centre rather than a corner: a demosaic has to invent its edges. +fn rendered(ctx: &GpuContext, level: u16, graph: &EditGraph) -> u8 { + let source = Demosaicer::new(ctx) + .expect("demosaicer") + .run(&flat_raw(level)) + .expect("demosaic"); + let shader = graph.compose(); + let mut adjust = AdjustPass::new(ctx); + adjust.render(&source, &shader, SIZE, SIZE).expect("render"); + let (pixels, _, _) = adjust.export_pixels().expect("readback"); + let centre = ((SIZE / 2) * SIZE + SIZE / 2) * 4; + pixels[centre as usize] +} + +#[test] +fn the_shader_agrees_with_the_cpu_reference() { + // TRACES: FR-DEV-3j + let Some(ctx) = ctx() else { + eprintln!("skipping: no GPU adapter"); + return; + }; + let curve = Sigmoid::default_curve(); + let graph = EditGraph::default_chain(); + for level in [0u16, 500, 4_000, 8_520, 32_768, 60_000, u16::MAX] { + let scene = f32::from(level) / f32::from(u16::MAX); + let display = curve.channel(scene).min(1.0); + let expected = (dr_types::Transfer::Srgb.encode(display) * 255.0).round() as i32; + let got = i32::from(rendered(&ctx, level, &graph)); + // Two 8-bit steps, for the `Rgba16Float` intermediate and the + // rounding either side of the encode. + assert!( + (got - expected).abs() <= 2, + "raw {level} rendered as {got}, expected about {expected}" + ); + } +} + +#[test] +fn highlights_above_one_stay_distinct() { + // TRACES: FR-DEV-2 | FR-DEV-3j + // The failure D19 names first. Two stops of exposure put these two + // frames at 1.0 and 1.5 of sensor saturation. The base curve was flat + // past 1.0, so both rendered as the same white; the view transform's + // shoulder still separates them. + let Some(ctx) = ctx() else { + eprintln!("skipping: no GPU adapter"); + return; + }; + let mut graph = EditGraph::default_chain(); + graph.set_param( + dr_pipeline::ops::exposure::ID, + dr_pipeline::ops::exposure::EXPOSURE, + 2.0, + ); + let lower = rendered(&ctx, u16::MAX / 4, &graph); + let upper = rendered(&ctx, (u16::MAX / 8) * 3, &graph); + assert!( + upper > lower, + "scene 1.0 rendered {lower} and scene 1.5 rendered {upper}" + ); + assert!(upper < 255, "scene 1.5 is below the default white point"); +} + +#[test] +fn the_rendering_is_monotone_through_the_whole_range() { + // TRACES: FR-DEV-3j + // A dip anywhere puts a dark band across a smooth gradient — a sky, most + // visibly. + let Some(ctx) = ctx() else { + eprintln!("skipping: no GPU adapter"); + return; + }; + let graph = EditGraph::default_chain(); + let mut last = 0u8; + for step in 0..=32u32 { + let level = (step * u32::from(u16::MAX) / 32) as u16; + let got = rendered(&ctx, level, &graph); + assert!(got >= last, "raw {level} rendered {got}, below {last}"); + last = got; + } +} diff --git a/core/dr-pipeline/ops/README.md b/core/dr-pipeline/ops/README.md index d71397b..7458ab3 100644 --- a/core/dr-pipeline/ops/README.md +++ b/core/dr-pipeline/ops/README.md @@ -294,40 +294,18 @@ in raw pixels is a different photograph on screen and in the exported file. ## What is not a node, and why Three things act on every pixel and are deliberately not in this directory: -the as-shot white balance, the camera matrix, and the **base curve** -(FR-DEV-3e). They are emitted by [`../src/operation.rs`](../src/operation.rs) -into the composed shader's fixed preamble, around the block of nodes. +the as-shot white balance, the camera matrix, and the **view transform** +(FR-DEV-3j). They are emitted by [`../src/operation.rs`](../src/operation.rs) +into the composed shader around the block of nodes. -The test is not "does it transform a colour" — all three do. It is **whose -decision is it**. A node is something a photographer chose: it has parameters, -it moves off a neutral, it lands in the sidecar, it can be undone. These three -are properties of the *file*, at the same standing as the masked-photosite crop -(FR-RAW-3) and the stored orientation (FR-DEV-3h). Nobody chose the sensor's -green sensitivity or the body's rendering; they are what reading the file -correctly means. - -Making the base curve a node would have said the opposite in four places at -once. It would have appeared in the develop panel as a control, so an -unprofiled body would show a slider that does nothing. Its values would have -gone into the sidecar, and sidecars are shared between devices and bodies -(FR-NC-9) — one camera's rendering would follow an edit onto another camera's -file. Its neutral would have had to be "the identity", so a profiled body would -open reporting itself modified. And there is no seam through which a node could -learn which camera took the frame: the profile arrives on the decoded image, -travels through `DemosaicedImage` beside the matrix it belongs with, and is -written into the uniform block by the same three lines in `dr-gpu` — which is -exactly the path the matrix already took, because it is exactly the same kind -of thing. - -What it *does* share with the tone curve node is the spline. The composer asks -`ToneCurve` for its `curve_span`/`curve_eval` helpers rather than emitting a -second copy, so a profile author placing a control point and a photographer -dragging one mean the same thing by it. - -The order still reads correctly from this directory: the base curve runs after -every node in the chain. That is the same reasoning `exposure` records under -`placement:` — corrections to capture are only meaningful on linear values, so -the rendering goes last. +The first two are properties of the *file*, at the same standing as the +masked-photosite crop (FR-RAW-3) and the stored orientation (FR-DEV-3h): nobody +chose the sensor's green sensitivity, and reading the file correctly means +undoing it. The view transform is different in kind — it is the one stage that +maps scene-linear colour to a display range (D19, ARCH §6.14) — and it runs +after every node, because corrections to capture are only meaningful on linear +values. It replaced the per-body **base curve**, which was looked up by camera +model and flat past 1.0, so it clipped every recovered highlight. ## Stages diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs index 215f628..009d63e 100644 --- a/core/dr-pipeline/src/lib.rs +++ b/core/dr-pipeline/src/lib.rs @@ -50,6 +50,7 @@ pub mod preset; pub mod sidecar; pub mod spot; pub mod state; +pub mod view; pub use coverage::Coverage; pub use declared::{Declaration, DeclaredOp}; @@ -66,8 +67,7 @@ pub use history::{Edit, Entry as HistoryEntry, History, Step}; 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, CLIP_ONSET, - RESERVED_UNIFORM_FIELDS, SAMPLE_CACHE_UNIFORM_OFFSET, + OutputMode, Stage, Uniform, CLIP_ONSET, RESERVED_UNIFORM_FIELDS, SAMPLE_CACHE_UNIFORM_OFFSET, }; pub use preset::{LibraryParseError, NameError, Preset, PresetLibrary, Reach, Scope}; pub use sidecar::{Sidecar, Version}; diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index b3e5277..e323961 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -557,12 +557,12 @@ pub struct ComposedShader { /// Fields the generated uniform struct always carries, before op uniforms. /// /// WGSL requires a uniform struct to be non-empty and 16-byte aligned; these -/// are needed by every generated shader in any case. +/// are needed by every generated shader in any case: the matrix, the as-shot +/// balance and the sample cache's flags. Framing's block follows. /// -/// Twelve of the twenty-eight are the camera profile's base curve -/// ([`BASE_CURVE_UNIFORM_FIELDS`]); the rest are the matrix, the as-shot -/// balance and framing's own block. -const BASE_UNIFORM_FIELDS: usize = 16 + SAMPLE_CACHE_UNIFORM_FIELDS + BASE_CURVE_UNIFORM_FIELDS; +/// Twelve fewer than before D19, which retired the base curve that sat at the +/// end of this block. +const BASE_UNIFORM_FIELDS: usize = 16 + SAMPLE_CACHE_UNIFORM_FIELDS; /// Slots the sample cache's two flags occupy: read, write, and two spare to /// keep the block a whole `vec4`. See [`ComposedShader::sample_key`]. @@ -571,36 +571,11 @@ const SAMPLE_CACHE_UNIFORM_FIELDS: usize = 4; /// Where the sample cache's flags sit in the generated uniform block: `x` says /// read the source colour from the cache, `y` says write it there. /// -/// Exported for the reason [`BASE_CURVE_UNIFORM_OFFSET`] is — `dr-gpu` writes +/// Exported for the reason [`RESERVED_UNIFORM_FIELDS`] is — `dr-gpu` writes /// these by index — and zero in every block the composer hands out, so a /// caller that never heard of the cache gets the direct read it always had. pub const SAMPLE_CACHE_UNIFORM_OFFSET: usize = 16; -/// TRACES: FR-DEV-3e -/// Slots the base curve occupies: five `(x, y)` points and an active flag. -/// -/// Twelve rather than eleven so the block stays a whole number of `vec4`s, -/// which is what std140 requires of a uniform struct's members. The spare -/// float is left zero rather than repurposed — a uniform slot that means one -/// thing today and two things next year is how a shader comes to read a -/// highlight rolloff out of a crop rectangle. -const BASE_CURVE_UNIFORM_FIELDS: usize = 12; - -/// TRACES: FR-DEV-3e -/// Where the base curve's slots begin in the generated uniform block. -/// -/// Exported for the same reason [`RESERVED_UNIFORM_FIELDS`] is: `dr-gpu` -/// writes these by index, and an offset computed independently at both ends is -/// an offset that will eventually disagree with itself. -pub const BASE_CURVE_UNIFORM_OFFSET: usize = - SAMPLE_CACHE_UNIFORM_OFFSET + SAMPLE_CACHE_UNIFORM_FIELDS; - -/// How many control points a base curve carries. -/// -/// The same five the tone curve widget has, deliberately — see the helper -/// selection in [`compose_full`]. -pub const BASE_CURVE_POINTS: usize = 5; - /// Where the highlight desaturation begins: the fraction of the white level /// above which a photosite is treated as clipped. /// @@ -869,42 +844,20 @@ fn compose_inner( \x20 // The sample cache (see `ComposedShader::sample_key`): `.x` reads\n\ \x20 // the source colour from `sampled`, `.y` writes it to\n\ \x20 // `sample_out`. Zero for both is the direct read.\n\ - \x20 sample_cache: vec4,\n\ - \x20 // The camera profile's base curve (FR-DEV-3e): five points on a\n\ - \x20 // monotone spline, packed as x0..x3, y0..y3, then (x4, y4, on).\n\ - \x20 // `.z` of the last is the flag, not padding — it is 0 for a\n\ - \x20 // body with no profile and for an already-rendered source.\n\ - \x20 base_curve_x: vec4,\n\ - \x20 base_curve_y: vec4,\n\ - \x20 base_curve_last: vec4,\n", + \x20 sample_cache: vec4,\n", ); uniform_values.resize(BASE_UNIFORM_FIELDS, 0.0); - // TRACES: FR-DEV-3e - // The spline the base curve is evaluated on is the *tone curve's* spline, - // reached through the trait rather than reimplemented here. - // - // Two reasons, and the second is the one that matters. The obvious one is - // that a shader carrying two `curve_eval`s would not compile, and the - // composer's helper de-duplication is what makes both stages able to ask - // for it. The real one is that a profile author placing a control point - // and a photographer dragging one must mean the same thing by it — down to - // the Fritsch-Carlson tangent limiting, which is what decides how a - // shoulder actually rolls off. Two implementations that agreed today would - // be two that could disagree later, and the disagreement would show up as - // a body whose profile renders subtly differently from the curve someone - // drew to match it. - // - // Emitted unconditionally, unlike an operation's helpers. The base curve - // is active for every RAW frame — an unprofiled body still gets the - // database's default rendering — so making the shader's shape depend on it - // would split the pipeline cache in two for no benefit. The uniform flag - // above turns it off for the cases that are genuinely already rendered, - // and a branch on a uniform is coherent across the whole dispatch. - for h in crate::ops::ToneCurve::new().helpers() { - if matches!(h.name, "curve_span" | "curve_eval") { - helpers.push(*h); - } + // TRACES: FR-DEV-3j + // The view transform's function, whenever the composer emits the view + // transform — which is every render but the camera-space tap and one a + // rendering operation has taken over. See `rendering_tail` below. + let views = !op_renders && output_mode != OutputMode::CameraLinear; + if views { + helpers.push(Helper { + name: "view_sigmoid", + source: crate::view::VIEW_SIGMOID_WGSL, + }); } // Framing's block follows the base one at a fixed offset, for the same @@ -1161,47 +1114,32 @@ fn compose_inner( ), }; - // The base curve, which an operation may have taken over. - let rendering_tail = if op_renders { - " // The base curve is absent: an operation declaring\n // `Operation::renders` has done its job, and doing it again would render\n // the picture twice.\n" + // The view transform, which an operation may have taken over. + let rendering_tail = if !views { + " // No view transform: an operation declaring `Operation::renders` has\n // mapped the scene to a display range itself, or this is the\n // camera-space tap, which stores the sensor's own numbers.\n" .to_string() } else { - " // ==== camera profile: the base curve (FR-DEV-3e) ==== + let curve = crate::view::Sigmoid::default_curve(); + format!( + " // ==== the view transform (FR-DEV-3j) ==== // // Marked with `====` and not the `----` an operation block carries: this // is not one, and the difference is what several tests count on to tell - // an edit apart from the reading of a file. + // an edit apart from the rendering of one. // - // After every operation, and — since D19 moved the matrix to the front of - // the chain — on working-space colour rather than the camera RGB it was - // tuned against. An interim placement: the per-body curve is being - // replaced by a view transform after the detail stage (FR-DEV-3j). + // The one stage allowed to map scene-linear colour to a display range + // (D19, ARCH §6.14), after every operation. Everything above it is + // unbounded; everything the shoulder has not brought under 1.0 is + // clipped by the output transform, at the last moment. // - // The branch is on a uniform, so the whole dispatch takes the same path. - // It is off for a JPEG and any other already-rendered source, which must - // not be rendered twice, and for a body the profile database declines to - // offer any curve for at all. - if (u.base_curve_last.z > 0.5) { - c = vec3( - curve_eval( - u.base_curve_x.x, u.base_curve_y.x, u.base_curve_x.y, u.base_curve_y.y, - u.base_curve_x.z, u.base_curve_y.z, u.base_curve_x.w, u.base_curve_y.w, - u.base_curve_last.x, u.base_curve_last.y, c.r, - ), - curve_eval( - u.base_curve_x.x, u.base_curve_y.x, u.base_curve_x.y, u.base_curve_y.y, - u.base_curve_x.z, u.base_curve_y.z, u.base_curve_x.w, u.base_curve_y.w, - u.base_curve_last.x, u.base_curve_last.y, c.g, - ), - curve_eval( - u.base_curve_x.x, u.base_curve_y.x, u.base_curve_x.y, u.base_curve_y.y, - u.base_curve_x.z, u.base_curve_y.z, u.base_curve_x.w, u.base_curve_y.w, - u.base_curve_last.x, u.base_curve_last.y, c.b, - ), - ); - } -" - .to_string() + // Skipped for an already-rendered source — a JPEG is a display rendering + // already, and rendering it again would compress it twice. + if (!non_linear) {{ + c = view_sigmoid(c, {:?}, {:?}, {:?}); + }} +", + curve.n, curve.inv_k, curve.w + ) }; // Formatted with Rust's `Display` so the shader reads the same threshold @@ -2270,10 +2208,10 @@ mod tests { } #[test] - fn a_rendering_operation_takes_over_the_base_curve() { - // TRACES: FR-DEV-3e | FR-DEV-3f - // A film stock's characteristic curve does the base curve's job. - // Emitting the profile's rendering as well would render the scene + fn a_rendering_operation_takes_over_the_view_transform() { + // TRACES: FR-DEV-3j | FR-DEV-3f + // A film stock's characteristic curve does the view transform's job. + // Emitting the default rendering as well would render the scene // twice — a picture that comes out looking like neither the camera's // rendering nor the film's, with a colour-management bug's signature // and no colour-management bug to find. @@ -2299,8 +2237,8 @@ mod tests { .find("---- film_sim ----") .expect("the operation itself must still be emitted"); assert!( - !source.contains("base_curve_last.z > 0.5"), - "the base curve is still being applied on top of the film" + !source.contains("view_sigmoid"), + "the view transform is still being applied on top of the film" ); // D19: the film no longer converts out of camera space itself. The // composer does, once, ahead of it — the film is handed working-space @@ -2323,7 +2261,7 @@ mod tests { // suppressed the tail unconditionally renders every ordinary edit // flat and uncorrected, which reads as a broken camera profile. let source = compose(&[fake(DESC_A.clone(), 2.0, false)]).source; - assert!(source.contains("base_curve_last.z > 0.5")); + assert!(source.contains("c = view_sigmoid(")); assert!(source.contains("camera profile: the matrix")); } @@ -2335,7 +2273,7 @@ mod tests { // catalogue, and it must not disturb the camera's own rendering. let film: Box = Box::new(crate::ops::FilmSim::new()); let source = compose(&[film, fake(DESC_A.clone(), 2.0, false)]).source; - assert!(source.contains("base_curve_last.z > 0.5")); + assert!(source.contains("c = view_sigmoid(")); assert!(source.contains("camera profile: the matrix")); } @@ -2374,8 +2312,8 @@ mod tests { } #[test] - fn the_base_curve_runs_after_the_operations() { - // TRACES: FR-DEV-3e + fn the_view_transform_runs_after_the_operations() { + // TRACES: FR-DEV-3j // Exposure and the tonal controls are corrections to capture, and // they are only meaningful on linear values. A stop is a doubling; run // exposure after a curve and it is not one any more, and every slider @@ -2383,66 +2321,23 @@ mod tests { let ops = vec![fake(DESC_A.clone(), 2.0, false)]; let source = compose(&ops).source; let op = source.find("---- op_a ----").expect("op present"); - let curve = source - .find("if (u.base_curve_last.z > 0.5)") - .expect("base curve applied"); - assert!(op < curve, "the base curve must come after the operations"); - } - - #[test] - fn the_base_curve_reaches_a_shader_with_no_operations_at_all() { - // TRACES: FR-DEV-3e - // The same property as as-shot white balance, and for the same reason: - // it is part of interpreting the file, not part of the edit. An - // unedited RAW must open looking like a photograph rather than like a - // scan of one. - let shader = compose(&[]); - assert!(shader.source.contains("u.base_curve_x")); + let view = source.find("c = view_sigmoid(").expect("view applied"); assert!( - shader.source.contains("fn curve_eval("), - "the spline it is evaluated on must be emitted too" + op < view, + "the view transform must come after the operations" ); } #[test] - fn the_base_curve_and_the_tone_curve_share_one_spline() { - // TRACES: FR-DEV-3e - // Two `curve_eval`s in one shader would not compile — but the reason - // the helper is *shared* rather than merely renamed is that a profile - // author placing a control point and a photographer dragging one must - // mean the same thing by it, down to the tangent limiting that decides - // how a shoulder rolls off. - let mut curve = crate::ops::ToneCurve::new(); - curve.set_param(crate::ops::curve::P2_Y, 0.7); - assert!( - curve.is_active(), - "the fixture must actually reach the shader" - ); - - let source = compose(&[Box::new(curve)]).source; - assert_eq!( - source.matches("fn curve_eval(").count(), - 1, - "the spline must be declared exactly once" - ); - assert_eq!(source.matches("fn curve_span(").count(), 1); - } - - #[test] - fn the_base_curve_owns_the_slots_dr_gpu_writes() { - // TRACES: FR-DEV-3e - // `dr-gpu` fills these by index. The offset is exported rather than - // recomputed there, and this asserts the exported number still points - // at the block the shader declares — the failure otherwise is a - // highlight rolloff read out of a crop rectangle, which renders as - // nonsense rather than as an error. - assert_eq!( - BASE_CURVE_UNIFORM_OFFSET + BASE_CURVE_UNIFORM_FIELDS, - BASE_UNIFORM_FIELDS, - "the base curve must be the last thing in the base block" - ); - assert_eq!(BASE_CURVE_POINTS * 2 + 1, BASE_CURVE_UNIFORM_FIELDS - 1); - assert!(compose(&[]).uniforms.len() >= BASE_UNIFORM_FIELDS); + fn the_view_transform_reaches_a_shader_with_no_operations_at_all() { + // TRACES: FR-DEV-3j + // An unedited RAW must open looking like a photograph rather than + // like a scan of one, and it is skipped only for a source that is + // already a rendering. + let source = compose(&[]).source; + assert!(source.contains("c = view_sigmoid(")); + assert!(source.contains("fn view_sigmoid(")); + assert!(source.contains("if (!non_linear)")); } /// Compose with neutral framing into a chosen output space. @@ -2450,6 +2345,15 @@ mod tests { compose_with_framing(ops, &Framing::new(), output) } + #[test] + fn the_camera_space_tap_has_no_view_transform() { + // TRACES: FR-MRG-2 + // A merge stitches the sensor's own numbers; a rendering in them + // would be developed a second time when the composite is opened. + let source = compose_camera_probe(&[], &crate::framing::Framing::default()).source; + assert!(!source.contains("view_sigmoid")); + } + #[test] fn an_srgb_render_is_byte_for_byte_what_it_was_before_output_spaces_existed() { // The display path is the shader compiled on nearly every frame, and diff --git a/core/dr-pipeline/src/view.rs b/core/dr-pipeline/src/view.rs new file mode 100644 index 0000000..911dc2b --- /dev/null +++ b/core/dr-pipeline/src/view.rs @@ -0,0 +1,276 @@ +//! TRACES: FR-DEV-3j | FR-DEV-2 +//! The view transform — the one stage that maps scene-linear colour to a +//! display range (D19, ARCH §6.14). +//! +//! # What it is +//! +//! A log-logistic sigmoid, per channel: +//! +//! ```text +//! f(x) = w · r / (1 + r), r = (x / k)^n +//! ``` +//! +//! `n` is the contrast — the slope in log-log terms, before the shoulder +//! bends it. `k` and `w` are solved from two conditions rather than set: +//! scene middle grey lands on display middle grey, and the scene white the +//! photographer chose lands on display white. So the curve has a toe, a +//! midtone slope and a shoulder that approaches `w` — a hair above 1.0 — +//! without ever reaching it. Everything the shoulder has not reached by the +//! white point is clipped by the output transform, which is the last moment +//! and the only place a clip belongs. +//! +//! # Why per channel, and why the middle channel is put back +//! +//! Per channel is what makes a bright saturated colour desaturate as it +//! approaches white — a blown sky rolls toward white rather than toward a +//! saturated corner of the gamut, which is what film and every camera JPEG +//! do. It also bends hue: the three channels sit at different places on the +//! curve, so their ratios change, and an orange flame drifts toward yellow. +//! So after the curve the middle channel is moved back to where it sat +//! *between the other two* before it — the same fraction of the way from the +//! smallest to the largest. The smallest and largest keep what the curve gave +//! them, which keeps the desaturation; the hue, which is decided by that +//! fraction, survives. It is the "preserve hue" step of darktable's sigmoid, +//! at full strength. +//! +//! # Why these defaults +//! +//! [`SCENE_GREY`] is where the retired default base curve put middle grey +//! (FR-DEV-3e): linear sensor data from a correctly exposed frame has it +//! near 13% of saturation, and a camera JPEG shows it at 18%. The contrast +//! and white defaults were chosen against that same retired curve: at 1.4 and +//! 4 stops the midtones stay within a quarter of a stop of it between scene +//! 0.03 and 1.0, while a highlight a stop past sensor saturation still rolls +//! into white rather than stopping dead at it. The upper midtones come out a +//! little darker than the curve had them, which is the price of that +//! headroom and what the white slider is for. + +/// Scene-linear middle grey: where the retired default curve placed it. +pub const SCENE_GREY: f32 = 0.13; + +/// Display-linear middle grey — what a camera JPEG shows a grey card as. +pub const DISPLAY_GREY: f32 = 0.18; + +/// The default contrast, the sigmoid's log-log slope parameter `n`. +pub const DEFAULT_CONTRAST: f32 = 1.4; + +/// The default white point, in stops above [`SCENE_GREY`]. +pub const DEFAULT_WHITE: f32 = 4.0; + +/// The contrast range a photographer is offered. +pub const CONTRAST_RANGE: (f32, f32) = (1.0, 3.0); + +/// The white point range, in stops above middle grey. +/// +/// The floor is not taste. The two conditions `k` and `w` are solved from +/// have a solution only while `2^(white · n)` exceeds `1 / DISPLAY_GREY`, +/// and at the lowest contrast that needs `white` above about 2.47 stops. +pub const WHITE_RANGE: (f32, f32) = (2.5, 10.0); + +/// The curve's three numbers, solved from the photographer's two. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Sigmoid { + /// Contrast: the exponent. + pub n: f32, + /// `1 / k`, so the shader multiplies rather than divides. + pub inv_k: f32, + /// The asymptote the shoulder approaches, a little above 1.0. + pub w: f32, +} + +impl Sigmoid { + /// Solve the curve for a contrast and a white point in stops. + /// + /// Out-of-range inputs are clamped to [`CONTRAST_RANGE`] and + /// [`WHITE_RANGE`] rather than trusted: they arrive from a sidecar, which + /// may have been written by a build with other limits, and outside them + /// the solution below divides by something that is no longer positive. + /// + /// With `r_g` the value of `r` at scene grey and `q = 2^(white · n)`, the + /// two conditions `f(grey) = display grey` and `f(grey · 2^white) = 1` + /// are `w·r_g/(1+r_g) = g` and `w·q·r_g/(1+q·r_g) = 1`. Dividing one by + /// the other eliminates `w` and leaves `r_g = (g·q − 1) / (q·(1 − g))`. + pub fn new(contrast: f32, white: f32) -> Self { + let n = contrast.clamp(CONTRAST_RANGE.0, CONTRAST_RANGE.1) as f64; + let white = white.clamp(WHITE_RANGE.0, WHITE_RANGE.1) as f64; + let g = f64::from(DISPLAY_GREY); + let q = (white * n).exp2(); + let r_grey = (g * q - 1.0) / (q * (1.0 - g)); + let w = g * (1.0 + r_grey) / r_grey; + // r = (x / k)^n, and r at scene grey is r_grey, so + // k = grey / r_grey^(1/n). + let k = f64::from(SCENE_GREY) / r_grey.powf(1.0 / n); + Self { + n: n as f32, + inv_k: (1.0 / k) as f32, + w: w as f32, + } + } + + /// The default curve. + pub fn default_curve() -> Self { + Self::new(DEFAULT_CONTRAST, DEFAULT_WHITE) + } + + /// One channel through the curve. The CPU reference the shader is + /// tested against. + pub fn channel(&self, x: f32) -> f32 { + let r = (x.max(0.0) * self.inv_k).powf(self.n); + self.w * r / (1.0 + r) + } + + /// A colour through the curve, with the middle channel put back between + /// the other two. See the module documentation. + pub fn apply(&self, c: [f32; 3]) -> [f32; 3] { + let x = c.map(|v| v.max(0.0)); + let y = x.map(|v| self.channel(v)); + let lo = x[0].min(x[1]).min(x[2]); + let hi = x[0].max(x[1]).max(x[2]); + if hi - lo <= 1e-9 { + return y; + } + let (y_lo, y_hi) = (self.channel(lo), self.channel(hi)); + x.map(|v| y_lo + (y_hi - y_lo) * (v - lo) / (hi - lo)) + } +} + +/// The WGSL twin of [`Sigmoid::apply`], as a helper function. +pub const VIEW_SIGMOID_WGSL: &str = "\ +// The view transform (FR-DEV-3j): a log-logistic sigmoid per channel, then the +// middle channel put back between the other two so that the hue survives the +// shoulder. See `dr_pipeline::view` for the derivation and the defaults. +fn view_sigmoid(c: vec3, n: f32, inv_k: f32, w: f32) -> vec3 { + // Negative components are colours outside the working primaries. They are + // floored here, at the last stage, which is the one place a gamut clip + // belongs. + let x = max(c, vec3(0.0)); + let lo = min(x.r, min(x.g, x.b)); + let hi = max(x.r, max(x.g, x.b)); + let r_lo = pow(lo * inv_k, n); + let r_hi = pow(hi * inv_k, n); + let y_lo = w * r_lo / (1.0 + r_lo); + let y_hi = w * r_hi / (1.0 + r_hi); + // Each channel's place between the smallest and the largest. A neutral + // has no spread, and every channel then takes the one value there is. + let spread = hi - lo; + let t = select((x - vec3(lo)) / max(spread, 1e-9), vec3(0.0), spread <= 1e-9); + return vec3(y_lo) + (y_hi - y_lo) * t; +} +"; + +#[cfg(test)] +mod tests { + use super::*; + + /// The retired default base curve, for the acceptance comparison: five + /// points through the unit square, sampled here by straight lines between + /// them in log-log terms — close enough to the monotone spline that drew + /// it for a tolerance measured in quarters of a stop. Below its second + /// point it was a straight line from the origin. + fn retired_default(x: f32) -> f32 { + const P: [(f32, f32); 4] = [(0.04, 0.043), (0.13, 0.175), (0.45, 0.690), (1.0, 1.0)]; + if x < 0.04 { + return x * (0.043 / 0.04); + } + let x = x.min(1.0); + let i = P.windows(2).position(|w| x <= w[1].0).unwrap_or(2); + let ((x0, y0), (x1, y1)) = (P[i], P[i + 1]); + let t = (x.ln() - x0.ln()) / (x1.ln() - x0.ln()); + (y0.ln() + t * (y1.ln() - y0.ln())).exp() + } + + #[test] + fn middle_grey_lands_on_display_grey() { + // TRACES: FR-DEV-3j + for (contrast, white) in [(1.0, 3.0), (1.4, 4.0), (2.5, 8.0), (3.0, 10.0)] { + let s = Sigmoid::new(contrast, white); + let got = s.channel(SCENE_GREY); + assert!( + (got - DISPLAY_GREY).abs() < 0.01, + "contrast {contrast}, white {white}: grey went to {got}" + ); + } + } + + #[test] + fn the_white_point_reaches_display_white() { + // TRACES: FR-DEV-3j + // The whole meaning of the slider: the scene value it names is where + // the picture reaches white, and not before. + for (contrast, white) in [(1.0, 3.0), (1.4, 4.0), (2.5, 8.0)] { + let s = Sigmoid::new(contrast, white); + let at = SCENE_GREY * white.exp2(); + assert!((s.channel(at) - 1.0).abs() < 1e-4, "{}", s.channel(at)); + assert!(s.channel(at * 0.9) < 1.0); + assert!(s.w > 1.0, "the shoulder must approach a value above white"); + } + } + + #[test] + fn the_curve_is_monotone_and_keeps_going_past_one() { + // TRACES: FR-DEV-3j | FR-DEV-2 + // What the base curve got wrong: it was flat past 1.0, so every + // recovered highlight left it as the same number. + let s = Sigmoid::default_curve(); + let mut last = -1.0; + for i in 0..=2000 { + let x = i as f32 * 0.004; + let y = s.channel(x); + assert!(y > last || (x == 0.0 && y == 0.0), "not increasing at {x}"); + last = y; + } + assert!(s.channel(2.0) > s.channel(1.0)); + } + + #[test] + fn the_default_stays_close_to_the_retired_curve() { + // TRACES: FR-DEV-3j | FR-DEV-3e + // D19's promise to every existing photograph: the midtones do not + // move by more than a third of a stop. + let s = Sigmoid::default_curve(); + let mut x = 0.03_f32; + while x <= 1.0 { + let ev = (s.channel(x) / retired_default(x)).log2(); + assert!(ev.abs() < 0.3, "at scene {x} the default moved {ev:+.2} EV"); + x *= 1.1; + } + } + + #[test] + fn a_neutral_stays_neutral() { + // TRACES: FR-DEV-3j + let s = Sigmoid::default_curve(); + for v in [0.0, 0.01, 0.13, 1.0, 7.0] { + let [r, g, b] = s.apply([v, v, v]); + assert_eq!(r, g); + assert_eq!(g, b); + } + } + + #[test] + fn the_hue_survives_the_shoulder() { + // TRACES: FR-DEV-3j + // The middle channel's place between the other two is what decides + // the hue. Without the correction an orange at the shoulder drifts + // toward yellow as the red channel saturates first. + let s = Sigmoid::default_curve(); + let orange = [2.0, 0.8, 0.1]; + let out = s.apply(orange); + let before = (orange[1] - orange[2]) / (orange[0] - orange[2]); + let after = (out[1] - out[2]) / (out[0] - out[2]); + assert!((before - after).abs() < 1e-5, "{before} became {after}"); + // And the extremes keep what the curve gave them — the desaturation + // toward white is the point of working per channel. + assert!((out[0] - s.channel(2.0)).abs() < 1e-6); + assert!((out[2] - s.channel(0.1)).abs() < 1e-6); + } + + #[test] + fn out_of_range_settings_are_clamped_not_trusted() { + // A sidecar from another build may carry anything, and outside the + // range the solution divides by a value that is no longer positive. + let s = Sigmoid::new(0.0, 0.0); + assert!(s.n.is_finite() && s.inv_k.is_finite() && s.w.is_finite()); + assert_eq!(s, Sigmoid::new(CONTRAST_RANGE.0, WHITE_RANGE.0)); + } +} diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index f66150d..9a88010 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -452,7 +452,7 @@ exactly the stall §6.1 exists to prevent. **Two reductions, not one.** The display histogram (FR-DSP-7) counts the frame the output transform produced: its axis is the output code value, and a clipped bin means a highlight that is gone as the image currently stands. The raw histogram for culling (FR-CULL-3) counts the **demosaiced -scene-linear texture** — before white balance, the camera matrix, the base curve and the tone chain +scene-linear texture** — before white balance, the camera matrix, the tone chain and the view transform — on an axis of stops below sensor saturation, which is how it reports headroom the embedded JPEG's histogram cannot. A culling decision needs the second, an export decision needs the first, and neither answers for the other. Both are drawn by the same panel and chosen between. diff --git a/docs/dev/mask-editing.md b/docs/dev/mask-editing.md index 6cb0bfb..a8c29ef 100644 --- a/docs/dev/mask-editing.md +++ b/docs/dev/mask-editing.md @@ -446,8 +446,8 @@ reading a flag. After the output transform, immediately before the clip and the encode — not among the layer blocks. Everything there runs on scene-referred colour in the -working space, where a flat tint would be pushed through the base curve and -the camera matrix and arrive as some other colour, and an alpha's white on +working space, where a flat tint would be pushed through the view transform +(the base curve and the camera matrix, before D19) and arrive as some other colour, and an alpha's white on black would arrive as neither. ### 6.3 Not on the graph diff --git a/docs/dev/panorama.md b/docs/dev/panorama.md index 02928a3..837b02f 100644 --- a/docs/dev/panorama.md +++ b/docs/dev/panorama.md @@ -128,7 +128,11 @@ stays hot for it. ### 5.1 The tap — S15.3, answered by reading the composer The fused shader's order, fixed by `operation.rs`'s own tests: warp → as-shot -white balance → operations → base curve → camera matrix → store. The store is +white balance → operations → base curve → camera matrix → store. *(Amended +2026-09-27, D19: warp → as-shot white balance → white balance → camera matrix +→ operations → view transform → store. The tap is unaffected: it has no +operations, its caller fills the matrix with the identity, and the composer +emits no view transform in `OutputMode::CameraLinear`.)* The store is either the display encode or, in `OutputMode::LinearWorking`, an unclipped `rgba16float` of linear sRGB. That mode exists for the detail stage and is selected from the operations, never by a caller flag, so that a shader and @@ -155,7 +159,7 @@ FR-MRG-11 already bounds. **What the DNG carries as a consequence:** the first source's `Make`, `Model` and `UniqueCameraModel` — so `base_curve::for_body` finds the 6D's -curve — its `ColorMatrix1`/`2` with illuminants, and its `AsShotNeutral`. The +curve (retired with the base curves, D19) — its `ColorMatrix1`/`2` with illuminants, and its `AsShotNeutral`. The composite then develops through the same profile as its sources, applied once. The spike's 64 × 48 file (§8) already carries the matrix and neutral; the body name is a string. diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index a6e2edd..b6dfda4 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -9,18 +9,18 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 470 | -| TRACES tags found | 1918 | +| Source files scanned | 469 | +| TRACES tags found | 1916 | | Requirements defined | 193 | | Requirements deferred (post-v1) | 24 | -| Requirements covered | 163 | -| **Coverage** | **84.5%** (163/193) | +| Requirements covered | 164 | +| **Coverage** | **85.0%** (164/193) | ### By type | Type | Covered | Defined | |---|---|---| -| FR | 123 | 135 | +| FR | 124 | 135 | | NFR | 35 | 51 | | R | 5 | 7 | @@ -45,7 +45,7 @@ _None._ | 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:65`](../../core/dr-catalog/src/walk.rs#L65), [`core/dr-sync/src/scan.rs:105`](../../core/dr-sync/src/scan.rs#L105), [`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:473`](../../ui/dr-ui/src/import.rs#L473), [`ui/dr-ui/src/import.rs:503`](../../ui/dr-ui/src/import.rs#L503), [`ui/dr-ui/src/library/sweep.rs:563`](../../ui/dr-ui/src/library/sweep.rs#L563), [`ui/dr-ui/src/library_ui/controller.rs:187`](../../ui/dr-ui/src/library_ui/controller.rs#L187), [`ui/dr-ui/src/library_ui/grid.rs:555`](../../ui/dr-ui/src/library_ui/grid.rs#L555), [`ui/dr-ui/src/library_ui/sync.rs:393`](../../ui/dr-ui/src/library_ui/sync.rs#L393), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/repairs.rs:696`](../../ui/dr-ui/src/repairs.rs#L696), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:781`](../../ui/dr-ui/ui/library.slint#L781), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375), [`ui/dr-ui/ui/settings.slint:74`](../../ui/dr-ui/ui/settings.slint#L74) | | 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:594`](../../core/dr-catalog/src/schema.rs#L594), [`ui/dr-ui/src/library/filters.rs:39`](../../ui/dr-ui/src/library/filters.rs#L39), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) | -| 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/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:827`](../../core/dr-catalog/src/rating.rs#L827), [`core/dr-catalog/src/rating.rs:851`](../../core/dr-catalog/src/rating.rs#L851), [`core/dr-catalog/src/rating.rs:870`](../../core/dr-catalog/src/rating.rs#L870), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:1742`](../../core/dr-catalog/src/schema.rs#L1742), [`core/dr-decode/src/lib.rs:307`](../../core/dr-decode/src/lib.rs#L307), [`core/dr-decode/src/lib.rs:313`](../../core/dr-decode/src/lib.rs#L313), [`core/dr-decode/src/lib.rs:323`](../../core/dr-decode/src/lib.rs#L323), [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2876`](../../core/dr-pipeline/src/sidecar.rs#L2876), [`core/dr-pipeline/src/sidecar.rs:2894`](../../core/dr-pipeline/src/sidecar.rs#L2894), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:209`](../../ui/dr-ui/src/collections_ui/controller.rs#L209), [`ui/dr-ui/src/collections_ui/controller.rs:418`](../../ui/dr-ui/src/collections_ui/controller.rs#L418), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/library/cells.rs:198`](../../ui/dr-ui/src/library/cells.rs#L198), [`ui/dr-ui/src/library/scan.rs:527`](../../ui/dr-ui/src/library/scan.rs#L527), [`ui/dr-ui/src/library/scan.rs:917`](../../ui/dr-ui/src/library/scan.rs#L917), [`ui/dr-ui/src/library_ui/controller.rs:836`](../../ui/dr-ui/src/library_ui/controller.rs#L836), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1154`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1154), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1178`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1178), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1195`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1195), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1401`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1401), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1412`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1412), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1425`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1425), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1440`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1440), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1449`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1449), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1667`](../../ui/dr-ui/ui/app.slint#L1667), [`ui/dr-ui/ui/app.slint:2822`](../../ui/dr-ui/ui/app.slint#L2822), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1768`](../../ui/dr-ui/ui/library.slint#L1768), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1849`](../../ui/dr-ui/ui/library.slint#L1849), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3934`](../../ui/dr-ui/ui/library.slint#L3934), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:515`](../../ui/dr-ui/ui/library.slint#L515), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:675`](../../ui/dr-ui/ui/library.slint#L675) | +| 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/rating.rs:396`](../../core/dr-catalog/src/rating.rs#L396), [`core/dr-catalog/src/rating.rs:411`](../../core/dr-catalog/src/rating.rs#L411), [`core/dr-catalog/src/rating.rs:422`](../../core/dr-catalog/src/rating.rs#L422), [`core/dr-catalog/src/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:52`](../../core/dr-catalog/src/rating.rs#L52), [`core/dr-catalog/src/rating.rs:827`](../../core/dr-catalog/src/rating.rs#L827), [`core/dr-catalog/src/rating.rs:851`](../../core/dr-catalog/src/rating.rs#L851), [`core/dr-catalog/src/rating.rs:870`](../../core/dr-catalog/src/rating.rs#L870), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-catalog/src/schema.rs:1742`](../../core/dr-catalog/src/schema.rs#L1742), [`core/dr-decode/src/lib.rs:292`](../../core/dr-decode/src/lib.rs#L292), [`core/dr-decode/src/lib.rs:298`](../../core/dr-decode/src/lib.rs#L298), [`core/dr-decode/src/lib.rs:308`](../../core/dr-decode/src/lib.rs#L308), [`core/dr-decode/src/lib.rs:435`](../../core/dr-decode/src/lib.rs#L435), [`core/dr-export/src/dng.rs:308`](../../core/dr-export/src/dng.rs#L308), [`core/dr-pipeline/src/sidecar.rs:1093`](../../core/dr-pipeline/src/sidecar.rs#L1093), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-pipeline/src/sidecar.rs:193`](../../core/dr-pipeline/src/sidecar.rs#L193), [`core/dr-pipeline/src/sidecar.rs:2876`](../../core/dr-pipeline/src/sidecar.rs#L2876), [`core/dr-pipeline/src/sidecar.rs:2894`](../../core/dr-pipeline/src/sidecar.rs#L2894), [`core/dr-pipeline/src/sidecar.rs:590`](../../core/dr-pipeline/src/sidecar.rs#L590), [`ui/dr-ui/src/collections_ui/controller.rs:16`](../../ui/dr-ui/src/collections_ui/controller.rs#L16), [`ui/dr-ui/src/collections_ui/controller.rs:209`](../../ui/dr-ui/src/collections_ui/controller.rs#L209), [`ui/dr-ui/src/collections_ui/controller.rs:418`](../../ui/dr-ui/src/collections_ui/controller.rs#L418), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:150`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L150), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:264`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L264), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:323`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L323), [`ui/dr-ui/src/decoder_seam.rs:183`](../../ui/dr-ui/src/decoder_seam.rs#L183), [`ui/dr-ui/src/library/cells.rs:198`](../../ui/dr-ui/src/library/cells.rs#L198), [`ui/dr-ui/src/library/scan.rs:527`](../../ui/dr-ui/src/library/scan.rs#L527), [`ui/dr-ui/src/library/scan.rs:917`](../../ui/dr-ui/src/library/scan.rs#L917), [`ui/dr-ui/src/library_ui/controller.rs:836`](../../ui/dr-ui/src/library_ui/controller.rs#L836), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1154`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1154), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1178`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1178), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:118`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L118), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1195`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1195), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1401`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1401), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1412`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1412), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1425`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1425), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1440`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1440), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1449`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1449), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:97`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L97), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:177`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L177), [`ui/dr-ui/tests/status_is_not_colour_alone.rs:215`](../../ui/dr-ui/tests/status_is_not_colour_alone.rs#L215), [`ui/dr-ui/ui/app.slint:1667`](../../ui/dr-ui/ui/app.slint#L1667), [`ui/dr-ui/ui/app.slint:2822`](../../ui/dr-ui/ui/app.slint#L2822), [`ui/dr-ui/ui/develop.slint:142`](../../ui/dr-ui/ui/develop.slint#L142), [`ui/dr-ui/ui/develop.slint:48`](../../ui/dr-ui/ui/develop.slint#L48), [`ui/dr-ui/ui/labels.slint:19`](../../ui/dr-ui/ui/labels.slint#L19), [`ui/dr-ui/ui/labels.slint:1`](../../ui/dr-ui/ui/labels.slint#L1), [`ui/dr-ui/ui/labels.slint:40`](../../ui/dr-ui/ui/labels.slint#L40), [`ui/dr-ui/ui/labels.slint:74`](../../ui/dr-ui/ui/labels.slint#L74), [`ui/dr-ui/ui/library.slint:1614`](../../ui/dr-ui/ui/library.slint#L1614), [`ui/dr-ui/ui/library.slint:1617`](../../ui/dr-ui/ui/library.slint#L1617), [`ui/dr-ui/ui/library.slint:1768`](../../ui/dr-ui/ui/library.slint#L1768), [`ui/dr-ui/ui/library.slint:1792`](../../ui/dr-ui/ui/library.slint#L1792), [`ui/dr-ui/ui/library.slint:1849`](../../ui/dr-ui/ui/library.slint#L1849), [`ui/dr-ui/ui/library.slint:21`](../../ui/dr-ui/ui/library.slint#L21), [`ui/dr-ui/ui/library.slint:3202`](../../ui/dr-ui/ui/library.slint#L3202), [`ui/dr-ui/ui/library.slint:3209`](../../ui/dr-ui/ui/library.slint#L3209), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:3318`](../../ui/dr-ui/ui/library.slint#L3318), [`ui/dr-ui/ui/library.slint:3934`](../../ui/dr-ui/ui/library.slint#L3934), [`ui/dr-ui/ui/library.slint:467`](../../ui/dr-ui/ui/library.slint#L467), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:5076`](../../ui/dr-ui/ui/library.slint#L5076), [`ui/dr-ui/ui/library.slint:515`](../../ui/dr-ui/ui/library.slint#L515), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:665`](../../ui/dr-ui/ui/library.slint#L665), [`ui/dr-ui/ui/library.slint:671`](../../ui/dr-ui/ui/library.slint#L671), [`ui/dr-ui/ui/library.slint:675`](../../ui/dr-ui/ui/library.slint#L675) | | 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/rating.rs:449`](../../core/dr-catalog/src/rating.rs#L449), [`core/dr-catalog/src/rating.rs:880`](../../core/dr-catalog/src/rating.rs#L880), [`core/dr-catalog/src/schema.rs:1155`](../../core/dr-catalog/src/schema.rs#L1155), [`core/dr-types/src/place.rs:138`](../../core/dr-types/src/place.rs#L138), [`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/filters.rs:156`](../../ui/dr-ui/src/library/filters.rs#L156), [`ui/dr-ui/src/library/filters.rs:357`](../../ui/dr-ui/src/library/filters.rs#L357), [`ui/dr-ui/src/library/filters.rs:442`](../../ui/dr-ui/src/library/filters.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/spans.rs:262`](../../ui/dr-ui/src/library/spans.rs#L262), [`ui/dr-ui/src/library_ui/controller.rs:412`](../../ui/dr-ui/src/library_ui/controller.rs#L412), [`ui/dr-ui/src/library_ui/controller.rs:524`](../../ui/dr-ui/src/library_ui/controller.rs#L524), [`ui/dr-ui/src/library_ui/filter_bar.rs:256`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L256), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/filter_bar.rs:406`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L406), [`ui/dr-ui/src/library_ui/filter_bar.rs:840`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L840), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:85`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L85), [`ui/dr-ui/ui/app.slint:656`](../../ui/dr-ui/ui/app.slint#L656), [`ui/dr-ui/ui/library.slint:113`](../../ui/dr-ui/ui/library.slint#L113), [`ui/dr-ui/ui/library.slint:1936`](../../ui/dr-ui/ui/library.slint#L1936), [`ui/dr-ui/ui/library.slint:1973`](../../ui/dr-ui/ui/library.slint#L1973), [`ui/dr-ui/ui/library.slint:2703`](../../ui/dr-ui/ui/library.slint#L2703), [`ui/dr-ui/ui/library.slint:3229`](../../ui/dr-ui/ui/library.slint#L3229), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5060`](../../ui/dr-ui/ui/library.slint#L5060), [`ui/dr-ui/ui/library.slint:621`](../../ui/dr-ui/ui/library.slint#L621), [`ui/dr-ui/ui/library.slint:679`](../../ui/dr-ui/ui/library.slint#L679), [`ui/dr-ui/ui/library.slint:701`](../../ui/dr-ui/ui/library.slint#L701), [`ui/dr-ui/ui/settings.slint:202`](../../ui/dr-ui/ui/settings.slint#L202), [`ui/dr-ui/ui/widgets.slint:271`](../../ui/dr-ui/ui/widgets.slint#L271) | | FR-CAT-7 | [`core/dr-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`core/dr-catalog/src/collections.rs:1`](../../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/dedup_people.rs:1`](../../core/dr-catalog/src/dedup_people.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/controller.rs:263`](../../ui/dr-ui/src/collections_ui/controller.rs#L263), [`ui/dr-ui/src/collections_ui/controller.rs:273`](../../ui/dr-ui/src/collections_ui/controller.rs#L273), [`ui/dr-ui/src/collections_ui/controller.rs:296`](../../ui/dr-ui/src/collections_ui/controller.rs#L296), [`ui/dr-ui/src/collections_ui/controller.rs:317`](../../ui/dr-ui/src/collections_ui/controller.rs#L317), [`ui/dr-ui/src/collections_ui/controller.rs:46`](../../ui/dr-ui/src/collections_ui/controller.rs#L46), [`ui/dr-ui/src/collections_ui/drag.rs:274`](../../ui/dr-ui/src/collections_ui/drag.rs#L274), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/collections_ui/press.rs:258`](../../ui/dr-ui/src/collections_ui/press.rs#L258), [`ui/dr-ui/src/collections_ui/press.rs:490`](../../ui/dr-ui/src/collections_ui/press.rs#L490), [`ui/dr-ui/src/collections_ui/press.rs:54`](../../ui/dr-ui/src/collections_ui/press.rs#L54), [`ui/dr-ui/src/collections_ui/rename_menu.rs:196`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L196), [`ui/dr-ui/src/collections_ui/rename_menu.rs:21`](../../ui/dr-ui/src/collections_ui/rename_menu.rs#L21), [`ui/dr-ui/src/collections_ui/tree_sync.rs:251`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L251), [`ui/dr-ui/src/collections_ui/tree_sync.rs:322`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L322), [`ui/dr-ui/src/collections_ui/tree_sync.rs:344`](../../ui/dr-ui/src/collections_ui/tree_sync.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:171`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L171), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:444`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L444), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:522`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L522), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:586`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L586), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:344`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L344), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:394`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L394), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:517`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L517), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:581`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L581), [`ui/dr-ui/src/derived_sync.rs:1`](../../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library/spans.rs:131`](../../ui/dr-ui/src/library/spans.rs#L131), [`ui/dr-ui/src/library/spans.rs:50`](../../ui/dr-ui/src/library/spans.rs#L50), [`ui/dr-ui/src/library/spans.rs:76`](../../ui/dr-ui/src/library/spans.rs#L76), [`ui/dr-ui/src/library_ui/sync.rs:236`](../../ui/dr-ui/src/library_ui/sync.rs#L236), [`ui/dr-ui/src/library_ui/timeline.rs:513`](../../ui/dr-ui/src/library_ui/timeline.rs#L513), [`ui/dr-ui/src/library_ui/window.rs:359`](../../ui/dr-ui/src/library_ui/window.rs#L359), [`ui/dr-ui/src/library_ui/window.rs:395`](../../ui/dr-ui/src/library_ui/window.rs#L395), [`ui/dr-ui/ui/app.slint:3521`](../../ui/dr-ui/ui/app.slint#L3521), [`ui/dr-ui/ui/collections.slint:1283`](../../ui/dr-ui/ui/collections.slint#L1283), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:616`](../../ui/dr-ui/ui/collections.slint#L616), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1734`](../../ui/dr-ui/ui/library.slint#L1734), [`ui/dr-ui/ui/library.slint:3685`](../../ui/dr-ui/ui/library.slint#L3685), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5038`](../../ui/dr-ui/ui/library.slint#L5038), [`ui/dr-ui/ui/library.slint:5180`](../../ui/dr-ui/ui/library.slint#L5180), [`ui/dr-ui/ui/library.slint:518`](../../ui/dr-ui/ui/library.slint#L518), [`ui/dr-ui/ui/library.slint:567`](../../ui/dr-ui/ui/library.slint#L567), [`ui/dr-ui/ui/library.slint:577`](../../ui/dr-ui/ui/library.slint#L577) | | FR-CAT-8 | [`core/dr-catalog/src/schema.rs:770`](../../core/dr-catalog/src/schema.rs#L770), [`core/dr-pipeline/src/coverage.rs:1`](../../core/dr-pipeline/src/coverage.rs#L1), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`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:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:2275`](../../core/dr-pipeline/src/sidecar.rs#L2275), [`core/dr-pipeline/src/sidecar.rs:99`](../../core/dr-pipeline/src/sidecar.rs#L99), [`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), [`core/dr-sync/src/scan.rs:31`](../../core/dr-sync/src/scan.rs#L31), [`core/dr-sync/src/scan.rs:56`](../../core/dr-sync/src/scan.rs#L56), [`core/dr-sync/src/scan.rs:926`](../../core/dr-sync/src/scan.rs#L926), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:600`](../../ui/dr-ui/src/develop/session.rs#L600), [`ui/dr-ui/src/export.rs:925`](../../ui/dr-ui/src/export.rs#L925), [`ui/dr-ui/src/lib.rs:1202`](../../ui/dr-ui/src/lib.rs#L1202), [`ui/dr-ui/src/lib.rs:1262`](../../ui/dr-ui/src/lib.rs#L1262), [`ui/dr-ui/src/lib.rs:2660`](../../ui/dr-ui/src/lib.rs#L2660), [`ui/dr-ui/src/lib.rs:2853`](../../ui/dr-ui/src/lib.rs#L2853), [`ui/dr-ui/src/lib.rs:809`](../../ui/dr-ui/src/lib.rs#L809), [`ui/dr-ui/src/library/scan.rs:227`](../../ui/dr-ui/src/library/scan.rs#L227), [`ui/dr-ui/src/library/scan.rs:262`](../../ui/dr-ui/src/library/scan.rs#L262), [`ui/dr-ui/src/library/scan.rs:35`](../../ui/dr-ui/src/library/scan.rs#L35), [`ui/dr-ui/src/library/sidecar.rs:103`](../../ui/dr-ui/src/library/sidecar.rs#L103), [`ui/dr-ui/src/library/sidecar.rs:12`](../../ui/dr-ui/src/library/sidecar.rs#L12), [`ui/dr-ui/src/library/sidecar.rs:381`](../../ui/dr-ui/src/library/sidecar.rs#L381), [`ui/dr-ui/src/library/sidecar.rs:66`](../../ui/dr-ui/src/library/sidecar.rs#L66), [`ui/dr-ui/src/library/thumbnails_fetch.rs:414`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L414), [`ui/dr-ui/src/library_ui/grid.rs:789`](../../ui/dr-ui/src/library_ui/grid.rs#L789), [`ui/dr-ui/src/library_ui/open.rs:456`](../../ui/dr-ui/src/library_ui/open.rs#L456), [`ui/dr-ui/src/sidecar_cache.rs:1`](../../ui/dr-ui/src/sidecar_cache.rs#L1) | @@ -55,8 +55,8 @@ _None._ | FR-CULL-11 | [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`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/filters.rs:110`](../../ui/dr-ui/src/library/filters.rs#L110), [`ui/dr-ui/src/library/filters.rs:164`](../../ui/dr-ui/src/library/filters.rs#L164), [`ui/dr-ui/ui/identity.slint:1`](../../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-12 | [`core/dr-catalog/src/dedup_people.rs:1`](../../core/dr-catalog/src/dedup_people.rs#L1), [`core/dr-catalog/src/faces.rs:1`](../../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:633`](../../core/dr-catalog/src/schema.rs#L633), [`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-13 | [`core/dr-catalog/src/schema.rs:947`](../../core/dr-catalog/src/schema.rs#L947), [`core/dr-types/src/place.rs:133`](../../core/dr-types/src/place.rs#L133), [`tools/traceability/src/verdicts.rs:1`](../../tools/traceability/src/verdicts.rs#L1), [`ui/dr-ui/src/identity.rs:104`](../../ui/dr-ui/src/identity.rs#L104), [`ui/dr-ui/src/library/filters.rs:136`](../../ui/dr-ui/src/library/filters.rs#L136), [`ui/dr-ui/src/library/filters.rs:763`](../../ui/dr-ui/src/library/filters.rs#L763), [`ui/dr-ui/src/library/filters.rs:806`](../../ui/dr-ui/src/library/filters.rs#L806), [`ui/dr-ui/src/library/filters.rs:844`](../../ui/dr-ui/src/library/filters.rs#L844), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1085`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1085), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:379`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L379), [`ui/dr-ui/ui/identity.slint:64`](../../ui/dr-ui/ui/identity.slint#L64), [`ui/dr-ui/ui/library.slint:1928`](../../ui/dr-ui/ui/library.slint#L1928), [`ui/dr-ui/ui/library.slint:735`](../../ui/dr-ui/ui/library.slint#L735) | -| 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/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/import.rs:473`](../../ui/dr-ui/src/import.rs#L473) | -| 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/render.rs:1492`](../../ui/dr-ui/src/develop/render.rs#L1492), [`ui/dr-ui/src/develop/render.rs:1556`](../../ui/dr-ui/src/develop/render.rs#L1556), [`ui/dr-ui/src/develop/render.rs:1579`](../../ui/dr-ui/src/develop/render.rs#L1579), [`ui/dr-ui/src/develop/render.rs:317`](../../ui/dr-ui/src/develop/render.rs#L317), [`ui/dr-ui/src/develop/render.rs:330`](../../ui/dr-ui/src/develop/render.rs#L330), [`ui/dr-ui/src/develop/render.rs:375`](../../ui/dr-ui/src/develop/render.rs#L375), [`ui/dr-ui/src/develop/render.rs:386`](../../ui/dr-ui/src/develop/render.rs#L386), [`ui/dr-ui/src/develop/render.rs:392`](../../ui/dr-ui/src/develop/render.rs#L392), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/develop/session.rs:122`](../../ui/dr-ui/src/develop/session.rs#L122), [`ui/dr-ui/src/develop/session.rs:142`](../../ui/dr-ui/src/develop/session.rs#L142), [`ui/dr-ui/src/develop/session.rs:148`](../../ui/dr-ui/src/develop/session.rs#L148), [`ui/dr-ui/src/develop_ui.rs:1419`](../../ui/dr-ui/src/develop_ui.rs#L1419), [`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:1291`](../../ui/dr-ui/src/lib.rs#L1291), [`ui/dr-ui/src/lib.rs:2174`](../../ui/dr-ui/src/lib.rs#L2174), [`ui/dr-ui/src/lib.rs:2304`](../../ui/dr-ui/src/lib.rs#L2304), [`ui/dr-ui/src/lib.rs:2347`](../../ui/dr-ui/src/lib.rs#L2347), [`ui/dr-ui/src/lib.rs:480`](../../ui/dr-ui/src/lib.rs#L480), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:182`](../../ui/dr-ui/ui/app.slint#L182), [`ui/dr-ui/ui/app.slint:1917`](../../ui/dr-ui/ui/app.slint#L1917), [`ui/dr-ui/ui/app.slint:3294`](../../ui/dr-ui/ui/app.slint#L3294), [`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), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) | +| 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/decoder_seam.rs:214`](../../ui/dr-ui/src/decoder_seam.rs#L214), [`ui/dr-ui/src/import.rs:473`](../../ui/dr-ui/src/import.rs#L473) | +| 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/render.rs:1491`](../../ui/dr-ui/src/develop/render.rs#L1491), [`ui/dr-ui/src/develop/render.rs:1555`](../../ui/dr-ui/src/develop/render.rs#L1555), [`ui/dr-ui/src/develop/render.rs:1578`](../../ui/dr-ui/src/develop/render.rs#L1578), [`ui/dr-ui/src/develop/render.rs:317`](../../ui/dr-ui/src/develop/render.rs#L317), [`ui/dr-ui/src/develop/render.rs:330`](../../ui/dr-ui/src/develop/render.rs#L330), [`ui/dr-ui/src/develop/render.rs:375`](../../ui/dr-ui/src/develop/render.rs#L375), [`ui/dr-ui/src/develop/render.rs:386`](../../ui/dr-ui/src/develop/render.rs#L386), [`ui/dr-ui/src/develop/render.rs:392`](../../ui/dr-ui/src/develop/render.rs#L392), [`ui/dr-ui/src/develop/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/develop/session.rs:122`](../../ui/dr-ui/src/develop/session.rs#L122), [`ui/dr-ui/src/develop/session.rs:142`](../../ui/dr-ui/src/develop/session.rs#L142), [`ui/dr-ui/src/develop/session.rs:148`](../../ui/dr-ui/src/develop/session.rs#L148), [`ui/dr-ui/src/develop_ui.rs:1419`](../../ui/dr-ui/src/develop_ui.rs#L1419), [`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:1291`](../../ui/dr-ui/src/lib.rs#L1291), [`ui/dr-ui/src/lib.rs:2174`](../../ui/dr-ui/src/lib.rs#L2174), [`ui/dr-ui/src/lib.rs:2304`](../../ui/dr-ui/src/lib.rs#L2304), [`ui/dr-ui/src/lib.rs:2347`](../../ui/dr-ui/src/lib.rs#L2347), [`ui/dr-ui/src/lib.rs:480`](../../ui/dr-ui/src/lib.rs#L480), [`ui/dr-ui/src/peaking.rs:1`](../../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:182`](../../ui/dr-ui/ui/app.slint#L182), [`ui/dr-ui/ui/app.slint:1917`](../../ui/dr-ui/ui/app.slint#L1917), [`ui/dr-ui/ui/app.slint:3294`](../../ui/dr-ui/ui/app.slint#L3294), [`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), [`ui/dr-ui/ui/peaking.slint:91`](../../ui/dr-ui/ui/peaking.slint#L91) | | FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:174`](../../core/dr-pipeline/src/sidecar.rs#L174), [`core/dr-xmp/src/lib.rs:442`](../../core/dr-xmp/src/lib.rs#L442), [`ui/dr-ui/src/library/filters.rs:64`](../../ui/dr-ui/src/library/filters.rs#L64), [`ui/dr-ui/src/library/sidecar.rs:12`](../../ui/dr-ui/src/library/sidecar.rs#L12), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1035`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1035), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1311`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1311), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:464`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L464), [`ui/dr-ui/ui/app.slint:1675`](../../ui/dr-ui/ui/app.slint#L1675), [`ui/dr-ui/ui/app.slint:2791`](../../ui/dr-ui/ui/app.slint#L2791), [`ui/dr-ui/ui/develop.slint:168`](../../ui/dr-ui/ui/develop.slint#L168), [`ui/dr-ui/ui/develop.slint:55`](../../ui/dr-ui/ui/develop.slint#L55), [`ui/dr-ui/ui/library.slint:1044`](../../ui/dr-ui/ui/library.slint#L1044), [`ui/dr-ui/ui/library.slint:1623`](../../ui/dr-ui/ui/library.slint#L1623), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488), [`ui/dr-ui/ui/library.slint:5093`](../../ui/dr-ui/ui/library.slint#L5093), [`ui/dr-ui/ui/library.slint:647`](../../ui/dr-ui/ui/library.slint#L647) | | FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:676`](../../core/dr-catalog/src/schema.rs#L676), [`ui/dr-ui/src/bursts.rs:1`](../../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library/cells.rs:21`](../../ui/dr-ui/src/library/cells.rs#L21), [`ui/dr-ui/src/library/cells.rs:776`](../../ui/dr-ui/src/library/cells.rs#L776), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:1085`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L1085), [`ui/dr-ui/ui/library.slint:488`](../../ui/dr-ui/ui/library.slint#L488) | | 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:1000`](../../core/dr-catalog/src/schema.rs#L1000), [`core/dr-catalog/src/schema.rs:1041`](../../core/dr-catalog/src/schema.rs#L1041), [`core/dr-catalog/src/schema.rs:744`](../../core/dr-catalog/src/schema.rs#L744), [`core/dr-face/src/align.rs:296`](../../core/dr-face/src/align.rs#L296), [`core/dr-types/src/settings.rs:176`](../../core/dr-types/src/settings.rs#L176), [`ui/dr-ui/examples/face_detectors.rs:1`](../../ui/dr-ui/examples/face_detectors.rs#L1), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/derived_sync.rs:128`](../../ui/dr-ui/src/derived_sync.rs#L128), [`ui/dr-ui/src/faces.rs:1`](../../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/faces.rs:447`](../../ui/dr-ui/src/faces.rs#L447), [`ui/dr-ui/src/faces.rs:463`](../../ui/dr-ui/src/faces.rs#L463), [`ui/dr-ui/src/faces.rs:585`](../../ui/dr-ui/src/faces.rs#L585), [`ui/dr-ui/src/identity_ui.rs:1472`](../../ui/dr-ui/src/identity_ui.rs#L1472), [`ui/dr-ui/src/identity_ui.rs:617`](../../ui/dr-ui/src/identity_ui.rs#L617), [`ui/dr-ui/src/lib.rs:1785`](../../ui/dr-ui/src/lib.rs#L1785), [`ui/dr-ui/src/lib.rs:3113`](../../ui/dr-ui/src/lib.rs#L3113), [`ui/dr-ui/src/library/sweep.rs:243`](../../ui/dr-ui/src/library/sweep.rs#L243), [`ui/dr-ui/src/library/sweep.rs:511`](../../ui/dr-ui/src/library/sweep.rs#L511), [`ui/dr-ui/src/library_ui/controller.rs:422`](../../ui/dr-ui/src/library_ui/controller.rs#L422), [`ui/dr-ui/src/library_ui/controller.rs:512`](../../ui/dr-ui/src/library_ui/controller.rs#L512), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1), [`ui/dr-ui/src/settings_ui.rs:174`](../../ui/dr-ui/src/settings_ui.rs#L174), [`ui/dr-ui/src/settings_ui.rs:537`](../../ui/dr-ui/src/settings_ui.rs#L537), [`ui/dr-ui/ui/app.slint:673`](../../ui/dr-ui/ui/app.slint#L673), [`ui/dr-ui/ui/settings.slint:101`](../../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:407`](../../ui/dr-ui/ui/settings.slint#L407), [`ui/dr-ui/ui/settings.slint:469`](../../ui/dr-ui/ui/settings.slint#L469), [`ui/dr-ui/ui/settings.slint:83`](../../ui/dr-ui/ui/settings.slint#L83), [`ui/dr-ui/ui/settings.slint:95`](../../ui/dr-ui/ui/settings.slint#L95) | @@ -71,46 +71,47 @@ _None._ | FR-DEV-19 | [`core/dr-pipeline/src/mask.rs:1341`](../../core/dr-pipeline/src/mask.rs#L1341) | | FR-DEV-19a | [`core/dr-gpu/src/mask.rs:655`](../../core/dr-gpu/src/mask.rs#L655), [`core/dr-gpu/src/mask.rs:773`](../../core/dr-gpu/src/mask.rs#L773), [`core/dr-gpu/tests/local_adjustments.rs:793`](../../core/dr-gpu/tests/local_adjustments.rs#L793), [`core/dr-gpu/tests/local_adjustments.rs:841`](../../core/dr-gpu/tests/local_adjustments.rs#L841), [`core/dr-pipeline/src/mask.rs:1022`](../../core/dr-pipeline/src/mask.rs#L1022), [`core/dr-pipeline/src/mask.rs:1635`](../../core/dr-pipeline/src/mask.rs#L1635), [`core/dr-pipeline/src/mask.rs:3282`](../../core/dr-pipeline/src/mask.rs#L3282), [`core/dr-pipeline/src/mask.rs:3403`](../../core/dr-pipeline/src/mask.rs#L3403), [`core/dr-pipeline/src/mask.rs:3438`](../../core/dr-pipeline/src/mask.rs#L3438), [`core/dr-pipeline/src/mask.rs:929`](../../core/dr-pipeline/src/mask.rs#L929), [`core/dr-pipeline/src/mask.rs:964`](../../core/dr-pipeline/src/mask.rs#L964), [`core/dr-pipeline/src/sidecar.rs:1259`](../../core/dr-pipeline/src/sidecar.rs#L1259), [`core/dr-pipeline/tests/mask_sidecar.rs:1347`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1347), [`core/dr-pipeline/tests/mask_sidecar.rs:1369`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1369), [`core/dr-pipeline/tests/mask_sidecar.rs:1395`](../../core/dr-pipeline/tests/mask_sidecar.rs#L1395), [`ui/dr-ui/src/develop/masks.rs:432`](../../ui/dr-ui/src/develop/masks.rs#L432), [`ui/dr-ui/src/develop/masks.rs:493`](../../ui/dr-ui/src/develop/masks.rs#L493), [`ui/dr-ui/src/develop/masks.rs:512`](../../ui/dr-ui/src/develop/masks.rs#L512), [`ui/dr-ui/src/develop/masks.rs:547`](../../ui/dr-ui/src/develop/masks.rs#L547), [`ui/dr-ui/src/develop/session.rs:192`](../../ui/dr-ui/src/develop/session.rs#L192), [`ui/dr-ui/src/labels.rs:73`](../../ui/dr-ui/src/labels.rs#L73), [`ui/dr-ui/src/masks_ui.rs:1083`](../../ui/dr-ui/src/masks_ui.rs#L1083), [`ui/dr-ui/src/masks_ui.rs:1140`](../../ui/dr-ui/src/masks_ui.rs#L1140), [`ui/dr-ui/src/masks_ui.rs:1196`](../../ui/dr-ui/src/masks_ui.rs#L1196), [`ui/dr-ui/src/masks_ui.rs:1555`](../../ui/dr-ui/src/masks_ui.rs#L1555), [`ui/dr-ui/src/masks_ui.rs:1578`](../../ui/dr-ui/src/masks_ui.rs#L1578), [`ui/dr-ui/src/masks_ui.rs:270`](../../ui/dr-ui/src/masks_ui.rs#L270), [`ui/dr-ui/ui/app.slint:2639`](../../ui/dr-ui/ui/app.slint#L2639), [`ui/dr-ui/ui/masks.slint:151`](../../ui/dr-ui/ui/masks.slint#L151), [`ui/dr-ui/ui/masks.slint:176`](../../ui/dr-ui/ui/masks.slint#L176), [`ui/dr-ui/ui/masks.slint:339`](../../ui/dr-ui/ui/masks.slint#L339), [`ui/dr-ui/ui/masks.slint:379`](../../ui/dr-ui/ui/masks.slint#L379), [`ui/dr-ui/ui/masks.slint:422`](../../ui/dr-ui/ui/masks.slint#L422), [`ui/dr-ui/ui/masks.slint:468`](../../ui/dr-ui/ui/masks.slint#L468), [`ui/dr-ui/ui/masks.slint:796`](../../ui/dr-ui/ui/masks.slint#L796), [`ui/dr-ui/ui/masks.slint:881`](../../ui/dr-ui/ui/masks.slint#L881) | | FR-DEV-19b | [`ui/dr-ui/src/develop/mask_ops.rs:606`](../../ui/dr-ui/src/develop/mask_ops.rs#L606), [`ui/dr-ui/src/develop/masks.rs:595`](../../ui/dr-ui/src/develop/masks.rs#L595), [`ui/dr-ui/src/develop/session.rs:201`](../../ui/dr-ui/src/develop/session.rs#L201), [`ui/dr-ui/src/develop/session.rs:208`](../../ui/dr-ui/src/develop/session.rs#L208), [`ui/dr-ui/src/labels.rs:67`](../../ui/dr-ui/src/labels.rs#L67), [`ui/dr-ui/src/masks_ui.rs:1229`](../../ui/dr-ui/src/masks_ui.rs#L1229), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:1476`](../../ui/dr-ui/src/masks_ui.rs#L1476), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:908`](../../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/ui/app.slint:2174`](../../ui/dr-ui/ui/app.slint#L2174), [`ui/dr-ui/ui/app.slint:857`](../../ui/dr-ui/ui/app.slint#L857), [`ui/dr-ui/ui/masks.slint:1156`](../../ui/dr-ui/ui/masks.slint#L1156), [`ui/dr-ui/ui/masks.slint:479`](../../ui/dr-ui/ui/masks.slint#L479), [`ui/dr-ui/ui/masks.slint:800`](../../ui/dr-ui/ui/masks.slint#L800), [`ui/dr-ui/ui/masks.slint:874`](../../ui/dr-ui/ui/masks.slint#L874), [`ui/dr-ui/ui/masks.slint:894`](../../ui/dr-ui/ui/masks.slint#L894) | -| FR-DEV-19c | [`core/dr-gpu/src/mask.rs:727`](../../core/dr-gpu/src/mask.rs#L727), [`core/dr-gpu/tests/local_adjustments.rs:1000`](../../core/dr-gpu/tests/local_adjustments.rs#L1000), [`core/dr-gpu/tests/local_adjustments.rs:1035`](../../core/dr-gpu/tests/local_adjustments.rs#L1035), [`core/dr-gpu/tests/local_adjustments.rs:1080`](../../core/dr-gpu/tests/local_adjustments.rs#L1080), [`core/dr-gpu/tests/local_adjustments.rs:943`](../../core/dr-gpu/tests/local_adjustments.rs#L943), [`core/dr-pipeline/src/graph.rs:899`](../../core/dr-pipeline/src/graph.rs#L899), [`core/dr-pipeline/src/mask.rs:1843`](../../core/dr-pipeline/src/mask.rs#L1843), [`core/dr-pipeline/src/mask.rs:1869`](../../core/dr-pipeline/src/mask.rs#L1869), [`core/dr-pipeline/src/mask.rs:1884`](../../core/dr-pipeline/src/mask.rs#L1884), [`core/dr-pipeline/src/mask.rs:2006`](../../core/dr-pipeline/src/mask.rs#L2006), [`core/dr-pipeline/src/mask.rs:2065`](../../core/dr-pipeline/src/mask.rs#L2065), [`core/dr-pipeline/src/mask.rs:2309`](../../core/dr-pipeline/src/mask.rs#L2309), [`core/dr-pipeline/src/mask.rs:2621`](../../core/dr-pipeline/src/mask.rs#L2621), [`core/dr-pipeline/src/operation.rs:1035`](../../core/dr-pipeline/src/operation.rs#L1035), [`core/dr-pipeline/src/operation.rs:2037`](../../core/dr-pipeline/src/operation.rs#L2037), [`core/dr-pipeline/src/operation.rs:698`](../../core/dr-pipeline/src/operation.rs#L698), [`ui/dr-ui/src/develop/mask_ops.rs:1199`](../../ui/dr-ui/src/develop/mask_ops.rs#L1199), [`ui/dr-ui/src/develop/mask_ops.rs:1235`](../../ui/dr-ui/src/develop/mask_ops.rs#L1235), [`ui/dr-ui/src/develop/mask_ops.rs:318`](../../ui/dr-ui/src/develop/mask_ops.rs#L318), [`ui/dr-ui/src/develop/mask_ops.rs:58`](../../ui/dr-ui/src/develop/mask_ops.rs#L58), [`ui/dr-ui/src/develop/masks.rs:12`](../../ui/dr-ui/src/develop/masks.rs#L12), [`ui/dr-ui/src/develop/masks.rs:176`](../../ui/dr-ui/src/develop/masks.rs#L176), [`ui/dr-ui/src/develop/masks.rs:21`](../../ui/dr-ui/src/develop/masks.rs#L21), [`ui/dr-ui/src/develop/masks.rs:43`](../../ui/dr-ui/src/develop/masks.rs#L43), [`ui/dr-ui/src/develop/render.rs:260`](../../ui/dr-ui/src/develop/render.rs#L260), [`ui/dr-ui/src/develop/session.rs:227`](../../ui/dr-ui/src/develop/session.rs#L227), [`ui/dr-ui/src/develop/session.rs:236`](../../ui/dr-ui/src/develop/session.rs#L236), [`ui/dr-ui/src/masks_ui.rs:1024`](../../ui/dr-ui/src/masks_ui.rs#L1024), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:152`](../../ui/dr-ui/src/masks_ui.rs#L152), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:291`](../../ui/dr-ui/src/masks_ui.rs#L291), [`ui/dr-ui/src/masks_ui.rs:317`](../../ui/dr-ui/src/masks_ui.rs#L317), [`ui/dr-ui/src/masks_ui.rs:677`](../../ui/dr-ui/src/masks_ui.rs#L677), [`ui/dr-ui/src/masks_ui.rs:996`](../../ui/dr-ui/src/masks_ui.rs#L996), [`ui/dr-ui/ui/icons.slint:340`](../../ui/dr-ui/ui/icons.slint#L340), [`ui/dr-ui/ui/masks.slint:1121`](../../ui/dr-ui/ui/masks.slint#L1121), [`ui/dr-ui/ui/masks.slint:184`](../../ui/dr-ui/ui/masks.slint#L184), [`ui/dr-ui/ui/masks.slint:294`](../../ui/dr-ui/ui/masks.slint#L294), [`ui/dr-ui/ui/masks.slint:543`](../../ui/dr-ui/ui/masks.slint#L543), [`ui/dr-ui/ui/masks.slint:70`](../../ui/dr-ui/ui/masks.slint#L70), [`ui/dr-ui/ui/masks.slint:808`](../../ui/dr-ui/ui/masks.slint#L808), [`ui/dr-ui/ui/masks.slint:876`](../../ui/dr-ui/ui/masks.slint#L876), [`ui/dr-ui/ui/masks.slint:968`](../../ui/dr-ui/ui/masks.slint#L968) | -| FR-DEV-2 | [`core/dr-pipeline/src/declared/decl.rs:279`](../../core/dr-pipeline/src/declared/decl.rs#L279), [`core/dr-pipeline/src/operation.rs:2344`](../../core/dr-pipeline/src/operation.rs#L2344), [`core/dr-pipeline/src/operation.rs:357`](../../core/dr-pipeline/src/operation.rs#L357), [`core/dr-pipeline/src/operation.rs:431`](../../core/dr-pipeline/src/operation.rs#L431), [`core/dr-pipeline/src/operation.rs:451`](../../core/dr-pipeline/src/operation.rs#L451), [`core/dr-pipeline/src/operation.rs:480`](../../core/dr-pipeline/src/operation.rs#L480) | -| FR-DEV-20 | [`core/dr-gpu/src/adjust.rs:2195`](../../core/dr-gpu/src/adjust.rs#L2195), [`core/dr-pipeline/src/framing.rs:1362`](../../core/dr-pipeline/src/framing.rs#L1362), [`core/dr-pipeline/src/framing.rs:170`](../../core/dr-pipeline/src/framing.rs#L170), [`core/dr-pipeline/src/framing.rs:388`](../../core/dr-pipeline/src/framing.rs#L388), [`core/dr-pipeline/src/framing.rs:482`](../../core/dr-pipeline/src/framing.rs#L482), [`core/dr-pipeline/src/framing.rs:639`](../../core/dr-pipeline/src/framing.rs#L639), [`core/dr-pipeline/src/framing.rs:650`](../../core/dr-pipeline/src/framing.rs#L650), [`core/dr-pipeline/src/framing.rs:88`](../../core/dr-pipeline/src/framing.rs#L88), [`core/dr-pipeline/src/framing.rs:925`](../../core/dr-pipeline/src/framing.rs#L925), [`core/dr-pipeline/src/framing.rs:92`](../../core/dr-pipeline/src/framing.rs#L92), [`core/dr-pipeline/src/framing.rs:981`](../../core/dr-pipeline/src/framing.rs#L981), [`core/dr-pipeline/src/preset.rs:1001`](../../core/dr-pipeline/src/preset.rs#L1001), [`core/dr-pipeline/src/sidecar.rs:2197`](../../core/dr-pipeline/src/sidecar.rs#L2197), [`ui/dr-ui/src/develop/framing.rs:1035`](../../ui/dr-ui/src/develop/framing.rs#L1035), [`ui/dr-ui/src/develop/framing.rs:418`](../../ui/dr-ui/src/develop/framing.rs#L418), [`ui/dr-ui/src/develop/framing.rs:474`](../../ui/dr-ui/src/develop/framing.rs#L474), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:489`](../../ui/dr-ui/src/develop/framing.rs#L489), [`ui/dr-ui/src/develop_ui.rs:1300`](../../ui/dr-ui/src/develop_ui.rs#L1300), [`ui/dr-ui/src/develop_ui.rs:1333`](../../ui/dr-ui/src/develop_ui.rs#L1333), [`ui/dr-ui/src/labels.rs:277`](../../ui/dr-ui/src/labels.rs#L277), [`ui/dr-ui/ui/adjust.slint:646`](../../ui/dr-ui/ui/adjust.slint#L646), [`ui/dr-ui/ui/adjust.slint:688`](../../ui/dr-ui/ui/adjust.slint#L688), [`ui/dr-ui/ui/adjust.slint:831`](../../ui/dr-ui/ui/adjust.slint#L831), [`ui/dr-ui/ui/app.slint:2663`](../../ui/dr-ui/ui/app.slint#L2663), [`ui/dr-ui/ui/app.slint:723`](../../ui/dr-ui/ui/app.slint#L723), [`ui/dr-ui/ui/app.slint:746`](../../ui/dr-ui/ui/app.slint#L746) | -| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:2795`](../../core/dr-gpu/src/adjust.rs#L2795), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:924`](../../core/dr-gpu/src/adjust.rs#L924), [`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:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/framing.rs:1098`](../../core/dr-pipeline/src/framing.rs#L1098), [`core/dr-pipeline/src/framing.rs:251`](../../core/dr-pipeline/src/framing.rs#L251), [`core/dr-pipeline/src/framing.rs:308`](../../core/dr-pipeline/src/framing.rs#L308), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`core/dr-pipeline/src/graph.rs:1354`](../../core/dr-pipeline/src/graph.rs#L1354), [`core/dr-pipeline/src/graph.rs:1385`](../../core/dr-pipeline/src/graph.rs#L1385), [`core/dr-pipeline/src/graph.rs:1408`](../../core/dr-pipeline/src/graph.rs#L1408), [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:1477`](../../core/dr-pipeline/src/graph.rs#L1477), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:866`](../../core/dr-pipeline/src/graph.rs#L866), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/operation.rs:674`](../../core/dr-pipeline/src/operation.rs#L674), [`core/dr-pipeline/src/operation.rs:747`](../../core/dr-pipeline/src/operation.rs#L747), [`core/dr-pipeline/src/operation.rs:939`](../../core/dr-pipeline/src/operation.rs#L939), [`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/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:2275`](../../core/dr-pipeline/src/sidecar.rs#L2275), [`core/dr-pipeline/src/sidecar.rs:2355`](../../core/dr-pipeline/src/sidecar.rs#L2355), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:298`](../../ui/dr-ui/src/develop/framing.rs#L298), [`ui/dr-ui/src/develop/framing.rs:328`](../../ui/dr-ui/src/develop/framing.rs#L328), [`ui/dr-ui/src/develop/framing.rs:394`](../../ui/dr-ui/src/develop/framing.rs#L394), [`ui/dr-ui/src/develop/framing.rs:408`](../../ui/dr-ui/src/develop/framing.rs#L408), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:263`](../../ui/dr-ui/src/develop/masks.rs#L263), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/masks.rs:773`](../../ui/dr-ui/src/develop/masks.rs#L773), [`ui/dr-ui/src/develop/render.rs:1244`](../../ui/dr-ui/src/develop/render.rs#L1244), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:162`](../../ui/dr-ui/src/develop/session.rs#L162), [`ui/dr-ui/src/develop/session.rs:255`](../../ui/dr-ui/src/develop/session.rs#L255), [`ui/dr-ui/src/develop/session.rs:284`](../../ui/dr-ui/src/develop/session.rs#L284), [`ui/dr-ui/src/develop/session.rs:434`](../../ui/dr-ui/src/develop/session.rs#L434), [`ui/dr-ui/src/develop/session.rs:494`](../../ui/dr-ui/src/develop/session.rs#L494), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:721`](../../ui/dr-ui/src/develop/session.rs#L721), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:276`](../../ui/dr-ui/src/develop/white_balance.rs#L276), [`ui/dr-ui/src/develop/white_balance.rs:338`](../../ui/dr-ui/src/develop/white_balance.rs#L338), [`ui/dr-ui/src/develop_ui.rs:1113`](../../ui/dr-ui/src/develop_ui.rs#L1113), [`ui/dr-ui/src/develop_ui.rs:117`](../../ui/dr-ui/src/develop_ui.rs#L117), [`ui/dr-ui/src/develop_ui.rs:1210`](../../ui/dr-ui/src/develop_ui.rs#L1210), [`ui/dr-ui/src/develop_ui.rs:1264`](../../ui/dr-ui/src/develop_ui.rs#L1264), [`ui/dr-ui/src/develop_ui.rs:514`](../../ui/dr-ui/src/develop_ui.rs#L514), [`ui/dr-ui/src/develop_ui.rs:734`](../../ui/dr-ui/src/develop_ui.rs#L734), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2155`](../../ui/dr-ui/src/lib.rs#L2155), [`ui/dr-ui/src/lib.rs:456`](../../ui/dr-ui/src/lib.rs#L456), [`ui/dr-ui/src/lib.rs:486`](../../ui/dr-ui/src/lib.rs#L486), [`ui/dr-ui/src/lib.rs:594`](../../ui/dr-ui/src/lib.rs#L594), [`ui/dr-ui/src/lib.rs:618`](../../ui/dr-ui/src/lib.rs#L618), [`ui/dr-ui/src/library/sidecar.rs:66`](../../ui/dr-ui/src/library/sidecar.rs#L66), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:127`](../../ui/dr-ui/ui/adjust.slint#L127), [`ui/dr-ui/ui/adjust.slint:1658`](../../ui/dr-ui/ui/adjust.slint#L1658), [`ui/dr-ui/ui/adjust.slint:173`](../../ui/dr-ui/ui/adjust.slint#L173), [`ui/dr-ui/ui/adjust.slint:47`](../../ui/dr-ui/ui/adjust.slint#L47), [`ui/dr-ui/ui/adjust.slint:626`](../../ui/dr-ui/ui/adjust.slint#L626), [`ui/dr-ui/ui/adjust.slint:662`](../../ui/dr-ui/ui/adjust.slint#L662), [`ui/dr-ui/ui/adjust.slint:681`](../../ui/dr-ui/ui/adjust.slint#L681), [`ui/dr-ui/ui/adjust.slint:874`](../../ui/dr-ui/ui/adjust.slint#L874), [`ui/dr-ui/ui/adjust.slint:973`](../../ui/dr-ui/ui/adjust.slint#L973), [`ui/dr-ui/ui/app.slint:2239`](../../ui/dr-ui/ui/app.slint#L2239), [`ui/dr-ui/ui/app.slint:2301`](../../ui/dr-ui/ui/app.slint#L2301), [`ui/dr-ui/ui/app.slint:242`](../../ui/dr-ui/ui/app.slint#L242), [`ui/dr-ui/ui/develop.slint:375`](../../ui/dr-ui/ui/develop.slint#L375), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/masks.slint:779`](../../ui/dr-ui/ui/masks.slint#L779), [`ui/dr-ui/ui/masks.slint:838`](../../ui/dr-ui/ui/masks.slint#L838) | +| FR-DEV-19c | [`core/dr-gpu/src/mask.rs:727`](../../core/dr-gpu/src/mask.rs#L727), [`core/dr-gpu/tests/local_adjustments.rs:1000`](../../core/dr-gpu/tests/local_adjustments.rs#L1000), [`core/dr-gpu/tests/local_adjustments.rs:1035`](../../core/dr-gpu/tests/local_adjustments.rs#L1035), [`core/dr-gpu/tests/local_adjustments.rs:1080`](../../core/dr-gpu/tests/local_adjustments.rs#L1080), [`core/dr-gpu/tests/local_adjustments.rs:943`](../../core/dr-gpu/tests/local_adjustments.rs#L943), [`core/dr-pipeline/src/graph.rs:899`](../../core/dr-pipeline/src/graph.rs#L899), [`core/dr-pipeline/src/mask.rs:1843`](../../core/dr-pipeline/src/mask.rs#L1843), [`core/dr-pipeline/src/mask.rs:1869`](../../core/dr-pipeline/src/mask.rs#L1869), [`core/dr-pipeline/src/mask.rs:1884`](../../core/dr-pipeline/src/mask.rs#L1884), [`core/dr-pipeline/src/mask.rs:2006`](../../core/dr-pipeline/src/mask.rs#L2006), [`core/dr-pipeline/src/mask.rs:2065`](../../core/dr-pipeline/src/mask.rs#L2065), [`core/dr-pipeline/src/mask.rs:2309`](../../core/dr-pipeline/src/mask.rs#L2309), [`core/dr-pipeline/src/mask.rs:2621`](../../core/dr-pipeline/src/mask.rs#L2621), [`core/dr-pipeline/src/operation.rs:1975`](../../core/dr-pipeline/src/operation.rs#L1975), [`core/dr-pipeline/src/operation.rs:673`](../../core/dr-pipeline/src/operation.rs#L673), [`core/dr-pipeline/src/operation.rs:988`](../../core/dr-pipeline/src/operation.rs#L988), [`ui/dr-ui/src/develop/mask_ops.rs:1199`](../../ui/dr-ui/src/develop/mask_ops.rs#L1199), [`ui/dr-ui/src/develop/mask_ops.rs:1235`](../../ui/dr-ui/src/develop/mask_ops.rs#L1235), [`ui/dr-ui/src/develop/mask_ops.rs:318`](../../ui/dr-ui/src/develop/mask_ops.rs#L318), [`ui/dr-ui/src/develop/mask_ops.rs:58`](../../ui/dr-ui/src/develop/mask_ops.rs#L58), [`ui/dr-ui/src/develop/masks.rs:12`](../../ui/dr-ui/src/develop/masks.rs#L12), [`ui/dr-ui/src/develop/masks.rs:176`](../../ui/dr-ui/src/develop/masks.rs#L176), [`ui/dr-ui/src/develop/masks.rs:21`](../../ui/dr-ui/src/develop/masks.rs#L21), [`ui/dr-ui/src/develop/masks.rs:43`](../../ui/dr-ui/src/develop/masks.rs#L43), [`ui/dr-ui/src/develop/render.rs:260`](../../ui/dr-ui/src/develop/render.rs#L260), [`ui/dr-ui/src/develop/session.rs:227`](../../ui/dr-ui/src/develop/session.rs#L227), [`ui/dr-ui/src/develop/session.rs:236`](../../ui/dr-ui/src/develop/session.rs#L236), [`ui/dr-ui/src/masks_ui.rs:1024`](../../ui/dr-ui/src/masks_ui.rs#L1024), [`ui/dr-ui/src/masks_ui.rs:1448`](../../ui/dr-ui/src/masks_ui.rs#L1448), [`ui/dr-ui/src/masks_ui.rs:152`](../../ui/dr-ui/src/masks_ui.rs#L152), [`ui/dr-ui/src/masks_ui.rs:1849`](../../ui/dr-ui/src/masks_ui.rs#L1849), [`ui/dr-ui/src/masks_ui.rs:291`](../../ui/dr-ui/src/masks_ui.rs#L291), [`ui/dr-ui/src/masks_ui.rs:317`](../../ui/dr-ui/src/masks_ui.rs#L317), [`ui/dr-ui/src/masks_ui.rs:677`](../../ui/dr-ui/src/masks_ui.rs#L677), [`ui/dr-ui/src/masks_ui.rs:996`](../../ui/dr-ui/src/masks_ui.rs#L996), [`ui/dr-ui/ui/icons.slint:340`](../../ui/dr-ui/ui/icons.slint#L340), [`ui/dr-ui/ui/masks.slint:1121`](../../ui/dr-ui/ui/masks.slint#L1121), [`ui/dr-ui/ui/masks.slint:184`](../../ui/dr-ui/ui/masks.slint#L184), [`ui/dr-ui/ui/masks.slint:294`](../../ui/dr-ui/ui/masks.slint#L294), [`ui/dr-ui/ui/masks.slint:543`](../../ui/dr-ui/ui/masks.slint#L543), [`ui/dr-ui/ui/masks.slint:70`](../../ui/dr-ui/ui/masks.slint#L70), [`ui/dr-ui/ui/masks.slint:808`](../../ui/dr-ui/ui/masks.slint#L808), [`ui/dr-ui/ui/masks.slint:876`](../../ui/dr-ui/ui/masks.slint#L876), [`ui/dr-ui/ui/masks.slint:968`](../../ui/dr-ui/ui/masks.slint#L968) | +| FR-DEV-2 | [`core/dr-gpu/tests/view_transform.rs:1`](../../core/dr-gpu/tests/view_transform.rs#L1), [`core/dr-gpu/tests/view_transform.rs:88`](../../core/dr-gpu/tests/view_transform.rs#L88), [`core/dr-pipeline/src/declared/decl.rs:279`](../../core/dr-pipeline/src/declared/decl.rs#L279), [`core/dr-pipeline/src/operation.rs:2282`](../../core/dr-pipeline/src/operation.rs#L2282), [`core/dr-pipeline/src/operation.rs:357`](../../core/dr-pipeline/src/operation.rs#L357), [`core/dr-pipeline/src/operation.rs:431`](../../core/dr-pipeline/src/operation.rs#L431), [`core/dr-pipeline/src/operation.rs:451`](../../core/dr-pipeline/src/operation.rs#L451), [`core/dr-pipeline/src/operation.rs:480`](../../core/dr-pipeline/src/operation.rs#L480), [`core/dr-pipeline/src/view.rs:1`](../../core/dr-pipeline/src/view.rs#L1), [`core/dr-pipeline/src/view.rs:211`](../../core/dr-pipeline/src/view.rs#L211) | +| FR-DEV-20 | [`core/dr-gpu/src/adjust.rs:2165`](../../core/dr-gpu/src/adjust.rs#L2165), [`core/dr-pipeline/src/framing.rs:1362`](../../core/dr-pipeline/src/framing.rs#L1362), [`core/dr-pipeline/src/framing.rs:170`](../../core/dr-pipeline/src/framing.rs#L170), [`core/dr-pipeline/src/framing.rs:388`](../../core/dr-pipeline/src/framing.rs#L388), [`core/dr-pipeline/src/framing.rs:482`](../../core/dr-pipeline/src/framing.rs#L482), [`core/dr-pipeline/src/framing.rs:639`](../../core/dr-pipeline/src/framing.rs#L639), [`core/dr-pipeline/src/framing.rs:650`](../../core/dr-pipeline/src/framing.rs#L650), [`core/dr-pipeline/src/framing.rs:88`](../../core/dr-pipeline/src/framing.rs#L88), [`core/dr-pipeline/src/framing.rs:925`](../../core/dr-pipeline/src/framing.rs#L925), [`core/dr-pipeline/src/framing.rs:92`](../../core/dr-pipeline/src/framing.rs#L92), [`core/dr-pipeline/src/framing.rs:981`](../../core/dr-pipeline/src/framing.rs#L981), [`core/dr-pipeline/src/preset.rs:1001`](../../core/dr-pipeline/src/preset.rs#L1001), [`core/dr-pipeline/src/sidecar.rs:2197`](../../core/dr-pipeline/src/sidecar.rs#L2197), [`ui/dr-ui/src/develop/framing.rs:1035`](../../ui/dr-ui/src/develop/framing.rs#L1035), [`ui/dr-ui/src/develop/framing.rs:418`](../../ui/dr-ui/src/develop/framing.rs#L418), [`ui/dr-ui/src/develop/framing.rs:474`](../../ui/dr-ui/src/develop/framing.rs#L474), [`ui/dr-ui/src/develop/framing.rs:480`](../../ui/dr-ui/src/develop/framing.rs#L480), [`ui/dr-ui/src/develop/framing.rs:489`](../../ui/dr-ui/src/develop/framing.rs#L489), [`ui/dr-ui/src/develop_ui.rs:1300`](../../ui/dr-ui/src/develop_ui.rs#L1300), [`ui/dr-ui/src/develop_ui.rs:1333`](../../ui/dr-ui/src/develop_ui.rs#L1333), [`ui/dr-ui/src/labels.rs:277`](../../ui/dr-ui/src/labels.rs#L277), [`ui/dr-ui/ui/adjust.slint:646`](../../ui/dr-ui/ui/adjust.slint#L646), [`ui/dr-ui/ui/adjust.slint:688`](../../ui/dr-ui/ui/adjust.slint#L688), [`ui/dr-ui/ui/adjust.slint:831`](../../ui/dr-ui/ui/adjust.slint#L831), [`ui/dr-ui/ui/app.slint:2663`](../../ui/dr-ui/ui/app.slint#L2663), [`ui/dr-ui/ui/app.slint:723`](../../ui/dr-ui/ui/app.slint#L723), [`ui/dr-ui/ui/app.slint:746`](../../ui/dr-ui/ui/app.slint#L746) | +| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:1047`](../../core/dr-gpu/src/adjust.rs#L1047), [`core/dr-gpu/src/adjust.rs:2763`](../../core/dr-gpu/src/adjust.rs#L2763), [`core/dr-gpu/src/adjust.rs:74`](../../core/dr-gpu/src/adjust.rs#L74), [`core/dr-gpu/src/adjust.rs:914`](../../core/dr-gpu/src/adjust.rs#L914), [`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:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/framing.rs:1098`](../../core/dr-pipeline/src/framing.rs#L1098), [`core/dr-pipeline/src/framing.rs:251`](../../core/dr-pipeline/src/framing.rs#L251), [`core/dr-pipeline/src/framing.rs:308`](../../core/dr-pipeline/src/framing.rs#L308), [`core/dr-pipeline/src/framing.rs:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`core/dr-pipeline/src/graph.rs:1354`](../../core/dr-pipeline/src/graph.rs#L1354), [`core/dr-pipeline/src/graph.rs:1385`](../../core/dr-pipeline/src/graph.rs#L1385), [`core/dr-pipeline/src/graph.rs:1408`](../../core/dr-pipeline/src/graph.rs#L1408), [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:1477`](../../core/dr-pipeline/src/graph.rs#L1477), [`core/dr-pipeline/src/graph.rs:225`](../../core/dr-pipeline/src/graph.rs#L225), [`core/dr-pipeline/src/graph.rs:306`](../../core/dr-pipeline/src/graph.rs#L306), [`core/dr-pipeline/src/graph.rs:344`](../../core/dr-pipeline/src/graph.rs#L344), [`core/dr-pipeline/src/graph.rs:350`](../../core/dr-pipeline/src/graph.rs#L350), [`core/dr-pipeline/src/graph.rs:866`](../../core/dr-pipeline/src/graph.rs#L866), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`core/dr-pipeline/src/lens.rs:93`](../../core/dr-pipeline/src/lens.rs#L93), [`core/dr-pipeline/src/mask.rs:205`](../../core/dr-pipeline/src/mask.rs#L205), [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:306`](../../core/dr-pipeline/src/neutral.rs#L306), [`core/dr-pipeline/src/neutral.rs:322`](../../core/dr-pipeline/src/neutral.rs#L322), [`core/dr-pipeline/src/neutral.rs:338`](../../core/dr-pipeline/src/neutral.rs#L338), [`core/dr-pipeline/src/neutral.rs:360`](../../core/dr-pipeline/src/neutral.rs#L360), [`core/dr-pipeline/src/neutral.rs:81`](../../core/dr-pipeline/src/neutral.rs#L81), [`core/dr-pipeline/src/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/operation.rs:649`](../../core/dr-pipeline/src/operation.rs#L649), [`core/dr-pipeline/src/operation.rs:722`](../../core/dr-pipeline/src/operation.rs#L722), [`core/dr-pipeline/src/operation.rs:892`](../../core/dr-pipeline/src/operation.rs#L892), [`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/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`core/dr-pipeline/src/sidecar.rs:1433`](../../core/dr-pipeline/src/sidecar.rs#L1433), [`core/dr-pipeline/src/sidecar.rs:206`](../../core/dr-pipeline/src/sidecar.rs#L206), [`core/dr-pipeline/src/sidecar.rs:2275`](../../core/dr-pipeline/src/sidecar.rs#L2275), [`core/dr-pipeline/src/sidecar.rs:2355`](../../core/dr-pipeline/src/sidecar.rs#L2355), [`core/dr-pipeline/tests/tone_curve.rs:1`](../../core/dr-pipeline/tests/tone_curve.rs#L1), [`core/dr-segment/src/refine.rs:937`](../../core/dr-segment/src/refine.rs#L937), [`ui/dr-ui/src/develop/curves.rs:163`](../../ui/dr-ui/src/develop/curves.rs#L163), [`ui/dr-ui/src/develop/framing.rs:10`](../../ui/dr-ui/src/develop/framing.rs#L10), [`ui/dr-ui/src/develop/framing.rs:298`](../../ui/dr-ui/src/develop/framing.rs#L298), [`ui/dr-ui/src/develop/framing.rs:328`](../../ui/dr-ui/src/develop/framing.rs#L328), [`ui/dr-ui/src/develop/framing.rs:394`](../../ui/dr-ui/src/develop/framing.rs#L394), [`ui/dr-ui/src/develop/framing.rs:408`](../../ui/dr-ui/src/develop/framing.rs#L408), [`ui/dr-ui/src/develop/framing.rs:644`](../../ui/dr-ui/src/develop/framing.rs#L644), [`ui/dr-ui/src/develop/framing.rs:71`](../../ui/dr-ui/src/develop/framing.rs#L71), [`ui/dr-ui/src/develop/mask_ops.rs:1076`](../../ui/dr-ui/src/develop/mask_ops.rs#L1076), [`ui/dr-ui/src/develop/mask_ops.rs:1340`](../../ui/dr-ui/src/develop/mask_ops.rs#L1340), [`ui/dr-ui/src/develop/mask_ops.rs:178`](../../ui/dr-ui/src/develop/mask_ops.rs#L178), [`ui/dr-ui/src/develop/mask_ops.rs:969`](../../ui/dr-ui/src/develop/mask_ops.rs#L969), [`ui/dr-ui/src/develop/masks.rs:263`](../../ui/dr-ui/src/develop/masks.rs#L263), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/masks.rs:743`](../../ui/dr-ui/src/develop/masks.rs#L743), [`ui/dr-ui/src/develop/masks.rs:773`](../../ui/dr-ui/src/develop/masks.rs#L773), [`ui/dr-ui/src/develop/render.rs:1244`](../../ui/dr-ui/src/develop/render.rs#L1244), [`ui/dr-ui/src/develop/rows.rs:106`](../../ui/dr-ui/src/develop/rows.rs#L106), [`ui/dr-ui/src/develop/rows.rs:195`](../../ui/dr-ui/src/develop/rows.rs#L195), [`ui/dr-ui/src/develop/rows.rs:231`](../../ui/dr-ui/src/develop/rows.rs#L231), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/segmentation.rs:139`](../../ui/dr-ui/src/develop/segmentation.rs#L139), [`ui/dr-ui/src/develop/segmentation.rs:302`](../../ui/dr-ui/src/develop/segmentation.rs#L302), [`ui/dr-ui/src/develop/segmentation.rs:339`](../../ui/dr-ui/src/develop/segmentation.rs#L339), [`ui/dr-ui/src/develop/segmentation.rs:572`](../../ui/dr-ui/src/develop/segmentation.rs#L572), [`ui/dr-ui/src/develop/segmentation.rs:590`](../../ui/dr-ui/src/develop/segmentation.rs#L590), [`ui/dr-ui/src/develop/segmentation.rs:604`](../../ui/dr-ui/src/develop/segmentation.rs#L604), [`ui/dr-ui/src/develop/segmentation.rs:626`](../../ui/dr-ui/src/develop/segmentation.rs#L626), [`ui/dr-ui/src/develop/segmentation.rs:77`](../../ui/dr-ui/src/develop/segmentation.rs#L77), [`ui/dr-ui/src/develop/segmentation.rs:806`](../../ui/dr-ui/src/develop/segmentation.rs#L806), [`ui/dr-ui/src/develop/session.rs:162`](../../ui/dr-ui/src/develop/session.rs#L162), [`ui/dr-ui/src/develop/session.rs:255`](../../ui/dr-ui/src/develop/session.rs#L255), [`ui/dr-ui/src/develop/session.rs:284`](../../ui/dr-ui/src/develop/session.rs#L284), [`ui/dr-ui/src/develop/session.rs:434`](../../ui/dr-ui/src/develop/session.rs#L434), [`ui/dr-ui/src/develop/session.rs:494`](../../ui/dr-ui/src/develop/session.rs#L494), [`ui/dr-ui/src/develop/session.rs:566`](../../ui/dr-ui/src/develop/session.rs#L566), [`ui/dr-ui/src/develop/session.rs:721`](../../ui/dr-ui/src/develop/session.rs#L721), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop/white_balance.rs:193`](../../ui/dr-ui/src/develop/white_balance.rs#L193), [`ui/dr-ui/src/develop/white_balance.rs:275`](../../ui/dr-ui/src/develop/white_balance.rs#L275), [`ui/dr-ui/src/develop/white_balance.rs:336`](../../ui/dr-ui/src/develop/white_balance.rs#L336), [`ui/dr-ui/src/develop_ui.rs:1113`](../../ui/dr-ui/src/develop_ui.rs#L1113), [`ui/dr-ui/src/develop_ui.rs:117`](../../ui/dr-ui/src/develop_ui.rs#L117), [`ui/dr-ui/src/develop_ui.rs:1210`](../../ui/dr-ui/src/develop_ui.rs#L1210), [`ui/dr-ui/src/develop_ui.rs:1264`](../../ui/dr-ui/src/develop_ui.rs#L1264), [`ui/dr-ui/src/develop_ui.rs:514`](../../ui/dr-ui/src/develop_ui.rs#L514), [`ui/dr-ui/src/develop_ui.rs:734`](../../ui/dr-ui/src/develop_ui.rs#L734), [`ui/dr-ui/src/labels.rs:35`](../../ui/dr-ui/src/labels.rs#L35), [`ui/dr-ui/src/lib.rs:2155`](../../ui/dr-ui/src/lib.rs#L2155), [`ui/dr-ui/src/lib.rs:456`](../../ui/dr-ui/src/lib.rs#L456), [`ui/dr-ui/src/lib.rs:486`](../../ui/dr-ui/src/lib.rs#L486), [`ui/dr-ui/src/lib.rs:594`](../../ui/dr-ui/src/lib.rs#L594), [`ui/dr-ui/src/lib.rs:618`](../../ui/dr-ui/src/lib.rs#L618), [`ui/dr-ui/src/library/sidecar.rs:66`](../../ui/dr-ui/src/library/sidecar.rs#L66), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/masks_ui.rs:44`](../../ui/dr-ui/src/masks_ui.rs#L44), [`ui/dr-ui/src/presets.rs:414`](../../ui/dr-ui/src/presets.rs#L414), [`ui/dr-ui/src/segmentation.rs:117`](../../ui/dr-ui/src/segmentation.rs#L117), [`ui/dr-ui/src/segmentation.rs:353`](../../ui/dr-ui/src/segmentation.rs#L353), [`ui/dr-ui/src/segmentation.rs:369`](../../ui/dr-ui/src/segmentation.rs#L369), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499), [`ui/dr-ui/src/segmentation.rs:527`](../../ui/dr-ui/src/segmentation.rs#L527), [`ui/dr-ui/ui/adjust.slint:1050`](../../ui/dr-ui/ui/adjust.slint#L1050), [`ui/dr-ui/ui/adjust.slint:127`](../../ui/dr-ui/ui/adjust.slint#L127), [`ui/dr-ui/ui/adjust.slint:1658`](../../ui/dr-ui/ui/adjust.slint#L1658), [`ui/dr-ui/ui/adjust.slint:173`](../../ui/dr-ui/ui/adjust.slint#L173), [`ui/dr-ui/ui/adjust.slint:47`](../../ui/dr-ui/ui/adjust.slint#L47), [`ui/dr-ui/ui/adjust.slint:626`](../../ui/dr-ui/ui/adjust.slint#L626), [`ui/dr-ui/ui/adjust.slint:662`](../../ui/dr-ui/ui/adjust.slint#L662), [`ui/dr-ui/ui/adjust.slint:681`](../../ui/dr-ui/ui/adjust.slint#L681), [`ui/dr-ui/ui/adjust.slint:874`](../../ui/dr-ui/ui/adjust.slint#L874), [`ui/dr-ui/ui/adjust.slint:973`](../../ui/dr-ui/ui/adjust.slint#L973), [`ui/dr-ui/ui/app.slint:2239`](../../ui/dr-ui/ui/app.slint#L2239), [`ui/dr-ui/ui/app.slint:2301`](../../ui/dr-ui/ui/app.slint#L2301), [`ui/dr-ui/ui/app.slint:242`](../../ui/dr-ui/ui/app.slint#L242), [`ui/dr-ui/ui/develop.slint:375`](../../ui/dr-ui/ui/develop.slint#L375), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/masks.slint:779`](../../ui/dr-ui/ui/masks.slint#L779), [`ui/dr-ui/ui/masks.slint:838`](../../ui/dr-ui/ui/masks.slint#L838) | | FR-DEV-3a | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:212`](../../core/dr-pipeline/src/descriptor.rs#L212), [`core/dr-pipeline/src/descriptor.rs:252`](../../core/dr-pipeline/src/descriptor.rs#L252), [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/descriptor.rs:331`](../../core/dr-pipeline/src/descriptor.rs#L331), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:25`](../../core/dr-pipeline/src/graph.rs#L25), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/graph.rs:534`](../../core/dr-pipeline/src/graph.rs#L534), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/mask.rs:1746`](../../core/dr-pipeline/src/mask.rs#L1746), [`core/dr-pipeline/src/neutral.rs:1`](../../core/dr-pipeline/src/neutral.rs#L1), [`core/dr-pipeline/src/neutral.rs:378`](../../core/dr-pipeline/src/neutral.rs#L378), [`core/dr-pipeline/src/operation.rs:232`](../../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:400`](../../core/dr-pipeline/src/operation.rs#L400), [`core/dr-pipeline/src/ops/curve.rs:319`](../../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop/curves.rs:36`](../../ui/dr-ui/src/develop/curves.rs#L36), [`ui/dr-ui/src/develop/rows.rs:118`](../../ui/dr-ui/src/develop/rows.rs#L118), [`ui/dr-ui/src/develop/rows.rs:679`](../../ui/dr-ui/src/develop/rows.rs#L679), [`ui/dr-ui/src/develop/rows.rs:877`](../../ui/dr-ui/src/develop/rows.rs#L877), [`ui/dr-ui/src/lib.rs:931`](../../ui/dr-ui/src/lib.rs#L931), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../../ui/dr-ui/tests/ui_names_no_operation.rs#L1), [`ui/dr-ui/ui/app.slint:2693`](../../ui/dr-ui/ui/app.slint#L2693) | | FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:276`](../../core/dr-pipeline/src/descriptor.rs#L276), [`core/dr-pipeline/src/framing.rs:555`](../../core/dr-pipeline/src/framing.rs#L555), [`core/dr-pipeline/src/graph.rs:60`](../../core/dr-pipeline/src/graph.rs#L60), [`core/dr-pipeline/src/operation.rs:400`](../../core/dr-pipeline/src/operation.rs#L400), [`ui/dr-ui/ui/app.slint:2977`](../../ui/dr-ui/ui/app.slint#L2977) | | FR-DEV-3c | [`core/dr-pipeline/ops/exposure.yaml:1`](../../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:393`](../../core/dr-pipeline/src/graph.rs#L393), [`core/dr-pipeline/src/graph.rs:47`](../../core/dr-pipeline/src/graph.rs#L47), [`core/dr-pipeline/src/mask.rs:1746`](../../core/dr-pipeline/src/mask.rs#L1746), [`core/dr-pipeline/src/preset.rs:927`](../../core/dr-pipeline/src/preset.rs#L927), [`ui/dr-ui/src/develop/rows.rs:928`](../../ui/dr-ui/src/develop/rows.rs#L928) | -| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:114`](../../core/dr-gpu/src/adjust.rs#L114), [`core/dr-gpu/src/adjust.rs:1297`](../../core/dr-gpu/src/adjust.rs#L1297), [`core/dr-gpu/src/adjust.rs:1391`](../../core/dr-gpu/src/adjust.rs#L1391), [`core/dr-gpu/src/adjust.rs:84`](../../core/dr-gpu/src/adjust.rs#L84), [`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:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/graph.rs:997`](../../core/dr-pipeline/src/graph.rs#L997), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:480`](../../core/dr-pipeline/src/operation.rs#L480), [`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:127`](../../core/dr-decode/src/lib.rs#L127), [`core/dr-decode/src/lib.rs:777`](../../core/dr-decode/src/lib.rs#L777), [`core/dr-decode/src/lib.rs:817`](../../core/dr-decode/src/lib.rs#L817), [`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:473`](../../core/dr-decode/src/profile.rs#L473), [`core/dr-decode/src/profile.rs:507`](../../core/dr-decode/src/profile.rs#L507), [`core/dr-decode/src/profile.rs:678`](../../core/dr-decode/src/profile.rs#L678), [`core/dr-gpu/src/adjust.rs:1278`](../../core/dr-gpu/src/adjust.rs#L1278), [`core/dr-gpu/src/adjust.rs:37`](../../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/demosaic.rs:112`](../../core/dr-gpu/src/demosaic.rs#L112), [`core/dr-gpu/src/demosaic.rs:168`](../../core/dr-gpu/src/demosaic.rs#L168), [`core/dr-gpu/tests/base_curve.rs:1`](../../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:279`](../../core/dr-pipeline/src/declared/decl.rs#L279), [`core/dr-pipeline/src/operation.rs:2274`](../../core/dr-pipeline/src/operation.rs#L2274), [`core/dr-pipeline/src/operation.rs:2344`](../../core/dr-pipeline/src/operation.rs#L2344), [`core/dr-pipeline/src/operation.rs:2368`](../../core/dr-pipeline/src/operation.rs#L2368), [`core/dr-pipeline/src/operation.rs:2378`](../../core/dr-pipeline/src/operation.rs#L2378), [`core/dr-pipeline/src/operation.rs:2394`](../../core/dr-pipeline/src/operation.rs#L2394), [`core/dr-pipeline/src/operation.rs:2409`](../../core/dr-pipeline/src/operation.rs#L2409), [`core/dr-pipeline/src/operation.rs:2433`](../../core/dr-pipeline/src/operation.rs#L2433), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:357`](../../core/dr-pipeline/src/operation.rs#L357), [`core/dr-pipeline/src/operation.rs:431`](../../core/dr-pipeline/src/operation.rs#L431), [`core/dr-pipeline/src/operation.rs:451`](../../core/dr-pipeline/src/operation.rs#L451), [`core/dr-pipeline/src/operation.rs:579`](../../core/dr-pipeline/src/operation.rs#L579), [`core/dr-pipeline/src/operation.rs:589`](../../core/dr-pipeline/src/operation.rs#L589), [`core/dr-pipeline/src/operation.rs:883`](../../core/dr-pipeline/src/operation.rs#L883) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:161`](../../core/dr-film/src/bake.rs#L161), [`core/dr-film/src/bake.rs:384`](../../core/dr-film/src/bake.rs#L384), [`core/dr-film/src/bake.rs:52`](../../core/dr-film/src/bake.rs#L52), [`core/dr-film/src/bake.rs:662`](../../core/dr-film/src/bake.rs#L662), [`core/dr-film/src/bake.rs:689`](../../core/dr-film/src/bake.rs#L689), [`core/dr-film/src/bake.rs:710`](../../core/dr-film/src/bake.rs#L710), [`core/dr-film/src/bake.rs:747`](../../core/dr-film/src/bake.rs#L747), [`core/dr-film/src/bake.rs:95`](../../core/dr-film/src/bake.rs#L95), [`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:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:422`](../../core/dr-gpu/src/adjust.rs#L422), [`core/dr-gpu/src/adjust.rs:601`](../../core/dr-gpu/src/adjust.rs#L601), [`core/dr-gpu/src/adjust.rs:731`](../../core/dr-gpu/src/adjust.rs#L731), [`core/dr-gpu/src/adjust.rs:77`](../../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-gpu/tests/film_sim.rs:214`](../../core/dr-gpu/tests/film_sim.rs#L214), [`core/dr-gpu/tests/film_sim.rs:348`](../../core/dr-gpu/tests/film_sim.rs#L348), [`core/dr-gpu/tests/film_sim.rs:387`](../../core/dr-gpu/tests/film_sim.rs#L387), [`core/dr-gpu/tests/film_sim.rs:416`](../../core/dr-gpu/tests/film_sim.rs#L416), [`core/dr-gpu/tests/film_sim.rs:456`](../../core/dr-gpu/tests/film_sim.rs#L456), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/mask.rs:2452`](../../core/dr-pipeline/src/mask.rs#L2452), [`core/dr-pipeline/src/mask.rs:2485`](../../core/dr-pipeline/src/mask.rs#L2485), [`core/dr-pipeline/src/operation.rs:1356`](../../core/dr-pipeline/src/operation.rs#L1356), [`core/dr-pipeline/src/operation.rs:1590`](../../core/dr-pipeline/src/operation.rs#L1590), [`core/dr-pipeline/src/operation.rs:1618`](../../core/dr-pipeline/src/operation.rs#L1618), [`core/dr-pipeline/src/operation.rs:1664`](../../core/dr-pipeline/src/operation.rs#L1664), [`core/dr-pipeline/src/operation.rs:2274`](../../core/dr-pipeline/src/operation.rs#L2274), [`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/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:997`](../../core/dr-pipeline/src/operation.rs#L997), [`core/dr-pipeline/src/ops/film_sim.rs:126`](../../core/dr-pipeline/src/ops/film_sim.rs#L126), [`core/dr-pipeline/src/ops/film_sim.rs:131`](../../core/dr-pipeline/src/ops/film_sim.rs#L131), [`core/dr-pipeline/src/ops/film_sim.rs:183`](../../core/dr-pipeline/src/ops/film_sim.rs#L183), [`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:230`](../../core/dr-pipeline/src/ops/film_sim.rs#L230), [`core/dr-pipeline/src/ops/film_sim.rs:320`](../../core/dr-pipeline/src/ops/film_sim.rs#L320), [`core/dr-pipeline/src/ops/film_sim.rs:429`](../../core/dr-pipeline/src/ops/film_sim.rs#L429), [`core/dr-pipeline/src/ops/film_sim.rs:45`](../../core/dr-pipeline/src/ops/film_sim.rs#L45), [`core/dr-pipeline/src/ops/film_sim.rs:727`](../../core/dr-pipeline/src/ops/film_sim.rs#L727), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2724`](../../core/dr-pipeline/src/sidecar.rs#L2724), [`core/dr-pipeline/src/sidecar.rs:2800`](../../core/dr-pipeline/src/sidecar.rs#L2800), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`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/render.rs:1256`](../../ui/dr-ui/src/develop/render.rs#L1256), [`ui/dr-ui/src/develop/render.rs:1296`](../../ui/dr-ui/src/develop/render.rs#L1296), [`ui/dr-ui/src/develop/render.rs:1331`](../../ui/dr-ui/src/develop/render.rs#L1331), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:788`](../../ui/dr-ui/src/develop/render.rs#L788), [`ui/dr-ui/src/develop/render.rs:799`](../../ui/dr-ui/src/develop/render.rs#L799), [`ui/dr-ui/src/develop/render.rs:856`](../../ui/dr-ui/src/develop/render.rs#L856), [`ui/dr-ui/src/develop/render.rs:907`](../../ui/dr-ui/src/develop/render.rs#L907), [`ui/dr-ui/src/develop/render.rs:933`](../../ui/dr-ui/src/develop/render.rs#L933), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:447`](../../ui/dr-ui/src/develop_ui.rs#L447), [`ui/dr-ui/src/lib.rs:857`](../../ui/dr-ui/src/lib.rs#L857), [`ui/dr-ui/src/lib.rs:922`](../../ui/dr-ui/src/lib.rs#L922), [`ui/dr-ui/src/library/sidecar.rs:325`](../../ui/dr-ui/src/library/sidecar.rs#L325), [`ui/dr-ui/src/library/sidecar.rs:50`](../../ui/dr-ui/src/library/sidecar.rs#L50), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:602`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L602), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) | -| FR-DEV-3h | [`core/dr-decode/src/lib.rs:450`](../../core/dr-decode/src/lib.rs#L450), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1477`](../../core/dr-pipeline/src/framing.rs#L1477), [`core/dr-pipeline/src/framing.rs:486`](../../core/dr-pipeline/src/framing.rs#L486), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`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/masks.rs:280`](../../ui/dr-ui/src/develop/masks.rs#L280), [`ui/dr-ui/src/develop/segmentation.rs:114`](../../ui/dr-ui/src/develop/segmentation.rs#L114), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499) | +| FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1047`](../../core/dr-gpu/src/adjust.rs#L1047), [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1271`](../../core/dr-gpu/src/adjust.rs#L1271), [`core/dr-gpu/src/adjust.rs:1365`](../../core/dr-gpu/src/adjust.rs#L1365), [`core/dr-gpu/src/adjust.rs:74`](../../core/dr-gpu/src/adjust.rs#L74), [`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:468`](../../core/dr-pipeline/src/framing.rs#L468), [`core/dr-pipeline/src/graph.rs:997`](../../core/dr-pipeline/src/graph.rs#L997), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:480`](../../core/dr-pipeline/src/operation.rs#L480), [`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/lib.rs:125`](../../core/dr-decode/src/lib.rs#L125), [`core/dr-decode/src/lib.rs:752`](../../core/dr-decode/src/lib.rs#L752), [`core/dr-decode/src/lib.rs:792`](../../core/dr-decode/src/lib.rs#L792), [`core/dr-decode/src/profile.rs:104`](../../core/dr-decode/src/profile.rs#L104), [`core/dr-decode/src/profile.rs:153`](../../core/dr-decode/src/profile.rs#L153), [`core/dr-decode/src/profile.rs:1`](../../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:237`](../../core/dr-decode/src/profile.rs#L237), [`core/dr-decode/src/profile.rs:288`](../../core/dr-decode/src/profile.rs#L288), [`core/dr-decode/src/profile.rs:345`](../../core/dr-decode/src/profile.rs#L345), [`core/dr-decode/src/profile.rs:475`](../../core/dr-decode/src/profile.rs#L475), [`core/dr-decode/src/profile.rs:509`](../../core/dr-decode/src/profile.rs#L509), [`core/dr-decode/src/profile.rs:680`](../../core/dr-decode/src/profile.rs#L680), [`core/dr-pipeline/src/declared/decl.rs:279`](../../core/dr-pipeline/src/declared/decl.rs#L279), [`core/dr-pipeline/src/operation.rs:2282`](../../core/dr-pipeline/src/operation.rs#L2282), [`core/dr-pipeline/src/operation.rs:2306`](../../core/dr-pipeline/src/operation.rs#L2306), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:357`](../../core/dr-pipeline/src/operation.rs#L357), [`core/dr-pipeline/src/operation.rs:431`](../../core/dr-pipeline/src/operation.rs#L431), [`core/dr-pipeline/src/operation.rs:451`](../../core/dr-pipeline/src/operation.rs#L451), [`core/dr-pipeline/src/view.rs:227`](../../core/dr-pipeline/src/view.rs#L227) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:161`](../../core/dr-film/src/bake.rs#L161), [`core/dr-film/src/bake.rs:384`](../../core/dr-film/src/bake.rs#L384), [`core/dr-film/src/bake.rs:52`](../../core/dr-film/src/bake.rs#L52), [`core/dr-film/src/bake.rs:662`](../../core/dr-film/src/bake.rs#L662), [`core/dr-film/src/bake.rs:689`](../../core/dr-film/src/bake.rs#L689), [`core/dr-film/src/bake.rs:710`](../../core/dr-film/src/bake.rs#L710), [`core/dr-film/src/bake.rs:747`](../../core/dr-film/src/bake.rs#L747), [`core/dr-film/src/bake.rs:95`](../../core/dr-film/src/bake.rs#L95), [`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:142`](../../core/dr-gpu/src/adjust.rs#L142), [`core/dr-gpu/src/adjust.rs:412`](../../core/dr-gpu/src/adjust.rs#L412), [`core/dr-gpu/src/adjust.rs:591`](../../core/dr-gpu/src/adjust.rs#L591), [`core/dr-gpu/src/adjust.rs:67`](../../core/dr-gpu/src/adjust.rs#L67), [`core/dr-gpu/src/adjust.rs:721`](../../core/dr-gpu/src/adjust.rs#L721), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-gpu/tests/film_sim.rs:213`](../../core/dr-gpu/tests/film_sim.rs#L213), [`core/dr-gpu/tests/film_sim.rs:347`](../../core/dr-gpu/tests/film_sim.rs#L347), [`core/dr-gpu/tests/film_sim.rs:386`](../../core/dr-gpu/tests/film_sim.rs#L386), [`core/dr-gpu/tests/film_sim.rs:415`](../../core/dr-gpu/tests/film_sim.rs#L415), [`core/dr-gpu/tests/film_sim.rs:455`](../../core/dr-gpu/tests/film_sim.rs#L455), [`core/dr-pipeline/src/graph.rs:103`](../../core/dr-pipeline/src/graph.rs#L103), [`core/dr-pipeline/src/graph.rs:168`](../../core/dr-pipeline/src/graph.rs#L168), [`core/dr-pipeline/src/graph.rs:566`](../../core/dr-pipeline/src/graph.rs#L566), [`core/dr-pipeline/src/mask.rs:2452`](../../core/dr-pipeline/src/mask.rs#L2452), [`core/dr-pipeline/src/mask.rs:2485`](../../core/dr-pipeline/src/mask.rs#L2485), [`core/dr-pipeline/src/operation.rs:1294`](../../core/dr-pipeline/src/operation.rs#L1294), [`core/dr-pipeline/src/operation.rs:1528`](../../core/dr-pipeline/src/operation.rs#L1528), [`core/dr-pipeline/src/operation.rs:1556`](../../core/dr-pipeline/src/operation.rs#L1556), [`core/dr-pipeline/src/operation.rs:1602`](../../core/dr-pipeline/src/operation.rs#L1602), [`core/dr-pipeline/src/operation.rs:2212`](../../core/dr-pipeline/src/operation.rs#L2212), [`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/operation.rs:321`](../../core/dr-pipeline/src/operation.rs#L321), [`core/dr-pipeline/src/operation.rs:336`](../../core/dr-pipeline/src/operation.rs#L336), [`core/dr-pipeline/src/operation.rs:950`](../../core/dr-pipeline/src/operation.rs#L950), [`core/dr-pipeline/src/ops/film_sim.rs:126`](../../core/dr-pipeline/src/ops/film_sim.rs#L126), [`core/dr-pipeline/src/ops/film_sim.rs:131`](../../core/dr-pipeline/src/ops/film_sim.rs#L131), [`core/dr-pipeline/src/ops/film_sim.rs:183`](../../core/dr-pipeline/src/ops/film_sim.rs#L183), [`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:230`](../../core/dr-pipeline/src/ops/film_sim.rs#L230), [`core/dr-pipeline/src/ops/film_sim.rs:320`](../../core/dr-pipeline/src/ops/film_sim.rs#L320), [`core/dr-pipeline/src/ops/film_sim.rs:429`](../../core/dr-pipeline/src/ops/film_sim.rs#L429), [`core/dr-pipeline/src/ops/film_sim.rs:45`](../../core/dr-pipeline/src/ops/film_sim.rs#L45), [`core/dr-pipeline/src/ops/film_sim.rs:727`](../../core/dr-pipeline/src/ops/film_sim.rs#L727), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2724`](../../core/dr-pipeline/src/sidecar.rs#L2724), [`core/dr-pipeline/src/sidecar.rs:2800`](../../core/dr-pipeline/src/sidecar.rs#L2800), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`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/render.rs:1256`](../../ui/dr-ui/src/develop/render.rs#L1256), [`ui/dr-ui/src/develop/render.rs:1296`](../../ui/dr-ui/src/develop/render.rs#L1296), [`ui/dr-ui/src/develop/render.rs:1331`](../../ui/dr-ui/src/develop/render.rs#L1331), [`ui/dr-ui/src/develop/render.rs:684`](../../ui/dr-ui/src/develop/render.rs#L684), [`ui/dr-ui/src/develop/render.rs:701`](../../ui/dr-ui/src/develop/render.rs#L701), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:751`](../../ui/dr-ui/src/develop/render.rs#L751), [`ui/dr-ui/src/develop/render.rs:788`](../../ui/dr-ui/src/develop/render.rs#L788), [`ui/dr-ui/src/develop/render.rs:799`](../../ui/dr-ui/src/develop/render.rs#L799), [`ui/dr-ui/src/develop/render.rs:856`](../../ui/dr-ui/src/develop/render.rs#L856), [`ui/dr-ui/src/develop/render.rs:907`](../../ui/dr-ui/src/develop/render.rs#L907), [`ui/dr-ui/src/develop/render.rs:933`](../../ui/dr-ui/src/develop/render.rs#L933), [`ui/dr-ui/src/develop/session.rs:588`](../../ui/dr-ui/src/develop/session.rs#L588), [`ui/dr-ui/src/develop/session.rs:608`](../../ui/dr-ui/src/develop/session.rs#L608), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:447`](../../ui/dr-ui/src/develop_ui.rs#L447), [`ui/dr-ui/src/lib.rs:857`](../../ui/dr-ui/src/lib.rs#L857), [`ui/dr-ui/src/lib.rs:922`](../../ui/dr-ui/src/lib.rs#L922), [`ui/dr-ui/src/library/sidecar.rs:325`](../../ui/dr-ui/src/library/sidecar.rs#L325), [`ui/dr-ui/src/library/sidecar.rs:50`](../../ui/dr-ui/src/library/sidecar.rs#L50), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:602`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L602), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) | +| FR-DEV-3h | [`core/dr-decode/src/lib.rs:435`](../../core/dr-decode/src/lib.rs#L435), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1477`](../../core/dr-pipeline/src/framing.rs#L1477), [`core/dr-pipeline/src/framing.rs:486`](../../core/dr-pipeline/src/framing.rs#L486), [`core/dr-pipeline/src/framing.rs:704`](../../core/dr-pipeline/src/framing.rs#L704), [`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/masks.rs:280`](../../ui/dr-ui/src/develop/masks.rs#L280), [`ui/dr-ui/src/develop/segmentation.rs:114`](../../ui/dr-ui/src/develop/segmentation.rs#L114), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499) | | FR-DEV-3i | [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-segment/src/lib.rs:1`](../../core/dr-segment/src/lib.rs#L1), [`ui/dr-ui/src/segmentation.rs:1`](../../ui/dr-ui/src/segmentation.rs#L1) | -| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) | +| FR-DEV-3j | [`core/dr-gpu/src/adjust.rs:3199`](../../core/dr-gpu/src/adjust.rs#L3199), [`core/dr-gpu/tests/view_transform.rs:114`](../../core/dr-gpu/tests/view_transform.rs#L114), [`core/dr-gpu/tests/view_transform.rs:1`](../../core/dr-gpu/tests/view_transform.rs#L1), [`core/dr-gpu/tests/view_transform.rs:65`](../../core/dr-gpu/tests/view_transform.rs#L65), [`core/dr-gpu/tests/view_transform.rs:88`](../../core/dr-gpu/tests/view_transform.rs#L88), [`core/dr-pipeline/src/operation.rs:2212`](../../core/dr-pipeline/src/operation.rs#L2212), [`core/dr-pipeline/src/operation.rs:2316`](../../core/dr-pipeline/src/operation.rs#L2316), [`core/dr-pipeline/src/operation.rs:2333`](../../core/dr-pipeline/src/operation.rs#L2333), [`core/dr-pipeline/src/operation.rs:851`](../../core/dr-pipeline/src/operation.rs#L851), [`core/dr-pipeline/src/view.rs:184`](../../core/dr-pipeline/src/view.rs#L184), [`core/dr-pipeline/src/view.rs:197`](../../core/dr-pipeline/src/view.rs#L197), [`core/dr-pipeline/src/view.rs:1`](../../core/dr-pipeline/src/view.rs#L1), [`core/dr-pipeline/src/view.rs:211`](../../core/dr-pipeline/src/view.rs#L211), [`core/dr-pipeline/src/view.rs:227`](../../core/dr-pipeline/src/view.rs#L227), [`core/dr-pipeline/src/view.rs:241`](../../core/dr-pipeline/src/view.rs#L241), [`core/dr-pipeline/src/view.rs:252`](../../core/dr-pipeline/src/view.rs#L252) | +| FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:1047`](../../core/dr-gpu/src/adjust.rs#L1047), [`core/dr-gpu/src/lib.rs:368`](../../core/dr-gpu/src/lib.rs#L368), [`ui/dr-ui/ui/crop.slint:1`](../../ui/dr-ui/ui/crop.slint#L1) | | FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:1446`](../../core/dr-pipeline/src/graph.rs#L1446), [`core/dr-pipeline/src/graph.rs:587`](../../core/dr-pipeline/src/graph.rs#L587), [`core/dr-pipeline/src/graph.rs:642`](../../core/dr-pipeline/src/graph.rs#L642), [`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/sidecar.rs:1067`](../../core/dr-pipeline/src/sidecar.rs#L1067), [`core/dr-pipeline/src/sidecar.rs:147`](../../core/dr-pipeline/src/sidecar.rs#L147), [`core/dr-pipeline/src/sidecar.rs:2515`](../../core/dr-pipeline/src/sidecar.rs#L2515), [`core/dr-pipeline/src/sidecar.rs:2551`](../../core/dr-pipeline/src/sidecar.rs#L2551), [`core/dr-pipeline/src/sidecar.rs:2563`](../../core/dr-pipeline/src/sidecar.rs#L2563), [`core/dr-pipeline/src/sidecar.rs:318`](../../core/dr-pipeline/src/sidecar.rs#L318), [`core/dr-pipeline/src/sidecar.rs:713`](../../core/dr-pipeline/src/sidecar.rs#L713), [`core/dr-pipeline/src/sidecar.rs:729`](../../core/dr-pipeline/src/sidecar.rs#L729), [`core/dr-pipeline/src/sidecar.rs:876`](../../core/dr-pipeline/src/sidecar.rs#L876), [`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/framing.rs:870`](../../ui/dr-ui/src/develop/framing.rs#L870), [`ui/dr-ui/src/develop/history.rs:11`](../../ui/dr-ui/src/develop/history.rs#L11), [`ui/dr-ui/src/develop/history.rs:152`](../../ui/dr-ui/src/develop/history.rs#L152), [`ui/dr-ui/src/develop/history.rs:165`](../../ui/dr-ui/src/develop/history.rs#L165), [`ui/dr-ui/src/develop/history.rs:178`](../../ui/dr-ui/src/develop/history.rs#L178), [`ui/dr-ui/src/develop/history.rs:190`](../../ui/dr-ui/src/develop/history.rs#L190), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:242`](../../ui/dr-ui/src/develop/history.rs#L242), [`ui/dr-ui/src/develop/history.rs:261`](../../ui/dr-ui/src/develop/history.rs#L261), [`ui/dr-ui/src/develop/history.rs:277`](../../ui/dr-ui/src/develop/history.rs#L277), [`ui/dr-ui/src/develop/history.rs:299`](../../ui/dr-ui/src/develop/history.rs#L299), [`ui/dr-ui/src/develop/history.rs:29`](../../ui/dr-ui/src/develop/history.rs#L29), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/history.rs:68`](../../ui/dr-ui/src/develop/history.rs#L68), [`ui/dr-ui/src/develop/history.rs:82`](../../ui/dr-ui/src/develop/history.rs#L82), [`ui/dr-ui/src/develop/history.rs:93`](../../ui/dr-ui/src/develop/history.rs#L93), [`ui/dr-ui/src/develop/render.rs:713`](../../ui/dr-ui/src/develop/render.rs#L713), [`ui/dr-ui/src/develop/render.rs:933`](../../ui/dr-ui/src/develop/render.rs#L933), [`ui/dr-ui/src/develop/render.rs:969`](../../ui/dr-ui/src/develop/render.rs#L969), [`ui/dr-ui/src/develop/session.rs:80`](../../ui/dr-ui/src/develop/session.rs#L80), [`ui/dr-ui/src/develop/session.rs:89`](../../ui/dr-ui/src/develop/session.rs#L89), [`ui/dr-ui/src/develop/white_balance.rs:11`](../../ui/dr-ui/src/develop/white_balance.rs#L11), [`ui/dr-ui/src/develop/white_balance.rs:162`](../../ui/dr-ui/src/develop/white_balance.rs#L162), [`ui/dr-ui/src/develop_ui.rs:765`](../../ui/dr-ui/src/develop_ui.rs#L765), [`ui/dr-ui/src/develop_ui.rs:785`](../../ui/dr-ui/src/develop_ui.rs#L785), [`ui/dr-ui/src/labels.rs:12`](../../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:284`](../../ui/dr-ui/src/labels.rs#L284), [`ui/dr-ui/src/labels.rs:31`](../../ui/dr-ui/src/labels.rs#L31), [`ui/dr-ui/src/lib.rs:2081`](../../ui/dr-ui/src/lib.rs#L2081), [`ui/dr-ui/src/lib.rs:2086`](../../ui/dr-ui/src/lib.rs#L2086), [`ui/dr-ui/src/lib.rs:2118`](../../ui/dr-ui/src/lib.rs#L2118), [`ui/dr-ui/src/lib.rs:2127`](../../ui/dr-ui/src/lib.rs#L2127), [`ui/dr-ui/src/lib.rs:2144`](../../ui/dr-ui/src/lib.rs#L2144), [`ui/dr-ui/src/library/sidecar.rs:351`](../../ui/dr-ui/src/library/sidecar.rs#L351), [`ui/dr-ui/src/library/sidecar.rs:61`](../../ui/dr-ui/src/library/sidecar.rs#L61), [`ui/dr-ui/src/presets.rs:1770`](../../ui/dr-ui/src/presets.rs#L1770), [`ui/dr-ui/src/presets.rs:373`](../../ui/dr-ui/src/presets.rs#L373), [`ui/dr-ui/src/presets.rs:437`](../../ui/dr-ui/src/presets.rs#L437), [`ui/dr-ui/src/presets.rs:557`](../../ui/dr-ui/src/presets.rs#L557), [`ui/dr-ui/ui/history.slint:192`](../../ui/dr-ui/ui/history.slint#L192), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:219`](../../ui/dr-ui/ui/history.slint#L219), [`ui/dr-ui/ui/history.slint:304`](../../ui/dr-ui/ui/history.slint#L304), [`ui/dr-ui/ui/history.slint:43`](../../ui/dr-ui/ui/history.slint#L43), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53) | | FR-DEV-6 | [`core/dr-pipeline/src/bundled.rs:1`](../../core/dr-pipeline/src/bundled.rs#L1), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1382`](../../core/dr-pipeline/src/preset.rs#L1382), [`core/dr-pipeline/src/preset.rs:1404`](../../core/dr-pipeline/src/preset.rs#L1404), [`core/dr-pipeline/src/preset.rs:1418`](../../core/dr-pipeline/src/preset.rs#L1418), [`core/dr-pipeline/src/preset.rs:1436`](../../core/dr-pipeline/src/preset.rs#L1436), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:1`](../../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:501`](../../core/dr-pipeline/src/preset.rs#L501), [`core/dr-pipeline/src/preset.rs:534`](../../core/dr-pipeline/src/preset.rs#L534), [`core/dr-preset-xmp/src/lib.rs:1`](../../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-preset-xmp/src/lib.rs:492`](../../core/dr-preset-xmp/src/lib.rs#L492), [`core/dr-types/src/settings.rs:443`](../../core/dr-types/src/settings.rs#L443), [`ui/dr-ui/src/develop/session.rs:560`](../../ui/dr-ui/src/develop/session.rs#L560), [`ui/dr-ui/src/develop/session.rs:581`](../../ui/dr-ui/src/develop/session.rs#L581), [`ui/dr-ui/src/develop_ui.rs:167`](../../ui/dr-ui/src/develop_ui.rs#L167), [`ui/dr-ui/src/develop_ui.rs:196`](../../ui/dr-ui/src/develop_ui.rs#L196), [`ui/dr-ui/src/lib.rs:1262`](../../ui/dr-ui/src/lib.rs#L1262), [`ui/dr-ui/src/library/sidecar.rs:12`](../../ui/dr-ui/src/library/sidecar.rs#L12), [`ui/dr-ui/src/library/sidecar.rs:40`](../../ui/dr-ui/src/library/sidecar.rs#L40), [`ui/dr-ui/src/library/thumbnails_fetch.rs:414`](../../ui/dr-ui/src/library/thumbnails_fetch.rs#L414), [`ui/dr-ui/src/library_ui/controller.rs:677`](../../ui/dr-ui/src/library_ui/controller.rs#L677), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:495`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L495), [`ui/dr-ui/src/library_ui/window.rs:678`](../../ui/dr-ui/src/library_ui/window.rs#L678), [`ui/dr-ui/src/preset_store.rs:1`](../../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1586`](../../ui/dr-ui/src/presets.rs#L1586), [`ui/dr-ui/src/presets.rs:1`](../../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:707`](../../ui/dr-ui/src/presets.rs#L707), [`ui/dr-ui/src/presets.rs:741`](../../ui/dr-ui/src/presets.rs#L741), [`ui/dr-ui/ui/adjust.slint:937`](../../ui/dr-ui/ui/adjust.slint#L937), [`ui/dr-ui/ui/adjust.slint:962`](../../ui/dr-ui/ui/adjust.slint#L962), [`ui/dr-ui/ui/app.slint:1090`](../../ui/dr-ui/ui/app.slint#L1090), [`ui/dr-ui/ui/app.slint:1511`](../../ui/dr-ui/ui/app.slint#L1511), [`ui/dr-ui/ui/app.slint:1791`](../../ui/dr-ui/ui/app.slint#L1791), [`ui/dr-ui/ui/app.slint:2495`](../../ui/dr-ui/ui/app.slint#L2495), [`ui/dr-ui/ui/app.slint:3433`](../../ui/dr-ui/ui/app.slint#L3433), [`ui/dr-ui/ui/app.slint:3478`](../../ui/dr-ui/ui/app.slint#L3478), [`ui/dr-ui/ui/app.slint:525`](../../ui/dr-ui/ui/app.slint#L525), [`ui/dr-ui/ui/app.slint:536`](../../ui/dr-ui/ui/app.slint#L536), [`ui/dr-ui/ui/app.slint:694`](../../ui/dr-ui/ui/app.slint#L694), [`ui/dr-ui/ui/develop.slint:257`](../../ui/dr-ui/ui/develop.slint#L257), [`ui/dr-ui/ui/icons.slint:390`](../../ui/dr-ui/ui/icons.slint#L390), [`ui/dr-ui/ui/library.slint:2000`](../../ui/dr-ui/ui/library.slint#L2000), [`ui/dr-ui/ui/library.slint:2006`](../../ui/dr-ui/ui/library.slint#L2006), [`ui/dr-ui/ui/library.slint:3196`](../../ui/dr-ui/ui/library.slint#L3196), [`ui/dr-ui/ui/library.slint:5132`](../../ui/dr-ui/ui/library.slint#L5132), [`ui/dr-ui/ui/library.slint:5150`](../../ui/dr-ui/ui/library.slint#L5150), [`ui/dr-ui/ui/presets.slint:132`](../../ui/dr-ui/ui/presets.slint#L132), [`ui/dr-ui/ui/presets.slint:140`](../../ui/dr-ui/ui/presets.slint#L140), [`ui/dr-ui/ui/presets.slint:20`](../../ui/dr-ui/ui/presets.slint#L20), [`ui/dr-ui/ui/presets.slint:310`](../../ui/dr-ui/ui/presets.slint#L310), [`ui/dr-ui/ui/presets.slint:32`](../../ui/dr-ui/ui/presets.slint#L32), [`ui/dr-ui/ui/presets.slint:348`](../../ui/dr-ui/ui/presets.slint#L348), [`ui/dr-ui/ui/presets.slint:383`](../../ui/dr-ui/ui/presets.slint#L383), [`ui/dr-ui/ui/presets.slint:45`](../../ui/dr-ui/ui/presets.slint#L45), [`ui/dr-ui/ui/presets.slint:6`](../../ui/dr-ui/ui/presets.slint#L6), [`ui/dr-ui/ui/presets.slint:86`](../../ui/dr-ui/ui/presets.slint#L86), [`ui/dr-ui/ui/settings.slint:126`](../../ui/dr-ui/ui/settings.slint#L126), [`ui/dr-ui/ui/settings.slint:690`](../../ui/dr-ui/ui/settings.slint#L690), [`ui/dr-ui/ui/toolrail.slint:122`](../../ui/dr-ui/ui/toolrail.slint#L122), [`ui/dr-ui/ui/toolrail.slint:441`](../../ui/dr-ui/ui/toolrail.slint#L441) | | 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/history.rs:107`](../../ui/dr-ui/src/develop/history.rs#L107), [`ui/dr-ui/src/develop/history.rs:124`](../../ui/dr-ui/src/develop/history.rs#L124), [`ui/dr-ui/src/develop/history.rs:206`](../../ui/dr-ui/src/develop/history.rs#L206), [`ui/dr-ui/src/develop/history.rs:238`](../../ui/dr-ui/src/develop/history.rs#L238), [`ui/dr-ui/src/develop/history.rs:359`](../../ui/dr-ui/src/develop/history.rs#L359), [`ui/dr-ui/src/develop/render.rs:493`](../../ui/dr-ui/src/develop/render.rs#L493), [`ui/dr-ui/src/develop/render.rs:534`](../../ui/dr-ui/src/develop/render.rs#L534), [`ui/dr-ui/src/develop/render.rs:969`](../../ui/dr-ui/src/develop/render.rs#L969), [`ui/dr-ui/src/develop/session.rs:105`](../../ui/dr-ui/src/develop/session.rs#L105), [`ui/dr-ui/src/develop_ui.rs:706`](../../ui/dr-ui/src/develop_ui.rs#L706), [`ui/dr-ui/src/develop_ui.rs:765`](../../ui/dr-ui/src/develop_ui.rs#L765), [`ui/dr-ui/src/develop_ui.rs:837`](../../ui/dr-ui/src/develop_ui.rs#L837), [`ui/dr-ui/src/lib.rs:2127`](../../ui/dr-ui/src/lib.rs#L2127), [`ui/dr-ui/src/lib.rs:2204`](../../ui/dr-ui/src/lib.rs#L2204), [`ui/dr-ui/src/lib.rs:2218`](../../ui/dr-ui/src/lib.rs#L2218), [`ui/dr-ui/src/lib.rs:448`](../../ui/dr-ui/src/lib.rs#L448), [`ui/dr-ui/ui/app.slint:224`](../../ui/dr-ui/ui/app.slint#L224), [`ui/dr-ui/ui/app.slint:2362`](../../ui/dr-ui/ui/app.slint#L2362), [`ui/dr-ui/ui/app.slint:2408`](../../ui/dr-ui/ui/app.slint#L2408), [`ui/dr-ui/ui/app.slint:2977`](../../ui/dr-ui/ui/app.slint#L2977), [`ui/dr-ui/ui/history.slint:103`](../../ui/dr-ui/ui/history.slint#L103), [`ui/dr-ui/ui/history.slint:1`](../../ui/dr-ui/ui/history.slint#L1), [`ui/dr-ui/ui/history.slint:227`](../../ui/dr-ui/ui/history.slint#L227), [`ui/dr-ui/ui/history.slint:53`](../../ui/dr-ui/ui/history.slint#L53), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | -| 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:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1098`](../../core/dr-pipeline/src/graph.rs#L1098), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/operation.rs:827`](../../core/dr-pipeline/src/operation.rs#L827), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`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/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:992`](../../ui/dr-ui/src/develop_ui.rs#L992), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2162`](../../ui/dr-ui/src/lib.rs#L2162), [`ui/dr-ui/src/lib.rs:491`](../../ui/dr-ui/src/lib.rs#L491), [`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/app.slint:2215`](../../ui/dr-ui/ui/app.slint#L2215), [`ui/dr-ui/ui/app.slint:2458`](../../ui/dr-ui/ui/app.slint#L2458), [`ui/dr-ui/ui/app.slint:3326`](../../ui/dr-ui/ui/app.slint#L3326), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) | -| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:1057`](../../core/dr-gpu/src/adjust.rs#L1057), [`core/dr-gpu/src/adjust.rs:2718`](../../core/dr-gpu/src/adjust.rs#L2718), [`core/dr-gpu/src/adjust.rs:2795`](../../core/dr-gpu/src/adjust.rs#L2795), [`core/dr-gpu/src/adjust.rs:2880`](../../core/dr-gpu/src/adjust.rs#L2880), [`core/dr-gpu/src/adjust.rs:54`](../../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:928`](../../core/dr-pipeline/src/graph.rs#L928), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`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/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:565`](../../core/dr-pipeline/src/ops/dehaze.rs#L565), [`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/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1026`](../../ui/dr-ui/src/develop/render.rs#L1026), [`ui/dr-ui/src/develop/render.rs:1060`](../../ui/dr-ui/src/develop/render.rs#L1060), [`ui/dr-ui/src/develop/render.rs:1198`](../../ui/dr-ui/src/develop/render.rs#L1198), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/lib.rs:1078`](../../ui/dr-ui/src/lib.rs#L1078), [`ui/dr-ui/src/lib.rs:116`](../../ui/dr-ui/src/lib.rs#L116) | +| 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:452`](../../core/dr-pipeline/src/detail.rs#L452), [`core/dr-pipeline/src/detail.rs:487`](../../core/dr-pipeline/src/detail.rs#L487), [`core/dr-pipeline/src/detail.rs:573`](../../core/dr-pipeline/src/detail.rs#L573), [`core/dr-pipeline/src/graph.rs:1098`](../../core/dr-pipeline/src/graph.rs#L1098), [`core/dr-pipeline/src/graph.rs:115`](../../core/dr-pipeline/src/graph.rs#L115), [`core/dr-pipeline/src/graph.rs:247`](../../core/dr-pipeline/src/graph.rs#L247), [`core/dr-pipeline/src/operation.rs:365`](../../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/operation.rs:802`](../../core/dr-pipeline/src/operation.rs#L802), [`core/dr-pipeline/src/sidecar.rs:1104`](../../core/dr-pipeline/src/sidecar.rs#L1104), [`core/dr-pipeline/src/sidecar.rs:1158`](../../core/dr-pipeline/src/sidecar.rs#L1158), [`core/dr-pipeline/src/sidecar.rs:1188`](../../core/dr-pipeline/src/sidecar.rs#L1188), [`core/dr-pipeline/src/sidecar.rs:233`](../../core/dr-pipeline/src/sidecar.rs#L233), [`core/dr-pipeline/src/sidecar.rs:410`](../../core/dr-pipeline/src/sidecar.rs#L410), [`core/dr-pipeline/src/sidecar.rs:910`](../../core/dr-pipeline/src/sidecar.rs#L910), [`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/repairs.rs:114`](../../ui/dr-ui/src/develop/repairs.rs#L114), [`ui/dr-ui/src/develop/repairs.rs:11`](../../ui/dr-ui/src/develop/repairs.rs#L11), [`ui/dr-ui/src/develop/repairs.rs:197`](../../ui/dr-ui/src/develop/repairs.rs#L197), [`ui/dr-ui/src/develop/repairs.rs:211`](../../ui/dr-ui/src/develop/repairs.rs#L211), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/develop/session.rs:216`](../../ui/dr-ui/src/develop/session.rs#L216), [`ui/dr-ui/src/develop_ui.rs:992`](../../ui/dr-ui/src/develop_ui.rs#L992), [`ui/dr-ui/src/labels.rs:80`](../../ui/dr-ui/src/labels.rs#L80), [`ui/dr-ui/src/lib.rs:2162`](../../ui/dr-ui/src/lib.rs#L2162), [`ui/dr-ui/src/lib.rs:491`](../../ui/dr-ui/src/lib.rs#L491), [`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/app.slint:2215`](../../ui/dr-ui/ui/app.slint#L2215), [`ui/dr-ui/ui/app.slint:2458`](../../ui/dr-ui/ui/app.slint#L2458), [`ui/dr-ui/ui/app.slint:3326`](../../ui/dr-ui/ui/app.slint#L3326), [`ui/dr-ui/ui/icons.slint:378`](../../ui/dr-ui/ui/icons.slint#L378), [`ui/dr-ui/ui/session.slint:102`](../../ui/dr-ui/ui/session.slint#L102), [`ui/dr-ui/ui/session.slint:52`](../../ui/dr-ui/ui/session.slint#L52), [`ui/dr-ui/ui/spots.slint:191`](../../ui/dr-ui/ui/spots.slint#L191), [`ui/dr-ui/ui/spots.slint:49`](../../ui/dr-ui/ui/spots.slint#L49), [`ui/dr-ui/ui/spots.slint:6`](../../ui/dr-ui/ui/spots.slint#L6), [`ui/dr-ui/ui/spots.slint:91`](../../ui/dr-ui/ui/spots.slint#L91) | +| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:1047`](../../core/dr-gpu/src/adjust.rs#L1047), [`core/dr-gpu/src/adjust.rs:2686`](../../core/dr-gpu/src/adjust.rs#L2686), [`core/dr-gpu/src/adjust.rs:2763`](../../core/dr-gpu/src/adjust.rs#L2763), [`core/dr-gpu/src/adjust.rs:2848`](../../core/dr-gpu/src/adjust.rs#L2848), [`core/dr-gpu/src/adjust.rs:44`](../../core/dr-gpu/src/adjust.rs#L44), [`core/dr-gpu/src/lib.rs:174`](../../core/dr-gpu/src/lib.rs#L174), [`core/dr-gpu/src/lib.rs:194`](../../core/dr-gpu/src/lib.rs#L194), [`core/dr-gpu/src/lib.rs:72`](../../core/dr-gpu/src/lib.rs#L72), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:542`](../../core/dr-pipeline/src/detail.rs#L542), [`core/dr-pipeline/src/graph.rs:928`](../../core/dr-pipeline/src/graph.rs#L928), [`core/dr-pipeline/src/graph.rs:958`](../../core/dr-pipeline/src/graph.rs#L958), [`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/dehaze.rs:1`](../../core/dr-pipeline/src/ops/dehaze.rs#L1), [`core/dr-pipeline/src/ops/dehaze.rs:565`](../../core/dr-pipeline/src/ops/dehaze.rs#L565), [`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/framing.rs:594`](../../ui/dr-ui/src/develop/framing.rs#L594), [`ui/dr-ui/src/develop/mod.rs:55`](../../ui/dr-ui/src/develop/mod.rs#L55), [`ui/dr-ui/src/develop/render.rs:1026`](../../ui/dr-ui/src/develop/render.rs#L1026), [`ui/dr-ui/src/develop/render.rs:1060`](../../ui/dr-ui/src/develop/render.rs#L1060), [`ui/dr-ui/src/develop/render.rs:1198`](../../ui/dr-ui/src/develop/render.rs#L1198), [`ui/dr-ui/src/develop/render.rs:217`](../../ui/dr-ui/src/develop/render.rs#L217), [`ui/dr-ui/src/develop/render.rs:79`](../../ui/dr-ui/src/develop/render.rs#L79), [`ui/dr-ui/src/lib.rs:1078`](../../ui/dr-ui/src/lib.rs#L1078), [`ui/dr-ui/src/lib.rs:116`](../../ui/dr-ui/src/lib.rs#L116) | | 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-4 | [`ui/dr-ui/src/lib.rs:2227`](../../ui/dr-ui/src/lib.rs#L2227), [`ui/dr-ui/src/lib.rs:2406`](../../ui/dr-ui/src/lib.rs#L2406), [`ui/dr-ui/src/refine.rs:1`](../../ui/dr-ui/src/refine.rs#L1), [`ui/dr-ui/ui/app.slint:112`](../../ui/dr-ui/ui/app.slint#L112), [`ui/dr-ui/ui/app.slint:1849`](../../ui/dr-ui/ui/app.slint#L1849), [`ui/dr-ui/ui/histogram.slint:182`](../../ui/dr-ui/ui/histogram.slint#L182), [`ui/dr-ui/ui/histogram.slint:250`](../../ui/dr-ui/ui/histogram.slint#L250) | | 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:636`](../../core/dr-pipeline/src/operation.rs#L636), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1605`](../../ui/dr-ui/src/develop/render.rs#L1605), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/lib.rs:2103`](../../ui/dr-ui/src/lib.rs#L2103), [`ui/dr-ui/src/lib.rs:2944`](../../ui/dr-ui/src/lib.rs#L2944) | +| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:611`](../../core/dr-pipeline/src/operation.rs#L611), [`core/dr-types/src/colour.rs:1`](../../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop/render.rs:1604`](../../ui/dr-ui/src/develop/render.rs#L1604), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/lib.rs:2103`](../../ui/dr-ui/src/lib.rs#L2103), [`ui/dr-ui/src/lib.rs:2944`](../../ui/dr-ui/src/lib.rs#L2944) | | 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/render.rs:1395`](../../ui/dr-ui/src/develop/render.rs#L1395), [`ui/dr-ui/src/develop/render.rs:1427`](../../ui/dr-ui/src/develop/render.rs#L1427), [`ui/dr-ui/src/develop/render.rs:285`](../../ui/dr-ui/src/develop/render.rs#L285), [`ui/dr-ui/src/develop/session.rs:116`](../../ui/dr-ui/src/develop/session.rs#L116), [`ui/dr-ui/src/histogram.rs:1`](../../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2259`](../../ui/dr-ui/src/lib.rs#L2259), [`ui/dr-ui/src/lib.rs:468`](../../ui/dr-ui/src/lib.rs#L468), [`ui/dr-ui/ui/histogram.slint:191`](../../ui/dr-ui/ui/histogram.slint#L191), [`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/render.rs:1159`](../../ui/dr-ui/src/develop/render.rs#L1159), [`ui/dr-ui/src/develop/render.rs:1605`](../../ui/dr-ui/src/develop/render.rs#L1605), [`ui/dr-ui/src/develop/render.rs:1651`](../../ui/dr-ui/src/develop/render.rs#L1651), [`ui/dr-ui/src/develop/render.rs:1670`](../../ui/dr-ui/src/develop/render.rs#L1670), [`ui/dr-ui/src/develop/render.rs:193`](../../ui/dr-ui/src/develop/render.rs#L193), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/session.rs:269`](../../ui/dr-ui/src/develop/session.rs#L269), [`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:1284`](../../ui/dr-ui/src/lib.rs#L1284), [`ui/dr-ui/src/lib.rs:2103`](../../ui/dr-ui/src/lib.rs#L2103), [`ui/dr-ui/src/lib.rs:2921`](../../ui/dr-ui/src/lib.rs#L2921), [`ui/dr-ui/src/lib.rs:2944`](../../ui/dr-ui/src/lib.rs#L2944), [`ui/dr-ui/ui/app.slint:130`](../../ui/dr-ui/ui/app.slint#L130), [`ui/dr-ui/ui/app.slint:1835`](../../ui/dr-ui/ui/app.slint#L1835), [`ui/dr-ui/ui/settings.slint:176`](../../ui/dr-ui/ui/settings.slint#L176), [`ui/dr-ui/ui/settings.slint:864`](../../ui/dr-ui/ui/settings.slint#L864) | +| 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/render.rs:1159`](../../ui/dr-ui/src/develop/render.rs#L1159), [`ui/dr-ui/src/develop/render.rs:1604`](../../ui/dr-ui/src/develop/render.rs#L1604), [`ui/dr-ui/src/develop/render.rs:1650`](../../ui/dr-ui/src/develop/render.rs#L1650), [`ui/dr-ui/src/develop/render.rs:1669`](../../ui/dr-ui/src/develop/render.rs#L1669), [`ui/dr-ui/src/develop/render.rs:193`](../../ui/dr-ui/src/develop/render.rs#L193), [`ui/dr-ui/src/develop/render.rs:252`](../../ui/dr-ui/src/develop/render.rs#L252), [`ui/dr-ui/src/develop/render.rs:47`](../../ui/dr-ui/src/develop/render.rs#L47), [`ui/dr-ui/src/develop/session.rs:269`](../../ui/dr-ui/src/develop/session.rs#L269), [`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:1284`](../../ui/dr-ui/src/lib.rs#L1284), [`ui/dr-ui/src/lib.rs:2103`](../../ui/dr-ui/src/lib.rs#L2103), [`ui/dr-ui/src/lib.rs:2921`](../../ui/dr-ui/src/lib.rs#L2921), [`ui/dr-ui/src/lib.rs:2944`](../../ui/dr-ui/src/lib.rs#L2944), [`ui/dr-ui/ui/app.slint:130`](../../ui/dr-ui/ui/app.slint#L130), [`ui/dr-ui/ui/app.slint:1835`](../../ui/dr-ui/ui/app.slint#L1835), [`ui/dr-ui/ui/settings.slint:176`](../../ui/dr-ui/ui/settings.slint#L176), [`ui/dr-ui/ui/settings.slint:864`](../../ui/dr-ui/ui/settings.slint#L864) | | 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-10 | [`core/dr-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`core/dr-types/src/settings.rs:731`](../../core/dr-types/src/settings.rs#L731), [`ui/dr-ui/src/albums_ui.rs:1`](../../ui/dr-ui/src/albums_ui.rs#L1), [`ui/dr-ui/src/collections_ui/controller.rs:129`](../../ui/dr-ui/src/collections_ui/controller.rs#L129), [`ui/dr-ui/src/collections_ui/controller.rs:432`](../../ui/dr-ui/src/collections_ui/controller.rs#L432), [`ui/dr-ui/src/develop_ui.rs:273`](../../ui/dr-ui/src/develop_ui.rs#L273), [`ui/dr-ui/src/export.rs:100`](../../ui/dr-ui/src/export.rs#L100), [`ui/dr-ui/src/export.rs:217`](../../ui/dr-ui/src/export.rs#L217), [`ui/dr-ui/src/export.rs:588`](../../ui/dr-ui/src/export.rs#L588), [`ui/dr-ui/src/lib.rs:1627`](../../ui/dr-ui/src/lib.rs#L1627), [`ui/dr-ui/src/library_ui/controller.rs:205`](../../ui/dr-ui/src/library_ui/controller.rs#L205), [`ui/dr-ui/src/library_ui/controller.rs:934`](../../ui/dr-ui/src/library_ui/controller.rs#L934), [`ui/dr-ui/src/library_ui/controller.rs:974`](../../ui/dr-ui/src/library_ui/controller.rs#L974), [`ui/dr-ui/src/library_ui/window.rs:359`](../../ui/dr-ui/src/library_ui/window.rs#L359), [`ui/dr-ui/src/saf.rs:1`](../../ui/dr-ui/src/saf.rs#L1), [`ui/dr-ui/ui/albums.slint:1`](../../ui/dr-ui/ui/albums.slint#L1), [`ui/dr-ui/ui/app.slint:3559`](../../ui/dr-ui/ui/app.slint#L3559), [`ui/dr-ui/ui/collections.slint:789`](../../ui/dr-ui/ui/collections.slint#L789), [`ui/dr-ui/ui/export.slint:204`](../../ui/dr-ui/ui/export.slint#L204) | -| 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:157`](../../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:3153`](../../core/dr-gpu/src/adjust.rs#L3153), [`core/dr-pipeline/src/graph.rs:841`](../../core/dr-pipeline/src/graph.rs#L841), [`core/dr-pipeline/src/graph.rs:972`](../../core/dr-pipeline/src/graph.rs#L972), [`core/dr-pipeline/src/operation.rs:636`](../../core/dr-pipeline/src/operation.rs#L636), [`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:976`](../../core/dr-types/src/settings.rs#L976), [`ui/dr-ui/src/develop/render.rs:1670`](../../ui/dr-ui/src/develop/render.rs#L1670), [`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:157`](../../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:3121`](../../core/dr-gpu/src/adjust.rs#L3121), [`core/dr-pipeline/src/graph.rs:841`](../../core/dr-pipeline/src/graph.rs#L841), [`core/dr-pipeline/src/graph.rs:972`](../../core/dr-pipeline/src/graph.rs#L972), [`core/dr-pipeline/src/operation.rs:611`](../../core/dr-pipeline/src/operation.rs#L611), [`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:976`](../../core/dr-types/src/settings.rs#L976), [`ui/dr-ui/src/develop/render.rs:1669`](../../ui/dr-ui/src/develop/render.rs#L1669), [`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:186`](../../core/dr-export/src/lib.rs#L186), [`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:1021`](../../core/dr-types/src/settings.rs#L1021), [`core/dr-types/src/settings.rs:1029`](../../core/dr-types/src/settings.rs#L1029), [`core/dr-types/src/settings.rs:1045`](../../core/dr-types/src/settings.rs#L1045), [`core/dr-types/src/settings.rs:1114`](../../core/dr-types/src/settings.rs#L1114), [`core/dr-types/src/settings.rs:1142`](../../core/dr-types/src/settings.rs#L1142), [`core/dr-types/src/settings.rs:1153`](../../core/dr-types/src/settings.rs#L1153), [`core/dr-types/src/settings.rs:1159`](../../core/dr-types/src/settings.rs#L1159), [`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), [`ui/dr-ui/src/settings_ui.rs:256`](../../ui/dr-ui/src/settings_ui.rs#L256), [`ui/dr-ui/src/settings_ui.rs:269`](../../ui/dr-ui/src/settings_ui.rs#L269), [`ui/dr-ui/src/settings_ui.rs:288`](../../ui/dr-ui/src/settings_ui.rs#L288), [`ui/dr-ui/src/settings_ui.rs:701`](../../ui/dr-ui/src/settings_ui.rs#L701), [`ui/dr-ui/ui/export.slint:138`](../../ui/dr-ui/ui/export.slint#L138), [`ui/dr-ui/ui/export.slint:148`](../../ui/dr-ui/ui/export.slint#L148) | | 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-catalog/src/albums.rs:1`](../../core/dr-catalog/src/albums.rs#L1), [`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/albums_ui.rs:1`](../../ui/dr-ui/src/albums_ui.rs#L1), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:652`](../../ui/dr-ui/src/lib.rs#L652), [`ui/dr-ui/src/settings_ui.rs:1`](../../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/albums.slint:1`](../../ui/dr-ui/ui/albums.slint#L1) | | FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/develop_ui.rs:370`](../../ui/dr-ui/src/develop_ui.rs#L370), [`ui/dr-ui/src/export.rs:1122`](../../ui/dr-ui/src/export.rs#L1122), [`ui/dr-ui/src/export.rs:1`](../../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:293`](../../ui/dr-ui/src/lib.rs#L293), [`ui/dr-ui/src/lib.rs:652`](../../ui/dr-ui/src/lib.rs#L652), [`ui/dr-ui/src/lib.rs:698`](../../ui/dr-ui/src/lib.rs#L698), [`ui/dr-ui/src/lib.rs:727`](../../ui/dr-ui/src/lib.rs#L727), [`ui/dr-ui/src/library_ui/controller.rs:1099`](../../ui/dr-ui/src/library_ui/controller.rs#L1099), [`ui/dr-ui/src/library_ui/controller.rs:1111`](../../ui/dr-ui/src/library_ui/controller.rs#L1111), [`ui/dr-ui/src/library_ui/controller.rs:1123`](../../ui/dr-ui/src/library_ui/controller.rs#L1123), [`ui/dr-ui/src/library_ui/controller.rs:768`](../../ui/dr-ui/src/library_ui/controller.rs#L768), [`ui/dr-ui/src/library_ui/controller.rs:868`](../../ui/dr-ui/src/library_ui/controller.rs#L868), [`ui/dr-ui/src/library_ui/controller.rs:925`](../../ui/dr-ui/src/library_ui/controller.rs#L925), [`ui/dr-ui/src/library_ui/sync.rs:101`](../../ui/dr-ui/src/library_ui/sync.rs#L101), [`ui/dr-ui/src/settings_ui.rs:394`](../../ui/dr-ui/src/settings_ui.rs#L394), [`ui/dr-ui/ui/app.slint:1090`](../../ui/dr-ui/ui/app.slint#L1090), [`ui/dr-ui/ui/app.slint:1521`](../../ui/dr-ui/ui/app.slint#L1521), [`ui/dr-ui/ui/app.slint:2551`](../../ui/dr-ui/ui/app.slint#L2551), [`ui/dr-ui/ui/app.slint:3459`](../../ui/dr-ui/ui/app.slint#L3459), [`ui/dr-ui/ui/app.slint:687`](../../ui/dr-ui/ui/app.slint#L687), [`ui/dr-ui/ui/app.slint:704`](../../ui/dr-ui/ui/app.slint#L704), [`ui/dr-ui/ui/app.slint:940`](../../ui/dr-ui/ui/app.slint#L940), [`ui/dr-ui/ui/export.slint:1`](../../ui/dr-ui/ui/export.slint#L1), [`ui/dr-ui/ui/export.slint:246`](../../ui/dr-ui/ui/export.slint#L246), [`ui/dr-ui/ui/library.slint:2009`](../../ui/dr-ui/ui/library.slint#L2009), [`ui/dr-ui/ui/library.slint:2018`](../../ui/dr-ui/ui/library.slint#L2018), [`ui/dr-ui/ui/library.slint:3177`](../../ui/dr-ui/ui/library.slint#L3177), [`ui/dr-ui/ui/library.slint:5205`](../../ui/dr-ui/ui/library.slint#L5205) | -| FR-EXP-8 | [`core/dr-decode/src/lib.rs:372`](../../core/dr-decode/src/lib.rs#L372), [`core/dr-decode/src/lib.rs:396`](../../core/dr-decode/src/lib.rs#L396), [`core/dr-decode/src/lib.rs:410`](../../core/dr-decode/src/lib.rs#L410), [`core/dr-decode/src/lib.rs:77`](../../core/dr-decode/src/lib.rs#L77), [`core/dr-decode/src/lib.rs:85`](../../core/dr-decode/src/lib.rs#L85), [`core/dr-decode/src/lib.rs:88`](../../core/dr-decode/src/lib.rs#L88), [`core/dr-decode/src/locate.rs:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`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:140`](../../core/dr-export/src/lib.rs#L140), [`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:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:417`](../../ui/dr-ui/src/develop/session.rs#L417), [`ui/dr-ui/src/develop/session.rs:529`](../../ui/dr-ui/src/develop/session.rs#L529), [`ui/dr-ui/src/export.rs:555`](../../ui/dr-ui/src/export.rs#L555), [`ui/dr-ui/src/export.rs:740`](../../ui/dr-ui/src/export.rs#L740), [`ui/dr-ui/src/export.rs:786`](../../ui/dr-ui/src/export.rs#L786), [`ui/dr-ui/src/export.rs:814`](../../ui/dr-ui/src/export.rs#L814), [`ui/dr-ui/src/export.rs:851`](../../ui/dr-ui/src/export.rs#L851), [`ui/dr-ui/src/export.rs:952`](../../ui/dr-ui/src/export.rs#L952), [`ui/dr-ui/src/export.rs:970`](../../ui/dr-ui/src/export.rs#L970), [`ui/dr-ui/src/lib.rs:293`](../../ui/dr-ui/src/lib.rs#L293), [`ui/dr-ui/src/lib.rs:317`](../../ui/dr-ui/src/lib.rs#L317), [`ui/dr-ui/src/lib.rs:684`](../../ui/dr-ui/src/lib.rs#L684), [`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:552`](../../core/dr-decode/src/lib.rs#L552), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1418`](../../core/dr-gpu/src/adjust.rs#L1418), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:588`](../../ui/dr-ui/src/develop/render.rs#L588), [`ui/dr-ui/src/develop/render.rs:626`](../../ui/dr-ui/src/develop/render.rs#L626), [`ui/dr-ui/src/lib.rs:652`](../../ui/dr-ui/src/lib.rs#L652), [`ui/dr-ui/src/library/sweep.rs:511`](../../ui/dr-ui/src/library/sweep.rs#L511), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | +| FR-EXP-8 | [`core/dr-decode/src/lib.rs:357`](../../core/dr-decode/src/lib.rs#L357), [`core/dr-decode/src/lib.rs:381`](../../core/dr-decode/src/lib.rs#L381), [`core/dr-decode/src/lib.rs:395`](../../core/dr-decode/src/lib.rs#L395), [`core/dr-decode/src/lib.rs:75`](../../core/dr-decode/src/lib.rs#L75), [`core/dr-decode/src/lib.rs:83`](../../core/dr-decode/src/lib.rs#L83), [`core/dr-decode/src/lib.rs:86`](../../core/dr-decode/src/lib.rs#L86), [`core/dr-decode/src/locate.rs:1233`](../../core/dr-decode/src/locate.rs#L1233), [`core/dr-decode/src/locate.rs:1292`](../../core/dr-decode/src/locate.rs#L1292), [`core/dr-decode/src/locate.rs:359`](../../core/dr-decode/src/locate.rs#L359), [`core/dr-decode/src/locate.rs:556`](../../core/dr-decode/src/locate.rs#L556), [`core/dr-decode/src/locate.rs:640`](../../core/dr-decode/src/locate.rs#L640), [`core/dr-decode/src/locate.rs:653`](../../core/dr-decode/src/locate.rs#L653), [`core/dr-decode/src/locate.rs:736`](../../core/dr-decode/src/locate.rs#L736), [`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:140`](../../core/dr-export/src/lib.rs#L140), [`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:671`](../../core/dr-types/src/settings.rs#L671), [`ui/dr-ui/src/decoder_seam.rs:255`](../../ui/dr-ui/src/decoder_seam.rs#L255), [`ui/dr-ui/src/develop/session.rs:37`](../../ui/dr-ui/src/develop/session.rs#L37), [`ui/dr-ui/src/develop/session.rs:417`](../../ui/dr-ui/src/develop/session.rs#L417), [`ui/dr-ui/src/develop/session.rs:529`](../../ui/dr-ui/src/develop/session.rs#L529), [`ui/dr-ui/src/export.rs:555`](../../ui/dr-ui/src/export.rs#L555), [`ui/dr-ui/src/export.rs:740`](../../ui/dr-ui/src/export.rs#L740), [`ui/dr-ui/src/export.rs:786`](../../ui/dr-ui/src/export.rs#L786), [`ui/dr-ui/src/export.rs:814`](../../ui/dr-ui/src/export.rs#L814), [`ui/dr-ui/src/export.rs:851`](../../ui/dr-ui/src/export.rs#L851), [`ui/dr-ui/src/export.rs:952`](../../ui/dr-ui/src/export.rs#L952), [`ui/dr-ui/src/export.rs:970`](../../ui/dr-ui/src/export.rs#L970), [`ui/dr-ui/src/lib.rs:293`](../../ui/dr-ui/src/lib.rs#L293), [`ui/dr-ui/src/lib.rs:317`](../../ui/dr-ui/src/lib.rs#L317), [`ui/dr-ui/src/lib.rs:684`](../../ui/dr-ui/src/lib.rs#L684), [`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:537`](../../core/dr-decode/src/lib.rs#L537), [`core/dr-export/src/lib.rs:132`](../../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1392`](../../core/dr-gpu/src/adjust.rs#L1392), [`ui/dr-ui/examples/face_native.rs:1`](../../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:255`](../../ui/dr-ui/src/decoder_seam.rs#L255), [`ui/dr-ui/src/develop/mask_ops.rs:1130`](../../ui/dr-ui/src/develop/mask_ops.rs#L1130), [`ui/dr-ui/src/develop/render.rs:588`](../../ui/dr-ui/src/develop/render.rs#L588), [`ui/dr-ui/src/develop/render.rs:626`](../../ui/dr-ui/src/develop/render.rs#L626), [`ui/dr-ui/src/lib.rs:652`](../../ui/dr-ui/src/lib.rs#L652), [`ui/dr-ui/src/library/sweep.rs:511`](../../ui/dr-ui/src/library/sweep.rs#L511), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-INF-1 | [`ui/dr-ui/src/lib.rs:1464`](../../ui/dr-ui/src/lib.rs#L1464), [`ui/dr-ui/ui/settings.slint:824`](../../ui/dr-ui/ui/settings.slint#L824) | | FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1890`](../../ui/dr-ui/src/lib.rs#L1890), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1526`](../../ui/dr-ui/ui/app.slint#L1526), [`ui/dr-ui/ui/app.slint:589`](../../ui/dr-ui/ui/app.slint#L589), [`ui/dr-ui/ui/library.slint:2023`](../../ui/dr-ui/ui/library.slint#L2023), [`ui/dr-ui/ui/library.slint:5162`](../../ui/dr-ui/ui/library.slint#L5162), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-10 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1), [`core/dr-pano/src/lib.rs:1`](../../core/dr-pano/src/lib.rs#L1) | | FR-MRG-11 | [`core/dr-gpu/src/merge.rs:1`](../../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../../core/dr-gpu/src/shaders/merge.wgsl#L1) | -| FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1441`](../../core/dr-gpu/src/adjust.rs#L1441), [`core/dr-gpu/src/adjust.rs:1570`](../../core/dr-gpu/src/adjust.rs#L1570), [`core/dr-gpu/src/adjust.rs:536`](../../core/dr-gpu/src/adjust.rs#L536), [`core/dr-pipeline/src/graph.rs:858`](../../core/dr-pipeline/src/graph.rs#L858), [`core/dr-pipeline/src/operation.rs:1087`](../../core/dr-pipeline/src/operation.rs#L1087), [`core/dr-pipeline/src/operation.rs:2486`](../../core/dr-pipeline/src/operation.rs#L2486), [`core/dr-pipeline/src/operation.rs:504`](../../core/dr-pipeline/src/operation.rs#L504), [`core/dr-pipeline/src/operation.rs:722`](../../core/dr-pipeline/src/operation.rs#L722), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | -| FR-MRG-3 | [`core/dr-decode/examples/linear_dng.rs:1`](../../core/dr-decode/examples/linear_dng.rs#L1), [`core/dr-decode/src/lib.rs:143`](../../core/dr-decode/src/lib.rs#L143), [`core/dr-decode/src/lib.rs:150`](../../core/dr-decode/src/lib.rs#L150), [`core/dr-decode/src/lib.rs:604`](../../core/dr-decode/src/lib.rs#L604), [`core/dr-decode/src/profile.rs:395`](../../core/dr-decode/src/profile.rs#L395), [`core/dr-export/src/dng.rs:1`](../../core/dr-export/src/dng.rs#L1), [`core/dr-gpu/src/demosaic.rs:302`](../../core/dr-gpu/src/demosaic.rs#L302), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | +| FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:1415`](../../core/dr-gpu/src/adjust.rs#L1415), [`core/dr-gpu/src/adjust.rs:1542`](../../core/dr-gpu/src/adjust.rs#L1542), [`core/dr-gpu/src/adjust.rs:526`](../../core/dr-gpu/src/adjust.rs#L526), [`core/dr-gpu/src/adjust.rs:94`](../../core/dr-gpu/src/adjust.rs#L94), [`core/dr-pipeline/src/graph.rs:858`](../../core/dr-pipeline/src/graph.rs#L858), [`core/dr-pipeline/src/operation.rs:1040`](../../core/dr-pipeline/src/operation.rs#L1040), [`core/dr-pipeline/src/operation.rs:2350`](../../core/dr-pipeline/src/operation.rs#L2350), [`core/dr-pipeline/src/operation.rs:2390`](../../core/dr-pipeline/src/operation.rs#L2390), [`core/dr-pipeline/src/operation.rs:504`](../../core/dr-pipeline/src/operation.rs#L504), [`core/dr-pipeline/src/operation.rs:697`](../../core/dr-pipeline/src/operation.rs#L697), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | +| FR-MRG-3 | [`core/dr-decode/examples/linear_dng.rs:1`](../../core/dr-decode/examples/linear_dng.rs#L1), [`core/dr-decode/src/lib.rs:128`](../../core/dr-decode/src/lib.rs#L128), [`core/dr-decode/src/lib.rs:135`](../../core/dr-decode/src/lib.rs#L135), [`core/dr-decode/src/lib.rs:580`](../../core/dr-decode/src/lib.rs#L580), [`core/dr-decode/src/profile.rs:397`](../../core/dr-decode/src/profile.rs#L397), [`core/dr-export/src/dng.rs:1`](../../core/dr-export/src/dng.rs#L1), [`core/dr-gpu/src/demosaic.rs:282`](../../core/dr-gpu/src/demosaic.rs#L282), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1) | | FR-MRG-4 | [`core/dr-export/src/inscribed.rs:1`](../../core/dr-export/src/inscribed.rs#L1), [`core/dr-pano/src/fill.rs:1`](../../core/dr-pano/src/fill.rs#L1), [`core/dr-pano/src/migan.rs:1`](../../core/dr-pano/src/migan.rs#L1), [`core/dr-pano/src/projection.rs:1`](../../core/dr-pano/src/projection.rs#L1), [`ui/dr-ui/src/library/paths.rs:365`](../../ui/dr-ui/src/library/paths.rs#L365), [`ui/dr-ui/src/library/paths.rs:369`](../../ui/dr-ui/src/library/paths.rs#L369), [`ui/dr-ui/src/merge.rs:161`](../../ui/dr-ui/src/merge.rs#L161), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1), [`ui/dr-ui/ui/merge.slint:50`](../../ui/dr-ui/ui/merge.slint#L50) | | FR-MRG-5 | [`core/dr-pano/src/align.rs:1`](../../core/dr-pano/src/align.rs#L1), [`ui/dr-ui/src/merge.rs:1`](../../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-6 | [`core/dr-pipeline/src/sidecar.rs:1055`](../../core/dr-pipeline/src/sidecar.rs#L1055), [`core/dr-pipeline/src/sidecar.rs:116`](../../core/dr-pipeline/src/sidecar.rs#L116), [`core/dr-pipeline/src/sidecar.rs:126`](../../core/dr-pipeline/src/sidecar.rs#L126), [`core/dr-pipeline/src/sidecar.rs:2284`](../../core/dr-pipeline/src/sidecar.rs#L2284), [`core/dr-pipeline/src/sidecar.rs:860`](../../core/dr-pipeline/src/sidecar.rs#L860), [`ui/dr-ui/src/merge.rs:921`](../../ui/dr-ui/src/merge.rs#L921) | @@ -121,7 +122,7 @@ _None._ | 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:1124`](../../core/dr-sync-nextcloud/src/lib.rs#L1124), [`core/dr-sync-nextcloud/src/lib.rs:40`](../../core/dr-sync-nextcloud/src/lib.rs#L40), [`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:87`](../../core/dr-sync/src/account.rs#L87), [`core/dr-sync/src/lib.rs:238`](../../core/dr-sync/src/lib.rs#L238), [`core/dr-sync/src/lib.rs:51`](../../core/dr-sync/src/lib.rs#L51), [`core/dr-sync/src/provider.rs:120`](../../core/dr-sync/src/provider.rs#L120), [`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:120`](../../core/dr-sync/src/provider.rs#L120), [`ui/dr-ui/src/launch_ui.rs:425`](../../ui/dr-ui/src/launch_ui.rs#L425), [`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:318`](../../core/dr-sync/src/account.rs#L318), [`core/dr-sync/src/account.rs:375`](../../core/dr-sync/src/account.rs#L375), [`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/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library/sweep.rs:563`](../../ui/dr-ui/src/library/sweep.rs#L563), [`ui/dr-ui/src/library_ui/grid.rs:555`](../../ui/dr-ui/src/library_ui/grid.rs#L555), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/src/library_ui/sync.rs:393`](../../ui/dr-ui/src/library_ui/sync.rs#L393), [`ui/dr-ui/ui/library.slint:781`](../../ui/dr-ui/ui/library.slint#L781), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375), [`ui/dr-ui/ui/settings.slint:74`](../../ui/dr-ui/ui/settings.slint#L74) | +| 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/decoder_seam.rs:214`](../../ui/dr-ui/src/decoder_seam.rs#L214), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library/sweep.rs:563`](../../ui/dr-ui/src/library/sweep.rs#L563), [`ui/dr-ui/src/library_ui/grid.rs:555`](../../ui/dr-ui/src/library_ui/grid.rs#L555), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/src/library_ui/sync.rs:393`](../../ui/dr-ui/src/library_ui/sync.rs#L393), [`ui/dr-ui/ui/library.slint:781`](../../ui/dr-ui/ui/library.slint#L781), [`ui/dr-ui/ui/settings.slint:375`](../../ui/dr-ui/ui/settings.slint#L375), [`ui/dr-ui/ui/settings.slint:74`](../../ui/dr-ui/ui/settings.slint#L74) | | 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:238`](../../core/dr-sync/src/lib.rs#L238), [`core/dr-sync/src/scan.rs:129`](../../core/dr-sync/src/scan.rs#L129), [`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:503`](../../ui/dr-ui/src/import.rs#L503) | | FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../../ui/dr-ui/src/activity.rs#L1) | @@ -137,18 +138,18 @@ _None._ | FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1000`](../../core/dr-catalog/src/walk.rs#L1000), [`core/dr-catalog/src/walk.rs:432`](../../core/dr-catalog/src/walk.rs#L432), [`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:211`](../../core/dr-sync/src/scan.rs#L211), [`core/dr-sync/src/scan.rs:519`](../../core/dr-sync/src/scan.rs#L519), [`core/dr-sync/src/scan.rs:545`](../../core/dr-sync/src/scan.rs#L545), [`core/dr-sync/src/scan.rs:574`](../../core/dr-sync/src/scan.rs#L574), [`ui/dr-ui/src/library/scan.rs:127`](../../ui/dr-ui/src/library/scan.rs#L127), [`ui/dr-ui/src/library/scan.rs:180`](../../ui/dr-ui/src/library/scan.rs#L180), [`ui/dr-ui/src/library/scan.rs:211`](../../ui/dr-ui/src/library/scan.rs#L211), [`ui/dr-ui/src/library/scan.rs:636`](../../ui/dr-ui/src/library/scan.rs#L636), [`ui/dr-ui/src/library_ui/controller.rs:344`](../../ui/dr-ui/src/library_ui/controller.rs#L344), [`ui/dr-ui/src/library_ui/controller.rs:592`](../../ui/dr-ui/src/library_ui/controller.rs#L592), [`ui/dr-ui/src/library_ui/offline.rs:569`](../../ui/dr-ui/src/library_ui/offline.rs#L569), [`ui/dr-ui/src/library_ui/open.rs:427`](../../ui/dr-ui/src/library_ui/open.rs#L427), [`ui/dr-ui/src/library_ui/open.rs:536`](../../ui/dr-ui/src/library_ui/open.rs#L536) | | 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), [`core/dr-catalog/tests/every_queued_kind_has_a_consumer.rs:1`](../../core/dr-catalog/tests/every_queued_kind_has_a_consumer.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:1340`](../../core/dr-gpu/src/adjust.rs#L1340), [`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/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1236`](../../ui/dr-ui/src/lib.rs#L1236), [`ui/dr-ui/src/lib.rs:1635`](../../ui/dr-ui/src/lib.rs#L1635), [`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:1314`](../../core/dr-gpu/src/adjust.rs#L1314), [`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/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:1236`](../../ui/dr-ui/src/lib.rs#L1236), [`ui/dr-ui/src/lib.rs:1635`](../../ui/dr-ui/src/lib.rs#L1635), [`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:443`](../../apps/darkroom-android/src/lib.rs#L443) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:1413`](../../ui/dr-ui/src/lib.rs#L1413), [`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-PLAT-WIN-1 | [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1) | | FR-PLAT-WIN-2 | [`apps/darkroom-desktop/build.rs:1`](../../apps/darkroom-desktop/build.rs#L1), [`apps/darkroom-desktop/src/main.rs:6`](../../apps/darkroom-desktop/src/main.rs#L6), [`ui/dr-ui/src/launch_ui.rs:937`](../../ui/dr-ui/src/launch_ui.rs#L937) | | FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:19`](../../apps/darkroom-desktop/src/main.rs#L19) | -| FR-RAW-1 | [`core/dr-decode/src/lib.rs:265`](../../core/dr-decode/src/lib.rs#L265), [`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) | -| FR-RAW-2 | [`core/dr-decode/src/decoder.rs:1`](../../core/dr-decode/src/decoder.rs#L1), [`core/dr-decode/src/decoder.rs:26`](../../core/dr-decode/src/decoder.rs#L26), [`core/dr-decode/src/decoder.rs:59`](../../core/dr-decode/src/decoder.rs#L59), [`core/dr-decode/src/decoder.rs:95`](../../core/dr-decode/src/decoder.rs#L95), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/decoder_seam.rs:1`](../../ui/dr-ui/src/decoder_seam.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/export.rs:605`](../../ui/dr-ui/src/export.rs#L605), [`ui/dr-ui/src/export.rs:851`](../../ui/dr-ui/src/export.rs#L851), [`ui/dr-ui/src/import.rs:96`](../../ui/dr-ui/src/import.rs#L96), [`ui/dr-ui/src/library/sweep.rs:280`](../../ui/dr-ui/src/library/sweep.rs#L280), [`ui/dr-ui/src/library/sweep.rs:602`](../../ui/dr-ui/src/library/sweep.rs#L602), [`ui/dr-ui/src/library/thumbnails_gen.rs:101`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L101), [`ui/dr-ui/src/merge.rs:108`](../../ui/dr-ui/src/merge.rs#L108), [`ui/dr-ui/src/repairs.rs:243`](../../ui/dr-ui/src/repairs.rs#L243) | -| FR-RAW-3 | [`core/dr-decode/src/lib.rs:161`](../../core/dr-decode/src/lib.rs#L161), [`core/dr-decode/src/lib.rs:552`](../../core/dr-decode/src/lib.rs#L552), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1069`](../../core/dr-gpu/src/demosaic.rs#L1069), [`core/dr-gpu/src/demosaic.rs:637`](../../core/dr-gpu/src/demosaic.rs#L637), [`core/dr-gpu/tests/hot_pixels.rs:1`](../../core/dr-gpu/tests/hot_pixels.rs#L1) | +| FR-RAW-1 | [`core/dr-decode/src/lib.rs:250`](../../core/dr-decode/src/lib.rs#L250), [`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) | +| FR-RAW-2 | [`core/dr-decode/src/decoder.rs:1`](../../core/dr-decode/src/decoder.rs#L1), [`core/dr-decode/src/decoder.rs:26`](../../core/dr-decode/src/decoder.rs#L26), [`core/dr-decode/src/decoder.rs:59`](../../core/dr-decode/src/decoder.rs#L59), [`core/dr-decode/src/decoder.rs:95`](../../core/dr-decode/src/decoder.rs#L95), [`ui/dr-ui/src/decoder_seam.rs:183`](../../ui/dr-ui/src/decoder_seam.rs#L183), [`ui/dr-ui/src/decoder_seam.rs:1`](../../ui/dr-ui/src/decoder_seam.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:214`](../../ui/dr-ui/src/decoder_seam.rs#L214), [`ui/dr-ui/src/decoder_seam.rs:255`](../../ui/dr-ui/src/decoder_seam.rs#L255), [`ui/dr-ui/src/export.rs:605`](../../ui/dr-ui/src/export.rs#L605), [`ui/dr-ui/src/export.rs:851`](../../ui/dr-ui/src/export.rs#L851), [`ui/dr-ui/src/import.rs:96`](../../ui/dr-ui/src/import.rs#L96), [`ui/dr-ui/src/library/sweep.rs:280`](../../ui/dr-ui/src/library/sweep.rs#L280), [`ui/dr-ui/src/library/sweep.rs:602`](../../ui/dr-ui/src/library/sweep.rs#L602), [`ui/dr-ui/src/library/thumbnails_gen.rs:101`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L101), [`ui/dr-ui/src/merge.rs:108`](../../ui/dr-ui/src/merge.rs#L108), [`ui/dr-ui/src/repairs.rs:243`](../../ui/dr-ui/src/repairs.rs#L243) | +| FR-RAW-3 | [`core/dr-decode/src/lib.rs:146`](../../core/dr-decode/src/lib.rs#L146), [`core/dr-decode/src/lib.rs:537`](../../core/dr-decode/src/lib.rs#L537), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1047`](../../core/dr-gpu/src/demosaic.rs#L1047), [`core/dr-gpu/src/demosaic.rs:616`](../../core/dr-gpu/src/demosaic.rs#L616), [`core/dr-gpu/tests/hot_pixels.rs:1`](../../core/dr-gpu/tests/hot_pixels.rs#L1) | | FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30), [`ui/dr-ui/src/lib.rs:293`](../../ui/dr-ui/src/lib.rs#L293) | -| FR-RAW-5 | [`core/dr-decode/src/lib.rs:189`](../../core/dr-decode/src/lib.rs#L189), [`core/dr-gpu/src/demosaic.rs:1067`](../../core/dr-gpu/src/demosaic.rs#L1067), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:864`](../../core/dr-gpu/src/demosaic.rs#L864), [`core/dr-gpu/src/demosaic.rs:943`](../../core/dr-gpu/src/demosaic.rs#L943) | +| FR-RAW-5 | [`core/dr-decode/src/lib.rs:174`](../../core/dr-decode/src/lib.rs#L174), [`core/dr-gpu/src/demosaic.rs:1045`](../../core/dr-gpu/src/demosaic.rs#L1045), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:842`](../../core/dr-gpu/src/demosaic.rs#L842), [`core/dr-gpu/src/demosaic.rs:921`](../../core/dr-gpu/src/demosaic.rs#L921) | | FR-UI-1 | [`core/dr-types/src/settings.rs:1390`](../../core/dr-types/src/settings.rs#L1390), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/develop_ui.rs:204`](../../ui/dr-ui/src/develop_ui.rs#L204), [`ui/dr-ui/src/lib.rs:124`](../../ui/dr-ui/src/lib.rs#L124), [`ui/dr-ui/src/lib.rs:139`](../../ui/dr-ui/src/lib.rs#L139), [`ui/dr-ui/src/lib.rs:161`](../../ui/dr-ui/src/lib.rs#L161), [`ui/dr-ui/src/lib.rs:3216`](../../ui/dr-ui/src/lib.rs#L3216), [`ui/dr-ui/src/lib.rs:3235`](../../ui/dr-ui/src/lib.rs#L3235), [`ui/dr-ui/src/lib.rs:3262`](../../ui/dr-ui/src/lib.rs#L3262), [`ui/dr-ui/src/lib.rs:3313`](../../ui/dr-ui/src/lib.rs#L3313), [`ui/dr-ui/src/lib.rs:3346`](../../ui/dr-ui/src/lib.rs#L3346), [`ui/dr-ui/src/library_ui/grid.rs:242`](../../ui/dr-ui/src/library_ui/grid.rs#L242), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/settings_ui.rs:143`](../../ui/dr-ui/src/settings_ui.rs#L143), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:3097`](../../ui/dr-ui/ui/app.slint#L3097), [`ui/dr-ui/ui/app.slint:666`](../../ui/dr-ui/ui/app.slint#L666), [`ui/dr-ui/ui/app.slint:811`](../../ui/dr-ui/ui/app.slint#L811), [`ui/dr-ui/ui/identity.slint:249`](../../ui/dr-ui/ui/identity.slint#L249), [`ui/dr-ui/ui/library.slint:1447`](../../ui/dr-ui/ui/library.slint#L1447), [`ui/dr-ui/ui/settings.slint:129`](../../ui/dr-ui/ui/settings.slint#L129), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:177`](../../ui/dr-ui/ui/toolrail.slint#L177), [`ui/dr-ui/ui/toolrail.slint:91`](../../ui/dr-ui/ui/toolrail.slint#L91), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) | | FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:124`](../../ui/dr-ui/src/lib.rs#L124), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/grid.rs:242`](../../ui/dr-ui/src/library_ui/grid.rs#L242), [`ui/dr-ui/src/library_ui/window.rs:1000`](../../ui/dr-ui/src/library_ui/window.rs#L1000), [`ui/dr-ui/tests/scroll_cue.rs:1`](../../ui/dr-ui/tests/scroll_cue.rs#L1), [`ui/dr-ui/ui/app.slint:137`](../../ui/dr-ui/ui/app.slint#L137), [`ui/dr-ui/ui/app.slint:3494`](../../ui/dr-ui/ui/app.slint#L3494), [`ui/dr-ui/ui/app.slint:795`](../../ui/dr-ui/ui/app.slint#L795), [`ui/dr-ui/ui/collections.slint:1354`](../../ui/dr-ui/ui/collections.slint#L1354), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1334`](../../ui/dr-ui/ui/library.slint#L1334), [`ui/dr-ui/ui/library.slint:1367`](../../ui/dr-ui/ui/library.slint#L1367), [`ui/dr-ui/ui/library.slint:1691`](../../ui/dr-ui/ui/library.slint#L1691), [`ui/dr-ui/ui/library.slint:1698`](../../ui/dr-ui/ui/library.slint#L1698), [`ui/dr-ui/ui/library.slint:1799`](../../ui/dr-ui/ui/library.slint#L1799), [`ui/dr-ui/ui/library.slint:3135`](../../ui/dr-ui/ui/library.slint#L3135), [`ui/dr-ui/ui/library.slint:572`](../../ui/dr-ui/ui/library.slint#L572), [`ui/dr-ui/ui/presets.slint:45`](../../ui/dr-ui/ui/presets.slint#L45), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) | | FR-UI-3 | [`ui/dr-ui/src/collections_ui/controller.rs:143`](../../ui/dr-ui/src/collections_ui/controller.rs#L143), [`ui/dr-ui/src/collections_ui/controller.rs:348`](../../ui/dr-ui/src/collections_ui/controller.rs#L348), [`ui/dr-ui/src/collections_ui/controller.rs:361`](../../ui/dr-ui/src/collections_ui/controller.rs#L361), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui/grid.rs:228`](../../ui/dr-ui/src/library_ui/grid.rs#L228), [`ui/dr-ui/src/masks_ui.rs:1700`](../../ui/dr-ui/src/masks_ui.rs#L1700), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2301`](../../ui/dr-ui/ui/app.slint#L2301), [`ui/dr-ui/ui/collections.slint:1228`](../../ui/dr-ui/ui/collections.slint#L1228), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:78`](../../ui/dr-ui/ui/collections.slint#L78), [`ui/dr-ui/ui/collections.slint:978`](../../ui/dr-ui/ui/collections.slint#L978), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | @@ -171,7 +172,7 @@ _None._ | 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/render.rs:409`](../../ui/dr-ui/src/develop/render.rs#L409), [`ui/dr-ui/src/lib.rs:2304`](../../ui/dr-ui/src/lib.rs#L2304) | | NFR-P3 | [`tools/bench/src/thumbnails.rs:1`](../../tools/bench/src/thumbnails.rs#L1) | -| NFR-P5 | [`core/dr-catalog/src/schema.rs:594`](../../core/dr-catalog/src/schema.rs#L594), [`core/dr-gpu/src/adjust.rs:176`](../../core/dr-gpu/src/adjust.rs#L176), [`core/dr-gpu/src/adjust.rs:2951`](../../core/dr-gpu/src/adjust.rs#L2951), [`core/dr-pipeline/src/detail.rs:1268`](../../core/dr-pipeline/src/detail.rs#L1268), [`core/dr-pipeline/src/detail.rs:667`](../../core/dr-pipeline/src/detail.rs#L667), [`ui/dr-ui/src/library/cells.rs:374`](../../ui/dr-ui/src/library/cells.rs#L374), [`ui/dr-ui/src/library/cells.rs:715`](../../ui/dr-ui/src/library/cells.rs#L715), [`ui/dr-ui/src/library_ui/controller.rs:58`](../../ui/dr-ui/src/library_ui/controller.rs#L58), [`ui/dr-ui/src/library_ui/timeline.rs:382`](../../ui/dr-ui/src/library_ui/timeline.rs#L382) | +| NFR-P5 | [`core/dr-catalog/src/schema.rs:594`](../../core/dr-catalog/src/schema.rs#L594), [`core/dr-gpu/src/adjust.rs:166`](../../core/dr-gpu/src/adjust.rs#L166), [`core/dr-gpu/src/adjust.rs:2919`](../../core/dr-gpu/src/adjust.rs#L2919), [`core/dr-pipeline/src/detail.rs:1268`](../../core/dr-pipeline/src/detail.rs#L1268), [`core/dr-pipeline/src/detail.rs:667`](../../core/dr-pipeline/src/detail.rs#L667), [`ui/dr-ui/src/library/cells.rs:374`](../../ui/dr-ui/src/library/cells.rs#L374), [`ui/dr-ui/src/library/cells.rs:715`](../../ui/dr-ui/src/library/cells.rs#L715), [`ui/dr-ui/src/library_ui/controller.rs:58`](../../ui/dr-ui/src/library_ui/controller.rs#L58), [`ui/dr-ui/src/library_ui/timeline.rs:382`](../../ui/dr-ui/src/library_ui/timeline.rs#L382) | | NFR-P9 | [`core/dr-catalog/src/backfilled.rs:1`](../../core/dr-catalog/src/backfilled.rs#L1), [`core/dr-catalog/src/schema.rs:947`](../../core/dr-catalog/src/schema.rs#L947), [`core/dr-face/src/references.rs:1`](../../core/dr-face/src/references.rs#L1), [`ui/dr-ui/src/collections_ui/mod.rs:1`](../../ui/dr-ui/src/collections_ui/mod.rs#L1), [`ui/dr-ui/src/duplicates.rs:1`](../../ui/dr-ui/src/duplicates.rs#L1), [`ui/dr-ui/src/duplicates_ui.rs:1`](../../ui/dr-ui/src/duplicates_ui.rs#L1), [`ui/dr-ui/src/executors.rs:136`](../../ui/dr-ui/src/executors.rs#L136), [`ui/dr-ui/src/executors.rs:220`](../../ui/dr-ui/src/executors.rs#L220), [`ui/dr-ui/src/export.rs:1122`](../../ui/dr-ui/src/export.rs#L1122), [`ui/dr-ui/src/library/mod.rs:1`](../../ui/dr-ui/src/library/mod.rs#L1), [`ui/dr-ui/src/library_ui/mod.rs:1`](../../ui/dr-ui/src/library_ui/mod.rs#L1), [`ui/dr-ui/src/net_runtime.rs:48`](../../ui/dr-ui/src/net_runtime.rs#L48), [`ui/dr-ui/src/trash.rs:1`](../../ui/dr-ui/src/trash.rs#L1) | | NFR-PORT-1 | [`core/dr-catalog/src/walk.rs:1`](../../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:272`](../../core/dr-types/src/lib.rs#L272), [`core/dr-types/src/lib.rs:305`](../../core/dr-types/src/lib.rs#L305), [`platform/dr-plat/src/dirs.rs:1`](../../platform/dr-plat/src/dirs.rs#L1), [`platform/dr-plat/src/display.rs:1`](../../platform/dr-plat/src/display.rs#L1), [`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:287`](../../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:344`](../../platform/dr-plat/src/storage.rs#L344), [`platform/dr-plat/src/volumes.rs:1`](../../platform/dr-plat/src/volumes.rs#L1), [`platform/dr-plat/src/volumes.rs:62`](../../platform/dr-plat/src/volumes.rs#L62) | | NFR-PORT-2 | [`core/dr-gpu/src/lib.rs:1`](../../core/dr-gpu/src/lib.rs#L1) | @@ -184,7 +185,7 @@ _None._ | NFR-R6 | [`core/dr-catalog/src/error.rs:1`](../../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/lib.rs:270`](../../core/dr-catalog/src/lib.rs#L270), [`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:1340`](../../core/dr-gpu/src/adjust.rs#L1340), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:116`](../../ui/dr-ui/src/lib.rs#L116), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | +| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1314`](../../core/dr-gpu/src/adjust.rs#L1314), [`core/dr-pipeline/src/history.rs:86`](../../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop/render.rs:561`](../../ui/dr-ui/src/develop/render.rs#L561), [`ui/dr-ui/src/identity_ui.rs:125`](../../ui/dr-ui/src/identity_ui.rs#L125), [`ui/dr-ui/src/lib.rs:116`](../../ui/dr-ui/src/lib.rs#L116), [`ui/dr-ui/src/memory.rs:1`](../../ui/dr-ui/src/memory.rs#L1) | | NFR-RES-2 | [`ui/dr-ui/src/faces.rs:463`](../../ui/dr-ui/src/faces.rs#L463), [`ui/dr-ui/src/library/sweep.rs:243`](../../ui/dr-ui/src/library/sweep.rs#L243), [`ui/dr-ui/src/repairs.rs:1150`](../../ui/dr-ui/src/repairs.rs#L1150), [`ui/dr-ui/src/repairs.rs:1`](../../ui/dr-ui/src/repairs.rs#L1) | | 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:1212`](../../core/dr-catalog/src/schema.rs#L1212), [`core/dr-gpu/src/lib.rs:95`](../../core/dr-gpu/src/lib.rs#L95), [`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:393`](../../core/dr-thumbs/src/lib.rs#L393) | | NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30) | @@ -229,7 +230,7 @@ Defined in `requirements.md` and marked `(post-v1)` on the defining line. Not in ## Not yet tagged -30 of 193 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built. +29 of 193 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
Show untagged requirements @@ -237,7 +238,6 @@ Defined in `requirements.md` and marked `(post-v1)` on the defining line. Not in - FR-CULL-6 - FR-CULL-7 - FR-DEV-3g -- FR-DEV-3j - FR-DSP-2 - FR-INF-2 - FR-INF-3 diff --git a/platform/dr-plat/src/diagnostics.rs b/platform/dr-plat/src/diagnostics.rs index 6b06110..cef31a3 100644 --- a/platform/dr-plat/src/diagnostics.rs +++ b/platform/dr-plat/src/diagnostics.rs @@ -451,7 +451,7 @@ mod tests { use super::*; /// An empty directory of this test's own. The convention elsewhere in the - /// workspace (`dr_decode::base_curve`), and it matters more here: two + /// workspace, and it matters more here: two /// tests sharing a log directory would rotate each other's files. fn a_log_dir(name: &str) -> PathBuf { let dir = std::env::temp_dir().join(format!("darkroom-diagnostics-{name}")); diff --git a/ui/dr-ui/src/decoder_seam.rs b/ui/dr-ui/src/decoder_seam.rs index 33c3de0..d28f241 100644 --- a/ui/dr-ui/src/decoder_seam.rs +++ b/ui/dr-ui/src/decoder_seam.rs @@ -16,8 +16,8 @@ use std::sync::Mutex; use dr_decode::{ - BaseCurve, CfaPattern, CropRect, DecodeError, Decoder, Metadata, Preview, PreviewLocation, - PreviewSize, RawImage, + CfaPattern, CropRect, DecodeError, Decoder, Metadata, Preview, PreviewLocation, PreviewSize, + RawImage, }; use dr_types::Orientation; @@ -121,7 +121,6 @@ impl Decoder for Stub { white_level: 4095, wb_coeffs: [1.0; 4], color_matrix: None, - base_curve: BaseCurve::IDENTITY, crop: CropRect { x: 0, y: 0, diff --git a/ui/dr-ui/src/develop/render.rs b/ui/dr-ui/src/develop/render.rs index 62fb2a2..a261af4 100644 --- a/ui/dr-ui/src/develop/render.rs +++ b/ui/dr-ui/src/develop/render.rs @@ -1475,7 +1475,6 @@ mod tests { white_level: WHITE, wb_coeffs: [1.0, 1.0, 1.0, 1.0], color_matrix: None, - base_curve: dr_decode::BaseCurve::IDENTITY, samples_per_pixel: 1, profile: None, make: String::new(), diff --git a/ui/dr-ui/src/develop/white_balance.rs b/ui/dr-ui/src/develop/white_balance.rs index 1f5f232..4f8cf5d 100644 --- a/ui/dr-ui/src/develop/white_balance.rs +++ b/ui/dr-ui/src/develop/white_balance.rs @@ -230,7 +230,6 @@ mod tests { white_level: 65535, wb_coeffs: [as_shot[0], as_shot[1], as_shot[2], 0.0], color_matrix: Some(cam_to_srgb), - base_curve: dr_decode::BaseCurve::IDENTITY, samples_per_pixel: 3, profile: None, make: String::new(), @@ -304,7 +303,6 @@ mod tests { white_level: 65535, wb_coeffs: [1.0, 1.0, 1.0, 0.0], color_matrix: None, - base_curve: dr_decode::BaseCurve::IDENTITY, samples_per_pixel: 3, profile: None, make: String::new(), @@ -357,7 +355,6 @@ mod tests { white_level: 65535, wb_coeffs: [1.9, 1.0, 1.7, 0.0], color_matrix: None, - base_curve: dr_decode::BaseCurve::IDENTITY, samples_per_pixel: 3, profile: None, make: String::new(),