diff --git a/.gitattributes b/.gitattributes index 4d03e0a..17249bf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ # Model weights live in LFS. # -# `core/dr-segment/models/*.onnx` is ~11 MB of binary that changes wholesale +# `models/**/*.onnx` is tens of MB of binary that changes wholesale # when it changes at all. In ordinary git objects every future revision of it # would be stored in full, in every clone, forever — and the one thing nobody # can do with it is a useful diff. diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index 0945317..db31c3a 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -57,7 +57,7 @@ jobs: # The model, which is in LFS and is not optional. # - # `core/dr-segment/models/*.onnx` is tracked in LFS (.gitattributes), so a + # `models/**/*.onnx` is tracked in LFS (.gitattributes), so a # plain checkout writes a ~130-byte pointer where 11 MB should be, and # `dr-segment`'s build script panics by design rather than embedding a # pointer and failing at inference. That failure reads like a broken build @@ -97,7 +97,7 @@ jobs: git config --local lfs.url \ "https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs" git lfs pull - ls -l core/dr-segment/models/ + ls -lR models/ - name: Cache cargo uses: actions/cache@v4 @@ -174,7 +174,7 @@ jobs: # The model, which is in LFS and is not optional. # - # `core/dr-segment/models/*.onnx` is tracked in LFS (.gitattributes), so a + # `models/**/*.onnx` is tracked in LFS (.gitattributes), so a # plain checkout writes a ~130-byte pointer where 11 MB should be, and # `dr-segment`'s build script panics by design rather than embedding a # pointer and failing at inference. That failure reads like a broken build @@ -214,7 +214,7 @@ jobs: git config --local lfs.url \ "https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs" git lfs pull - ls -l core/dr-segment/models/ + ls -lR models/ - name: Cache cargo uses: actions/cache@v4 diff --git a/apps/darkroom-android/src/lib.rs b/apps/darkroom-android/src/lib.rs index f8c417f..ac0b0ec 100644 --- a/apps/darkroom-android/src/lib.rs +++ b/apps/darkroom-android/src/lib.rs @@ -60,7 +60,7 @@ fn android_main(app: slint::android::AndroidApp) { } // After the data dir and before anything asks whether a model is present. - install_bundled_face_models(&app); + install_bundled_models(&app); // Before `init_with_event_listener`, which takes `app` by value and is the // last moment anything can ask the activity a question. Not an ordering @@ -126,38 +126,65 @@ fn android_main(app: slint::android::AndroidApp) { } } -/// Unpack the face models the APK carries, if it carries any. +/// Unpack the models the APK carries, if it carries any. /// /// # Why Android needs this and no other platform does /// -/// The weights are not a build input and are not in the repository — the -/// InsightFace grant is research-only and incompatible with this project's -/// licence (docs/faces.md §2), so a desktop user fetches them, runs -/// `tools/fix-face-model-shapes.sh` over them, and drops the result into -/// `~/.local/share/darkroom/models/`. **That gesture does not exist on -/// Android.** `internal_data_path` is app-private, `run-as` needs a debuggable -/// build, and there is no picker and no fetch in the app, so a phone had no way -/// to acquire a model at all and face indexing reported itself permanently off. +/// A desktop build reads its models from a path — the account's directory, the +/// shared one, or `$XDG_DATA_DIRS` where a package put them. **Android has no +/// such path.** `internal_data_path` is app-private, `run-as` needs a +/// debuggable build, and an asset inside a package is not a path anything can +/// open (ARCH §6.9), so a phone had no way to reach a model at all. /// -/// So a locally-built APK may carry the pair in `assets/models/`, which -/// `assemble-apk.sh` includes when the tree has them and omits when it does -/// not. Nothing changes about what the repository holds or what a published -/// build could redistribute; this only gives a self-built APK the same route a -/// desktop build has always had. +/// So the APK carries them in `assets/models/` and this copies them out, once, +/// into the same shared directory a desktop install uses. After that every +/// lookup in `dr_ui::library` finds them exactly where it finds a desktop +/// user's. /// -/// Absent assets are the ordinary case, not an error — the same quiet "no model -/// installed" state a fresh desktop install is in. +/// # The two sets are not the same kind of thing +/// +/// **Face weights are absent from the repository by design.** The InsightFace +/// grant is research-only and incompatible with this project's licence +/// (docs/faces.md §2), so a desktop user fetches them, runs +/// `tools/fix-face-model-shapes.sh` over them, and drops the result in. A build +/// that carries none is the ordinary case and face indexing simply stays off. +/// +/// **The scene model is committed** (AGPL, compatible — `models/LICENCE.md`), +/// so a build carrying none means a checkout without `git lfs pull` rather than +/// a deliberate omission. It is still not an error here: the scene tab reports +/// itself unavailable the same way face indexing does, because a photo editor +/// that refuses to start over a missing grading feature is worse than one that +/// starts without it. +/// +/// # Why it is not `include_bytes!` like the instance model +/// +/// Size. The instance model is 11 MB and compiled in; the scene model is 24 MB +/// on top of that, and a 35 MB constant in the binary is paid by every install +/// whether or not the tab is opened. Assets are also *stored* rather than +/// deflated in the APK (see `assemble-apk.sh`), so unpacking is a copy rather +/// than an inflate. #[cfg(target_os = "android")] -fn install_bundled_face_models(app: &slint::android::AndroidApp) { +fn install_bundled_models(app: &slint::android::AndroidApp) { use std::io::Read; - // The **shape-fixed** names, matching what `library::face_models` looks - // for: tract cannot parse either InsightFace graph with its dynamic input - // dimension, so what ships here has already been through - // `tools/fix-face-model-shapes.sh`. - const BUNDLED: [(&std::ffi::CStr, &str); 2] = [ + // The face names are the **shape-fixed** exports, matching what + // `library::face_models` looks for: tract cannot parse either InsightFace + // graph with its dynamic input dimension, so what ships here has already + // been through `tools/fix-face-model-shapes.sh`. + // + // The scene entries are three files rather than one because the graph alone + // decodes to 150 anonymous channels — `library::scene_model` wants the + // vocabulary and the category descriptor beside it, and requires all three + // before it reports the tab available. + const BUNDLED: [(&std::ffi::CStr, &str); 5] = [ (c"models/scrfd_500m_640.onnx", "scrfd_500m_640.onnx"), (c"models/arcface_mbf_b1.onnx", "arcface_mbf_b1.onnx"), + (c"models/yolo26s-sem-ade20k.onnx", "yolo26s-sem-ade20k.onnx"), + ( + c"models/yolo26s-sem-ade20k.classes.json", + "yolo26s-sem-ade20k.classes.json", + ), + (c"models/categories.txt", "categories.txt"), ]; let dir = dr_ui::shared_face_models_dir(); @@ -172,7 +199,7 @@ fn install_bundled_face_models(app: &slint::android::AndroidApp) { continue; } let Some(mut asset) = assets.open(asset_path) else { - log::info!("no bundled {name} in this APK; face indexing stays off"); + log::info!("no bundled {name} in this APK; the feature needing it stays off"); continue; }; let mut bytes = Vec::new(); @@ -185,8 +212,8 @@ fn install_bundled_face_models(app: &slint::android::AndroidApp) { return; } // Written under a temporary name and renamed, because - // `library::face_models` decides face indexing is available on - // `is_file()` alone. A truncated write — the process backgrounded and + // `library::face_models` and `library::scene_model` both decide a + // feature is available on `is_file()` alone. A truncated write — the process backgrounded and // killed mid-copy — would otherwise leave a file that passes that test // and fails inside tract, reported to the user as a broken model rather // than a missing one. diff --git a/core/dr-segment/Cargo.toml b/core/dr-segment/Cargo.toml index ecb0457..0733375 100644 --- a/core/dr-segment/Cargo.toml +++ b/core/dr-segment/Cargo.toml @@ -44,3 +44,13 @@ semantic = ["dep:ort", "dep:ort-tract", "dep:ndarray"] # it must be embedded; a desktop packager pointing at a system model directory, # or a test that only needs the decoder, wants the runtime without the 11 MB. embedded-model = ["semantic"] + +# Compile the *scene* model in too, and off by default where `embedded-model` +# is on. +# +# The asymmetry is its size. At 24 MB it is more than twice the instance model, +# and Android reaches it the way it reaches the face weights — unpacked from +# APK assets at first launch — rather than by carrying it in the binary. This +# feature is for a desktop build with nowhere else to read it from, and for +# tests that want the real graph. +embedded-scene-model = ["semantic"] diff --git a/core/dr-segment/build.rs b/core/dr-segment/build.rs index dcd300e..276ff85 100644 --- a/core/dr-segment/build.rs +++ b/core/dr-segment/build.rs @@ -1,6 +1,6 @@ //! Check the model is a model and not an LFS pointer. //! -//! `models/*.onnx` is stored in Git LFS (see `.gitattributes`). A clone made +//! `models/segment/*.onnx` is stored in Git LFS (see `.gitattributes`). A clone made //! without git-lfs installed, or with `GIT_LFS_SKIP_SMUDGE` set, leaves a //! ~130-byte text pointer at that path instead of the weights. //! @@ -12,7 +12,7 @@ use std::path::Path; -const MODEL: &str = "models/yolo26n-seg.onnx"; +const MODEL: &str = "../../models/segment/yolo26n-seg.onnx"; fn main() { println!("cargo:rerun-if-changed={MODEL}"); diff --git a/core/dr-segment/examples/scene.rs b/core/dr-segment/examples/scene.rs new file mode 100644 index 0000000..d350fd0 --- /dev/null +++ b/core/dr-segment/examples/scene.rs @@ -0,0 +1,174 @@ +//! Run the scene model over a JPEG, time it, and write what it saw. +//! +//! Two jobs in one example because they need the same setup and answering +//! either one alone leaves the other open. +//! +//! **Looking.** Same argument as `detect`: no unit test settles whether the +//! letterbox inverse in `Scene::rasterise` is right, because an off-by-one +//! produces perfectly plausible weights over slightly the wrong pixels. A sky +//! mask laid over the photograph settles it in one glance. +//! +//! **Timing.** Every number quoted while this model was being chosen came off a +//! laptop that was compiling other things at the time, which makes them upper +//! bounds and nothing better. This exists so the figure that ends up in a +//! document came from a quiet machine and can be reproduced on another one. +//! +//! ```sh +//! cargo run -p dr-segment --example scene --release --features embedded-scene-model -- photo.jpg +//! cargo run -p dr-segment --example scene --release -- photo.jpg out 20 \ +//! models/scene/yolo26s-sem-ade20k.onnx +//! ``` +//! +//! Writes `-.ppm` per category — the photograph darkened +//! where the category is absent, so the mask is legible *against the picture it +//! came from* rather than as an abstract grey field. PPM for the same reason +//! the other examples use it: no encoder dependency, and every viewer reads it. +//! +//! Timings are reported as a median over the requested run count, with the +//! first run excluded. That first pass pays for tract's lazy allocation and is +//! not representative of the second image a session decodes. + +use std::time::Instant; + +use dr_segment::scene::SceneModel; + +fn main() { + env_logger::init(); + + let mut args = std::env::args().skip(1); + let Some(path) = args.next() else { + eprintln!( + "usage: scene [out-prefix] [runs] [model.onnx classes.json categories.txt]" + ); + eprintln!(" with --features embedded-scene-model the model arguments may be omitted"); + std::process::exit(2); + }; + let prefix = args.next().unwrap_or_else(|| "scene".into()); + let runs: usize = args + .next() + .and_then(|r| r.parse().ok()) + .unwrap_or(10) + .max(1); + + let (rgb, width, height) = read_jpeg(&path); + println!("{path}: {width}×{height}"); + + let mut model = match (args.next(), args.next(), args.next()) { + (Some(m), Some(c), Some(g)) => { + SceneModel::from_path(m, c, g).expect("could not load the scene model") + } + _ => embedded(), + }; + + // Excluded from the statistics deliberately — see the header. + let warm = Instant::now(); + let scene = model + .analyse(&rgb, width, height) + .expect("inference failed"); + println!("first run: {:?} (allocation included)", warm.elapsed()); + + let mut times: Vec = Vec::with_capacity(runs); + for _ in 0..runs { + let start = Instant::now(); + let _ = model + .analyse(&rgb, width, height) + .expect("inference failed"); + times.push(start.elapsed().as_secs_f64() * 1000.0); + } + times.sort_by(f64::total_cmp); + println!( + "{runs} runs: median {:.0} ms (min {:.0}, max {:.0})", + times[times.len() / 2], + times[0], + times[times.len() - 1], + ); + + let (gw, gh) = scene.grid_size(); + println!("logit grid: {gw}×{gh}"); + println!(); + + // Coverage first and sorted, because on any given photograph most + // categories are absent and the two or three that are not are the whole + // story. + let mut ranked: Vec<(usize, f32)> = (0..scene.categories().len()) + .map(|k| (k, scene.coverage(k))) + .collect(); + ranked.sort_by(|a, b| b.1.total_cmp(&a.1)); + + for (k, coverage) in ranked { + let name = &scene.categories()[k]; + println!("{name:>14} {:5.1}%", coverage * 100.0); + // A category covering essentially nothing produces a black image and a + // file nobody wants; the threshold is what the scene tab would use to + // decide whether to offer a slider at all. + if coverage < 0.005 { + continue; + } + let mask = scene + .rasterise(k, width, height) + .expect("category index came from the same Scene"); + write_overlay(&format!("{prefix}-{name}.ppm"), &rgb, &mask, width, height); + } +} + +#[cfg(feature = "embedded-scene-model")] +fn embedded() -> SceneModel { + SceneModel::embedded().expect("could not load the embedded scene model") +} + +#[cfg(not(feature = "embedded-scene-model"))] +fn embedded() -> SceneModel { + eprintln!( + "no model given, and this build has no embedded one.\n\ + Either pass the three paths, or rebuild with --features embedded-scene-model." + ); + std::process::exit(2); +} + +/// The photograph, dimmed where the category is not. +/// +/// Not a bare greyscale mask: the question being asked is "does this weight +/// land on the sky", and a mask on its own cannot answer it — you have to see +/// the sky underneath. A floor rather than a multiply, so that a region the +/// model gave up on is still visible enough to recognise. +fn write_overlay(path: &str, rgb: &[f32], mask: &[f32], width: usize, height: usize) { + let mut out = String::with_capacity(64); + out.push_str(&format!("P3\n{width} {height}\n255\n")); + let mut bytes = out.into_bytes(); + + for i in 0..width * height { + let w = mask[i].clamp(0.0, 1.0); + let gain = 0.15 + 0.85 * w; + for c in 0..3 { + let v = (rgb[i * 3 + c] * gain * 255.0).clamp(0.0, 255.0) as u8; + bytes.extend_from_slice(v.to_string().as_bytes()); + bytes.push(if c == 2 { b'\n' } else { b' ' }); + } + } + + match std::fs::write(path, bytes) { + Ok(()) => println!(" wrote {path}"), + Err(e) => eprintln!(" could not write {path}: {e}"), + } +} + +/// Decode to the tightly packed `f32` RGB the model wants. +fn read_jpeg(path: &str) -> (Vec, usize, usize) { + let bytes = std::fs::read(path).expect("could not read the photograph"); + let mut decoder = zune_jpeg::JpegDecoder::new(&bytes); + let pixels = decoder.decode().expect("could not decode the photograph"); + let info = decoder.info().expect("decoded image has no dimensions"); + let (width, height) = (info.width as usize, info.height as usize); + + // zune hands back whatever the file had. Three channels is the ordinary + // case; one is a greyscale scan, which is worth handling because a + // black-and-white frame is exactly the kind of thing someone reaches for + // when a colour one looks wrong. + let components = pixels.len() / (width * height); + let rgb = match components { + 3 => pixels.iter().map(|&p| p as f32 / 255.0).collect(), + 1 => pixels.iter().flat_map(|&p| [p as f32 / 255.0; 3]).collect(), + n => panic!("unsupported component count: {n}"), + }; + (rgb, width, height) +} diff --git a/core/dr-segment/models/LICENCE.md b/core/dr-segment/models/LICENCE.md deleted file mode 100644 index 83235e8..0000000 --- a/core/dr-segment/models/LICENCE.md +++ /dev/null @@ -1,54 +0,0 @@ -# Model weights — licensing - -`yolo26n-seg.onnx` is exported from Ultralytics YOLO26n-seg -(`https://huggingface.co/Ultralytics/YOLO26`, `yolo26n-seg.pt`) by -`tools/export-seg-model.sh`. `yolo26n-seg.classes.json` is that checkpoint's -class vocabulary, written out by the same script. - -## The grant - -**Ultralytics releases YOLO under AGPL-3.0**, and the weights carry the same -grant as the framework — the HuggingFace repository declares `agpl-3.0` for the -checkpoints themselves, not merely for the training code. A commercial licence -is offered separately; DarkRoom does not use it and does not need it. - -## What that means for DarkRoom - -DarkRoom is GPL-3.0-or-later. **GPLv3 §13 explicitly permits combination with -AGPL-3.0 code**, so redistributing these weights inside this repository is -allowed — this is *not* the situation the InsightFace "buffalo" weights would -have created, where a non-commercial research grant is simply incompatible with -the project's licence and with F-Droid, Flatpak and Play distribution -(NFR-COMPAT-2, D13). - -The consequence, and it is a real one: **the combined work is effectively -AGPL-3.0.** §13's permission runs one way — the AGPL's §13 network-use condition -attaches to the portion under that licence. For a local-first desktop and -Android photo editor that condition has no practical bite, because there is no -network service offering the combined work to remote users. It would acquire -bite the moment any hosted or server-side rendering appeared, and that is the -thing to remember rather than rediscover. - -This was decided deliberately (D14), not arrived at by accident, and -`docs/segmentation.md` §7 records the reasoning. - -## Class vocabulary — a caveat worth reading - -`docs/segmentation.md` §4 specified YOLO **pretrained on ADE20K**, whose 150 -classes include the *stuff* categories that matter most in photography — sky, -vegetation, water, wall, mountain. - -**No such model exists in usable form.** Checked 2026-08-21: Ultralytics ships -YOLO26-seg trained on **COCO**, whose 80 classes are all *things* — person, -dog, car, bird, potted plant — and the one HuggingFace repository claiming a -YOLO/ADE20K combination (`laxmacl/yolov8-ade20k`) is empty. ADE20K semantic -models do exist, but as SegFormer/OneFormer/MaskFormer transformers, not YOLO. - -So the shipped vocabulary selects **subjects**, not **stuff**. "Select the -person" works; "select the sky" does not come from the model and must come from -the watershed hierarchy instead. That is a narrower arm B than §4 assumed, and -it raises rather than lowers the importance of arm C. - -The loader treats the vocabulary as model metadata rather than compiled-in -knowledge, so adding a stuff-class model later is a file plus a descriptor, not -a code change. diff --git a/core/dr-segment/src/lib.rs b/core/dr-segment/src/lib.rs index 4909f5b..e9a44c1 100644 --- a/core/dr-segment/src/lib.rs +++ b/core/dr-segment/src/lib.rs @@ -28,17 +28,30 @@ //! model is COCO-trained, so it recognises subjects and has no class for sky, //! foliage or wall (`models/LICENCE.md`). Selecting those falls to arm A, //! which never needed a vocabulary to begin with. +//! +//! # And [`scene`], which is not one of the arms +//! +//! The three arms all serve *local* adjustment: they exist so a mask can be +//! snapped to one region of the picture. [`scene`] serves the opposite move — +//! one grade applied to every pixel of a category at once, sky or foliage or +//! water — and reads a second, ADE20K-trained model to do it. It shares this +//! crate because it shares the runtime and the letterbox, not because it is +//! another way of doing the same thing. pub mod distance; pub mod hierarchy; pub mod prior; #[cfg(feature = "semantic")] +pub mod scene; +#[cfg(feature = "semantic")] pub mod semantic; pub use distance::{signed_distance, Falloff, Morphology, Shaped}; pub use hierarchy::{Edge, Merge, MergeTree, RegionField}; pub use prior::{Membership, PriorOptions}; #[cfg(feature = "semantic")] +pub use scene::{Category, Scene, SceneModel}; +#[cfg(feature = "semantic")] pub use semantic::{Instance, SemanticModel, SemanticOptions, Tiling}; /// What can go wrong between an image and a region map. @@ -58,4 +71,10 @@ pub enum SegmentError { /// different model, or a different export of the same one. #[error("model output '{0}' did not have the expected shape")] OutputShape(&'static str), + + /// `models/scene/categories.txt` and the model disagree, or the descriptor + /// is malformed. Its own variant rather than a parse error because every + /// case carries a specific sentence about what to fix. + #[error("category descriptor: {0}")] + CategoryDescriptor(String), } diff --git a/core/dr-segment/src/scene.rs b/core/dr-segment/src/scene.rs new file mode 100644 index 0000000..b5ae215 --- /dev/null +++ b/core/dr-segment/src/scene.rs @@ -0,0 +1,525 @@ +//! Per-category weights over the whole frame — what the scene tab grades. +//! +//! [`semantic`](crate::semantic) answers "what objects are in this picture, and +//! which pixels are each one". This module answers a different question: "how +//! much of each pixel is sky". They are not the same question and they do not +//! want the same model. +//! +//! # Why a second model rather than a second reading of the first +//! +//! The instance model is COCO-trained, and COCO is eighty classes of *things*. +//! There is no class for sky, none for foliage, none for water — the categories +//! a landscape is mostly made of. That gap is recorded in `models/LICENCE.md` +//! and it is why the scene model exists: ADE20K's 150 classes are a scene +//! parse, *stuff* included. +//! +//! Going the other way is just as impossible. A semantic model merges every +//! pixel of a class into one region, so it cannot tell three people apart, and +//! telling three people apart is exactly what clicking a subject needs. Neither +//! model substitutes for the other, which is why both ship. +//! +//! # The partition of unity, and why it is the point +//! +//! [`Scene::weight`] is not a mask per category that each independently says +//! yes or no. It is a *partition*: at every pixel the listed categories plus +//! the unlisted remainder sum to one, because they come from one softmax over +//! all 150 channels, summed within each category. +//! +//! That property is what makes feathering safe. Feather a hard label map +//! outward from sky and outward from vegetation and the boundary band belongs +//! to both, so a `+20` on sky and a `−10` on vegetation both land there and +//! every horizon acquires a visible seam. Feather a partition of unity and the +//! weights still sum to one — the band gets a blend of the two grades, which is +//! what a photographer drawing that boundary by hand would have painted. +//! +//! # Resolution, stated plainly +//! +//! The graph's logits are `[1, 150, 80, 80]`: an eighth of the input edge, and +//! that is the real spatial resolution of everything here. The stock export +//! ends with a `Resize` to 640×640 and an `ArgMax`, and +//! `tools/export-seg-model.sh` cuts both — the upsample adds no information and +//! the argmax destroys the per-class scores this module needs. [`Scene`] keeps +//! the native grid and resamples on demand ([`Scene::rasterise`]) so that the +//! coarseness is visible in the type rather than hidden behind an early +//! upsample. +//! +//! Practically: a graduated grade over sky or water is unbothered by 80×80. A +//! hard edge — a rooftop against sky at 100% zoom — will show it, and no +//! feather setting invents detail the model never had. +//! +//! # Cost +//! +//! One inference per image, on the same background precompute as the instance +//! pass and never on the frame path (ARCH §6.1). The scene tab's sliders read +//! [`Scene`] and re-run nothing. + +use std::sync::Arc; + +use ndarray::ArrayView3; + +use crate::semantic::{install_backend, Letterbox, Window}; +use crate::SegmentError; + +/// Classes in the ADE20K vocabulary the scene model was trained on. +/// +/// Checked against the graph's output rather than trusted: a re-export against +/// a different dataset would otherwise be decoded as though its channels meant +/// what these ones mean, which produces plausible weights for the wrong thing. +pub const CLASSES: usize = 150; + +/// Logit grid stride — the graph's output is this many times coarser than its +/// input edge, giving the 80×80 grid at [`crate::semantic::INPUT_EDGE`] 640. +const GRID_STRIDE: usize = 8; + +/// One photographic category and the ADE20K classes it marginalises over. +#[derive(Debug, Clone)] +pub struct Category { + pub name: Arc, + /// Indices into the model's vocabulary. Resolved from names at load, so a + /// descriptor cannot silently drift out of step with a re-exported model. + pub classes: Vec, +} + +/// The scene model, and the categories it has been told to report. +pub struct SceneModel { + session: ort::session::Session, + categories: Vec, +} + +/// The weights that ship in `models/scene/` (AGPL — see `models/LICENCE.md`). +/// +/// Behind its own feature and **off by default**: this graph is 24 MB, where +/// the instance model is 11, and Android carries it as an unpacked asset +/// rather than inside the binary (`install_bundled_models`). A desktop build +/// or a test that wants it compiled in opts in. +#[cfg(feature = "embedded-scene-model")] +const EMBEDDED_MODEL: &[u8] = include_bytes!("../../../models/scene/yolo26s-sem-ade20k.onnx"); +#[cfg(feature = "embedded-scene-model")] +const EMBEDDED_CLASSES: &str = + include_str!("../../../models/scene/yolo26s-sem-ade20k.classes.json"); +#[cfg(feature = "embedded-scene-model")] +const EMBEDDED_CATEGORIES: &str = include_str!("../../../models/scene/categories.txt"); + +impl SceneModel { + /// Load the scene model compiled into the binary. + #[cfg(feature = "embedded-scene-model")] + pub fn embedded() -> Result { + let classes = crate::semantic::parse_classes(EMBEDDED_CLASSES); + let categories = parse_categories(EMBEDDED_CATEGORIES, &classes)?; + Self::from_bytes(EMBEDDED_MODEL, categories) + } + + /// Load from files on disk: the graph, its vocabulary, and the category + /// descriptor that groups the vocabulary into what the scene tab shows. + /// + /// Three paths rather than one directory because a packager may put the + /// weights somewhere the descriptor is not, and because a caller + /// experimenting with a different grouping should not have to move a 24 MB + /// file to try it. + pub fn from_path( + model: impl AsRef, + classes: impl AsRef, + categories: impl AsRef, + ) -> Result { + let bytes = std::fs::read(model).map_err(SegmentError::ModelRead)?; + let classes = std::fs::read_to_string(classes).map_err(SegmentError::ModelRead)?; + let categories = std::fs::read_to_string(categories).map_err(SegmentError::ModelRead)?; + let classes = crate::semantic::parse_classes(&classes); + let categories = parse_categories(&categories, &classes)?; + Self::from_bytes(&bytes, categories) + } + + pub fn from_bytes(bytes: &[u8], categories: Vec) -> Result { + install_backend(); + + let session = ort::session::Session::builder() + .map_err(SegmentError::Inference)? + .commit_from_memory(bytes) + .map_err(SegmentError::Inference)?; + + Ok(Self { + session, + categories, + }) + } + + pub fn categories(&self) -> &[Category] { + &self.categories + } + + /// Weigh every category over one image. + /// + /// `rgb` is tightly packed `f32` RGB in `0.0..=1.0`, row-major — the same + /// proxy buffer the instance pass reads, so the two describe one picture. + /// + /// One inference over the whole frame. There is no tiling counterpart to + /// [`crate::semantic::Tiling`] here on purpose: tiling buys resolution on a + /// small subject, and no category in the descriptor is a small subject. + pub fn analyse( + &mut self, + rgb: &[f32], + width: usize, + height: usize, + ) -> Result { + if rgb.len() != width * height * 3 { + return Err(SegmentError::ImageShape { + expected: width * height * 3, + got: rgb.len(), + }); + } + + // Split the borrow: `run` needs the session mutably while + // `marginalise` needs the categories, and going through `self` for + // both at once is what the borrow checker objects to. + let Self { + session, + categories, + } = self; + + let window = Window { + x: 0.0, + y: 0.0, + w: width as f32, + h: height as f32, + }; + let letterbox = Letterbox::fit(window.w, window.h); + let input = letterbox.sample(rgb, width, height, &window); + + let outputs = session + .run(ort::inputs![ + ort::value::Tensor::from_array(input).map_err(SegmentError::Inference)? + ]) + .map_err(SegmentError::Inference)?; + + let (shape, logits) = outputs[0] + .try_extract_tensor::() + .map_err(|_| SegmentError::OutputShape("logits"))?; + + // `[1, 150, gh, gw]`. Checked rather than assumed: the stock export + // ends in an ArgMax and returns `[1, 640, 640]` u8 instead, and that + // mistake should read as "wrong model" rather than as garbled output. + if shape.len() != 4 || shape[0] != 1 || shape[1] as usize != CLASSES { + return Err(SegmentError::OutputShape("logits")); + } + let (gh, gw) = (shape[2] as usize, shape[3] as usize); + let logits = ArrayView3::from_shape((CLASSES, gh, gw), &logits[..CLASSES * gh * gw]) + .map_err(|_| SegmentError::OutputShape("logits"))?; + + Ok(marginalise(categories, logits, gw, gh, letterbox, window)) + } +} + +/// Softmax over the vocabulary, then sum within each category. +/// +/// The summation is what makes the result a partition: softmax gives 150 +/// numbers summing to one, and grouping them cannot change that total. The +/// remainder — every class no category claims — is simply not reported, which +/// is why the listed weights sum to *at most* one rather than to one. +/// +/// Free rather than a method so it can be called while the session is borrowed +/// mutably, and so the tests can reach it without a graph. +fn marginalise( + categories: &[Category], + logits: ArrayView3, + gw: usize, + gh: usize, + letterbox: Letterbox, + window: Window, +) -> Scene { + let cells = gw * gh; + let mut weight = vec![0.0f32; categories.len() * cells]; + let mut probability = vec![0.0f32; CLASSES]; + + for cell in 0..cells { + let (y, x) = (cell / gw, cell % gw); + + // Shift by the maximum before exponentiating. The logits here are + // small enough that the naive form would not actually overflow, + // but a re-export with a hotter head would, and the cost is one + // pass over 150 floats. + let mut peak = f32::NEG_INFINITY; + for c in 0..CLASSES { + peak = peak.max(logits[[c, y, x]]); + } + let mut total = 0.0f32; + for c in 0..CLASSES { + let p = (logits[[c, y, x]] - peak).exp(); + probability[c] = p; + total += p; + } + let norm = if total > 0.0 { 1.0 / total } else { 0.0 }; + + for (k, category) in categories.iter().enumerate() { + let mut sum = 0.0f32; + for &class in &category.classes { + sum += probability[class as usize]; + } + weight[k * cells + cell] = sum * norm; + } + } + + Scene { + names: categories.iter().map(|c| c.name.clone()).collect(), + weight, + grid_width: gw, + grid_height: gh, + letterbox, + window, + } +} + +/// One image's category weights, at the model's own resolution. +#[derive(Debug, Clone)] +pub struct Scene { + names: Vec>, + /// `[category][y * grid_width + x]`, each in `0.0..=1.0`, and across + /// categories summing to at most one at every cell. + weight: Vec, + grid_width: usize, + grid_height: usize, + letterbox: Letterbox, + window: Window, +} + +impl Scene { + pub fn categories(&self) -> &[Arc] { + &self.names + } + + pub fn grid_size(&self) -> (usize, usize) { + (self.grid_width, self.grid_height) + } + + /// One category's weights over the logit grid. + pub fn weight(&self, category: usize) -> Option<&[f32]> { + let cells = self.grid_width * self.grid_height; + self.weight.get(category * cells..(category + 1) * cells) + } + + pub fn index_of(&self, name: &str) -> Option { + self.names.iter().position(|n| &**n == name) + } + + /// How much of the frame this category covers, `0.0..=1.0`. + /// + /// Cheap, and the scene tab needs it: a category weighing essentially + /// nothing should not be offered a slider, because a control that does + /// nothing when moved is worse than an absent one. + pub fn coverage(&self, category: usize) -> f32 { + match self.weight(category) { + Some(w) if !w.is_empty() => w.iter().sum::() / w.len() as f32, + _ => 0.0, + } + } + + /// Resample one category to source-image resolution. + /// + /// Bilinear over the logit grid. This does not add detail and is not meant + /// to — see the module header on resolution — it exists because a mask has + /// to be the size of the picture before it can weight an adjustment, and + /// doing the resample here keeps the one correct letterbox inverse in one + /// place. + pub fn rasterise(&self, category: usize, width: usize, height: usize) -> Option> { + let grid = self.weight(category)?; + let mut out = vec![0.0f32; width * height]; + + for y in 0..height { + for x in 0..width { + let (gx, gy) = self.letterbox.to_grid( + x as f32 + 0.5, + y as f32 + 0.5, + &self.window, + GRID_STRIDE as f32, + ); + // Half-cell shift: `to_grid` lands on the grid's coordinate + // space, where a cell's *centre* is at its index plus a half. + let (gx, gy) = (gx - 0.5, gy - 0.5); + let x0 = gx.floor(); + let y0 = gy.floor(); + let (fx, fy) = (gx - x0, gy - y0); + let x0 = (x0 as isize).clamp(0, self.grid_width as isize - 1) as usize; + let y0 = (y0 as isize).clamp(0, self.grid_height as isize - 1) as usize; + let x1 = (x0 + 1).min(self.grid_width - 1); + let y1 = (y0 + 1).min(self.grid_height - 1); + + let at = |gx: usize, gy: usize| grid[gy * self.grid_width + gx]; + let top = at(x0, y0) * (1.0 - fx) + at(x1, y0) * fx; + let bot = at(x0, y1) * (1.0 - fx) + at(x1, y1) * fx; + out[y * width + x] = top * (1.0 - fy) + bot * fy; + } + } + + Some(out) + } +} + +/// Read `models/scene/categories.txt`, resolving class names to indices. +/// +/// Hand-written rather than generated, unlike the `.classes.json` beside it, +/// which is why the format is line-oriented with comments: the *reasoning* for +/// a grouping belongs next to the grouping, and JSON has nowhere to put it. +pub fn parse_categories(text: &str, classes: &[Arc]) -> Result, SegmentError> { + let mut out: Vec = Vec::new(); + let mut claimed: Vec>> = vec![None; classes.len()]; + + for line in text.lines() { + let line = line.split('#').next().unwrap_or("").trim(); + if line.is_empty() { + continue; + } + let Some((name, members)) = line.split_once('=') else { + return Err(SegmentError::CategoryDescriptor(format!( + "line is not `name = class, class, ...`: {line}" + ))); + }; + let name: Arc = name.trim().into(); + + let mut indices = Vec::new(); + for member in members.split(',') { + let member = member.trim(); + if member.is_empty() { + continue; + } + let Some(index) = classes.iter().position(|c| &**c == member) else { + return Err(SegmentError::CategoryDescriptor(format!( + "category '{name}' names class '{member}', which this model does not have" + ))); + }; + // Two categories sharing a class would each count its probability, + // so the weights would exceed one where it appears and the + // partition — the whole reason for summing after a softmax — would + // be quietly untrue. + if let Some(owner) = &claimed[index] { + return Err(SegmentError::CategoryDescriptor(format!( + "class '{member}' is claimed by both '{owner}' and '{name}'" + ))); + } + claimed[index] = Some(name.clone()); + indices.push(index as u16); + } + + if indices.is_empty() { + return Err(SegmentError::CategoryDescriptor(format!( + "category '{name}' lists no classes" + ))); + } + out.push(Category { + name, + classes: indices, + }); + } + + if out.is_empty() { + return Err(SegmentError::CategoryDescriptor( + "descriptor defines no categories".into(), + )); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn vocabulary() -> Vec> { + ["sky", "tree", "grass", "person", "wall"] + .iter() + .map(|s| Arc::from(*s)) + .collect() + } + + #[test] + fn descriptor_resolves_names_to_indices() { + let v = vocabulary(); + let cats = parse_categories("sky = sky\nvegetation = tree, grass\n", &v).unwrap(); + assert_eq!(cats.len(), 2); + assert_eq!(&*cats[0].name, "sky"); + assert_eq!(cats[0].classes, vec![0]); + assert_eq!(cats[1].classes, vec![1, 2]); + } + + #[test] + fn comments_and_blank_lines_are_ignored() { + let v = vocabulary(); + let cats = parse_categories("# a note\n\nsky = sky # trailing\n", &v).unwrap(); + assert_eq!(cats.len(), 1); + assert_eq!(cats[0].classes, vec![0]); + } + + #[test] + fn an_unknown_class_is_refused() { + let v = vocabulary(); + let e = parse_categories("sky = cloud\n", &v).unwrap_err(); + assert!(format!("{e}").contains("cloud"), "{e}"); + } + + /// The partition is the module's one load-bearing property, so the + /// descriptor is not allowed to break it before inference even runs. + #[test] + fn a_class_in_two_categories_is_refused() { + let v = vocabulary(); + let e = parse_categories("a = tree\nb = grass, tree\n", &v).unwrap_err(); + assert!(format!("{e}").contains("claimed by both"), "{e}"); + } + + #[test] + fn the_shipped_descriptor_matches_the_shipped_vocabulary() { + let classes = crate::semantic::parse_classes(include_str!( + "../../../models/scene/yolo26s-sem-ade20k.classes.json" + )); + assert_eq!(classes.len(), CLASSES); + let cats = parse_categories( + include_str!("../../../models/scene/categories.txt"), + &classes, + ) + .expect("shipped descriptor must load against the shipped vocabulary"); + assert!(cats.iter().any(|c| &*c.name == "sky")); + assert!(cats.iter().any(|c| &*c.name == "vegetation")); + } + + /// Softmax then group: the reported weights must never exceed one, and + /// must equal one exactly when the categories name every class. + #[test] + fn marginalising_preserves_the_partition() { + let classes: Vec> = vocabulary(); + let cats = parse_categories( + "sky = sky\nvegetation = tree, grass\nrest = person, wall\n", + &classes, + ) + .unwrap(); + + // Hand-rolled rather than run through a graph: this test is about the + // arithmetic, and a model would only make it slower and less certain. + let (gw, gh) = (2usize, 2usize); + let mut logits = vec![0.0f32; classes.len() * gw * gh]; + for (i, v) in logits.iter_mut().enumerate() { + *v = (i % 7) as f32 * 0.3; + } + let view = ArrayView3::from_shape((classes.len(), gh, gw), &logits).unwrap(); + + // `marginalise` is a method for access to `self.categories`; build the + // smallest thing that owns them rather than a session. + let cells = gw * gh; + let mut weight = vec![0.0f32; cats.len() * cells]; + for cell in 0..cells { + let (y, x) = (cell / gw, cell % gw); + let peak = (0..classes.len()).fold(f32::NEG_INFINITY, |m, c| m.max(view[[c, y, x]])); + let p: Vec = (0..classes.len()) + .map(|c| (view[[c, y, x]] - peak).exp()) + .collect(); + let total: f32 = p.iter().sum(); + for (k, category) in cats.iter().enumerate() { + let s: f32 = category.classes.iter().map(|&c| p[c as usize]).sum(); + weight[k * cells + cell] = s / total; + } + } + + for cell in 0..cells { + let sum: f32 = (0..cats.len()).map(|k| weight[k * cells + cell]).sum(); + assert!( + (sum - 1.0).abs() < 1e-5, + "categories covering every class must sum to 1, got {sum}" + ); + } + } +} diff --git a/core/dr-segment/src/semantic.rs b/core/dr-segment/src/semantic.rs index d01865a..c47179c 100644 --- a/core/dr-segment/src/semantic.rs +++ b/core/dr-segment/src/semantic.rs @@ -20,7 +20,7 @@ //! behaviour, and it is worth being glad of rather than working around. //! //! So arm B here contributes *subjects*, and the watershed contributes -//! everything else. See `models/LICENCE.md` for why no ADE20K variant is +//! everything else. See `models/LICENCE.md` at the repository root for why no ADE20K variant is //! shipped instead. //! //! # Cost, and where it may run @@ -198,15 +198,15 @@ pub struct SemanticModel { classes: Vec>, } -/// The weights that ship with this crate (`models/`, AGPL — see LICENCE.md). +/// The weights, from the repository-root `models/segment/` (AGPL — see `models/LICENCE.md`). /// /// Embedded rather than read from a path because Android hands the app no /// filesystem location to read from (ARCH §6.9) — the same reasoning that has /// the Lensfun database shipping inside its crate. #[cfg(feature = "embedded-model")] -const EMBEDDED_MODEL: &[u8] = include_bytes!("../models/yolo26n-seg.onnx"); +const EMBEDDED_MODEL: &[u8] = include_bytes!("../../../models/segment/yolo26n-seg.onnx"); #[cfg(feature = "embedded-model")] -const EMBEDDED_CLASSES: &str = include_str!("../models/yolo26n-seg.classes.json"); +const EMBEDDED_CLASSES: &str = include_str!("../../../models/segment/yolo26n-seg.classes.json"); impl SemanticModel { /// Load the model that ships with this crate. @@ -446,16 +446,16 @@ fn decode( /// A source-space rectangle fed through one inference. #[derive(Debug, Clone, Copy)] -struct Window { - x: f32, - y: f32, - w: f32, - h: f32, +pub(crate) struct Window { + pub(crate) x: f32, + pub(crate) y: f32, + pub(crate) w: f32, + pub(crate) h: f32, } /// The scale-and-pad that fits an arbitrary rectangle into the square input. #[derive(Debug, Clone, Copy)] -struct Letterbox { +pub(crate) struct Letterbox { /// Input pixels per source pixel. scale: f32, pad_x: f32, @@ -463,7 +463,7 @@ struct Letterbox { } impl Letterbox { - fn fit(w: f32, h: f32) -> Self { + pub(crate) fn fit(w: f32, h: f32) -> Self { let scale = (INPUT_EDGE as f32 / w).min(INPUT_EDGE as f32 / h); Self { scale, @@ -477,7 +477,13 @@ impl Letterbox { /// Bilinear, and grey (`0.5`) in the padding — the value the network sees /// least as an edge, where black would draw a hard border across the frame /// and invite a detection along it. - fn sample(&self, rgb: &[f32], width: usize, height: usize, window: &Window) -> Array4 { + pub(crate) fn sample( + &self, + rgb: &[f32], + width: usize, + height: usize, + window: &Window, + ) -> Array4 { let mut input = Array4::::from_elem((1, 3, INPUT_EDGE, INPUT_EDGE), 0.5); for iy in 0..INPUT_EDGE { @@ -519,12 +525,23 @@ impl Letterbox { ) } + /// Source pixel to the coordinates of an output grid `stride` times + /// coarser than the graph's input. + /// + /// Every dense output this crate reads is some even fraction of the input + /// edge — YOLO's mask prototypes at a quarter, the scene model's logits at + /// an eighth — and they all sit inside the same letterboxed square, so the + /// mapping differs only in that divisor. + pub(crate) fn to_grid(self, sx: f32, sy: f32, w: &Window, stride: f32) -> (f32, f32) { + ( + ((sx - w.x) * self.scale + self.pad_x) / stride, + ((sy - w.y) * self.scale + self.pad_y) / stride, + ) + } + /// Source pixel to prototype-grid coordinates. fn to_proto(self, sx: f32, sy: f32, w: &Window) -> (f32, f32) { - ( - ((sx - w.x) * self.scale + self.pad_x) / PROTO_STRIDE as f32, - ((sy - w.y) * self.scale + self.pad_y) / PROTO_STRIDE as f32, - ) + self.to_grid(sx, sy, w, PROTO_STRIDE as f32) } } @@ -648,7 +665,7 @@ fn steps(extent: f32, edge: f32, stride: f32) -> usize { } /// Point `ort` at tract, exactly once per process. -fn install_backend() { +pub(crate) fn install_backend() { use std::sync::Once; static ONCE: Once = Once::new(); ONCE.call_once(|| { diff --git a/docker/android/assemble-apk.sh b/docker/android/assemble-apk.sh index 6e11e09..351facd 100755 --- a/docker/android/assemble-apk.sh +++ b/docker/android/assemble-apk.sh @@ -255,25 +255,37 @@ fi cp "${SO}" "${OUT}/staging/lib/${ABI}/libdarkroom.so" cp "${DEX}" "${OUT}/staging/classes.dex" -# The face models. Android has no other route to one — app-private storage is -# not user-reachable and the in-app fetch is unbuilt (docs/faces.md §2.2a) — so +# The models. Android has no other route to one — app-private storage is not +# user-reachable and the in-app fetch is unbuilt (docs/faces.md §2.2a) — so # they go in the APK and `android_main` unpacks them on first launch. The -# source is `models/face/`, shared with the Arch package rather than living -# under this one platform's directory. +# sources are `models/face/` and `models/scene/`, shared with the Arch package +# rather than living under this one platform's directory. +# +# Two directories, and they are not the same kind of thing. The face weights +# are absent from most checkouts by design (research-only grant), so finding +# none is ordinary. The scene model is committed, so finding none means a +# broken checkout — but this script still only warns, because the failure it +# would otherwise cause is at APK build time on a machine that may legitimately +# be building the face-less variant. # # Through the staging directory rather than aapt2's `-A`: the .so and the dex # already go in with `zip` below, and one mechanism for "extra files in the # APK" is easier to follow than two. -ASSETS="${REPO}/models/face" +# # Cleared first: a previous run that died between staging and cleanup would # otherwise leave models in the APK that are no longer in the tree. rm -rf "${OUT}/staging/assets" -if compgen -G "${ASSETS}/*.onnx" >/dev/null; then +mkdir -p "${OUT}/staging/assets/models" +_bundled="" +for _dir in face scene; do + ASSETS="${REPO}/models/${_dir}" + compgen -G "${ASSETS}/*.onnx" >/dev/null || continue # An LFS pointer is ~130 bytes and looks exactly like a model to `cp`. Left # unchecked it reaches the device and fails inside tract, which reports a # broken graph rather than a clone that needs `git lfs pull`. Same guard # dr-segment's build script applies to yolo26n-seg.onnx, and the same - # reason. + # reason. Only the weights are checked: the vocabulary and the category + # descriptor beside them are legitimately a few kilobytes. for m in "${ASSETS}"/*.onnx; do if [[ "$(stat -c%s "${m}")" -lt 100000 ]]; then echo "error: $(basename "${m}") is $(stat -c%s "${m}") bytes — an LFS pointer, not a model." >&2 @@ -281,11 +293,21 @@ if compgen -G "${ASSETS}/*.onnx" >/dev/null; then exit 1 fi done - mkdir -p "${OUT}/staging/assets/models" - cp "${ASSETS}"/*.onnx "${OUT}/staging/assets/models/" - echo " assets: $(ls "${ASSETS}" | grep '\.onnx$' | tr '\n' ' ')" + # The scene model is three files: the graph, its vocabulary, and the + # category descriptor. All three are needed to decode anything, so they + # travel together; README.md is documentation and stays out of the APK. + for f in "${ASSETS}"/*; do + case "$(basename "${f}")" in + README.md) continue ;; + esac + cp "${f}" "${OUT}/staging/assets/models/" + _bundled="${_bundled} $(basename "${f}")" + done +done +if [[ -n "${_bundled}" ]]; then + echo " assets:${_bundled}" else - echo " assets: no face models found (face indexing will be off on the device)" + echo " assets: no models found (face indexing and the scene tab will be off on the device)" fi # -0 "" stores the .so without compression so Android can mmap it directly diff --git a/docs/segmentation.md b/docs/segmentation.md index 8d440b2..6381e84 100644 --- a/docs/segmentation.md +++ b/docs/segmentation.md @@ -435,3 +435,69 @@ dependency detail (`core/dr-segment/models/LICENCE.md`). generalises: `ort` + `ort-tract` gives ONNX inference in pure Rust, so the face pipeline of §3.9.1 needs no C dependency either. The *model licensing* half of D13 is untouched — the InsightFace weights are still non-commercial and still unusable here. + +--- + +## 16. The scene model — per-category grades + +Added 2026-08-30, after §4's premise stopped being true. + +### What changed + +§4 specified a semantic model pretrained on ADE20K, whose 150 classes include the *stuff* categories +photography cares about. §13 recorded that no such model existed in usable form and that arm B would +therefore contribute subjects only, which made "select the sky" arm A's problem. Re-checked +2026-08-30: **Ultralytics now ships a `semantic` task with ADE20K checkpoints** +(`docs.ultralytics.com/tasks/semantic`). `yolo26s-sem-ade20k` is in `models/scene/`. + +### It is an addition, not a correction to arm B + +The instance model stays exactly where it was, and the reason is the one §13 already gave and was +right about: a semantic model merges every pixel of a class into one region, so it cannot separate +two people, and separating two people is what clicking a subject requires. Swapping arm B for this +would regress the primary interaction to fix a secondary one. + +So the two divide by *what the user is doing*, not by which is better: + +| | `models/segment/` (COCO instances) | `models/scene/` (ADE20K semantics) | +|---|---|---| +| Question | which pixels are *that* dog | how much of this pixel is sky | +| Granularity | per instance | per category, whole frame | +| Drives | local adjustments, subject selection | the scene tab's per-category sliders | +| Vocabulary | 80 things | 150 classes, stuff included | + +### The export is truncated, and both reasons matter + +Ultralytics ends the graph with `Resize → ArgMax → Cast`, returning a `[1, 640, 640]` u8 label map. +`tools/export-seg-model.sh` cuts that tail and ships the classifier's `[1, 150, 80, 80]` f32 logits. + +**Cost.** The `Resize` materialises 150 × 640 × 640 × f32 — 246 MB — and the `ArgMax` then reduces +across the channel axis, striding 409,600 elements per comparison. Measured under load it was +roughly four fifths of total runtime, spent on work the application discards. + +**Softness, which is the more important one.** `ArgMax` destroys the per-class scores, and the whole +design of the scene tab rests on keeping them. Softmax over the 150 channels, summed within each +category, produces per-category weights that sum to one at every pixel — a partition of unity. +Feathering that cannot double-grade a boundary. Feathering *hard labels* outward from two adjacent +categories paints both grades into the overlap, and every horizon in the frame acquires a seam. + +### The resolution is 80×80, and no setting changes that + +The discarded upsample was never information. `Scene` keeps the native grid and resamples on demand, +so the coarseness is visible in the type rather than hidden. A graduated grade over sky or water is +untroubled by it; a rooftop against sky at 100% zoom will show it. This is the constraint most likely +to decide whether the tab feels good, and it is not addressable by choosing a larger checkpoint — +`yolo26n-sem` and `yolo26s-sem` have the same output grid. + +### Licence + +Unchanged. Same AGPL-3.0 grant as the instance model, same GPLv3 §13 permission, same consequence +already accepted in D14 — so this needed no new licence decision, which is most of why it was cheap. +See `models/LICENCE.md`. + +### Measurement + +Timings taken while this was chosen came off a laptop compiling other things and are upper bounds +only. `cargo run -p dr-segment --example scene --release --features embedded-scene-model` reports a +median over N runs with the first excluded; a number worth quoting should come from that, on an idle +machine. diff --git a/docs/traceability.md b/docs/traceability.md index 8bdaf40..b4b2291 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -9,7 +9,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 310 | +| Source files scanned | 312 | | TRACES tags found | 1013 | | Requirements defined | 179 | | Requirements covered | 122 | @@ -34,62 +34,62 @@ _None._ | ID | Tagged in | |---|---| | FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:109`](../core/dr-catalog/src/walk.rs#L109), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201), [`core/dr-types/src/lib.rs:270`](../core/dr-types/src/lib.rs#L270), [`core/dr-types/src/lib.rs:303`](../core/dr-types/src/lib.rs#L303), [`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), [`tools/traceability/src/lib.rs:481`](../tools/traceability/src/lib.rs#L481), [`tools/traceability/src/lib.rs:513`](../tools/traceability/src/lib.rs#L513), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1) | -| FR-CAT-10 | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`platform/dr-plat/src/storage.rs:287`](../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:586`](../platform/dr-plat/src/storage.rs#L586), [`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), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1221`](../ui/dr-ui/src/lib.rs#L1221), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1047`](../ui/dr-ui/ui/library.slint#L1047), [`ui/dr-ui/ui/library.slint:1226`](../ui/dr-ui/ui/library.slint#L1226), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879) | -| FR-CAT-11 | [`core/dr-catalog/src/dedup.rs:1`](../core/dr-catalog/src/dedup.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:392`](../core/dr-ingest/src/lib.rs#L392), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1221`](../ui/dr-ui/src/lib.rs#L1221), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:2491`](../ui/dr-ui/src/library.rs#L2491), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | +| FR-CAT-10 | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`platform/dr-plat/src/storage.rs:287`](../platform/dr-plat/src/storage.rs#L287), [`platform/dr-plat/src/storage.rs:586`](../platform/dr-plat/src/storage.rs#L586), [`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), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1230`](../ui/dr-ui/src/lib.rs#L1230), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5), [`ui/dr-ui/ui/library.slint:1047`](../ui/dr-ui/ui/library.slint#L1047), [`ui/dr-ui/ui/library.slint:1226`](../ui/dr-ui/ui/library.slint#L1226), [`ui/dr-ui/ui/library.slint:879`](../ui/dr-ui/ui/library.slint#L879) | +| FR-CAT-11 | [`core/dr-catalog/src/dedup.rs:1`](../core/dr-catalog/src/dedup.rs#L1), [`core/dr-ingest/src/lib.rs:1`](../core/dr-ingest/src/lib.rs#L1), [`core/dr-ingest/src/lib.rs:392`](../core/dr-ingest/src/lib.rs#L392), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1230`](../ui/dr-ui/src/lib.rs#L1230), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:2491`](../ui/dr-ui/src/library.rs#L2491), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | | FR-CAT-12 | [`core/dr-pipeline/src/sidecar.rs:119`](../core/dr-pipeline/src/sidecar.rs#L119) | | FR-CAT-13 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1) | -| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:720`](../core/dr-catalog/src/schema.rs#L720), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:457`](../core/dr-sync-nextcloud/src/lib.rs#L457), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:563`](../core/dr-sync/src/scan.rs#L563), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1265`](../ui/dr-ui/src/collections_ui.rs#L1265), [`ui/dr-ui/src/collections_ui.rs:2164`](../ui/dr-ui/src/collections_ui.rs#L2164), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:192`](../ui/dr-ui/src/library.rs#L192), [`ui/dr-ui/src/library.rs:241`](../ui/dr-ui/src/library.rs#L241), [`ui/dr-ui/src/library.rs:3992`](../ui/dr-ui/src/library.rs#L3992), [`ui/dr-ui/src/library.rs:4026`](../ui/dr-ui/src/library.rs#L4026), [`ui/dr-ui/src/library_ui.rs:190`](../ui/dr-ui/src/library_ui.rs#L190), [`ui/dr-ui/src/library_ui.rs:778`](../ui/dr-ui/src/library_ui.rs#L778), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) | +| FR-CAT-15 | [`core/dr-catalog/src/schema.rs:720`](../core/dr-catalog/src/schema.rs#L720), [`core/dr-catalog/src/trash.rs:1`](../core/dr-catalog/src/trash.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:457`](../core/dr-sync-nextcloud/src/lib.rs#L457), [`core/dr-sync/src/lib.rs:135`](../core/dr-sync/src/lib.rs#L135), [`core/dr-sync/src/scan.rs:563`](../core/dr-sync/src/scan.rs#L563), [`core/dr-sync/src/scan.rs:57`](../core/dr-sync/src/scan.rs#L57), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/collections_ui.rs:1265`](../ui/dr-ui/src/collections_ui.rs#L1265), [`ui/dr-ui/src/collections_ui.rs:2164`](../ui/dr-ui/src/collections_ui.rs#L2164), [`ui/dr-ui/src/library.rs:175`](../ui/dr-ui/src/library.rs#L175), [`ui/dr-ui/src/library.rs:192`](../ui/dr-ui/src/library.rs#L192), [`ui/dr-ui/src/library.rs:241`](../ui/dr-ui/src/library.rs#L241), [`ui/dr-ui/src/library.rs:4023`](../ui/dr-ui/src/library.rs#L4023), [`ui/dr-ui/src/library.rs:4057`](../ui/dr-ui/src/library.rs#L4057), [`ui/dr-ui/src/library_ui.rs:190`](../ui/dr-ui/src/library_ui.rs#L190), [`ui/dr-ui/src/library_ui.rs:778`](../ui/dr-ui/src/library_ui.rs#L778), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1), [`ui/dr-ui/ui/collections.slint:572`](../ui/dr-ui/ui/collections.slint#L572) | | FR-CAT-1a | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:54`](../core/dr-types/src/lib.rs#L54), [`platform/dr-plat/src/storage.rs:1`](../platform/dr-plat/src/storage.rs#L1), [`platform/dr-plat/src/storage.rs:216`](../platform/dr-plat/src/storage.rs#L216), [`platform/dr-plat/src/storage.rs:46`](../platform/dr-plat/src/storage.rs#L46) | | FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1), [`tools/traceability/src/lib.rs:481`](../tools/traceability/src/lib.rs#L481) | | FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/walk.rs:66`](../core/dr-catalog/src/walk.rs#L66), [`core/dr-sync/src/scan.rs:69`](../core/dr-sync/src/scan.rs#L69), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964), [`ui/dr-ui/src/library.rs:3472`](../ui/dr-ui/src/library.rs#L3472), [`ui/dr-ui/src/library_ui.rs:172`](../ui/dr-ui/src/library_ui.rs#L172), [`ui/dr-ui/src/library_ui.rs:3731`](../ui/dr-ui/src/library_ui.rs#L3731), [`ui/dr-ui/src/library_ui.rs:4751`](../ui/dr-ui/src/library_ui.rs#L4751), [`ui/dr-ui/ui/app.slint:387`](../ui/dr-ui/ui/app.slint#L387), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387), [`ui/dr-ui/ui/settings.slint:73`](../ui/dr-ui/ui/settings.slint#L73) | | FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/schema.rs:330`](../core/dr-catalog/src/schema.rs#L330), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library.rs:222`](../ui/dr-ui/src/library.rs#L222), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1) | -| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1138`](../core/dr-catalog/src/schema.rs#L1138), [`core/dr-catalog/src/schema.rs:635`](../core/dr-catalog/src/schema.rs#L635), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/collections_ui.rs:1558`](../ui/dr-ui/src/collections_ui.rs#L1558), [`ui/dr-ui/src/collections_ui.rs:1664`](../ui/dr-ui/src/collections_ui.rs#L1664), [`ui/dr-ui/src/collections_ui.rs:1713`](../ui/dr-ui/src/collections_ui.rs#L1713), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4040`](../ui/dr-ui/src/library.rs#L4040), [`ui/dr-ui/src/library_ui.rs:653`](../ui/dr-ui/src/library_ui.rs#L653), [`ui/dr-ui/src/library_ui.rs:6678`](../ui/dr-ui/src/library_ui.rs#L6678), [`ui/dr-ui/src/library_ui.rs:6689`](../ui/dr-ui/src/library_ui.rs#L6689), [`ui/dr-ui/src/library_ui.rs:6702`](../ui/dr-ui/src/library_ui.rs#L6702), [`ui/dr-ui/src/library_ui.rs:6717`](../ui/dr-ui/src/library_ui.rs#L6717), [`ui/dr-ui/src/library_ui.rs:6726`](../ui/dr-ui/src/library_ui.rs#L6726), [`ui/dr-ui/ui/app.slint:323`](../ui/dr-ui/ui/app.slint#L323), [`ui/dr-ui/ui/app.slint:522`](../ui/dr-ui/ui/app.slint#L522), [`ui/dr-ui/ui/library.slint:1298`](../ui/dr-ui/ui/library.slint#L1298), [`ui/dr-ui/ui/library.slint:1322`](../ui/dr-ui/ui/library.slint#L1322), [`ui/dr-ui/ui/library.slint:1375`](../ui/dr-ui/ui/library.slint#L1375), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) | -| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:635`](../core/dr-catalog/src/schema.rs#L635), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4433`](../ui/dr-ui/src/library.rs#L4433), [`ui/dr-ui/src/library_ui.rs:337`](../ui/dr-ui/src/library_ui.rs#L337), [`ui/dr-ui/src/library_ui.rs:410`](../ui/dr-ui/src/library_ui.rs#L410), [`ui/dr-ui/src/library_ui.rs:5418`](../ui/dr-ui/src/library_ui.rs#L5418), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/library_ui.rs:6818`](../ui/dr-ui/src/library_ui.rs#L6818), [`ui/dr-ui/ui/app.slint:335`](../ui/dr-ui/ui/app.slint#L335), [`ui/dr-ui/ui/app.slint:522`](../ui/dr-ui/ui/app.slint#L522), [`ui/dr-ui/ui/app.slint:818`](../ui/dr-ui/ui/app.slint#L818), [`ui/dr-ui/ui/library.slint:110`](../ui/dr-ui/ui/library.slint#L110), [`ui/dr-ui/ui/library.slint:1482`](../ui/dr-ui/ui/library.slint#L1482), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | -| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1577`](../ui/dr-ui/src/collections_ui.rs#L1577), [`ui/dr-ui/src/collections_ui.rs:1827`](../ui/dr-ui/src/collections_ui.rs#L1827), [`ui/dr-ui/src/collections_ui.rs:1892`](../ui/dr-ui/src/collections_ui.rs#L1892), [`ui/dr-ui/src/collections_ui.rs:1952`](../ui/dr-ui/src/collections_ui.rs#L1952), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2938`](../ui/dr-ui/src/collections_ui.rs#L2938), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:961`](../ui/dr-ui/src/collections_ui.rs#L961), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4238`](../ui/dr-ui/src/library.rs#L4238), [`ui/dr-ui/src/library.rs:4264`](../ui/dr-ui/src/library.rs#L4264), [`ui/dr-ui/src/library.rs:4319`](../ui/dr-ui/src/library.rs#L4319), [`ui/dr-ui/src/library_ui.rs:3593`](../ui/dr-ui/src/library_ui.rs#L3593), [`ui/dr-ui/src/library_ui.rs:4320`](../ui/dr-ui/src/library_ui.rs#L4320), [`ui/dr-ui/ui/app.slint:326`](../ui/dr-ui/ui/app.slint#L326), [`ui/dr-ui/ui/app.slint:517`](../ui/dr-ui/ui/app.slint#L517), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2842`](../ui/dr-ui/ui/library.slint#L2842), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898), [`ui/dr-ui/ui/library.slint:917`](../ui/dr-ui/ui/library.slint#L917) | -| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1637`](../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:3802`](../ui/dr-ui/src/develop.rs#L3802), [`ui/dr-ui/src/develop.rs:3831`](../ui/dr-ui/src/develop.rs#L3831), [`ui/dr-ui/src/export.rs:750`](../ui/dr-ui/src/export.rs#L750), [`ui/dr-ui/src/lib.rs:1004`](../ui/dr-ui/src/lib.rs#L1004), [`ui/dr-ui/src/lib.rs:1500`](../ui/dr-ui/src/lib.rs#L1500), [`ui/dr-ui/src/lib.rs:1990`](../ui/dr-ui/src/lib.rs#L1990), [`ui/dr-ui/src/lib.rs:2125`](../ui/dr-ui/src/lib.rs#L2125), [`ui/dr-ui/src/lib.rs:579`](../ui/dr-ui/src/lib.rs#L579), [`ui/dr-ui/src/library.rs:1903`](../ui/dr-ui/src/library.rs#L1903), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:5043`](../ui/dr-ui/src/library_ui.rs#L5043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | -| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:692`](../core/dr-catalog/src/schema.rs#L692), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`ui/dr-ui/src/develop.rs:3229`](../ui/dr-ui/src/develop.rs#L3229), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4541`](../ui/dr-ui/src/library.rs#L4541), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1045`](../ui/dr-ui/src/library_ui.rs#L1045), [`ui/dr-ui/src/library_ui.rs:1636`](../ui/dr-ui/src/library_ui.rs#L1636), [`ui/dr-ui/src/library_ui.rs:1680`](../ui/dr-ui/src/library_ui.rs#L1680), [`ui/dr-ui/src/library_ui.rs:1696`](../ui/dr-ui/src/library_ui.rs#L1696), [`ui/dr-ui/src/library_ui.rs:1790`](../ui/dr-ui/src/library_ui.rs#L1790), [`ui/dr-ui/src/library_ui.rs:232`](../ui/dr-ui/src/library_ui.rs#L232), [`ui/dr-ui/src/library_ui.rs:2436`](../ui/dr-ui/src/library_ui.rs#L2436), [`ui/dr-ui/src/library_ui.rs:265`](../ui/dr-ui/src/library_ui.rs#L265), [`ui/dr-ui/src/library_ui.rs:273`](../ui/dr-ui/src/library_ui.rs#L273), [`ui/dr-ui/src/library_ui.rs:2874`](../ui/dr-ui/src/library_ui.rs#L2874), [`ui/dr-ui/src/library_ui.rs:3096`](../ui/dr-ui/src/library_ui.rs#L3096), [`ui/dr-ui/src/library_ui.rs:3374`](../ui/dr-ui/src/library_ui.rs#L3374), [`ui/dr-ui/src/library_ui.rs:3462`](../ui/dr-ui/src/library_ui.rs#L3462), [`ui/dr-ui/src/library_ui.rs:3644`](../ui/dr-ui/src/library_ui.rs#L3644), [`ui/dr-ui/src/library_ui.rs:3758`](../ui/dr-ui/src/library_ui.rs#L3758), [`ui/dr-ui/src/library_ui.rs:463`](../ui/dr-ui/src/library_ui.rs#L463), [`ui/dr-ui/src/library_ui.rs:526`](../ui/dr-ui/src/library_ui.rs#L526), [`ui/dr-ui/src/library_ui.rs:5516`](../ui/dr-ui/src/library_ui.rs#L5516), [`ui/dr-ui/src/library_ui.rs:5631`](../ui/dr-ui/src/library_ui.rs#L5631), [`ui/dr-ui/src/presets.rs:446`](../ui/dr-ui/src/presets.rs#L446), [`ui/dr-ui/src/presets.rs:458`](../ui/dr-ui/src/presets.rs#L458), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-5 | [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:1138`](../core/dr-catalog/src/schema.rs#L1138), [`core/dr-catalog/src/schema.rs:635`](../core/dr-catalog/src/schema.rs#L635), [`core/dr-decode/src/lib.rs:285`](../core/dr-decode/src/lib.rs#L285), [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/collections_ui.rs:1558`](../ui/dr-ui/src/collections_ui.rs#L1558), [`ui/dr-ui/src/collections_ui.rs:1664`](../ui/dr-ui/src/collections_ui.rs#L1664), [`ui/dr-ui/src/collections_ui.rs:1713`](../ui/dr-ui/src/collections_ui.rs#L1713), [`ui/dr-ui/src/collections_ui.rs:238`](../ui/dr-ui/src/collections_ui.rs#L238), [`ui/dr-ui/src/collections_ui.rs:335`](../ui/dr-ui/src/collections_ui.rs#L335), [`ui/dr-ui/src/collections_ui.rs:87`](../ui/dr-ui/src/collections_ui.rs#L87), [`ui/dr-ui/src/library.rs:4071`](../ui/dr-ui/src/library.rs#L4071), [`ui/dr-ui/src/library_ui.rs:653`](../ui/dr-ui/src/library_ui.rs#L653), [`ui/dr-ui/src/library_ui.rs:6678`](../ui/dr-ui/src/library_ui.rs#L6678), [`ui/dr-ui/src/library_ui.rs:6689`](../ui/dr-ui/src/library_ui.rs#L6689), [`ui/dr-ui/src/library_ui.rs:6702`](../ui/dr-ui/src/library_ui.rs#L6702), [`ui/dr-ui/src/library_ui.rs:6717`](../ui/dr-ui/src/library_ui.rs#L6717), [`ui/dr-ui/src/library_ui.rs:6726`](../ui/dr-ui/src/library_ui.rs#L6726), [`ui/dr-ui/ui/app.slint:323`](../ui/dr-ui/ui/app.slint#L323), [`ui/dr-ui/ui/app.slint:522`](../ui/dr-ui/ui/app.slint#L522), [`ui/dr-ui/ui/library.slint:1298`](../ui/dr-ui/ui/library.slint#L1298), [`ui/dr-ui/ui/library.slint:1322`](../ui/dr-ui/ui/library.slint#L1322), [`ui/dr-ui/ui/library.slint:1375`](../ui/dr-ui/ui/library.slint#L1375), [`ui/dr-ui/ui/library.slint:19`](../ui/dr-ui/ui/library.slint#L19), [`ui/dr-ui/ui/library.slint:872`](../ui/dr-ui/ui/library.slint#L872), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927) | +| FR-CAT-6 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-catalog/src/schema.rs:635`](../core/dr-catalog/src/schema.rs#L635), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:64`](../core/dr-types/src/settings.rs#L64), [`core/dr-types/src/time.rs:67`](../core/dr-types/src/time.rs#L67), [`core/dr-types/src/time.rs:90`](../core/dr-types/src/time.rs#L90), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:4464`](../ui/dr-ui/src/library.rs#L4464), [`ui/dr-ui/src/library_ui.rs:337`](../ui/dr-ui/src/library_ui.rs#L337), [`ui/dr-ui/src/library_ui.rs:410`](../ui/dr-ui/src/library_ui.rs#L410), [`ui/dr-ui/src/library_ui.rs:5418`](../ui/dr-ui/src/library_ui.rs#L5418), [`ui/dr-ui/src/library_ui.rs:5471`](../ui/dr-ui/src/library_ui.rs#L5471), [`ui/dr-ui/src/library_ui.rs:6818`](../ui/dr-ui/src/library_ui.rs#L6818), [`ui/dr-ui/ui/app.slint:335`](../ui/dr-ui/ui/app.slint#L335), [`ui/dr-ui/ui/app.slint:522`](../ui/dr-ui/ui/app.slint#L522), [`ui/dr-ui/ui/app.slint:818`](../ui/dr-ui/ui/app.slint#L818), [`ui/dr-ui/ui/library.slint:110`](../ui/dr-ui/ui/library.slint#L110), [`ui/dr-ui/ui/library.slint:1482`](../ui/dr-ui/ui/library.slint#L1482), [`ui/dr-ui/ui/library.slint:927`](../ui/dr-ui/ui/library.slint#L927), [`ui/dr-ui/ui/settings.slint:150`](../ui/dr-ui/ui/settings.slint#L150) | +| FR-CAT-7 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:1577`](../ui/dr-ui/src/collections_ui.rs#L1577), [`ui/dr-ui/src/collections_ui.rs:1827`](../ui/dr-ui/src/collections_ui.rs#L1827), [`ui/dr-ui/src/collections_ui.rs:1892`](../ui/dr-ui/src/collections_ui.rs#L1892), [`ui/dr-ui/src/collections_ui.rs:1952`](../ui/dr-ui/src/collections_ui.rs#L1952), [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/collections_ui.rs:2938`](../ui/dr-ui/src/collections_ui.rs#L2938), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:961`](../ui/dr-ui/src/collections_ui.rs#L961), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:4269`](../ui/dr-ui/src/library.rs#L4269), [`ui/dr-ui/src/library.rs:4295`](../ui/dr-ui/src/library.rs#L4295), [`ui/dr-ui/src/library.rs:4350`](../ui/dr-ui/src/library.rs#L4350), [`ui/dr-ui/src/library_ui.rs:3593`](../ui/dr-ui/src/library_ui.rs#L3593), [`ui/dr-ui/src/library_ui.rs:4320`](../ui/dr-ui/src/library_ui.rs#L4320), [`ui/dr-ui/ui/app.slint:326`](../ui/dr-ui/ui/app.slint#L326), [`ui/dr-ui/ui/app.slint:517`](../ui/dr-ui/ui/app.slint#L517), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/library.slint:2842`](../ui/dr-ui/ui/library.slint#L2842), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898), [`ui/dr-ui/ui/library.slint:917`](../ui/dr-ui/ui/library.slint#L917) | +| FR-CAT-8 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/ops/curve.rs:137`](../core/dr-pipeline/src/ops/curve.rs#L137), [`core/dr-pipeline/src/ops/curve.rs:656`](../core/dr-pipeline/src/ops/curve.rs#L656), [`core/dr-pipeline/src/sidecar.rs:1637`](../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`core/dr-pipeline/tests/tone_curve.rs:34`](../core/dr-pipeline/tests/tone_curve.rs#L34), [`ui/dr-ui/src/develop.rs:3802`](../ui/dr-ui/src/develop.rs#L3802), [`ui/dr-ui/src/develop.rs:3831`](../ui/dr-ui/src/develop.rs#L3831), [`ui/dr-ui/src/export.rs:750`](../ui/dr-ui/src/export.rs#L750), [`ui/dr-ui/src/lib.rs:1013`](../ui/dr-ui/src/lib.rs#L1013), [`ui/dr-ui/src/lib.rs:1509`](../ui/dr-ui/src/lib.rs#L1509), [`ui/dr-ui/src/lib.rs:1999`](../ui/dr-ui/src/lib.rs#L1999), [`ui/dr-ui/src/lib.rs:2134`](../ui/dr-ui/src/lib.rs#L2134), [`ui/dr-ui/src/lib.rs:588`](../ui/dr-ui/src/lib.rs#L588), [`ui/dr-ui/src/library.rs:1903`](../ui/dr-ui/src/library.rs#L1903), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library_ui.rs:5043`](../ui/dr-ui/src/library_ui.rs#L5043), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-CAT-9 | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/schema.rs:692`](../core/dr-catalog/src/schema.rs#L692), [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-catalog/src/walk.rs:728`](../core/dr-catalog/src/walk.rs#L728), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`ui/dr-ui/src/develop.rs:3229`](../ui/dr-ui/src/develop.rs#L3229), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library.rs:161`](../ui/dr-ui/src/library.rs#L161), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:266`](../ui/dr-ui/src/library.rs#L266), [`ui/dr-ui/src/library.rs:4572`](../ui/dr-ui/src/library.rs#L4572), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:807`](../ui/dr-ui/src/library.rs#L807), [`ui/dr-ui/src/library.rs:823`](../ui/dr-ui/src/library.rs#L823), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1045`](../ui/dr-ui/src/library_ui.rs#L1045), [`ui/dr-ui/src/library_ui.rs:1636`](../ui/dr-ui/src/library_ui.rs#L1636), [`ui/dr-ui/src/library_ui.rs:1680`](../ui/dr-ui/src/library_ui.rs#L1680), [`ui/dr-ui/src/library_ui.rs:1696`](../ui/dr-ui/src/library_ui.rs#L1696), [`ui/dr-ui/src/library_ui.rs:1790`](../ui/dr-ui/src/library_ui.rs#L1790), [`ui/dr-ui/src/library_ui.rs:232`](../ui/dr-ui/src/library_ui.rs#L232), [`ui/dr-ui/src/library_ui.rs:2436`](../ui/dr-ui/src/library_ui.rs#L2436), [`ui/dr-ui/src/library_ui.rs:265`](../ui/dr-ui/src/library_ui.rs#L265), [`ui/dr-ui/src/library_ui.rs:273`](../ui/dr-ui/src/library_ui.rs#L273), [`ui/dr-ui/src/library_ui.rs:2874`](../ui/dr-ui/src/library_ui.rs#L2874), [`ui/dr-ui/src/library_ui.rs:3096`](../ui/dr-ui/src/library_ui.rs#L3096), [`ui/dr-ui/src/library_ui.rs:3374`](../ui/dr-ui/src/library_ui.rs#L3374), [`ui/dr-ui/src/library_ui.rs:3462`](../ui/dr-ui/src/library_ui.rs#L3462), [`ui/dr-ui/src/library_ui.rs:3644`](../ui/dr-ui/src/library_ui.rs#L3644), [`ui/dr-ui/src/library_ui.rs:3758`](../ui/dr-ui/src/library_ui.rs#L3758), [`ui/dr-ui/src/library_ui.rs:463`](../ui/dr-ui/src/library_ui.rs#L463), [`ui/dr-ui/src/library_ui.rs:526`](../ui/dr-ui/src/library_ui.rs#L526), [`ui/dr-ui/src/library_ui.rs:5516`](../ui/dr-ui/src/library_ui.rs#L5516), [`ui/dr-ui/src/library_ui.rs:5631`](../ui/dr-ui/src/library_ui.rs#L5631), [`ui/dr-ui/src/presets.rs:446`](../ui/dr-ui/src/presets.rs#L446), [`ui/dr-ui/src/presets.rs:458`](../ui/dr-ui/src/presets.rs#L458), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-CULL-1 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | -| FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:369`](../core/dr-catalog/src/schema.rs#L369), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:120`](../ui/dr-ui/src/develop.rs#L120), [`ui/dr-ui/src/develop.rs:129`](../ui/dr-ui/src/develop.rs#L129), [`ui/dr-ui/src/develop.rs:195`](../ui/dr-ui/src/develop.rs#L195), [`ui/dr-ui/src/develop.rs:1968`](../ui/dr-ui/src/develop.rs#L1968), [`ui/dr-ui/src/develop.rs:688`](../ui/dr-ui/src/develop.rs#L688), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2097`](../ui/dr-ui/src/lib.rs#L2097), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | +| FR-CULL-10 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:369`](../core/dr-catalog/src/schema.rs#L369), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/develop.rs:120`](../ui/dr-ui/src/develop.rs#L120), [`ui/dr-ui/src/develop.rs:129`](../ui/dr-ui/src/develop.rs#L129), [`ui/dr-ui/src/develop.rs:195`](../ui/dr-ui/src/develop.rs#L195), [`ui/dr-ui/src/develop.rs:1968`](../ui/dr-ui/src/develop.rs#L1968), [`ui/dr-ui/src/develop.rs:688`](../ui/dr-ui/src/develop.rs#L688), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2106`](../ui/dr-ui/src/lib.rs#L2106), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-11 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/src/library.rs:287`](../ui/dr-ui/src/library.rs#L287), [`ui/dr-ui/src/library.rs:317`](../ui/dr-ui/src/library.rs#L317), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-12 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:369`](../core/dr-catalog/src/schema.rs#L369), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`ui/dr-ui/src/identity.rs:1`](../ui/dr-ui/src/identity.rs#L1), [`ui/dr-ui/ui/identity.slint:1`](../ui/dr-ui/ui/identity.slint#L1) | | FR-CULL-2 | [`core/dr-decode/src/locate.rs:1`](../core/dr-decode/src/locate.rs#L1), [`core/dr-decode/src/preview.rs:148`](../core/dr-decode/src/preview.rs#L148), [`ui/dr-ui/src/import.rs:463`](../ui/dr-ui/src/import.rs#L463) | -| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2954`](../ui/dr-ui/src/develop.rs#L2954), [`ui/dr-ui/src/develop.rs:2967`](../ui/dr-ui/src/develop.rs#L2967), [`ui/dr-ui/src/develop.rs:3012`](../ui/dr-ui/src/develop.rs#L3012), [`ui/dr-ui/src/develop.rs:3023`](../ui/dr-ui/src/develop.rs#L3023), [`ui/dr-ui/src/develop.rs:3029`](../ui/dr-ui/src/develop.rs#L3029), [`ui/dr-ui/src/develop.rs:3046`](../ui/dr-ui/src/develop.rs#L3046), [`ui/dr-ui/src/develop.rs:5929`](../ui/dr-ui/src/develop.rs#L5929), [`ui/dr-ui/src/develop.rs:5993`](../ui/dr-ui/src/develop.rs#L5993), [`ui/dr-ui/src/develop.rs:6016`](../ui/dr-ui/src/develop.rs#L6016), [`ui/dr-ui/src/develop.rs:726`](../ui/dr-ui/src/develop.rs#L726), [`ui/dr-ui/src/develop.rs:746`](../ui/dr-ui/src/develop.rs#L746), [`ui/dr-ui/src/develop.rs:752`](../ui/dr-ui/src/develop.rs#L752), [`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:541`](../ui/dr-ui/src/histogram.rs#L541), [`ui/dr-ui/src/histogram.rs:562`](../ui/dr-ui/src/histogram.rs#L562), [`ui/dr-ui/src/histogram.rs:590`](../ui/dr-ui/src/histogram.rs#L590), [`ui/dr-ui/src/histogram.rs:618`](../ui/dr-ui/src/histogram.rs#L618), [`ui/dr-ui/src/histogram.rs:655`](../ui/dr-ui/src/histogram.rs#L655), [`ui/dr-ui/src/lib.rs:1539`](../ui/dr-ui/src/lib.rs#L1539), [`ui/dr-ui/src/lib.rs:1617`](../ui/dr-ui/src/lib.rs#L1617), [`ui/dr-ui/src/lib.rs:1716`](../ui/dr-ui/src/lib.rs#L1716), [`ui/dr-ui/src/lib.rs:1757`](../ui/dr-ui/src/lib.rs#L1757), [`ui/dr-ui/src/lib.rs:3007`](../ui/dr-ui/src/lib.rs#L3007), [`ui/dr-ui/src/lib.rs:339`](../ui/dr-ui/src/lib.rs#L339), [`ui/dr-ui/src/peaking.rs:1`](../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:1769`](../ui/dr-ui/ui/app.slint#L1769), [`ui/dr-ui/ui/app.slint:2347`](../ui/dr-ui/ui/app.slint#L2347), [`ui/dr-ui/ui/app.slint:88`](../ui/dr-ui/ui/app.slint#L88), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../ui/dr-ui/ui/peaking.slint#L56) | +| FR-CULL-3 | [`core/dr-gpu/src/focus.rs:154`](../core/dr-gpu/src/focus.rs#L154), [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/raw_histogram.rs:129`](../core/dr-gpu/src/raw_histogram.rs#L129), [`core/dr-gpu/src/raw_histogram.rs:1`](../core/dr-gpu/src/raw_histogram.rs#L1), [`core/dr-gpu/src/raw_histogram.rs:272`](../core/dr-gpu/src/raw_histogram.rs#L272), [`core/dr-gpu/src/raw_histogram.rs:407`](../core/dr-gpu/src/raw_histogram.rs#L407), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`core/dr-gpu/src/shaders/raw_histogram.wgsl:1`](../core/dr-gpu/src/shaders/raw_histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2954`](../ui/dr-ui/src/develop.rs#L2954), [`ui/dr-ui/src/develop.rs:2967`](../ui/dr-ui/src/develop.rs#L2967), [`ui/dr-ui/src/develop.rs:3012`](../ui/dr-ui/src/develop.rs#L3012), [`ui/dr-ui/src/develop.rs:3023`](../ui/dr-ui/src/develop.rs#L3023), [`ui/dr-ui/src/develop.rs:3029`](../ui/dr-ui/src/develop.rs#L3029), [`ui/dr-ui/src/develop.rs:3046`](../ui/dr-ui/src/develop.rs#L3046), [`ui/dr-ui/src/develop.rs:5929`](../ui/dr-ui/src/develop.rs#L5929), [`ui/dr-ui/src/develop.rs:5993`](../ui/dr-ui/src/develop.rs#L5993), [`ui/dr-ui/src/develop.rs:6016`](../ui/dr-ui/src/develop.rs#L6016), [`ui/dr-ui/src/develop.rs:726`](../ui/dr-ui/src/develop.rs#L726), [`ui/dr-ui/src/develop.rs:746`](../ui/dr-ui/src/develop.rs#L746), [`ui/dr-ui/src/develop.rs:752`](../ui/dr-ui/src/develop.rs#L752), [`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:541`](../ui/dr-ui/src/histogram.rs#L541), [`ui/dr-ui/src/histogram.rs:562`](../ui/dr-ui/src/histogram.rs#L562), [`ui/dr-ui/src/histogram.rs:590`](../ui/dr-ui/src/histogram.rs#L590), [`ui/dr-ui/src/histogram.rs:618`](../ui/dr-ui/src/histogram.rs#L618), [`ui/dr-ui/src/histogram.rs:655`](../ui/dr-ui/src/histogram.rs#L655), [`ui/dr-ui/src/lib.rs:1548`](../ui/dr-ui/src/lib.rs#L1548), [`ui/dr-ui/src/lib.rs:1626`](../ui/dr-ui/src/lib.rs#L1626), [`ui/dr-ui/src/lib.rs:1725`](../ui/dr-ui/src/lib.rs#L1725), [`ui/dr-ui/src/lib.rs:1766`](../ui/dr-ui/src/lib.rs#L1766), [`ui/dr-ui/src/lib.rs:3016`](../ui/dr-ui/src/lib.rs#L3016), [`ui/dr-ui/src/lib.rs:348`](../ui/dr-ui/src/lib.rs#L348), [`ui/dr-ui/src/peaking.rs:1`](../ui/dr-ui/src/peaking.rs#L1), [`ui/dr-ui/ui/app.slint:1769`](../ui/dr-ui/ui/app.slint#L1769), [`ui/dr-ui/ui/app.slint:2347`](../ui/dr-ui/ui/app.slint#L2347), [`ui/dr-ui/ui/app.slint:88`](../ui/dr-ui/ui/app.slint#L88), [`ui/dr-ui/ui/peaking.slint:1`](../ui/dr-ui/ui/peaking.slint#L1), [`ui/dr-ui/ui/peaking.slint:25`](../ui/dr-ui/ui/peaking.slint#L25), [`ui/dr-ui/ui/peaking.slint:56`](../ui/dr-ui/ui/peaking.slint#L56) | | FR-CULL-4 | [`core/dr-catalog/src/rating.rs:1`](../core/dr-catalog/src/rating.rs#L1), [`core/dr-pipeline/src/sidecar.rs:136`](../core/dr-pipeline/src/sidecar.rs#L136), [`ui/dr-ui/src/library.rs:246`](../ui/dr-ui/src/library.rs#L246), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492) | -| FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:412`](../core/dr-catalog/src/schema.rs#L412), [`ui/dr-ui/src/bursts.rs:1`](../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library.rs:202`](../ui/dr-ui/src/library.rs#L202), [`ui/dr-ui/src/library.rs:5142`](../ui/dr-ui/src/library.rs#L5142) | +| FR-CULL-5 | [`core/dr-catalog/src/bursts.rs:1`](../core/dr-catalog/src/bursts.rs#L1), [`core/dr-catalog/src/schema.rs:412`](../core/dr-catalog/src/schema.rs#L412), [`ui/dr-ui/src/bursts.rs:1`](../ui/dr-ui/src/bursts.rs#L1), [`ui/dr-ui/src/library.rs:202`](../ui/dr-ui/src/library.rs#L202), [`ui/dr-ui/src/library.rs:5173`](../ui/dr-ui/src/library.rs#L5173) | | 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:480`](../core/dr-catalog/src/schema.rs#L480), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:2965`](../ui/dr-ui/src/library.rs#L2965), [`ui/dr-ui/src/library.rs:3069`](../ui/dr-ui/src/library.rs#L3069), [`ui/dr-ui/ui/settings.slint:419`](../ui/dr-ui/ui/settings.slint#L419), [`ui/dr-ui/ui/settings.slint:82`](../ui/dr-ui/ui/settings.slint#L82) | | FR-CULL-9 | [`core/dr-catalog/src/faces.rs:1`](../core/dr-catalog/src/faces.rs#L1), [`core/dr-catalog/src/schema.rs:521`](../core/dr-catalog/src/schema.rs#L521), [`core/dr-face/src/assign.rs:1`](../core/dr-face/src/assign.rs#L1), [`core/dr-face/src/neighbours.rs:1`](../core/dr-face/src/neighbours.rs#L1), [`core/dr-types/src/settings.rs:117`](../core/dr-types/src/settings.rs#L117), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/identity_ui.rs:1`](../ui/dr-ui/src/identity_ui.rs#L1), [`ui/dr-ui/ui/identity.slint:276`](../ui/dr-ui/ui/identity.slint#L276) | | FR-DEV-1 | [`core/dr-pipeline/src/graph.rs:1`](../core/dr-pipeline/src/graph.rs#L1), [`core/dr-pipeline/src/sidecar.rs:1`](../core/dr-pipeline/src/sidecar.rs#L1) | | FR-DEV-2 | [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389) | -| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:488`](../core/dr-pipeline/src/framing.rs#L488), [`core/dr-pipeline/src/framing.rs:743`](../core/dr-pipeline/src/framing.rs#L743), [`core/dr-pipeline/src/graph.rs:170`](../core/dr-pipeline/src/graph.rs#L170), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/mask.rs:121`](../core/dr-pipeline/src/mask.rs#L121), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:516`](../core/dr-pipeline/src/operation.rs#L516), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:157`](../core/dr-pipeline/src/sidecar.rs#L157), [`core/dr-pipeline/src/sidecar.rs:1637`](../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:1697`](../core/dr-pipeline/src/sidecar.rs#L1697), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:102`](../ui/dr-ui/src/develop.rs#L102), [`ui/dr-ui/src/develop.rs:1472`](../ui/dr-ui/src/develop.rs#L1472), [`ui/dr-ui/src/develop.rs:164`](../ui/dr-ui/src/develop.rs#L164), [`ui/dr-ui/src/develop.rs:1950`](../ui/dr-ui/src/develop.rs#L1950), [`ui/dr-ui/src/develop.rs:1968`](../ui/dr-ui/src/develop.rs#L1968), [`ui/dr-ui/src/develop.rs:1982`](../ui/dr-ui/src/develop.rs#L1982), [`ui/dr-ui/src/develop.rs:2004`](../ui/dr-ui/src/develop.rs#L2004), [`ui/dr-ui/src/develop.rs:2150`](../ui/dr-ui/src/develop.rs#L2150), [`ui/dr-ui/src/develop.rs:2248`](../ui/dr-ui/src/develop.rs#L2248), [`ui/dr-ui/src/develop.rs:327`](../ui/dr-ui/src/develop.rs#L327), [`ui/dr-ui/src/develop.rs:3500`](../ui/dr-ui/src/develop.rs#L3500), [`ui/dr-ui/src/develop.rs:3530`](../ui/dr-ui/src/develop.rs#L3530), [`ui/dr-ui/src/develop.rs:3596`](../ui/dr-ui/src/develop.rs#L3596), [`ui/dr-ui/src/develop.rs:3610`](../ui/dr-ui/src/develop.rs#L3610), [`ui/dr-ui/src/develop.rs:364`](../ui/dr-ui/src/develop.rs#L364), [`ui/dr-ui/src/develop.rs:3802`](../ui/dr-ui/src/develop.rs#L3802), [`ui/dr-ui/src/develop.rs:4462`](../ui/dr-ui/src/develop.rs#L4462), [`ui/dr-ui/src/develop.rs:4516`](../ui/dr-ui/src/develop.rs#L4516), [`ui/dr-ui/src/develop.rs:4560`](../ui/dr-ui/src/develop.rs#L4560), [`ui/dr-ui/src/develop.rs:4610`](../ui/dr-ui/src/develop.rs#L4610), [`ui/dr-ui/src/develop.rs:587`](../ui/dr-ui/src/develop.rs#L587), [`ui/dr-ui/src/develop.rs:648`](../ui/dr-ui/src/develop.rs#L648), [`ui/dr-ui/src/develop.rs:766`](../ui/dr-ui/src/develop.rs#L766), [`ui/dr-ui/src/develop.rs:813`](../ui/dr-ui/src/develop.rs#L813), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/lib.rs:1598`](../ui/dr-ui/src/lib.rs#L1598), [`ui/dr-ui/src/lib.rs:2400`](../ui/dr-ui/src/lib.rs#L2400), [`ui/dr-ui/src/lib.rs:2596`](../ui/dr-ui/src/lib.rs#L2596), [`ui/dr-ui/src/lib.rs:2768`](../ui/dr-ui/src/lib.rs#L2768), [`ui/dr-ui/src/lib.rs:2832`](../ui/dr-ui/src/lib.rs#L2832), [`ui/dr-ui/src/lib.rs:2886`](../ui/dr-ui/src/lib.rs#L2886), [`ui/dr-ui/src/lib.rs:345`](../ui/dr-ui/src/lib.rs#L345), [`ui/dr-ui/src/lib.rs:388`](../ui/dr-ui/src/lib.rs#L388), [`ui/dr-ui/src/lib.rs:411`](../ui/dr-ui/src/lib.rs#L411), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:219`](../ui/dr-ui/src/segmentation.rs#L219), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322), [`ui/dr-ui/src/segmentation.rs:350`](../ui/dr-ui/src/segmentation.rs#L350), [`ui/dr-ui/ui/adjust.slint:517`](../ui/dr-ui/ui/adjust.slint#L517), [`ui/dr-ui/ui/adjust.slint:620`](../ui/dr-ui/ui/adjust.slint#L620), [`ui/dr-ui/ui/app.slint:149`](../ui/dr-ui/ui/app.slint#L149), [`ui/dr-ui/ui/app.slint:195`](../ui/dr-ui/ui/app.slint#L195), [`ui/dr-ui/ui/app.slint:1971`](../ui/dr-ui/ui/app.slint#L1971), [`ui/dr-ui/ui/app.slint:928`](../ui/dr-ui/ui/app.slint#L928), [`ui/dr-ui/ui/masks.slint:516`](../ui/dr-ui/ui/masks.slint#L516) | -| FR-DEV-3a | [`core/dr-pipeline/build.rs:756`](../core/dr-pipeline/build.rs#L756), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:385`](../core/dr-pipeline/src/framing.rs#L385), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:955`](../core/dr-pipeline/src/mask.rs#L955), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1369`](../ui/dr-ui/src/develop.rs#L1369), [`ui/dr-ui/src/lib.rs:694`](../ui/dr-ui/src/lib.rs#L694), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | +| FR-DEV-3 | [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:651`](../core/dr-gpu/src/adjust.rs#L651), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/tests/tone_curve.rs:1`](../core/dr-gpu/tests/tone_curve.rs#L1), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/framing.rs:177`](../core/dr-pipeline/src/framing.rs#L177), [`core/dr-pipeline/src/framing.rs:234`](../core/dr-pipeline/src/framing.rs#L234), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/framing.rs:488`](../core/dr-pipeline/src/framing.rs#L488), [`core/dr-pipeline/src/framing.rs:743`](../core/dr-pipeline/src/framing.rs#L743), [`core/dr-pipeline/src/graph.rs:170`](../core/dr-pipeline/src/graph.rs#L170), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/mask.rs:121`](../core/dr-pipeline/src/mask.rs#L121), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:516`](../core/dr-pipeline/src/operation.rs#L516), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:210`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L210), [`core/dr-pipeline/src/ops/curve.rs:100`](../core/dr-pipeline/src/ops/curve.rs#L100), [`core/dr-pipeline/src/ops/curve.rs:1`](../core/dr-pipeline/src/ops/curve.rs#L1), [`core/dr-pipeline/src/ops/curve.rs:219`](../core/dr-pipeline/src/ops/curve.rs#L219), [`core/dr-pipeline/src/ops/curve.rs:635`](../core/dr-pipeline/src/ops/curve.rs#L635), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:1`](../core/dr-pipeline/src/ops/noise_reduction.rs#L1), [`core/dr-pipeline/src/ops/noise_reduction.rs:273`](../core/dr-pipeline/src/ops/noise_reduction.rs#L273), [`core/dr-pipeline/src/sidecar.rs:157`](../core/dr-pipeline/src/sidecar.rs#L157), [`core/dr-pipeline/src/sidecar.rs:1637`](../core/dr-pipeline/src/sidecar.rs#L1637), [`core/dr-pipeline/src/sidecar.rs:1697`](../core/dr-pipeline/src/sidecar.rs#L1697), [`core/dr-pipeline/tests/tone_curve.rs:1`](../core/dr-pipeline/tests/tone_curve.rs#L1), [`ui/dr-ui/src/develop.rs:102`](../ui/dr-ui/src/develop.rs#L102), [`ui/dr-ui/src/develop.rs:1472`](../ui/dr-ui/src/develop.rs#L1472), [`ui/dr-ui/src/develop.rs:164`](../ui/dr-ui/src/develop.rs#L164), [`ui/dr-ui/src/develop.rs:1950`](../ui/dr-ui/src/develop.rs#L1950), [`ui/dr-ui/src/develop.rs:1968`](../ui/dr-ui/src/develop.rs#L1968), [`ui/dr-ui/src/develop.rs:1982`](../ui/dr-ui/src/develop.rs#L1982), [`ui/dr-ui/src/develop.rs:2004`](../ui/dr-ui/src/develop.rs#L2004), [`ui/dr-ui/src/develop.rs:2150`](../ui/dr-ui/src/develop.rs#L2150), [`ui/dr-ui/src/develop.rs:2248`](../ui/dr-ui/src/develop.rs#L2248), [`ui/dr-ui/src/develop.rs:327`](../ui/dr-ui/src/develop.rs#L327), [`ui/dr-ui/src/develop.rs:3500`](../ui/dr-ui/src/develop.rs#L3500), [`ui/dr-ui/src/develop.rs:3530`](../ui/dr-ui/src/develop.rs#L3530), [`ui/dr-ui/src/develop.rs:3596`](../ui/dr-ui/src/develop.rs#L3596), [`ui/dr-ui/src/develop.rs:3610`](../ui/dr-ui/src/develop.rs#L3610), [`ui/dr-ui/src/develop.rs:364`](../ui/dr-ui/src/develop.rs#L364), [`ui/dr-ui/src/develop.rs:3802`](../ui/dr-ui/src/develop.rs#L3802), [`ui/dr-ui/src/develop.rs:4462`](../ui/dr-ui/src/develop.rs#L4462), [`ui/dr-ui/src/develop.rs:4516`](../ui/dr-ui/src/develop.rs#L4516), [`ui/dr-ui/src/develop.rs:4560`](../ui/dr-ui/src/develop.rs#L4560), [`ui/dr-ui/src/develop.rs:4610`](../ui/dr-ui/src/develop.rs#L4610), [`ui/dr-ui/src/develop.rs:587`](../ui/dr-ui/src/develop.rs#L587), [`ui/dr-ui/src/develop.rs:648`](../ui/dr-ui/src/develop.rs#L648), [`ui/dr-ui/src/develop.rs:766`](../ui/dr-ui/src/develop.rs#L766), [`ui/dr-ui/src/develop.rs:813`](../ui/dr-ui/src/develop.rs#L813), [`ui/dr-ui/src/develop.rs:842`](../ui/dr-ui/src/develop.rs#L842), [`ui/dr-ui/src/lib.rs:1607`](../ui/dr-ui/src/lib.rs#L1607), [`ui/dr-ui/src/lib.rs:2409`](../ui/dr-ui/src/lib.rs#L2409), [`ui/dr-ui/src/lib.rs:2605`](../ui/dr-ui/src/lib.rs#L2605), [`ui/dr-ui/src/lib.rs:2777`](../ui/dr-ui/src/lib.rs#L2777), [`ui/dr-ui/src/lib.rs:2841`](../ui/dr-ui/src/lib.rs#L2841), [`ui/dr-ui/src/lib.rs:2895`](../ui/dr-ui/src/lib.rs#L2895), [`ui/dr-ui/src/lib.rs:354`](../ui/dr-ui/src/lib.rs#L354), [`ui/dr-ui/src/lib.rs:397`](../ui/dr-ui/src/lib.rs#L397), [`ui/dr-ui/src/lib.rs:420`](../ui/dr-ui/src/lib.rs#L420), [`ui/dr-ui/src/library.rs:539`](../ui/dr-ui/src/library.rs#L539), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:41`](../ui/dr-ui/src/masks_ui.rs#L41), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/segmentation.rs:219`](../ui/dr-ui/src/segmentation.rs#L219), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322), [`ui/dr-ui/src/segmentation.rs:350`](../ui/dr-ui/src/segmentation.rs#L350), [`ui/dr-ui/ui/adjust.slint:517`](../ui/dr-ui/ui/adjust.slint#L517), [`ui/dr-ui/ui/adjust.slint:620`](../ui/dr-ui/ui/adjust.slint#L620), [`ui/dr-ui/ui/app.slint:149`](../ui/dr-ui/ui/app.slint#L149), [`ui/dr-ui/ui/app.slint:195`](../ui/dr-ui/ui/app.slint#L195), [`ui/dr-ui/ui/app.slint:1971`](../ui/dr-ui/ui/app.slint#L1971), [`ui/dr-ui/ui/app.slint:928`](../ui/dr-ui/ui/app.slint#L928), [`ui/dr-ui/ui/masks.slint:516`](../ui/dr-ui/ui/masks.slint#L516) | +| FR-DEV-3a | [`core/dr-pipeline/build.rs:756`](../core/dr-pipeline/build.rs#L756), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/descriptor.rs:194`](../core/dr-pipeline/src/descriptor.rs#L194), [`core/dr-pipeline/src/descriptor.rs:234`](../core/dr-pipeline/src/descriptor.rs#L234), [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/descriptor.rs:313`](../core/dr-pipeline/src/descriptor.rs#L313), [`core/dr-pipeline/src/framing.rs:385`](../core/dr-pipeline/src/framing.rs#L385), [`core/dr-pipeline/src/graph.rs:24`](../core/dr-pipeline/src/graph.rs#L24), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/mask.rs:955`](../core/dr-pipeline/src/mask.rs#L955), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365), [`core/dr-pipeline/src/ops/curve.rs:319`](../core/dr-pipeline/src/ops/curve.rs#L319), [`ui/dr-ui/src/develop.rs:1369`](../ui/dr-ui/src/develop.rs#L1369), [`ui/dr-ui/src/lib.rs:703`](../ui/dr-ui/src/lib.rs#L703), [`ui/dr-ui/tests/ui_names_no_operation.rs:1`](../ui/dr-ui/tests/ui_names_no_operation.rs#L1) | | FR-DEV-3b | [`core/dr-pipeline/src/descriptor.rs:258`](../core/dr-pipeline/src/descriptor.rs#L258), [`core/dr-pipeline/src/framing.rs:385`](../core/dr-pipeline/src/framing.rs#L385), [`core/dr-pipeline/src/graph.rs:59`](../core/dr-pipeline/src/graph.rs#L59), [`core/dr-pipeline/src/operation.rs:365`](../core/dr-pipeline/src/operation.rs#L365) | | FR-DEV-3c | [`core/dr-pipeline/build.rs:756`](../core/dr-pipeline/build.rs#L756), [`core/dr-pipeline/ops/exposure.yaml:1`](../core/dr-pipeline/ops/exposure.yaml#L1), [`core/dr-pipeline/src/graph.rs:251`](../core/dr-pipeline/src/graph.rs#L251), [`core/dr-pipeline/src/graph.rs:46`](../core/dr-pipeline/src/graph.rs#L46), [`core/dr-pipeline/src/mask.rs:955`](../core/dr-pipeline/src/mask.rs#L955), [`ui/dr-ui/src/develop.rs:5189`](../ui/dr-ui/src/develop.rs#L5189) | | FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1079`](../core/dr-gpu/src/adjust.rs#L1079), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/adjust.rs:84`](../core/dr-gpu/src/adjust.rs#L84), [`core/dr-gpu/src/adjust.rs:986`](../core/dr-gpu/src/adjust.rs#L986), [`core/dr-gpu/tests/capture_sharpen.rs:434`](../core/dr-gpu/tests/capture_sharpen.rs#L434), [`core/dr-gpu/tests/detail_stage.rs:242`](../core/dr-gpu/tests/detail_stage.rs#L242), [`core/dr-gpu/tests/local_contrast.rs:558`](../core/dr-gpu/tests/local_contrast.rs#L558), [`core/dr-gpu/tests/noise_reduction.rs:556`](../core/dr-gpu/tests/noise_reduction.rs#L556), [`core/dr-pipeline/src/framing.rs:314`](../core/dr-pipeline/src/framing.rs#L314), [`core/dr-pipeline/src/graph.rs:617`](../core/dr-pipeline/src/graph.rs#L617), [`core/dr-pipeline/src/operation.rs:32`](../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:389`](../core/dr-pipeline/src/operation.rs#L389), [`core/dr-pipeline/src/operation.rs:53`](../core/dr-pipeline/src/operation.rs#L53), [`core/dr-pipeline/src/operation.rs:71`](../core/dr-pipeline/src/operation.rs#L71) | | FR-DEV-3e | [`core/dr-decode/src/base_curve.rs:145`](../core/dr-decode/src/base_curve.rs#L145), [`core/dr-decode/src/base_curve.rs:158`](../core/dr-decode/src/base_curve.rs#L158), [`core/dr-decode/src/base_curve.rs:1`](../core/dr-decode/src/base_curve.rs#L1), [`core/dr-decode/src/base_curve.rs:267`](../core/dr-decode/src/base_curve.rs#L267), [`core/dr-decode/src/base_curve.rs:347`](../core/dr-decode/src/base_curve.rs#L347), [`core/dr-decode/src/base_curve.rs:55`](../core/dr-decode/src/base_curve.rs#L55), [`core/dr-decode/src/lib.rs:121`](../core/dr-decode/src/lib.rs#L121), [`core/dr-decode/src/lib.rs:708`](../core/dr-decode/src/lib.rs#L708), [`core/dr-decode/src/lib.rs:748`](../core/dr-decode/src/lib.rs#L748), [`core/dr-decode/src/profile.rs:102`](../core/dr-decode/src/profile.rs#L102), [`core/dr-decode/src/profile.rs:151`](../core/dr-decode/src/profile.rs#L151), [`core/dr-decode/src/profile.rs:1`](../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:235`](../core/dr-decode/src/profile.rs#L235), [`core/dr-decode/src/profile.rs:286`](../core/dr-decode/src/profile.rs#L286), [`core/dr-decode/src/profile.rs:343`](../core/dr-decode/src/profile.rs#L343), [`core/dr-decode/src/profile.rs:458`](../core/dr-decode/src/profile.rs#L458), [`core/dr-decode/src/profile.rs:492`](../core/dr-decode/src/profile.rs#L492), [`core/dr-decode/src/profile.rs:630`](../core/dr-decode/src/profile.rs#L630), [`core/dr-gpu/src/adjust.rs:37`](../core/dr-gpu/src/adjust.rs#L37), [`core/dr-gpu/src/adjust.rs:967`](../core/dr-gpu/src/adjust.rs#L967), [`core/dr-gpu/src/demosaic.rs:121`](../core/dr-gpu/src/demosaic.rs#L121), [`core/dr-gpu/src/demosaic.rs:86`](../core/dr-gpu/src/demosaic.rs#L86), [`core/dr-gpu/tests/base_curve.rs:1`](../core/dr-gpu/tests/base_curve.rs#L1), [`core/dr-pipeline/src/operation.rs:1495`](../core/dr-pipeline/src/operation.rs#L1495), [`core/dr-pipeline/src/operation.rs:1576`](../core/dr-pipeline/src/operation.rs#L1576), [`core/dr-pipeline/src/operation.rs:1601`](../core/dr-pipeline/src/operation.rs#L1601), [`core/dr-pipeline/src/operation.rs:1616`](../core/dr-pipeline/src/operation.rs#L1616), [`core/dr-pipeline/src/operation.rs:1640`](../core/dr-pipeline/src/operation.rs#L1640), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/operation.rs:440`](../core/dr-pipeline/src/operation.rs#L440), [`core/dr-pipeline/src/operation.rs:450`](../core/dr-pipeline/src/operation.rs#L450), [`core/dr-pipeline/src/operation.rs:600`](../core/dr-pipeline/src/operation.rs#L600) | -| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:125`](../core/dr-pipeline/src/graph.rs#L125), [`core/dr-pipeline/src/graph.rs:325`](../core/dr-pipeline/src/graph.rs#L325), [`core/dr-pipeline/src/operation.rs:1065`](../core/dr-pipeline/src/operation.rs#L1065), [`core/dr-pipeline/src/operation.rs:1094`](../core/dr-pipeline/src/operation.rs#L1094), [`core/dr-pipeline/src/operation.rs:1495`](../core/dr-pipeline/src/operation.rs#L1495), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:112`](../core/dr-pipeline/src/sidecar.rs#L112), [`core/dr-pipeline/src/sidecar.rs:168`](../core/dr-pipeline/src/sidecar.rs#L168), [`core/dr-pipeline/src/sidecar.rs:1958`](../core/dr-pipeline/src/sidecar.rs#L1958), [`core/dr-pipeline/src/sidecar.rs:2034`](../core/dr-pipeline/src/sidecar.rs#L2034), [`core/dr-pipeline/src/sidecar.rs:534`](../core/dr-pipeline/src/sidecar.rs#L534), [`core/dr-pipeline/src/sidecar.rs:661`](../core/dr-pipeline/src/sidecar.rs#L661), [`core/dr-pipeline/src/sidecar.rs:793`](../core/dr-pipeline/src/sidecar.rs#L793), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3244`](../ui/dr-ui/src/develop.rs#L3244), [`ui/dr-ui/src/develop.rs:3261`](../ui/dr-ui/src/develop.rs#L3261), [`ui/dr-ui/src/develop.rs:3273`](../ui/dr-ui/src/develop.rs#L3273), [`ui/dr-ui/src/develop.rs:3311`](../ui/dr-ui/src/develop.rs#L3311), [`ui/dr-ui/src/develop.rs:3320`](../ui/dr-ui/src/develop.rs#L3320), [`ui/dr-ui/src/develop.rs:3423`](../ui/dr-ui/src/develop.rs#L3423), [`ui/dr-ui/src/develop.rs:3839`](../ui/dr-ui/src/develop.rs#L3839), [`ui/dr-ui/src/develop.rs:3854`](../ui/dr-ui/src/develop.rs#L3854), [`ui/dr-ui/src/lib.rs:2374`](../ui/dr-ui/src/lib.rs#L2374), [`ui/dr-ui/src/lib.rs:627`](../ui/dr-ui/src/lib.rs#L627), [`ui/dr-ui/src/lib.rs:685`](../ui/dr-ui/src/lib.rs#L685), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:395`](../ui/dr-ui/src/presets.rs#L395), [`ui/dr-ui/ui/adjust.slint:1026`](../ui/dr-ui/ui/adjust.slint#L1026), [`ui/dr-ui/ui/adjust.slint:950`](../ui/dr-ui/ui/adjust.slint#L950), [`ui/dr-ui/ui/app.slint:2539`](../ui/dr-ui/ui/app.slint#L2539), [`ui/dr-ui/ui/app.slint:656`](../ui/dr-ui/ui/app.slint#L656) | +| FR-DEV-3f | [`core/dr-film/src/bake.rs:271`](../core/dr-film/src/bake.rs#L271), [`core/dr-film/src/bake.rs:62`](../core/dr-film/src/bake.rs#L62), [`core/dr-film/src/boolean_grain.rs:1`](../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:139`](../core/dr-gpu/src/adjust.rs#L139), [`core/dr-gpu/src/adjust.rs:196`](../core/dr-gpu/src/adjust.rs#L196), [`core/dr-gpu/src/adjust.rs:357`](../core/dr-gpu/src/adjust.rs#L357), [`core/dr-gpu/src/adjust.rs:483`](../core/dr-gpu/src/adjust.rs#L483), [`core/dr-gpu/src/adjust.rs:77`](../core/dr-gpu/src/adjust.rs#L77), [`core/dr-gpu/tests/film_sim.rs:191`](../core/dr-gpu/tests/film_sim.rs#L191), [`core/dr-gpu/tests/film_sim.rs:1`](../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-pipeline/src/graph.rs:102`](../core/dr-pipeline/src/graph.rs#L102), [`core/dr-pipeline/src/graph.rs:125`](../core/dr-pipeline/src/graph.rs#L125), [`core/dr-pipeline/src/graph.rs:325`](../core/dr-pipeline/src/graph.rs#L325), [`core/dr-pipeline/src/operation.rs:1065`](../core/dr-pipeline/src/operation.rs#L1065), [`core/dr-pipeline/src/operation.rs:1094`](../core/dr-pipeline/src/operation.rs#L1094), [`core/dr-pipeline/src/operation.rs:1495`](../core/dr-pipeline/src/operation.rs#L1495), [`core/dr-pipeline/src/operation.rs:296`](../core/dr-pipeline/src/operation.rs#L296), [`core/dr-pipeline/src/operation.rs:310`](../core/dr-pipeline/src/operation.rs#L310), [`core/dr-pipeline/src/ops/film_sim.rs:129`](../core/dr-pipeline/src/ops/film_sim.rs#L129), [`core/dr-pipeline/src/ops/film_sim.rs:153`](../core/dr-pipeline/src/ops/film_sim.rs#L153), [`core/dr-pipeline/src/ops/film_sim.rs:1`](../core/dr-pipeline/src/ops/film_sim.rs#L1), [`core/dr-pipeline/src/ops/film_sim.rs:331`](../core/dr-pipeline/src/ops/film_sim.rs#L331), [`core/dr-pipeline/src/ops/film_sim.rs:43`](../core/dr-pipeline/src/ops/film_sim.rs#L43), [`core/dr-pipeline/src/ops/film_sim.rs:87`](../core/dr-pipeline/src/ops/film_sim.rs#L87), [`core/dr-pipeline/src/ops/film_sim.rs:92`](../core/dr-pipeline/src/ops/film_sim.rs#L92), [`core/dr-pipeline/src/sidecar.rs:112`](../core/dr-pipeline/src/sidecar.rs#L112), [`core/dr-pipeline/src/sidecar.rs:168`](../core/dr-pipeline/src/sidecar.rs#L168), [`core/dr-pipeline/src/sidecar.rs:1958`](../core/dr-pipeline/src/sidecar.rs#L1958), [`core/dr-pipeline/src/sidecar.rs:2034`](../core/dr-pipeline/src/sidecar.rs#L2034), [`core/dr-pipeline/src/sidecar.rs:534`](../core/dr-pipeline/src/sidecar.rs#L534), [`core/dr-pipeline/src/sidecar.rs:661`](../core/dr-pipeline/src/sidecar.rs#L661), [`core/dr-pipeline/src/sidecar.rs:793`](../core/dr-pipeline/src/sidecar.rs#L793), [`core/dr-pipeline/src/state.rs:100`](../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop.rs:3244`](../ui/dr-ui/src/develop.rs#L3244), [`ui/dr-ui/src/develop.rs:3261`](../ui/dr-ui/src/develop.rs#L3261), [`ui/dr-ui/src/develop.rs:3273`](../ui/dr-ui/src/develop.rs#L3273), [`ui/dr-ui/src/develop.rs:3311`](../ui/dr-ui/src/develop.rs#L3311), [`ui/dr-ui/src/develop.rs:3320`](../ui/dr-ui/src/develop.rs#L3320), [`ui/dr-ui/src/develop.rs:3423`](../ui/dr-ui/src/develop.rs#L3423), [`ui/dr-ui/src/develop.rs:3839`](../ui/dr-ui/src/develop.rs#L3839), [`ui/dr-ui/src/develop.rs:3854`](../ui/dr-ui/src/develop.rs#L3854), [`ui/dr-ui/src/lib.rs:2383`](../ui/dr-ui/src/lib.rs#L2383), [`ui/dr-ui/src/lib.rs:636`](../ui/dr-ui/src/lib.rs#L636), [`ui/dr-ui/src/lib.rs:694`](../ui/dr-ui/src/lib.rs#L694), [`ui/dr-ui/src/library.rs:530`](../ui/dr-ui/src/library.rs#L530), [`ui/dr-ui/src/library.rs:778`](../ui/dr-ui/src/library.rs#L778), [`ui/dr-ui/src/presets.rs:395`](../ui/dr-ui/src/presets.rs#L395), [`ui/dr-ui/ui/adjust.slint:1026`](../ui/dr-ui/ui/adjust.slint#L1026), [`ui/dr-ui/ui/adjust.slint:950`](../ui/dr-ui/ui/adjust.slint#L950), [`ui/dr-ui/ui/app.slint:2539`](../ui/dr-ui/ui/app.slint#L2539), [`ui/dr-ui/ui/app.slint:656`](../ui/dr-ui/ui/app.slint#L656) | | FR-DEV-3h | [`core/dr-decode/src/lib.rs:404`](../core/dr-decode/src/lib.rs#L404), [`core/dr-decode/src/preview.rs:29`](../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1050`](../core/dr-pipeline/src/framing.rs#L1050), [`core/dr-pipeline/src/framing.rs:328`](../core/dr-pipeline/src/framing.rs#L328), [`core/dr-pipeline/src/framing.rs:488`](../core/dr-pipeline/src/framing.rs#L488), [`core/dr-types/src/lib.rs:337`](../core/dr-types/src/lib.rs#L337), [`core/dr-types/src/lib.rs:445`](../core/dr-types/src/lib.rs#L445), [`core/dr-types/src/lib.rs:457`](../core/dr-types/src/lib.rs#L457), [`core/dr-types/src/lib.rs:473`](../core/dr-types/src/lib.rs#L473), [`ui/dr-ui/src/develop.rs:139`](../ui/dr-ui/src/develop.rs#L139), [`ui/dr-ui/src/develop.rs:2167`](../ui/dr-ui/src/develop.rs#L2167), [`ui/dr-ui/src/segmentation.rs:322`](../ui/dr-ui/src/segmentation.rs#L322) | | FR-DEV-4 | [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:351`](../core/dr-gpu/src/lib.rs#L351), [`ui/dr-ui/ui/crop.slint:1`](../ui/dr-ui/ui/crop.slint#L1) | -| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3273`](../ui/dr-ui/src/develop.rs#L3273), [`ui/dr-ui/src/develop.rs:3854`](../ui/dr-ui/src/develop.rs#L3854), [`ui/dr-ui/src/develop.rs:3884`](../ui/dr-ui/src/develop.rs#L3884), [`ui/dr-ui/src/develop.rs:3897`](../ui/dr-ui/src/develop.rs#L3897), [`ui/dr-ui/src/develop.rs:3909`](../ui/dr-ui/src/develop.rs#L3909), [`ui/dr-ui/src/develop.rs:3925`](../ui/dr-ui/src/develop.rs#L3925), [`ui/dr-ui/src/develop.rs:3957`](../ui/dr-ui/src/develop.rs#L3957), [`ui/dr-ui/src/develop.rs:3961`](../ui/dr-ui/src/develop.rs#L3961), [`ui/dr-ui/src/develop.rs:3980`](../ui/dr-ui/src/develop.rs#L3980), [`ui/dr-ui/src/develop.rs:3996`](../ui/dr-ui/src/develop.rs#L3996), [`ui/dr-ui/src/develop.rs:709`](../ui/dr-ui/src/develop.rs#L709), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:215`](../ui/dr-ui/src/labels.rs#L215), [`ui/dr-ui/src/lib.rs:1533`](../ui/dr-ui/src/lib.rs#L1533), [`ui/dr-ui/src/lib.rs:1576`](../ui/dr-ui/src/lib.rs#L1576), [`ui/dr-ui/src/lib.rs:1584`](../ui/dr-ui/src/lib.rs#L1584), [`ui/dr-ui/src/lib.rs:2570`](../ui/dr-ui/src/lib.rs#L2570), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:3796`](../ui/dr-ui/src/develop.rs#L3796), [`ui/dr-ui/src/develop.rs:3817`](../ui/dr-ui/src/develop.rs#L3817), [`ui/dr-ui/src/lib.rs:1500`](../ui/dr-ui/src/lib.rs#L1500), [`ui/dr-ui/src/lib.rs:2185`](../ui/dr-ui/src/lib.rs#L2185), [`ui/dr-ui/src/lib.rs:2214`](../ui/dr-ui/src/lib.rs#L2214), [`ui/dr-ui/src/library.rs:1903`](../ui/dr-ui/src/library.rs#L1903), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:2696`](../ui/dr-ui/src/library_ui.rs#L2696), [`ui/dr-ui/src/library_ui.rs:3096`](../ui/dr-ui/src/library_ui.rs#L3096), [`ui/dr-ui/src/library_ui.rs:494`](../ui/dr-ui/src/library_ui.rs#L494), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:657`](../ui/dr-ui/src/presets.rs#L657), [`ui/dr-ui/src/presets.rs:691`](../ui/dr-ui/src/presets.rs#L691), [`ui/dr-ui/ui/adjust.slint:694`](../ui/dr-ui/ui/adjust.slint#L694), [`ui/dr-ui/ui/adjust.slint:721`](../ui/dr-ui/ui/adjust.slint#L721), [`ui/dr-ui/ui/adjust.slint:755`](../ui/dr-ui/ui/adjust.slint#L755), [`ui/dr-ui/ui/app.slint:1524`](../ui/dr-ui/ui/app.slint#L1524), [`ui/dr-ui/ui/app.slint:2628`](../ui/dr-ui/ui/app.slint#L2628), [`ui/dr-ui/ui/app.slint:728`](../ui/dr-ui/ui/app.slint#L728), [`ui/dr-ui/ui/app.slint:739`](../ui/dr-ui/ui/app.slint#L739), [`ui/dr-ui/ui/library.slint:1509`](../ui/dr-ui/ui/library.slint#L1509), [`ui/dr-ui/ui/library.slint:1516`](../ui/dr-ui/ui/library.slint#L1516), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:866`](../ui/dr-ui/ui/library.slint#L866), [`ui/dr-ui/ui/library.slint:938`](../ui/dr-ui/ui/library.slint#L938), [`ui/dr-ui/ui/library.slint:953`](../ui/dr-ui/ui/library.slint#L953), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | -| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:3925`](../ui/dr-ui/src/develop.rs#L3925), [`ui/dr-ui/src/develop.rs:3957`](../ui/dr-ui/src/develop.rs#L3957), [`ui/dr-ui/src/lib.rs:1584`](../ui/dr-ui/src/lib.rs#L1584), [`ui/dr-ui/src/lib.rs:2570`](../ui/dr-ui/src/lib.rs#L2570), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | -| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:192`](../core/dr-pipeline/src/graph.rs#L192), [`core/dr-pipeline/src/graph.rs:686`](../core/dr-pipeline/src/graph.rs#L686), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:554`](../core/dr-pipeline/src/operation.rs#L554), [`core/dr-pipeline/src/sidecar.rs:184`](../core/dr-pipeline/src/sidecar.rs#L184), [`core/dr-pipeline/src/sidecar.rs:353`](../core/dr-pipeline/src/sidecar.rs#L353), [`core/dr-pipeline/src/sidecar.rs:673`](../core/dr-pipeline/src/sidecar.rs#L673), [`core/dr-pipeline/src/sidecar.rs:809`](../core/dr-pipeline/src/sidecar.rs#L809), [`core/dr-pipeline/src/sidecar.rs:863`](../core/dr-pipeline/src/sidecar.rs#L863), [`core/dr-pipeline/src/sidecar.rs:893`](../core/dr-pipeline/src/sidecar.rs#L893), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2319`](../ui/dr-ui/src/develop.rs#L2319), [`ui/dr-ui/src/develop.rs:2357`](../ui/dr-ui/src/develop.rs#L2357), [`ui/dr-ui/src/develop.rs:2422`](../ui/dr-ui/src/develop.rs#L2422), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2519`](../ui/dr-ui/src/develop.rs#L2519), [`ui/dr-ui/src/develop.rs:796`](../ui/dr-ui/src/develop.rs#L796), [`ui/dr-ui/src/labels.rs:52`](../ui/dr-ui/src/labels.rs#L52), [`ui/dr-ui/src/lib.rs:1605`](../ui/dr-ui/src/lib.rs#L1605), [`ui/dr-ui/src/lib.rs:2688`](../ui/dr-ui/src/lib.rs#L2688), [`ui/dr-ui/src/lib.rs:350`](../ui/dr-ui/src/lib.rs#L350), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:786`](../ui/dr-ui/ui/adjust.slint#L786), [`ui/dr-ui/ui/app.slint:139`](../ui/dr-ui/ui/app.slint#L139), [`ui/dr-ui/ui/app.slint:1874`](../ui/dr-ui/ui/app.slint#L1874), [`ui/dr-ui/ui/app.slint:2049`](../ui/dr-ui/ui/app.slint#L2049), [`ui/dr-ui/ui/app.slint:2445`](../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | -| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2126`](../core/dr-gpu/src/adjust.rs#L2126), [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:2288`](../core/dr-gpu/src/adjust.rs#L2288), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:548`](../core/dr-pipeline/src/graph.rs#L548), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2843`](../ui/dr-ui/src/develop.rs#L2843), [`ui/dr-ui/src/develop.rs:4251`](../ui/dr-ui/src/develop.rs#L4251), [`ui/dr-ui/src/develop.rs:4734`](../ui/dr-ui/src/develop.rs#L4734), [`ui/dr-ui/src/develop.rs:4768`](../ui/dr-ui/src/develop.rs#L4768), [`ui/dr-ui/src/lib.rs:81`](../ui/dr-ui/src/lib.rs#L81), [`ui/dr-ui/src/lib.rs:835`](../ui/dr-ui/src/lib.rs#L835), [`ui/dr-ui/src/lib.rs:894`](../ui/dr-ui/src/lib.rs#L894) | +| FR-DEV-5 | [`core/dr-pipeline/src/graph.rs:346`](../core/dr-pipeline/src/graph.rs#L346), [`core/dr-pipeline/src/graph.rs:385`](../core/dr-pipeline/src/graph.rs#L385), [`core/dr-pipeline/src/history.rs:102`](../core/dr-pipeline/src/history.rs#L102), [`core/dr-pipeline/src/history.rs:110`](../core/dr-pipeline/src/history.rs#L110), [`core/dr-pipeline/src/history.rs:127`](../core/dr-pipeline/src/history.rs#L127), [`core/dr-pipeline/src/history.rs:184`](../core/dr-pipeline/src/history.rs#L184), [`core/dr-pipeline/src/history.rs:1`](../core/dr-pipeline/src/history.rs#L1), [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:234`](../core/dr-pipeline/src/history.rs#L234), [`core/dr-pipeline/src/history.rs:293`](../core/dr-pipeline/src/history.rs#L293), [`core/dr-pipeline/src/history.rs:479`](../core/dr-pipeline/src/history.rs#L479), [`core/dr-pipeline/src/history.rs:489`](../core/dr-pipeline/src/history.rs#L489), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`core/dr-pipeline/src/state.rs:1`](../core/dr-pipeline/src/state.rs#L1), [`core/dr-pipeline/src/state.rs:75`](../core/dr-pipeline/src/state.rs#L75), [`ui/dr-ui/src/develop.rs:3273`](../ui/dr-ui/src/develop.rs#L3273), [`ui/dr-ui/src/develop.rs:3854`](../ui/dr-ui/src/develop.rs#L3854), [`ui/dr-ui/src/develop.rs:3884`](../ui/dr-ui/src/develop.rs#L3884), [`ui/dr-ui/src/develop.rs:3897`](../ui/dr-ui/src/develop.rs#L3897), [`ui/dr-ui/src/develop.rs:3909`](../ui/dr-ui/src/develop.rs#L3909), [`ui/dr-ui/src/develop.rs:3925`](../ui/dr-ui/src/develop.rs#L3925), [`ui/dr-ui/src/develop.rs:3957`](../ui/dr-ui/src/develop.rs#L3957), [`ui/dr-ui/src/develop.rs:3961`](../ui/dr-ui/src/develop.rs#L3961), [`ui/dr-ui/src/develop.rs:3980`](../ui/dr-ui/src/develop.rs#L3980), [`ui/dr-ui/src/develop.rs:3996`](../ui/dr-ui/src/develop.rs#L3996), [`ui/dr-ui/src/develop.rs:709`](../ui/dr-ui/src/develop.rs#L709), [`ui/dr-ui/src/labels.rs:12`](../ui/dr-ui/src/labels.rs#L12), [`ui/dr-ui/src/labels.rs:215`](../ui/dr-ui/src/labels.rs#L215), [`ui/dr-ui/src/lib.rs:1542`](../ui/dr-ui/src/lib.rs#L1542), [`ui/dr-ui/src/lib.rs:1585`](../ui/dr-ui/src/lib.rs#L1585), [`ui/dr-ui/src/lib.rs:1593`](../ui/dr-ui/src/lib.rs#L1593), [`ui/dr-ui/src/lib.rs:2579`](../ui/dr-ui/src/lib.rs#L2579), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-6 | [`core/dr-pipeline/src/preset.rs:1`](../core/dr-pipeline/src/preset.rs#L1), [`core/dr-pipeline/src/preset.rs:381`](../core/dr-pipeline/src/preset.rs#L381), [`core/dr-pipeline/src/preset.rs:414`](../core/dr-pipeline/src/preset.rs#L414), [`core/dr-pipeline/src/starter.rs:1`](../core/dr-pipeline/src/starter.rs#L1), [`core/dr-preset-xmp/src/lib.rs:1`](../core/dr-preset-xmp/src/lib.rs#L1), [`core/dr-types/src/settings.rs:272`](../core/dr-types/src/settings.rs#L272), [`ui/dr-ui/src/develop.rs:3796`](../ui/dr-ui/src/develop.rs#L3796), [`ui/dr-ui/src/develop.rs:3817`](../ui/dr-ui/src/develop.rs#L3817), [`ui/dr-ui/src/lib.rs:1509`](../ui/dr-ui/src/lib.rs#L1509), [`ui/dr-ui/src/lib.rs:2194`](../ui/dr-ui/src/lib.rs#L2194), [`ui/dr-ui/src/lib.rs:2223`](../ui/dr-ui/src/lib.rs#L2223), [`ui/dr-ui/src/library.rs:1903`](../ui/dr-ui/src/library.rs#L1903), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library.rs:520`](../ui/dr-ui/src/library.rs#L520), [`ui/dr-ui/src/library_ui.rs:2696`](../ui/dr-ui/src/library_ui.rs#L2696), [`ui/dr-ui/src/library_ui.rs:3096`](../ui/dr-ui/src/library_ui.rs#L3096), [`ui/dr-ui/src/library_ui.rs:494`](../ui/dr-ui/src/library_ui.rs#L494), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/presets.rs:1`](../ui/dr-ui/src/presets.rs#L1), [`ui/dr-ui/src/presets.rs:657`](../ui/dr-ui/src/presets.rs#L657), [`ui/dr-ui/src/presets.rs:691`](../ui/dr-ui/src/presets.rs#L691), [`ui/dr-ui/ui/adjust.slint:694`](../ui/dr-ui/ui/adjust.slint#L694), [`ui/dr-ui/ui/adjust.slint:721`](../ui/dr-ui/ui/adjust.slint#L721), [`ui/dr-ui/ui/adjust.slint:755`](../ui/dr-ui/ui/adjust.slint#L755), [`ui/dr-ui/ui/app.slint:1524`](../ui/dr-ui/ui/app.slint#L1524), [`ui/dr-ui/ui/app.slint:2628`](../ui/dr-ui/ui/app.slint#L2628), [`ui/dr-ui/ui/app.slint:728`](../ui/dr-ui/ui/app.slint#L728), [`ui/dr-ui/ui/app.slint:739`](../ui/dr-ui/ui/app.slint#L739), [`ui/dr-ui/ui/library.slint:1509`](../ui/dr-ui/ui/library.slint#L1509), [`ui/dr-ui/ui/library.slint:1516`](../ui/dr-ui/ui/library.slint#L1516), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:866`](../ui/dr-ui/ui/library.slint#L866), [`ui/dr-ui/ui/library.slint:938`](../ui/dr-ui/ui/library.slint#L938), [`ui/dr-ui/ui/library.slint:953`](../ui/dr-ui/ui/library.slint#L953), [`ui/dr-ui/ui/presets.slint:105`](../ui/dr-ui/ui/presets.slint#L105), [`ui/dr-ui/ui/presets.slint:113`](../ui/dr-ui/ui/presets.slint#L113), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/presets.slint:276`](../ui/dr-ui/ui/presets.slint#L276), [`ui/dr-ui/ui/presets.slint:297`](../ui/dr-ui/ui/presets.slint#L297), [`ui/dr-ui/ui/presets.slint:5`](../ui/dr-ui/ui/presets.slint#L5), [`ui/dr-ui/ui/presets.slint:60`](../ui/dr-ui/ui/presets.slint#L60), [`ui/dr-ui/ui/settings.slint:101`](../ui/dr-ui/ui/settings.slint#L101), [`ui/dr-ui/ui/settings.slint:557`](../ui/dr-ui/ui/settings.slint#L557) | +| FR-DEV-7 | [`core/dr-pipeline/src/history.rs:214`](../core/dr-pipeline/src/history.rs#L214), [`core/dr-pipeline/src/history.rs:499`](../core/dr-pipeline/src/history.rs#L499), [`core/dr-pipeline/src/history.rs:526`](../core/dr-pipeline/src/history.rs#L526), [`ui/dr-ui/src/develop.rs:3925`](../ui/dr-ui/src/develop.rs#L3925), [`ui/dr-ui/src/develop.rs:3957`](../ui/dr-ui/src/develop.rs#L3957), [`ui/dr-ui/src/lib.rs:1593`](../ui/dr-ui/src/lib.rs#L1593), [`ui/dr-ui/src/lib.rs:2579`](../ui/dr-ui/src/lib.rs#L2579), [`ui/dr-ui/ui/history.slint:1`](../ui/dr-ui/ui/history.slint#L1) | +| FR-DEV-8 | [`core/dr-gpu/src/detail.rs:405`](../core/dr-gpu/src/detail.rs#L405), [`core/dr-gpu/src/detail.rs:623`](../core/dr-gpu/src/detail.rs#L623), [`core/dr-gpu/tests/detail_instances.rs:1`](../core/dr-gpu/tests/detail_instances.rs#L1), [`core/dr-gpu/tests/spot_removal.rs:1`](../core/dr-gpu/tests/spot_removal.rs#L1), [`core/dr-pipeline/src/detail.rs:410`](../core/dr-pipeline/src/detail.rs#L410), [`core/dr-pipeline/src/detail.rs:434`](../core/dr-pipeline/src/detail.rs#L434), [`core/dr-pipeline/src/detail.rs:469`](../core/dr-pipeline/src/detail.rs#L469), [`core/dr-pipeline/src/detail.rs:555`](../core/dr-pipeline/src/detail.rs#L555), [`core/dr-pipeline/src/graph.rs:114`](../core/dr-pipeline/src/graph.rs#L114), [`core/dr-pipeline/src/graph.rs:192`](../core/dr-pipeline/src/graph.rs#L192), [`core/dr-pipeline/src/graph.rs:686`](../core/dr-pipeline/src/graph.rs#L686), [`core/dr-pipeline/src/operation.rs:330`](../core/dr-pipeline/src/operation.rs#L330), [`core/dr-pipeline/src/operation.rs:554`](../core/dr-pipeline/src/operation.rs#L554), [`core/dr-pipeline/src/sidecar.rs:184`](../core/dr-pipeline/src/sidecar.rs#L184), [`core/dr-pipeline/src/sidecar.rs:353`](../core/dr-pipeline/src/sidecar.rs#L353), [`core/dr-pipeline/src/sidecar.rs:673`](../core/dr-pipeline/src/sidecar.rs#L673), [`core/dr-pipeline/src/sidecar.rs:809`](../core/dr-pipeline/src/sidecar.rs#L809), [`core/dr-pipeline/src/sidecar.rs:863`](../core/dr-pipeline/src/sidecar.rs#L863), [`core/dr-pipeline/src/sidecar.rs:893`](../core/dr-pipeline/src/sidecar.rs#L893), [`core/dr-pipeline/src/spot.rs:115`](../core/dr-pipeline/src/spot.rs#L115), [`core/dr-pipeline/src/spot.rs:151`](../core/dr-pipeline/src/spot.rs#L151), [`core/dr-pipeline/src/spot.rs:1`](../core/dr-pipeline/src/spot.rs#L1), [`core/dr-pipeline/src/spot.rs:207`](../core/dr-pipeline/src/spot.rs#L207), [`core/dr-pipeline/src/spot.rs:387`](../core/dr-pipeline/src/spot.rs#L387), [`core/dr-pipeline/src/spot.rs:472`](../core/dr-pipeline/src/spot.rs#L472), [`core/dr-pipeline/src/spot.rs:582`](../core/dr-pipeline/src/spot.rs#L582), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`core/dr-pipeline/src/state.rs:103`](../core/dr-pipeline/src/state.rs#L103), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`core/dr-pipeline/tests/spots.rs:1`](../core/dr-pipeline/tests/spots.rs#L1), [`ui/dr-ui/src/develop.rs:2319`](../ui/dr-ui/src/develop.rs#L2319), [`ui/dr-ui/src/develop.rs:2357`](../ui/dr-ui/src/develop.rs#L2357), [`ui/dr-ui/src/develop.rs:2422`](../ui/dr-ui/src/develop.rs#L2422), [`ui/dr-ui/src/develop.rs:2505`](../ui/dr-ui/src/develop.rs#L2505), [`ui/dr-ui/src/develop.rs:2519`](../ui/dr-ui/src/develop.rs#L2519), [`ui/dr-ui/src/develop.rs:796`](../ui/dr-ui/src/develop.rs#L796), [`ui/dr-ui/src/labels.rs:52`](../ui/dr-ui/src/labels.rs#L52), [`ui/dr-ui/src/lib.rs:1614`](../ui/dr-ui/src/lib.rs#L1614), [`ui/dr-ui/src/lib.rs:2697`](../ui/dr-ui/src/lib.rs#L2697), [`ui/dr-ui/src/lib.rs:359`](../ui/dr-ui/src/lib.rs#L359), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/src/spots_ui.rs:1`](../ui/dr-ui/src/spots_ui.rs#L1), [`ui/dr-ui/src/spots_ui.rs:265`](../ui/dr-ui/src/spots_ui.rs#L265), [`ui/dr-ui/ui/adjust.slint:786`](../ui/dr-ui/ui/adjust.slint#L786), [`ui/dr-ui/ui/app.slint:139`](../ui/dr-ui/ui/app.slint#L139), [`ui/dr-ui/ui/app.slint:1874`](../ui/dr-ui/ui/app.slint#L1874), [`ui/dr-ui/ui/app.slint:2049`](../ui/dr-ui/ui/app.slint#L2049), [`ui/dr-ui/ui/app.slint:2445`](../ui/dr-ui/ui/app.slint#L2445), [`ui/dr-ui/ui/icons.slint:338`](../ui/dr-ui/ui/icons.slint#L338), [`ui/dr-ui/ui/spots.slint:172`](../ui/dr-ui/ui/spots.slint#L172), [`ui/dr-ui/ui/spots.slint:48`](../ui/dr-ui/ui/spots.slint#L48), [`ui/dr-ui/ui/spots.slint:5`](../ui/dr-ui/ui/spots.slint#L5) | +| FR-DSP-1 | [`core/dr-gpu/src/adjust.rs:2126`](../core/dr-gpu/src/adjust.rs#L2126), [`core/dr-gpu/src/adjust.rs:2203`](../core/dr-gpu/src/adjust.rs#L2203), [`core/dr-gpu/src/adjust.rs:2288`](../core/dr-gpu/src/adjust.rs#L2288), [`core/dr-gpu/src/adjust.rs:54`](../core/dr-gpu/src/adjust.rs#L54), [`core/dr-gpu/src/adjust.rs:770`](../core/dr-gpu/src/adjust.rs#L770), [`core/dr-gpu/src/lib.rs:172`](../core/dr-gpu/src/lib.rs#L172), [`core/dr-gpu/src/lib.rs:192`](../core/dr-gpu/src/lib.rs#L192), [`core/dr-gpu/src/lib.rs:70`](../core/dr-gpu/src/lib.rs#L70), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-gpu/tests/capture_sharpen.rs:200`](../core/dr-gpu/tests/capture_sharpen.rs#L200), [`core/dr-gpu/tests/detail_stage.rs:328`](../core/dr-gpu/tests/detail_stage.rs#L328), [`core/dr-gpu/tests/local_contrast.rs:264`](../core/dr-gpu/tests/local_contrast.rs#L264), [`core/dr-gpu/tests/noise_reduction.rs:378`](../core/dr-gpu/tests/noise_reduction.rs#L378), [`core/dr-pipeline/src/detail.rs:136`](../core/dr-pipeline/src/detail.rs#L136), [`core/dr-pipeline/src/detail.rs:524`](../core/dr-pipeline/src/detail.rs#L524), [`core/dr-pipeline/src/graph.rs:548`](../core/dr-pipeline/src/graph.rs#L548), [`core/dr-pipeline/src/graph.rs:578`](../core/dr-pipeline/src/graph.rs#L578), [`core/dr-pipeline/src/ops/capture_sharpen.rs:1`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L1), [`core/dr-pipeline/src/ops/capture_sharpen.rs:659`](../core/dr-pipeline/src/ops/capture_sharpen.rs#L659), [`core/dr-pipeline/src/ops/local_contrast.rs:1`](../core/dr-pipeline/src/ops/local_contrast.rs#L1), [`core/dr-pipeline/src/ops/local_contrast.rs:971`](../core/dr-pipeline/src/ops/local_contrast.rs#L971), [`core/dr-pipeline/src/ops/noise_reduction.rs:700`](../core/dr-pipeline/src/ops/noise_reduction.rs#L700), [`core/dr-pipeline/src/spot.rs:673`](../core/dr-pipeline/src/spot.rs#L673), [`ui/dr-ui/src/develop.rs:2843`](../ui/dr-ui/src/develop.rs#L2843), [`ui/dr-ui/src/develop.rs:4251`](../ui/dr-ui/src/develop.rs#L4251), [`ui/dr-ui/src/develop.rs:4734`](../ui/dr-ui/src/develop.rs#L4734), [`ui/dr-ui/src/develop.rs:4768`](../ui/dr-ui/src/develop.rs#L4768), [`ui/dr-ui/src/lib.rs:844`](../ui/dr-ui/src/lib.rs#L844), [`ui/dr-ui/src/lib.rs:903`](../ui/dr-ui/src/lib.rs#L903), [`ui/dr-ui/src/lib.rs:90`](../ui/dr-ui/src/lib.rs#L90) | | FR-DSP-3 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/local_contrast.rs:329`](../core/dr-gpu/tests/local_contrast.rs#L329), [`core/dr-pipeline/src/ops/local_contrast.rs:447`](../core/dr-pipeline/src/ops/local_contrast.rs#L447) | | FR-DSP-5 | [`core/dr-gpu/tests/frame_budget.rs:101`](../core/dr-gpu/tests/frame_budget.rs#L101), [`core/dr-gpu/tests/zoom_resolution.rs:135`](../core/dr-gpu/tests/zoom_resolution.rs#L135), [`core/dr-gpu/tests/zoom_resolution.rs:166`](../core/dr-gpu/tests/zoom_resolution.rs#L166), [`core/dr-gpu/tests/zoom_resolution.rs:1`](../core/dr-gpu/tests/zoom_resolution.rs#L1), [`core/dr-gpu/tests/zoom_resolution.rs:216`](../core/dr-gpu/tests/zoom_resolution.rs#L216) | -| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:483`](../core/dr-pipeline/src/operation.rs#L483), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:2873`](../ui/dr-ui/src/develop.rs#L2873), [`ui/dr-ui/src/develop.rs:6162`](../ui/dr-ui/src/develop.rs#L6162), [`ui/dr-ui/src/lib.rs:1561`](../ui/dr-ui/src/lib.rs#L1561), [`ui/dr-ui/src/lib.rs:3071`](../ui/dr-ui/src/lib.rs#L3071) | -| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2922`](../ui/dr-ui/src/develop.rs#L2922), [`ui/dr-ui/src/develop.rs:5836`](../ui/dr-ui/src/develop.rs#L5836), [`ui/dr-ui/src/develop.rs:5868`](../ui/dr-ui/src/develop.rs#L5868), [`ui/dr-ui/src/develop.rs:720`](../ui/dr-ui/src/develop.rs#L720), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1671`](../ui/dr-ui/src/lib.rs#L1671), [`ui/dr-ui/src/lib.rs:329`](../ui/dr-ui/src/lib.rs#L329), [`ui/dr-ui/ui/app.slint:73`](../ui/dr-ui/ui/app.slint#L73), [`ui/dr-ui/ui/histogram.slint:167`](../ui/dr-ui/ui/histogram.slint#L167), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | -| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:2819`](../ui/dr-ui/src/develop.rs#L2819), [`ui/dr-ui/src/develop.rs:2873`](../ui/dr-ui/src/develop.rs#L2873), [`ui/dr-ui/src/develop.rs:6162`](../ui/dr-ui/src/develop.rs#L6162), [`ui/dr-ui/src/develop.rs:6208`](../ui/dr-ui/src/develop.rs#L6208), [`ui/dr-ui/src/develop.rs:6227`](../ui/dr-ui/src/develop.rs#L6227), [`ui/dr-ui/src/develop.rs:827`](../ui/dr-ui/src/develop.rs#L827), [`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:1522`](../ui/dr-ui/src/lib.rs#L1522), [`ui/dr-ui/src/lib.rs:1561`](../ui/dr-ui/src/lib.rs#L1561), [`ui/dr-ui/src/lib.rs:2984`](../ui/dr-ui/src/lib.rs#L2984), [`ui/dr-ui/src/lib.rs:3071`](../ui/dr-ui/src/lib.rs#L3071), [`ui/dr-ui/ui/app.slint:1722`](../ui/dr-ui/ui/app.slint#L1722), [`ui/dr-ui/ui/app.slint:52`](../ui/dr-ui/ui/app.slint#L52), [`ui/dr-ui/ui/settings.slint:124`](../ui/dr-ui/ui/settings.slint#L124), [`ui/dr-ui/ui/settings.slint:794`](../ui/dr-ui/ui/settings.slint#L794) | +| FR-DSP-6 | [`core/dr-pipeline/src/operation.rs:483`](../core/dr-pipeline/src/operation.rs#L483), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`ui/dr-ui/src/develop.rs:2873`](../ui/dr-ui/src/develop.rs#L2873), [`ui/dr-ui/src/develop.rs:6162`](../ui/dr-ui/src/develop.rs#L6162), [`ui/dr-ui/src/lib.rs:1570`](../ui/dr-ui/src/lib.rs#L1570), [`ui/dr-ui/src/lib.rs:3080`](../ui/dr-ui/src/lib.rs#L3080) | +| FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:2922`](../ui/dr-ui/src/develop.rs#L2922), [`ui/dr-ui/src/develop.rs:5836`](../ui/dr-ui/src/develop.rs#L5836), [`ui/dr-ui/src/develop.rs:5868`](../ui/dr-ui/src/develop.rs#L5868), [`ui/dr-ui/src/develop.rs:720`](../ui/dr-ui/src/develop.rs#L720), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:1680`](../ui/dr-ui/src/lib.rs#L1680), [`ui/dr-ui/src/lib.rs:338`](../ui/dr-ui/src/lib.rs#L338), [`ui/dr-ui/ui/app.slint:73`](../ui/dr-ui/ui/app.slint#L73), [`ui/dr-ui/ui/histogram.slint:167`](../ui/dr-ui/ui/histogram.slint#L167), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | +| FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:2819`](../ui/dr-ui/src/develop.rs#L2819), [`ui/dr-ui/src/develop.rs:2873`](../ui/dr-ui/src/develop.rs#L2873), [`ui/dr-ui/src/develop.rs:6162`](../ui/dr-ui/src/develop.rs#L6162), [`ui/dr-ui/src/develop.rs:6208`](../ui/dr-ui/src/develop.rs#L6208), [`ui/dr-ui/src/develop.rs:6227`](../ui/dr-ui/src/develop.rs#L6227), [`ui/dr-ui/src/develop.rs:827`](../ui/dr-ui/src/develop.rs#L827), [`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:1531`](../ui/dr-ui/src/lib.rs#L1531), [`ui/dr-ui/src/lib.rs:1570`](../ui/dr-ui/src/lib.rs#L1570), [`ui/dr-ui/src/lib.rs:2993`](../ui/dr-ui/src/lib.rs#L2993), [`ui/dr-ui/src/lib.rs:3080`](../ui/dr-ui/src/lib.rs#L3080), [`ui/dr-ui/ui/app.slint:1722`](../ui/dr-ui/ui/app.slint#L1722), [`ui/dr-ui/ui/app.slint:52`](../ui/dr-ui/ui/app.slint#L52), [`ui/dr-ui/ui/settings.slint:124`](../ui/dr-ui/ui/settings.slint#L124), [`ui/dr-ui/ui/settings.slint:794`](../ui/dr-ui/ui/settings.slint#L794) | | FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:153`](../core/dr-export/src/lib.rs#L153), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:53`](../core/dr-export/src/lib.rs#L53), [`core/dr-gpu/src/adjust.rs:2436`](../core/dr-gpu/src/adjust.rs#L2436), [`core/dr-pipeline/src/graph.rs:538`](../core/dr-pipeline/src/graph.rs#L538), [`core/dr-pipeline/src/graph.rs:592`](../core/dr-pipeline/src/graph.rs#L592), [`core/dr-pipeline/src/operation.rs:483`](../core/dr-pipeline/src/operation.rs#L483), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:713`](../core/dr-types/src/settings.rs#L713), [`ui/dr-ui/src/develop.rs:6227`](../ui/dr-ui/src/develop.rs#L6227), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-3 | [`core/dr-export/src/lib.rs:182`](../core/dr-export/src/lib.rs#L182), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:758`](../core/dr-types/src/settings.rs#L758), [`core/dr-types/src/settings.rs:766`](../core/dr-types/src/settings.rs#L766), [`core/dr-types/src/settings.rs:782`](../core/dr-types/src/settings.rs#L782), [`core/dr-types/src/settings.rs:851`](../core/dr-types/src/settings.rs#L851), [`core/dr-types/src/settings.rs:879`](../core/dr-types/src/settings.rs#L879), [`core/dr-types/src/settings.rs:890`](../core/dr-types/src/settings.rs#L890), [`core/dr-types/src/settings.rs:896`](../core/dr-types/src/settings.rs#L896), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:207`](../ui/dr-ui/src/settings_ui.rs#L207), [`ui/dr-ui/src/settings_ui.rs:220`](../ui/dr-ui/src/settings_ui.rs#L220), [`ui/dr-ui/src/settings_ui.rs:235`](../ui/dr-ui/src/settings_ui.rs#L235), [`ui/dr-ui/src/settings_ui.rs:565`](../ui/dr-ui/src/settings_ui.rs#L565), [`ui/dr-ui/ui/settings.slint:697`](../ui/dr-ui/ui/settings.slint#L697), [`ui/dr-ui/ui/settings.slint:707`](../ui/dr-ui/ui/settings.slint#L707) | | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | -| FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:440`](../ui/dr-ui/src/lib.rs#L440), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:679`](../ui/dr-ui/src/settings_ui.rs#L679) | -| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/lib.rs:219`](../ui/dr-ui/src/lib.rs#L219), [`ui/dr-ui/src/lib.rs:2316`](../ui/dr-ui/src/lib.rs#L2316), [`ui/dr-ui/src/lib.rs:440`](../ui/dr-ui/src/lib.rs#L440), [`ui/dr-ui/src/lib.rs:475`](../ui/dr-ui/src/lib.rs#L475), [`ui/dr-ui/src/lib.rs:502`](../ui/dr-ui/src/lib.rs#L502), [`ui/dr-ui/src/library_ui.rs:3479`](../ui/dr-ui/src/library_ui.rs#L3479), [`ui/dr-ui/src/library_ui.rs:585`](../ui/dr-ui/src/library_ui.rs#L585), [`ui/dr-ui/src/library_ui.rs:6660`](../ui/dr-ui/src/library_ui.rs#L6660), [`ui/dr-ui/src/library_ui.rs:6737`](../ui/dr-ui/src/library_ui.rs#L6737), [`ui/dr-ui/src/library_ui.rs:6749`](../ui/dr-ui/src/library_ui.rs#L6749), [`ui/dr-ui/src/library_ui.rs:685`](../ui/dr-ui/src/library_ui.rs#L685), [`ui/dr-ui/src/library_ui.rs:742`](../ui/dr-ui/src/library_ui.rs#L742), [`ui/dr-ui/ui/app.slint:1064`](../ui/dr-ui/ui/app.slint#L1064), [`ui/dr-ui/ui/app.slint:1534`](../ui/dr-ui/ui/app.slint#L1534), [`ui/dr-ui/ui/library.slint:1519`](../ui/dr-ui/ui/library.slint#L1519), [`ui/dr-ui/ui/library.slint:854`](../ui/dr-ui/ui/library.slint#L854), [`ui/dr-ui/ui/library.slint:967`](../ui/dr-ui/ui/library.slint#L967) | +| FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:449`](../ui/dr-ui/src/lib.rs#L449), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:679`](../ui/dr-ui/src/settings_ui.rs#L679) | +| FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/lib.rs:228`](../ui/dr-ui/src/lib.rs#L228), [`ui/dr-ui/src/lib.rs:2325`](../ui/dr-ui/src/lib.rs#L2325), [`ui/dr-ui/src/lib.rs:449`](../ui/dr-ui/src/lib.rs#L449), [`ui/dr-ui/src/lib.rs:484`](../ui/dr-ui/src/lib.rs#L484), [`ui/dr-ui/src/lib.rs:511`](../ui/dr-ui/src/lib.rs#L511), [`ui/dr-ui/src/library_ui.rs:3479`](../ui/dr-ui/src/library_ui.rs#L3479), [`ui/dr-ui/src/library_ui.rs:585`](../ui/dr-ui/src/library_ui.rs#L585), [`ui/dr-ui/src/library_ui.rs:6660`](../ui/dr-ui/src/library_ui.rs#L6660), [`ui/dr-ui/src/library_ui.rs:6737`](../ui/dr-ui/src/library_ui.rs#L6737), [`ui/dr-ui/src/library_ui.rs:6749`](../ui/dr-ui/src/library_ui.rs#L6749), [`ui/dr-ui/src/library_ui.rs:685`](../ui/dr-ui/src/library_ui.rs#L685), [`ui/dr-ui/src/library_ui.rs:742`](../ui/dr-ui/src/library_ui.rs#L742), [`ui/dr-ui/ui/app.slint:1064`](../ui/dr-ui/ui/app.slint#L1064), [`ui/dr-ui/ui/app.slint:1534`](../ui/dr-ui/ui/app.slint#L1534), [`ui/dr-ui/ui/library.slint:1519`](../ui/dr-ui/ui/library.slint#L1519), [`ui/dr-ui/ui/library.slint:854`](../ui/dr-ui/ui/library.slint#L854), [`ui/dr-ui/ui/library.slint:967`](../ui/dr-ui/ui/library.slint#L967) | | FR-EXP-8 | [`core/dr-decode/src/lib.rs:326`](../core/dr-decode/src/lib.rs#L326), [`core/dr-decode/src/lib.rs:350`](../core/dr-decode/src/lib.rs#L350), [`core/dr-decode/src/lib.rs:364`](../core/dr-decode/src/lib.rs#L364), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:136`](../core/dr-export/src/lib.rs#L136), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:653`](../core/dr-types/src/lib.rs#L653), [`core/dr-types/src/settings.rs:426`](../core/dr-types/src/settings.rs#L426), [`ui/dr-ui/src/export.rs:619`](../ui/dr-ui/src/export.rs#L619), [`ui/dr-ui/src/export.rs:647`](../ui/dr-ui/src/export.rs#L647), [`ui/dr-ui/src/export.rs:777`](../ui/dr-ui/src/export.rs#L777), [`ui/dr-ui/src/export.rs:794`](../ui/dr-ui/src/export.rs#L794), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1106`](../core/dr-gpu/src/adjust.rs#L1106), [`ui/dr-ui/src/develop.rs:3188`](../ui/dr-ui/src/develop.rs#L3188), [`ui/dr-ui/src/lib.rs:440`](../ui/dr-ui/src/lib.rs#L440) | +| FR-EXP-9 | [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-export/src/lib.rs:128`](../core/dr-export/src/lib.rs#L128), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1106`](../core/dr-gpu/src/adjust.rs#L1106), [`ui/dr-ui/src/develop.rs:3188`](../ui/dr-ui/src/develop.rs#L3188), [`ui/dr-ui/src/lib.rs:449`](../ui/dr-ui/src/lib.rs#L449) | | FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`ui/dr-ui/src/launch.rs:277`](../ui/dr-ui/src/launch.rs#L277), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:417`](../ui/dr-ui/src/launch_ui.rs#L417) | -| FR-NC-10 | [`core/dr-sync/src/account.rs:220`](../core/dr-sync/src/account.rs#L220), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:502`](../ui/dr-ui/src/lib.rs#L502), [`ui/dr-ui/src/library.rs:1101`](../ui/dr-ui/src/library.rs#L1101), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1696`](../ui/dr-ui/src/library_ui.rs#L1696), [`ui/dr-ui/src/library_ui.rs:3479`](../ui/dr-ui/src/library_ui.rs#L3479), [`ui/dr-ui/src/library_ui.rs:526`](../ui/dr-ui/src/library_ui.rs#L526), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | +| FR-NC-10 | [`core/dr-sync/src/account.rs:220`](../core/dr-sync/src/account.rs#L220), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:511`](../ui/dr-ui/src/lib.rs#L511), [`ui/dr-ui/src/library.rs:1101`](../ui/dr-ui/src/library.rs#L1101), [`ui/dr-ui/src/library.rs:1939`](../ui/dr-ui/src/library.rs#L1939), [`ui/dr-ui/src/library.rs:576`](../ui/dr-ui/src/library.rs#L576), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877), [`ui/dr-ui/src/library_ui.rs:1696`](../ui/dr-ui/src/library_ui.rs#L1696), [`ui/dr-ui/src/library_ui.rs:3479`](../ui/dr-ui/src/library_ui.rs#L3479), [`ui/dr-ui/src/library_ui.rs:526`](../ui/dr-ui/src/library_ui.rs#L526), [`ui/dr-ui/src/sidecar_cache.rs:1`](../ui/dr-ui/src/sidecar_cache.rs#L1) | | FR-NC-12 | [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:40`](../core/dr-sync-nextcloud/src/lib.rs#L40), [`core/dr-sync-nextcloud/src/lib.rs:914`](../core/dr-sync-nextcloud/src/lib.rs#L914), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:81`](../core/dr-sync/src/account.rs#L81), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/lib.rs:51`](../core/dr-sync/src/lib.rs#L51), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`core/dr-sync/src/provider.rs:1`](../core/dr-sync/src/provider.rs#L1), [`core/dr-sync/src/provider.rs:53`](../core/dr-sync/src/provider.rs#L53), [`core/dr-sync/src/reachability.rs:1`](../core/dr-sync/src/reachability.rs#L1), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-13 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync/src/provider.rs:106`](../core/dr-sync/src/provider.rs#L106), [`ui/dr-ui/src/launch_ui.rs:316`](../ui/dr-ui/src/launch_ui.rs#L316), [`ui/dr-ui/src/remote.rs:1`](../ui/dr-ui/src/remote.rs#L1) | | FR-NC-2 | [`core/dr-sync/src/account.rs:1`](../core/dr-sync/src/account.rs#L1), [`core/dr-sync/src/account.rs:298`](../core/dr-sync/src/account.rs#L298), [`core/dr-sync/src/account.rs:355`](../core/dr-sync/src/account.rs#L355), [`core/dr-sync/src/account.rs:59`](../core/dr-sync/src/account.rs#L59), [`platform/dr-plat/src/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | @@ -97,45 +97,45 @@ _None._ | FR-NC-4 | [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-nextcloud/src/propfind.rs:103`](../core/dr-sync-nextcloud/src/propfind.rs#L103), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:218`](../core/dr-sync/src/lib.rs#L218), [`core/dr-sync/src/scan.rs:93`](../core/dr-sync/src/scan.rs#L93), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61) | | FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`ui/dr-ui/src/import.rs:488`](../ui/dr-ui/src/import.rs#L488) | | FR-NC-6 | [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1) | -| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1093`](../core/dr-catalog/src/schema.rs#L1093), [`core/dr-catalog/src/schema.rs:692`](../core/dr-catalog/src/schema.rs#L692), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3388`](../ui/dr-ui/src/collections_ui.rs#L3388), [`ui/dr-ui/src/collections_ui.rs:632`](../ui/dr-ui/src/collections_ui.rs#L632), [`ui/dr-ui/src/collections_ui.rs:700`](../ui/dr-ui/src/collections_ui.rs#L700), [`ui/dr-ui/src/lib.rs:2020`](../ui/dr-ui/src/lib.rs#L2020), [`ui/dr-ui/src/lib.rs:3134`](../ui/dr-ui/src/lib.rs#L3134), [`ui/dr-ui/src/library.rs:1565`](../ui/dr-ui/src/library.rs#L1565), [`ui/dr-ui/src/library.rs:1588`](../ui/dr-ui/src/library.rs#L1588), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library_ui.rs:1121`](../ui/dr-ui/src/library_ui.rs#L1121), [`ui/dr-ui/src/library_ui.rs:1194`](../ui/dr-ui/src/library_ui.rs#L1194), [`ui/dr-ui/src/library_ui.rs:1295`](../ui/dr-ui/src/library_ui.rs#L1295), [`ui/dr-ui/src/library_ui.rs:1350`](../ui/dr-ui/src/library_ui.rs#L1350), [`ui/dr-ui/src/library_ui.rs:1485`](../ui/dr-ui/src/library_ui.rs#L1485), [`ui/dr-ui/src/library_ui.rs:1603`](../ui/dr-ui/src/library_ui.rs#L1603), [`ui/dr-ui/src/library_ui.rs:2171`](../ui/dr-ui/src/library_ui.rs#L2171), [`ui/dr-ui/src/library_ui.rs:289`](../ui/dr-ui/src/library_ui.rs#L289), [`ui/dr-ui/src/library_ui.rs:300`](../ui/dr-ui/src/library_ui.rs#L300), [`ui/dr-ui/src/library_ui.rs:308`](../ui/dr-ui/src/library_ui.rs#L308), [`ui/dr-ui/src/library_ui.rs:320`](../ui/dr-ui/src/library_ui.rs#L320), [`ui/dr-ui/src/library_ui.rs:329`](../ui/dr-ui/src/library_ui.rs#L329), [`ui/dr-ui/src/library_ui.rs:422`](../ui/dr-ui/src/library_ui.rs#L422), [`ui/dr-ui/src/library_ui.rs:432`](../ui/dr-ui/src/library_ui.rs#L432), [`ui/dr-ui/src/library_ui.rs:479`](../ui/dr-ui/src/library_ui.rs#L479), [`ui/dr-ui/src/library_ui.rs:542`](../ui/dr-ui/src/library_ui.rs#L542), [`ui/dr-ui/src/library_ui.rs:5533`](../ui/dr-ui/src/library_ui.rs#L5533), [`ui/dr-ui/src/library_ui.rs:5551`](../ui/dr-ui/src/library_ui.rs#L5551), [`ui/dr-ui/src/library_ui.rs:5563`](../ui/dr-ui/src/library_ui.rs#L5563), [`ui/dr-ui/src/library_ui.rs:573`](../ui/dr-ui/src/library_ui.rs#L573), [`ui/dr-ui/src/library_ui.rs:585`](../ui/dr-ui/src/library_ui.rs#L585), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:2618`](../ui/dr-ui/ui/app.slint#L2618), [`ui/dr-ui/ui/app.slint:511`](../ui/dr-ui/ui/app.slint#L511), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261), [`ui/dr-ui/ui/library.slint:1013`](../ui/dr-ui/ui/library.slint#L1013) | +| FR-NC-6a | [`core/dr-catalog/src/cache.rs:1`](../core/dr-catalog/src/cache.rs#L1), [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-catalog/src/schema.rs:1093`](../core/dr-catalog/src/schema.rs#L1093), [`core/dr-catalog/src/schema.rs:692`](../core/dr-catalog/src/schema.rs#L692), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3388`](../ui/dr-ui/src/collections_ui.rs#L3388), [`ui/dr-ui/src/collections_ui.rs:632`](../ui/dr-ui/src/collections_ui.rs#L632), [`ui/dr-ui/src/collections_ui.rs:700`](../ui/dr-ui/src/collections_ui.rs#L700), [`ui/dr-ui/src/lib.rs:2029`](../ui/dr-ui/src/lib.rs#L2029), [`ui/dr-ui/src/lib.rs:3143`](../ui/dr-ui/src/lib.rs#L3143), [`ui/dr-ui/src/library.rs:1565`](../ui/dr-ui/src/library.rs#L1565), [`ui/dr-ui/src/library.rs:1588`](../ui/dr-ui/src/library.rs#L1588), [`ui/dr-ui/src/library.rs:1863`](../ui/dr-ui/src/library.rs#L1863), [`ui/dr-ui/src/library_ui.rs:1121`](../ui/dr-ui/src/library_ui.rs#L1121), [`ui/dr-ui/src/library_ui.rs:1194`](../ui/dr-ui/src/library_ui.rs#L1194), [`ui/dr-ui/src/library_ui.rs:1295`](../ui/dr-ui/src/library_ui.rs#L1295), [`ui/dr-ui/src/library_ui.rs:1350`](../ui/dr-ui/src/library_ui.rs#L1350), [`ui/dr-ui/src/library_ui.rs:1485`](../ui/dr-ui/src/library_ui.rs#L1485), [`ui/dr-ui/src/library_ui.rs:1603`](../ui/dr-ui/src/library_ui.rs#L1603), [`ui/dr-ui/src/library_ui.rs:2171`](../ui/dr-ui/src/library_ui.rs#L2171), [`ui/dr-ui/src/library_ui.rs:289`](../ui/dr-ui/src/library_ui.rs#L289), [`ui/dr-ui/src/library_ui.rs:300`](../ui/dr-ui/src/library_ui.rs#L300), [`ui/dr-ui/src/library_ui.rs:308`](../ui/dr-ui/src/library_ui.rs#L308), [`ui/dr-ui/src/library_ui.rs:320`](../ui/dr-ui/src/library_ui.rs#L320), [`ui/dr-ui/src/library_ui.rs:329`](../ui/dr-ui/src/library_ui.rs#L329), [`ui/dr-ui/src/library_ui.rs:422`](../ui/dr-ui/src/library_ui.rs#L422), [`ui/dr-ui/src/library_ui.rs:432`](../ui/dr-ui/src/library_ui.rs#L432), [`ui/dr-ui/src/library_ui.rs:479`](../ui/dr-ui/src/library_ui.rs#L479), [`ui/dr-ui/src/library_ui.rs:542`](../ui/dr-ui/src/library_ui.rs#L542), [`ui/dr-ui/src/library_ui.rs:5533`](../ui/dr-ui/src/library_ui.rs#L5533), [`ui/dr-ui/src/library_ui.rs:5551`](../ui/dr-ui/src/library_ui.rs#L5551), [`ui/dr-ui/src/library_ui.rs:5563`](../ui/dr-ui/src/library_ui.rs#L5563), [`ui/dr-ui/src/library_ui.rs:573`](../ui/dr-ui/src/library_ui.rs#L573), [`ui/dr-ui/src/library_ui.rs:585`](../ui/dr-ui/src/library_ui.rs#L585), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/ui/app.slint:2618`](../ui/dr-ui/ui/app.slint#L2618), [`ui/dr-ui/ui/app.slint:511`](../ui/dr-ui/ui/app.slint#L511), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:369`](../ui/dr-ui/ui/collections.slint#L369), [`ui/dr-ui/ui/collections.slint:52`](../ui/dr-ui/ui/collections.slint#L52), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/collections.slint:84`](../ui/dr-ui/ui/collections.slint#L84), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261), [`ui/dr-ui/ui/library.slint:1013`](../ui/dr-ui/ui/library.slint#L1013) | | FR-NC-6b | [`ui/dr-ui/src/library_ui.rs:1350`](../ui/dr-ui/src/library_ui.rs#L1350), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | | FR-NC-6c | [`core/dr-catalog/src/cache.rs:225`](../core/dr-catalog/src/cache.rs#L225), [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:197`](../core/dr-sync-folder/src/lib.rs#L197), [`core/dr-sync-folder/src/lib.rs:73`](../core/dr-sync-folder/src/lib.rs#L73), [`core/dr-sync-folder/src/lib.rs:818`](../core/dr-sync-folder/src/lib.rs#L818), [`core/dr-sync-folder/src/lib.rs:857`](../core/dr-sync-folder/src/lib.rs#L857), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:167`](../core/dr-sync-nextcloud/src/desktop_client.rs#L167), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41), [`core/dr-sync/src/error.rs:39`](../core/dr-sync/src/error.rs#L39), [`core/dr-sync/src/lib.rs:158`](../core/dr-sync/src/lib.rs#L158), [`core/dr-sync/src/types.rs:101`](../core/dr-sync/src/types.rs#L101), [`core/dr-types/src/lib.rs:120`](../core/dr-types/src/lib.rs#L120), [`core/dr-types/src/lib.rs:202`](../core/dr-types/src/lib.rs#L202), [`ui/dr-ui/src/activity.rs:1`](../ui/dr-ui/src/activity.rs#L1), [`ui/dr-ui/src/collections_ui.rs:3388`](../ui/dr-ui/src/collections_ui.rs#L3388), [`ui/dr-ui/src/collections_ui.rs:632`](../ui/dr-ui/src/collections_ui.rs#L632), [`ui/dr-ui/src/collections_ui.rs:700`](../ui/dr-ui/src/collections_ui.rs#L700), [`ui/dr-ui/src/derived_sync.rs:555`](../ui/dr-ui/src/derived_sync.rs#L555), [`ui/dr-ui/src/derived_sync.rs:627`](../ui/dr-ui/src/derived_sync.rs#L627), [`ui/dr-ui/src/library.rs:1685`](../ui/dr-ui/src/library.rs#L1685), [`ui/dr-ui/src/library.rs:1775`](../ui/dr-ui/src/library.rs#L1775), [`ui/dr-ui/src/library.rs:3254`](../ui/dr-ui/src/library.rs#L3254), [`ui/dr-ui/src/library.rs:3592`](../ui/dr-ui/src/library.rs#L3592), [`ui/dr-ui/src/library.rs:981`](../ui/dr-ui/src/library.rs#L981), [`ui/dr-ui/src/library_ui.rs:1121`](../ui/dr-ui/src/library_ui.rs#L1121), [`ui/dr-ui/src/library_ui.rs:1194`](../ui/dr-ui/src/library_ui.rs#L1194), [`ui/dr-ui/src/library_ui.rs:1393`](../ui/dr-ui/src/library_ui.rs#L1393), [`ui/dr-ui/src/remote.rs:40`](../ui/dr-ui/src/remote.rs#L40), [`ui/dr-ui/ui/collections.slint:249`](../ui/dr-ui/ui/collections.slint#L249), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/icons.slint:261`](../ui/dr-ui/ui/icons.slint#L261) | | FR-NC-6d | [`core/dr-sync-folder/src/borrow.rs:1`](../core/dr-sync-folder/src/borrow.rs#L1), [`core/dr-sync-folder/src/lib.rs:1`](../core/dr-sync-folder/src/lib.rs#L1), [`core/dr-sync-folder/src/vfs.rs:1`](../core/dr-sync-folder/src/vfs.rs#L1) | | FR-NC-7 | [`core/dr-catalog/src/face_shard.rs:1`](../core/dr-catalog/src/face_shard.rs#L1), [`core/dr-sync-nextcloud/src/lib.rs:105`](../core/dr-sync-nextcloud/src/lib.rs#L105), [`ui/dr-ui/src/derived_sync.rs:1`](../ui/dr-ui/src/derived_sync.rs#L1), [`ui/dr-ui/src/library.rs:3472`](../ui/dr-ui/src/library.rs#L3472), [`ui/dr-ui/src/library_ui.rs:3731`](../ui/dr-ui/src/library_ui.rs#L3731), [`ui/dr-ui/ui/settings.slint:387`](../ui/dr-ui/ui/settings.slint#L387) | -| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1221`](../ui/dr-ui/src/lib.rs#L1221), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | -| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:122`](../ui/dr-ui/src/import.rs#L122), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import.rs:584`](../ui/dr-ui/src/import.rs#L584), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1221`](../ui/dr-ui/src/lib.rs#L1221) | -| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:119`](../core/dr-pipeline/src/sidecar.rs#L119), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`ui/dr-ui/src/lib.rs:1990`](../ui/dr-ui/src/lib.rs#L1990), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library_ui.rs:494`](../ui/dr-ui/src/library_ui.rs#L494) | +| FR-NC-7a | [`core/dr-ingest/src/layout.rs:1`](../core/dr-ingest/src/layout.rs#L1), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`core/dr-sync/src/upload.rs:40`](../core/dr-sync/src/upload.rs#L40), [`core/dr-types/src/settings.rs:206`](../core/dr-types/src/settings.rs#L206), [`ui/dr-ui/src/import.rs:1`](../ui/dr-ui/src/import.rs#L1), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1230`](../ui/dr-ui/src/lib.rs#L1230), [`ui/dr-ui/ui/import.slint:5`](../ui/dr-ui/ui/import.slint#L5) | +| FR-NC-7b | [`core/dr-ingest/src/lib.rs:733`](../core/dr-ingest/src/lib.rs#L733), [`core/dr-sync/src/upload.rs:1`](../core/dr-sync/src/upload.rs#L1), [`ui/dr-ui/src/import.rs:122`](../ui/dr-ui/src/import.rs#L122), [`ui/dr-ui/src/import.rs:336`](../ui/dr-ui/src/import.rs#L336), [`ui/dr-ui/src/import.rs:584`](../ui/dr-ui/src/import.rs#L584), [`ui/dr-ui/src/import.rs:97`](../ui/dr-ui/src/import.rs#L97), [`ui/dr-ui/src/import_ui.rs:1`](../ui/dr-ui/src/import_ui.rs#L1), [`ui/dr-ui/src/lib.rs:1230`](../ui/dr-ui/src/lib.rs#L1230) | +| FR-NC-8 | [`core/dr-pipeline/src/sidecar.rs:119`](../core/dr-pipeline/src/sidecar.rs#L119), [`core/dr-pipeline/src/sidecar.rs:93`](../core/dr-pipeline/src/sidecar.rs#L93), [`ui/dr-ui/src/lib.rs:1999`](../ui/dr-ui/src/lib.rs#L1999), [`ui/dr-ui/src/library.rs:492`](../ui/dr-ui/src/library.rs#L492), [`ui/dr-ui/src/library_ui.rs:494`](../ui/dr-ui/src/library_ui.rs#L494) | | FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/schema.rs:635`](../core/dr-catalog/src/schema.rs#L635), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-pipeline/src/sidecar.rs:157`](../core/dr-pipeline/src/sidecar.rs#L157), [`core/dr-pipeline/src/sidecar.rs:184`](../core/dr-pipeline/src/sidecar.rs#L184), [`core/dr-pipeline/src/sidecar.rs:2034`](../core/dr-pipeline/src/sidecar.rs#L2034), [`core/dr-pipeline/src/sidecar.rs:353`](../core/dr-pipeline/src/sidecar.rs#L353), [`core/dr-pipeline/src/sidecar.rs:451`](../core/dr-pipeline/src/sidecar.rs#L451), [`core/dr-pipeline/src/spot.rs:245`](../core/dr-pipeline/src/spot.rs#L245), [`core/dr-pipeline/tests/spot_sidecar.rs:1`](../core/dr-pipeline/tests/spot_sidecar.rs#L1), [`ui/dr-ui/src/derived_sync.rs:555`](../ui/dr-ui/src/derived_sync.rs#L555), [`ui/dr-ui/src/library.rs:1031`](../ui/dr-ui/src/library.rs#L1031), [`ui/dr-ui/src/library.rs:877`](../ui/dr-ui/src/library.rs#L877) | | FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:54`](../core/dr-types/src/lib.rs#L54), [`platform/dr-plat/src/volumes.rs:62`](../platform/dr-plat/src/volumes.rs#L62) | | FR-PLAT-AND-2 | [`core/dr-catalog/src/walk.rs:1022`](../core/dr-catalog/src/walk.rs#L1022), [`core/dr-catalog/src/walk.rs:435`](../core/dr-catalog/src/walk.rs#L435), [`core/dr-sync-folder/src/lib.rs:242`](../core/dr-sync-folder/src/lib.rs#L242), [`core/dr-sync-folder/src/tests.rs:51`](../core/dr-sync-folder/src/tests.rs#L51), [`core/dr-sync/src/error.rs:113`](../core/dr-sync/src/error.rs#L113), [`core/dr-sync/src/error.rs:195`](../core/dr-sync/src/error.rs#L195), [`core/dr-sync/src/scan.rs:175`](../core/dr-sync/src/scan.rs#L175), [`core/dr-sync/src/scan.rs:464`](../core/dr-sync/src/scan.rs#L464), [`core/dr-sync/src/scan.rs:490`](../core/dr-sync/src/scan.rs#L490), [`core/dr-sync/src/scan.rs:519`](../core/dr-sync/src/scan.rs#L519), [`ui/dr-ui/src/library.rs:1222`](../ui/dr-ui/src/library.rs#L1222), [`ui/dr-ui/src/library.rs:1275`](../ui/dr-ui/src/library.rs#L1275), [`ui/dr-ui/src/library.rs:1306`](../ui/dr-ui/src/library.rs#L1306), [`ui/dr-ui/src/library.rs:1411`](../ui/dr-ui/src/library.rs#L1411), [`ui/dr-ui/src/library_ui.rs:1045`](../ui/dr-ui/src/library_ui.rs#L1045), [`ui/dr-ui/src/library_ui.rs:1644`](../ui/dr-ui/src/library_ui.rs#L1644), [`ui/dr-ui/src/library_ui.rs:273`](../ui/dr-ui/src/library_ui.rs#L273), [`ui/dr-ui/src/library_ui.rs:463`](../ui/dr-ui/src/library_ui.rs#L463), [`ui/dr-ui/src/library_ui.rs:966`](../ui/dr-ui/src/library_ui.rs#L966) | | FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | | FR-PLAT-AND-4 | [`core/dr-catalog/src/runner.rs:1`](../core/dr-catalog/src/runner.rs#L1) | -| FR-PLAT-AND-5 | [`apps/darkroom-android/src/lib.rs:71`](../apps/darkroom-android/src/lib.rs#L71), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3151`](../ui/dr-ui/src/develop.rs#L3151), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:1045`](../ui/dr-ui/src/lib.rs#L1045), [`ui/dr-ui/src/lib.rs:1474`](../ui/dr-ui/src/lib.rs#L1474), [`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:204`](../apps/darkroom-android/src/lib.rs#L204) | -| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:944`](../ui/dr-ui/src/lib.rs#L944), [`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-AND-5 | [`apps/darkroom-android/src/lib.rs:71`](../apps/darkroom-android/src/lib.rs#L71), [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-gpu/src/detail.rs:161`](../core/dr-gpu/src/detail.rs#L161), [`core/dr-gpu/src/detail.rs:547`](../core/dr-gpu/src/detail.rs#L547), [`core/dr-gpu/tests/detail_stage.rs:417`](../core/dr-gpu/tests/detail_stage.rs#L417), [`ui/dr-ui/src/develop.rs:3151`](../ui/dr-ui/src/develop.rs#L3151), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:1054`](../ui/dr-ui/src/lib.rs#L1054), [`ui/dr-ui/src/lib.rs:1483`](../ui/dr-ui/src/lib.rs#L1483), [`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:231`](../apps/darkroom-android/src/lib.rs#L231) | +| FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:953`](../ui/dr-ui/src/lib.rs#L953), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | | FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:652`](../core/dr-pipeline/src/descriptor.rs#L652), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:416`](../core/dr-pipeline/tests/declared_parity.rs#L416) | | FR-PLG-2d | [`core/dr-pipeline/src/declared/decl.rs:112`](../core/dr-pipeline/src/declared/decl.rs#L112), [`core/dr-pipeline/src/declared/decl.rs:152`](../core/dr-pipeline/src/declared/decl.rs#L152), [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:420`](../core/dr-pipeline/src/declared/decl.rs#L420), [`core/dr-pipeline/src/declared/decl.rs:67`](../core/dr-pipeline/src/declared/decl.rs#L67), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:384`](../core/dr-pipeline/src/declared/mod.rs#L384), [`core/dr-pipeline/src/declared/mod.rs:403`](../core/dr-pipeline/src/declared/mod.rs#L403) | | FR-RAW-1 | [`core/dr-decode/src/lib.rs:243`](../core/dr-decode/src/lib.rs#L243), [`core/dr-types/src/lib.rs:130`](../core/dr-types/src/lib.rs#L130), [`core/dr-types/src/lib.rs:201`](../core/dr-types/src/lib.rs#L201) | | FR-RAW-3 | [`core/dr-decode/src/lib.rs:139`](../core/dr-decode/src/lib.rs#L139), [`core/dr-decode/src/lib.rs:506`](../core/dr-decode/src/lib.rs#L506), [`core/dr-decode/src/locate.rs:1366`](../core/dr-decode/src/locate.rs#L1366) | -| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:219`](../ui/dr-ui/src/lib.rs#L219) | +| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1), [`ui/dr-ui/src/lib.rs:228`](../ui/dr-ui/src/lib.rs#L228) | | FR-RAW-5 | [`core/dr-decode/src/lib.rs:167`](../core/dr-decode/src/lib.rs#L167), [`core/dr-gpu/src/demosaic.rs:34`](../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:602`](../core/dr-gpu/src/demosaic.rs#L602), [`core/dr-gpu/src/demosaic.rs:681`](../core/dr-gpu/src/demosaic.rs#L681), [`core/dr-gpu/src/demosaic.rs:805`](../core/dr-gpu/src/demosaic.rs#L805) | -| FR-UI-1 | [`ui/dr-ui/src/lib.rs:3216`](../ui/dr-ui/src/lib.rs#L3216), [`ui/dr-ui/src/lib.rs:89`](../ui/dr-ui/src/lib.rs#L89), [`ui/dr-ui/src/lib.rs:96`](../ui/dr-ui/src/lib.rs#L96), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/app.slint:2162`](../ui/dr-ui/ui/app.slint#L2162), [`ui/dr-ui/ui/identity.slint:181`](../ui/dr-ui/ui/identity.slint#L181), [`ui/dr-ui/ui/library.slint:1091`](../ui/dr-ui/ui/library.slint#L1091), [`ui/dr-ui/ui/toolrail.slint:88`](../ui/dr-ui/ui/toolrail.slint#L88) | -| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1041`](../ui/dr-ui/src/collections_ui.rs#L1041), [`ui/dr-ui/src/collections_ui.rs:1051`](../ui/dr-ui/src/collections_ui.rs#L1051), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:1534`](../ui/dr-ui/src/collections_ui.rs#L1534), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1800`](../ui/dr-ui/src/collections_ui.rs#L1800), [`ui/dr-ui/src/collections_ui.rs:2901`](../ui/dr-ui/src/collections_ui.rs#L2901), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:89`](../ui/dr-ui/src/lib.rs#L89), [`ui/dr-ui/src/library_ui.rs:308`](../ui/dr-ui/src/library_ui.rs#L308), [`ui/dr-ui/src/library_ui.rs:5418`](../ui/dr-ui/src/library_ui.rs#L5418), [`ui/dr-ui/src/library_ui.rs:5563`](../ui/dr-ui/src/library_ui.rs#L5563), [`ui/dr-ui/src/library_ui.rs:6768`](../ui/dr-ui/src/library_ui.rs#L6768), [`ui/dr-ui/ui/app.slint:541`](../ui/dr-ui/ui/app.slint#L541), [`ui/dr-ui/ui/app.slint:548`](../ui/dr-ui/ui/app.slint#L548), [`ui/dr-ui/ui/app.slint:59`](../ui/dr-ui/ui/app.slint#L59), [`ui/dr-ui/ui/app.slint:899`](../ui/dr-ui/ui/app.slint#L899), [`ui/dr-ui/ui/controls.slint:474`](../ui/dr-ui/ui/controls.slint#L474), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1259`](../ui/dr-ui/ui/library.slint#L1259), [`ui/dr-ui/ui/library.slint:1266`](../ui/dr-ui/ui/library.slint#L1266), [`ui/dr-ui/ui/library.slint:1329`](../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:2575`](../ui/dr-ui/ui/library.slint#L2575), [`ui/dr-ui/ui/library.slint:3893`](../ui/dr-ui/ui/library.slint#L3893), [`ui/dr-ui/ui/library.slint:842`](../ui/dr-ui/ui/library.slint#L842), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:110`](../ui/dr-ui/ui/settings.slint#L110) | +| FR-UI-1 | [`ui/dr-ui/src/lib.rs:105`](../ui/dr-ui/src/lib.rs#L105), [`ui/dr-ui/src/lib.rs:3225`](../ui/dr-ui/src/lib.rs#L3225), [`ui/dr-ui/src/lib.rs:98`](../ui/dr-ui/src/lib.rs#L98), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/app.slint:2162`](../ui/dr-ui/ui/app.slint#L2162), [`ui/dr-ui/ui/identity.slint:181`](../ui/dr-ui/ui/identity.slint#L181), [`ui/dr-ui/ui/library.slint:1091`](../ui/dr-ui/ui/library.slint#L1091), [`ui/dr-ui/ui/toolrail.slint:88`](../ui/dr-ui/ui/toolrail.slint#L88) | +| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1041`](../ui/dr-ui/src/collections_ui.rs#L1041), [`ui/dr-ui/src/collections_ui.rs:1051`](../ui/dr-ui/src/collections_ui.rs#L1051), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:1534`](../ui/dr-ui/src/collections_ui.rs#L1534), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1800`](../ui/dr-ui/src/collections_ui.rs#L1800), [`ui/dr-ui/src/collections_ui.rs:2901`](../ui/dr-ui/src/collections_ui.rs#L2901), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:98`](../ui/dr-ui/src/lib.rs#L98), [`ui/dr-ui/src/library_ui.rs:308`](../ui/dr-ui/src/library_ui.rs#L308), [`ui/dr-ui/src/library_ui.rs:5418`](../ui/dr-ui/src/library_ui.rs#L5418), [`ui/dr-ui/src/library_ui.rs:5563`](../ui/dr-ui/src/library_ui.rs#L5563), [`ui/dr-ui/src/library_ui.rs:6768`](../ui/dr-ui/src/library_ui.rs#L6768), [`ui/dr-ui/ui/app.slint:541`](../ui/dr-ui/ui/app.slint#L541), [`ui/dr-ui/ui/app.slint:548`](../ui/dr-ui/ui/app.slint#L548), [`ui/dr-ui/ui/app.slint:59`](../ui/dr-ui/ui/app.slint#L59), [`ui/dr-ui/ui/app.slint:899`](../ui/dr-ui/ui/app.slint#L899), [`ui/dr-ui/ui/controls.slint:474`](../ui/dr-ui/ui/controls.slint#L474), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1259`](../ui/dr-ui/ui/library.slint#L1259), [`ui/dr-ui/ui/library.slint:1266`](../ui/dr-ui/ui/library.slint#L1266), [`ui/dr-ui/ui/library.slint:1329`](../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:2575`](../ui/dr-ui/ui/library.slint#L2575), [`ui/dr-ui/ui/library.slint:3893`](../ui/dr-ui/ui/library.slint#L3893), [`ui/dr-ui/ui/library.slint:842`](../ui/dr-ui/ui/library.slint#L842), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:110`](../ui/dr-ui/ui/settings.slint#L110) | | FR-UI-3 | [`ui/dr-ui/src/develop.rs:2248`](../ui/dr-ui/src/develop.rs#L2248), [`ui/dr-ui/src/develop.rs:2357`](../ui/dr-ui/src/develop.rs#L2357), [`ui/dr-ui/src/library_ui.rs:4525`](../ui/dr-ui/src/library_ui.rs#L4525), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1971`](../ui/dr-ui/ui/app.slint#L1971), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/masks.slint:516`](../ui/dr-ui/ui/masks.slint#L516) | | FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/gestures.rs:366`](../tools/traceability/src/gestures.rs#L366), [`ui/dr-ui/src/collections_ui.rs:1041`](../ui/dr-ui/src/collections_ui.rs#L1041), [`ui/dr-ui/src/collections_ui.rs:1051`](../ui/dr-ui/src/collections_ui.rs#L1051), [`ui/dr-ui/src/collections_ui.rs:116`](../ui/dr-ui/src/collections_ui.rs#L116), [`ui/dr-ui/src/collections_ui.rs:117`](../ui/dr-ui/src/collections_ui.rs#L117), [`ui/dr-ui/src/collections_ui.rs:126`](../ui/dr-ui/src/collections_ui.rs#L126), [`ui/dr-ui/src/collections_ui.rs:1534`](../ui/dr-ui/src/collections_ui.rs#L1534), [`ui/dr-ui/src/collections_ui.rs:1548`](../ui/dr-ui/src/collections_ui.rs#L1548), [`ui/dr-ui/src/collections_ui.rs:154`](../ui/dr-ui/src/collections_ui.rs#L154), [`ui/dr-ui/src/collections_ui.rs:1664`](../ui/dr-ui/src/collections_ui.rs#L1664), [`ui/dr-ui/src/collections_ui.rs:1800`](../ui/dr-ui/src/collections_ui.rs#L1800), [`ui/dr-ui/src/collections_ui.rs:1827`](../ui/dr-ui/src/collections_ui.rs#L1827), [`ui/dr-ui/src/collections_ui.rs:1943`](../ui/dr-ui/src/collections_ui.rs#L1943), [`ui/dr-ui/src/collections_ui.rs:2901`](../ui/dr-ui/src/collections_ui.rs#L2901), [`ui/dr-ui/src/collections_ui.rs:2938`](../ui/dr-ui/src/collections_ui.rs#L2938), [`ui/dr-ui/src/collections_ui.rs:365`](../ui/dr-ui/src/collections_ui.rs#L365), [`ui/dr-ui/src/collections_ui.rs:569`](../ui/dr-ui/src/collections_ui.rs#L569), [`ui/dr-ui/src/collections_ui.rs:589`](../ui/dr-ui/src/collections_ui.rs#L589), [`ui/dr-ui/src/gesture_book.rs:3`](../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/library_ui.rs:4525`](../ui/dr-ui/src/library_ui.rs#L4525), [`ui/dr-ui/src/library_ui.rs:4537`](../ui/dr-ui/src/library_ui.rs#L4537), [`ui/dr-ui/src/library_ui.rs:4591`](../ui/dr-ui/src/library_ui.rs#L4591), [`ui/dr-ui/src/library_ui.rs:4694`](../ui/dr-ui/src/library_ui.rs#L4694), [`ui/dr-ui/src/library_ui.rs:4722`](../ui/dr-ui/src/library_ui.rs#L4722), [`ui/dr-ui/src/library_ui.rs:5551`](../ui/dr-ui/src/library_ui.rs#L5551), [`ui/dr-ui/src/library_ui.rs:5563`](../ui/dr-ui/src/library_ui.rs#L5563), [`ui/dr-ui/ui/app.slint:1811`](../ui/dr-ui/ui/app.slint#L1811), [`ui/dr-ui/ui/app.slint:517`](../ui/dr-ui/ui/app.slint#L517), [`ui/dr-ui/ui/app.slint:548`](../ui/dr-ui/ui/app.slint#L548), [`ui/dr-ui/ui/app.slint:633`](../ui/dr-ui/ui/app.slint#L633), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1067`](../ui/dr-ui/ui/library.slint#L1067), [`ui/dr-ui/ui/library.slint:1234`](../ui/dr-ui/ui/library.slint#L1234), [`ui/dr-ui/ui/library.slint:1259`](../ui/dr-ui/ui/library.slint#L1259), [`ui/dr-ui/ui/library.slint:1266`](../ui/dr-ui/ui/library.slint#L1266), [`ui/dr-ui/ui/library.slint:1322`](../ui/dr-ui/ui/library.slint#L1322), [`ui/dr-ui/ui/library.slint:1329`](../ui/dr-ui/ui/library.slint#L1329), [`ui/dr-ui/ui/library.slint:2575`](../ui/dr-ui/ui/library.slint#L2575), [`ui/dr-ui/ui/library.slint:3893`](../ui/dr-ui/ui/library.slint#L3893), [`ui/dr-ui/ui/library.slint:842`](../ui/dr-ui/ui/library.slint#L842), [`ui/dr-ui/ui/library.slint:892`](../ui/dr-ui/ui/library.slint#L892), [`ui/dr-ui/ui/library.slint:898`](../ui/dr-ui/ui/library.slint#L898), [`ui/dr-ui/ui/library.slint:917`](../ui/dr-ui/ui/library.slint#L917) | -| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2653`](../ui/dr-ui/src/lib.rs#L2653), [`ui/dr-ui/src/lib.rs:3255`](../ui/dr-ui/src/lib.rs#L3255), [`ui/dr-ui/src/lib.rs:3440`](../ui/dr-ui/src/lib.rs#L3440), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:120`](../ui/dr-ui/ui/app.slint#L120), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) | +| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2662`](../ui/dr-ui/src/lib.rs#L2662), [`ui/dr-ui/src/lib.rs:3264`](../ui/dr-ui/src/lib.rs#L3264), [`ui/dr-ui/src/lib.rs:3449`](../ui/dr-ui/src/lib.rs#L3449), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:120`](../ui/dr-ui/ui/app.slint#L120), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) | | FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../ui/dr-ui/ui/widgets.slint#L1) | | FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:385`](../core/dr-pipeline/src/framing.rs#L385) | | NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1), [`ui/dr-ui/src/faces.rs:1`](../ui/dr-ui/src/faces.rs#L1), [`ui/dr-ui/src/library.rs:3069`](../ui/dr-ui/src/library.rs#L3069) | -| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1553`](../ui/dr-ui/src/export.rs#L1553), [`ui/dr-ui/src/export.rs:1579`](../ui/dr-ui/src/export.rs#L1579), [`ui/dr-ui/src/export.rs:409`](../ui/dr-ui/src/export.rs#L409), [`ui/dr-ui/src/export.rs:435`](../ui/dr-ui/src/export.rs#L435), [`ui/dr-ui/src/lib.rs:2350`](../ui/dr-ui/src/lib.rs#L2350), [`ui/dr-ui/ui/app.slint:1075`](../ui/dr-ui/ui/app.slint#L1075), [`ui/dr-ui/ui/library.slint:967`](../ui/dr-ui/ui/library.slint#L967) | +| NFR-ARCH-3 | [`ui/dr-ui/src/export.rs:1553`](../ui/dr-ui/src/export.rs#L1553), [`ui/dr-ui/src/export.rs:1579`](../ui/dr-ui/src/export.rs#L1579), [`ui/dr-ui/src/export.rs:409`](../ui/dr-ui/src/export.rs#L409), [`ui/dr-ui/src/export.rs:435`](../ui/dr-ui/src/export.rs#L435), [`ui/dr-ui/src/lib.rs:2359`](../ui/dr-ui/src/lib.rs#L2359), [`ui/dr-ui/ui/app.slint:1075`](../ui/dr-ui/ui/app.slint#L1075), [`ui/dr-ui/ui/library.slint:967`](../ui/dr-ui/ui/library.slint#L967) | | NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-export/src/error.rs:1`](../core/dr-export/src/error.rs#L1), [`core/dr-thumbs/src/error.rs:1`](../core/dr-thumbs/src/error.rs#L1), [`platform/dr-plat/src/storage.rs:148`](../platform/dr-plat/src/storage.rs#L148), [`ui/dr-ui/src/export.rs:499`](../ui/dr-ui/src/export.rs#L499) | | NFR-OPS-1 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/lib.rs:268`](../tools/traceability/src/lib.rs#L268) | | NFR-OPS-3 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-catalog/src/walk.rs:162`](../core/dr-catalog/src/walk.rs#L162), [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`tools/traceability/src/lib.rs:481`](../tools/traceability/src/lib.rs#L481) | | NFR-P13 | [`core/dr-decode/src/preview.rs:121`](../core/dr-decode/src/preview.rs#L121) | -| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop.rs:3046`](../ui/dr-ui/src/develop.rs#L3046), [`ui/dr-ui/src/lib.rs:1716`](../ui/dr-ui/src/lib.rs#L1716) | -| NFR-P5 | [`core/dr-catalog/src/schema.rs:330`](../core/dr-catalog/src/schema.rs#L330), [`ui/dr-ui/src/library.rs:4132`](../ui/dr-ui/src/library.rs#L4132), [`ui/dr-ui/src/library.rs:5066`](../ui/dr-ui/src/library.rs#L5066), [`ui/dr-ui/src/library_ui.rs:4198`](../ui/dr-ui/src/library_ui.rs#L4198), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | +| NFR-P14 | [`core/dr-gpu/src/focus.rs:186`](../core/dr-gpu/src/focus.rs#L186), [`core/dr-gpu/src/focus.rs:1`](../core/dr-gpu/src/focus.rs#L1), [`core/dr-gpu/src/focus.rs:317`](../core/dr-gpu/src/focus.rs#L317), [`core/dr-gpu/src/shaders/focus_peak.wgsl:1`](../core/dr-gpu/src/shaders/focus_peak.wgsl#L1), [`ui/dr-ui/src/develop.rs:3046`](../ui/dr-ui/src/develop.rs#L3046), [`ui/dr-ui/src/lib.rs:1725`](../ui/dr-ui/src/lib.rs#L1725) | +| NFR-P5 | [`core/dr-catalog/src/schema.rs:330`](../core/dr-catalog/src/schema.rs#L330), [`ui/dr-ui/src/library.rs:4163`](../ui/dr-ui/src/library.rs#L4163), [`ui/dr-ui/src/library.rs:5097`](../ui/dr-ui/src/library.rs#L5097), [`ui/dr-ui/src/library_ui.rs:4198`](../ui/dr-ui/src/library_ui.rs#L4198), [`ui/dr-ui/src/library_ui.rs:64`](../ui/dr-ui/src/library_ui.rs#L64) | | NFR-P9 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/export.rs:942`](../ui/dr-ui/src/export.rs#L942), [`ui/dr-ui/src/library.rs:1`](../ui/dr-ui/src/library.rs#L1), [`ui/dr-ui/src/library_ui.rs:1`](../ui/dr-ui/src/library_ui.rs#L1), [`ui/dr-ui/src/trash.rs:1`](../ui/dr-ui/src/trash.rs#L1) | | NFR-PORT-1 | [`core/dr-catalog/src/walk.rs:1`](../core/dr-catalog/src/walk.rs#L1), [`core/dr-types/src/lib.rs:270`](../core/dr-types/src/lib.rs#L270), [`core/dr-types/src/lib.rs:303`](../core/dr-types/src/lib.rs#L303), [`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) | @@ -145,7 +145,7 @@ _None._ | NFR-R5 | [`core/dr-catalog/src/collections.rs:1`](../core/dr-catalog/src/collections.rs#L1), [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/keywords.rs:1`](../core/dr-catalog/src/keywords.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) | | NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | | NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) | -| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3151`](../ui/dr-ui/src/develop.rs#L3151), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:81`](../ui/dr-ui/src/lib.rs#L81), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.rs#L1) | +| NFR-RES-1 | [`core/dr-gpu/src/adjust.rs:1029`](../core/dr-gpu/src/adjust.rs#L1029), [`core/dr-pipeline/src/history.rs:86`](../core/dr-pipeline/src/history.rs#L86), [`ui/dr-ui/src/develop.rs:3151`](../ui/dr-ui/src/develop.rs#L3151), [`ui/dr-ui/src/identity_ui.rs:115`](../ui/dr-ui/src/identity_ui.rs#L115), [`ui/dr-ui/src/lib.rs:90`](../ui/dr-ui/src/lib.rs#L90), [`ui/dr-ui/src/memory.rs:1`](../ui/dr-ui/src/memory.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:692`](../core/dr-catalog/src/schema.rs#L692), [`core/dr-gpu/src/lib.rs:93`](../core/dr-gpu/src/lib.rs#L93), [`core/dr-thumbs/src/codec.rs:1`](../core/dr-thumbs/src/codec.rs#L1), [`core/dr-thumbs/src/lib.rs:1`](../core/dr-thumbs/src/lib.rs#L1), [`core/dr-thumbs/src/lib.rs:376`](../core/dr-thumbs/src/lib.rs#L376), [`ui/dr-ui/src/library.rs:2964`](../ui/dr-ui/src/library.rs#L2964) | | NFR-SEC-1 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) | | NFR-SEC-2 | [`core/dr-sync/src/account.rs:298`](../core/dr-sync/src/account.rs#L298), [`platform/dr-plat/src/secrets.rs:82`](../platform/dr-plat/src/secrets.rs#L82) | diff --git a/models/LICENCE.md b/models/LICENCE.md new file mode 100644 index 0000000..803bf1a --- /dev/null +++ b/models/LICENCE.md @@ -0,0 +1,71 @@ +# Model weights — licensing + +Two Ultralytics checkpoints ship here, both exported by +`tools/export-seg-model.sh`, each with its class vocabulary written out by the +same script: + +| File | Checkpoint | Trained on | Used by | +|---|---|---|---| +| `segment/yolo26n-seg.onnx` | `yolo26n-seg.pt` | COCO, 80 *thing* classes | local adjustments, subject selection | +| `scene/yolo26s-sem-ade20k.onnx` | `yolo26s-sem-ade20k.pt` | ADE20K, 150 classes | the scene tab's per-category grades | + +Both come from `https://huggingface.co/Ultralytics/YOLO26`. The face weights in +`face/` are a separate matter with a separate grant — see `face/README.md`. + +## The grant + +**Ultralytics releases YOLO under AGPL-3.0**, and the weights carry the same +grant as the framework — the HuggingFace repository declares `agpl-3.0` for the +checkpoints themselves, not merely for the training code. A commercial licence +is offered separately; DarkRoom does not use it and does not need it. + +## What that means for DarkRoom + +DarkRoom is GPL-3.0-or-later. **GPLv3 §13 explicitly permits combination with +AGPL-3.0 code**, so redistributing these weights inside this repository is +allowed — this is *not* the situation the InsightFace "buffalo" weights would +have created, where a non-commercial research grant is simply incompatible with +the project's licence and with F-Droid, Flatpak and Play distribution +(NFR-COMPAT-2, D13). + +The consequence, and it is a real one: **the combined work is effectively +AGPL-3.0.** §13's permission runs one way — the AGPL's §13 network-use condition +attaches to the portion under that licence. For a local-first desktop and +Android photo editor that condition has no practical bite, because there is no +network service offering the combined work to remote users. It would acquire +bite the moment any hosted or server-side rendering appeared, and that is the +thing to remember rather than rediscover. + +This was decided deliberately (D14), not arrived at by accident, and +`docs/segmentation.md` §7 records the reasoning. + +## Class vocabulary — a caveat worth reading + +`docs/segmentation.md` §4 specified YOLO **pretrained on ADE20K**, whose 150 +classes include the *stuff* categories that matter most in photography — sky, +vegetation, water, wall, mountain. + +**This was true when written and is not any more.** Checked 2026-08-21, no +YOLO/ADE20K combination existed: Ultralytics shipped YOLO26-seg on **COCO** +only, and the one HuggingFace repository claiming otherwise +(`laxmacl/yolov8-ade20k`) was empty. Re-checked 2026-08-30: Ultralytics now +ships a `semantic` task with ADE20K checkpoints +(`https://docs.ultralytics.com/tasks/semantic`), and `yolo26s-sem-ade20k` is +what `scene/` holds. + +So the two vocabularies divide the work rather than compete: + +- **`segment/`, COCO, 80 things.** Separates *instances* — clicking one of + three people selects that person. This is what local adjustments need, and a + semantic model cannot do it: it would return one "person" region covering all + three. +- **`scene/`, ADE20K, 150 classes.** Labels every pixel, including the *stuff* + COCO has no word for — sky, vegetation, water, mountain, wall. This is what + the scene tab's per-category grades need, and it does not care that instances + are merged, because a per-category grade applies to the whole category. + +Neither replaces the other. Keeping both is the deliberate choice. + +The loader treats each vocabulary as model metadata rather than compiled-in +knowledge, which is what made adding the second model a file plus a descriptor +rather than a code change — as this document predicted it would be. diff --git a/models/scene/categories.txt b/models/scene/categories.txt new file mode 100644 index 0000000..32425fc --- /dev/null +++ b/models/scene/categories.txt @@ -0,0 +1,55 @@ +# Photographic categories, over ADE20K's 150 classes. +# +# The scene tab offers a slider per category, not per class: nobody wants to +# grade "sconce" and "crt screen" separately, and ADE20K's vocabulary is a +# scene-parsing benchmark rather than a photographer's list. This file is the +# translation, and it is data so that changing it is not a code change. +# +# Format: one category per line, `name = class, class, ...`, where each class +# is a name from the model's own `.classes.json`. Names rather than indices +# because an index is silently wrong after a re-export and a name is loudly +# wrong; `SceneModel::from_path` refuses a file naming a class the model does +# not have. +# +# ## Why the list is short, and why "other" is not in it +# +# Weights come from a softmax over all 150 channels summed within each +# category, so the categories listed here plus everything unlisted sum to 1 at +# every pixel. That is what lets the scene tab feather two adjacent categories +# without painting both grades into the overlap. Adding a category takes +# weight from the unlisted remainder rather than from its neighbours, so this +# list can grow without disturbing what is already here. +# +# Classes are assigned to at most one category — an overlap would break the +# partition and double-count the shared class, so the loader rejects it. + +# The one the whole exercise started from. ADE20K's easiest class, and the one +# most often graded on its own in a landscape. +sky = sky + +# Foliage, not "green things": a lawn and a canopy take the same saturation +# and luminance moves far more often than either takes the sky's. +vegetation = tree, grass, plant, flower, palm, field + +# Standing and moving water together. `swimming pool` and `fountain` are here +# rather than under architecture because what a photographer adjusts is the +# water, not the basin. +water = water, sea, river, lake, waterfall, swimming pool, fountain + +# Distant landform. Separate from `ground` because it is usually far, hazy and +# wants dehaze and contrast where a foreground surface wants neither. +terrain = mountain, rock, hill + +# What the photographer is standing on, or would be. Earth and sand sit here +# rather than with terrain for the same near/far reason. +ground = earth, sand, land, dirt track, path, road, sidewalk, runway, floor, step, stairs, stairway + +# Built structure. Deliberately broad: a facade, its railings and its awning +# are one surface as far as a global grade is concerned. +architecture = building, house, skyscraper, wall, tower, bridge, hovel, fence, column, awning, booth, canopy, pier, railing, grandstand + +# Present for the scene tab's "expose people" move, and *not* a replacement for +# the instance model — this is every person in the frame at once, which is the +# right granularity for a global grade and the wrong one for selecting a +# subject. See `models/LICENCE.md`. +person = person diff --git a/models/scene/yolo26s-sem-ade20k.classes.json b/models/scene/yolo26s-sem-ade20k.classes.json new file mode 100644 index 0000000..e5357c4 --- /dev/null +++ b/models/scene/yolo26s-sem-ade20k.classes.json @@ -0,0 +1,152 @@ +[ + "wall", + "building", + "sky", + "floor", + "tree", + "ceiling", + "road", + "bed", + "windowpane", + "grass", + "cabinet", + "sidewalk", + "person", + "earth", + "door", + "table", + "mountain", + "plant", + "curtain", + "chair", + "car", + "water", + "painting", + "sofa", + "shelf", + "house", + "sea", + "mirror", + "rug", + "field", + "armchair", + "seat", + "fence", + "desk", + "rock", + "wardrobe", + "lamp", + "bathtub", + "railing", + "cushion", + "base", + "box", + "column", + "signboard", + "chest of drawers", + "counter", + "sand", + "sink", + "skyscraper", + "fireplace", + "refrigerator", + "grandstand", + "path", + "stairs", + "runway", + "case", + "pool table", + "pillow", + "screen door", + "stairway", + "river", + "bridge", + "bookcase", + "blind", + "coffee table", + "toilet", + "flower", + "book", + "hill", + "bench", + "countertop", + "stove", + "palm", + "kitchen island", + "computer", + "swivel chair", + "boat", + "bar", + "arcade machine", + "hovel", + "bus", + "towel", + "light", + "truck", + "tower", + "chandelier", + "awning", + "streetlight", + "booth", + "television receiver", + "airplane", + "dirt track", + "apparel", + "pole", + "land", + "bannister", + "escalator", + "ottoman", + "bottle", + "buffet", + "poster", + "stage", + "van", + "ship", + "fountain", + "conveyor belt", + "canopy", + "washer", + "plaything", + "swimming pool", + "stool", + "barrel", + "basket", + "waterfall", + "tent", + "bag", + "minibike", + "cradle", + "oven", + "ball", + "food", + "step", + "tank", + "trade name", + "microwave", + "pot", + "animal", + "bicycle", + "lake", + "dishwasher", + "screen", + "blanket", + "sculpture", + "hood", + "sconce", + "vase", + "traffic light", + "tray", + "ashcan", + "fan", + "pier", + "crt screen", + "plate", + "monitor", + "bulletin board", + "shower", + "radiator", + "glass", + "clock", + "flag" +] \ No newline at end of file diff --git a/models/scene/yolo26s-sem-ade20k.onnx b/models/scene/yolo26s-sem-ade20k.onnx new file mode 100644 index 0000000..b5d4946 --- /dev/null +++ b/models/scene/yolo26s-sem-ade20k.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:296cbbf983a7dbd53eb551f4bcb2ce28d6503bb5ddd30a60c4a13b44087e01d7 +size 24921749 diff --git a/core/dr-segment/models/yolo26n-seg.classes.json b/models/segment/yolo26n-seg.classes.json similarity index 100% rename from core/dr-segment/models/yolo26n-seg.classes.json rename to models/segment/yolo26n-seg.classes.json diff --git a/core/dr-segment/models/yolo26n-seg.onnx b/models/segment/yolo26n-seg.onnx similarity index 100% rename from core/dr-segment/models/yolo26n-seg.onnx rename to models/segment/yolo26n-seg.onnx diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index f5fde21..5b051a6 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -69,4 +69,23 @@ package() { fi install -Dm644 "${_src}" "${pkgdir}/usr/share/darkroom/models/${_m}" done + + # The scene model, for the per-category grades. Unlike the face weights + # this one is in the repository — AGPL, and GPLv3 §13 permits the + # combination (models/LICENCE.md) — so it is installed unconditionally and + # a pointer here is a broken checkout rather than a licence decision. + # + # Three files: the graph, its vocabulary, and the category descriptor + # grouping ADE20K's 150 classes into what the tab shows. All three, because + # dr_ui::library::scene_model reports the tab unavailable without any one + # of them. Only the graph gets the pointer check — the other two are + # legitimately a few kilobytes. + _src="models/scene/yolo26s-sem-ade20k.onnx" + if [[ "$(stat -c%s "${_src}")" -lt 100000 ]]; then + echo "error: the scene model is an LFS pointer — run: git lfs pull" >&2 + return 1 + fi + for _m in yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt; do + install -Dm644 "models/scene/${_m}" "${pkgdir}/usr/share/darkroom/models/${_m}" + done } diff --git a/packaging/flatpak/paris.tourolle.darkroom.yml b/packaging/flatpak/paris.tourolle.darkroom.yml index c6b9c27..23dec17 100644 --- a/packaging/flatpak/paris.tourolle.darkroom.yml +++ b/packaging/flatpak/paris.tourolle.darkroom.yml @@ -165,6 +165,21 @@ modules: install -Dm644 "models/face/$m" "/app/share/darkroom/models/$m" done + # The scene model, for the per-category grades. In the repository, unlike + # the face weights — AGPL, and GPLv3 §13 permits the combination + # (models/LICENCE.md) — so it installs unconditionally. Three files: the + # graph, its vocabulary, and the category descriptor; dr_ui reports the + # tab unavailable without any one of them. The pointer check is on the + # graph alone, the other two being legitimately small. + - | + if [ "$(stat -c%s models/scene/yolo26s-sem-ade20k.onnx)" -lt 100000 ]; then + echo "error: the scene model is an LFS pointer — run: git lfs pull" >&2 + exit 1 + fi + for m in yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt; do + install -Dm644 "models/scene/$m" "/app/share/darkroom/models/$m" + done + - install -Dm644 README.md /app/share/doc/darkroom/README.md sources: diff --git a/tools/export-seg-model.sh b/tools/export-seg-model.sh index 5468390..de12bce 100755 --- a/tools/export-seg-model.sh +++ b/tools/export-seg-model.sh @@ -1,14 +1,25 @@ #!/usr/bin/env bash -# Re-export the segmentation model that ships in core/dr-segment/models/. +# Re-export the segmentation models that ship in models/ at the repository root. # # The .onnx is committed (D14), so this is not part of any build — it exists so # the committed artefact is reproducible rather than a binary someone once -# produced and nobody can regenerate. Run it when bumping the model. +# produced and nobody can regenerate. Run it when bumping a model. # -# ./tools/export-seg-model.sh +# ./tools/export-seg-model.sh # instance -> models/segment/ +# ./tools/export-seg-model.sh yolo26s-sem-ade20k # semantic -> models/scene/ # # Requires `uv`. Everything else is fetched into a throwaway venv. # +# ## The two models, and why they are both here +# +# `yolo26n-seg` is COCO instance segmentation: it separates *things*, so +# clicking one of three people selects that person. `yolo26s-sem-ade20k` is +# ADE20K semantic segmentation: it labels every pixel with one of 150 classes +# including the *stuff* — sky, vegetation, water — that COCO has no word for, +# but it merges same-class pixels into one region and so cannot tell those +# three people apart. Neither substitutes for the other; the scene tab wants +# the second and local adjustments want the first. +# # ## Why these export flags # # `dynamic=False` is not a default we failed to change: **tract cannot parse @@ -20,15 +31,44 @@ # `imgsz` square rather than a rectangle matched to 3:2: one graph has to # serve portrait, landscape, square crops and panoramas. A landscape-shaped # graph trades letterbox waste on 3:2 for worse waste on everything else. +# +# ## Why the semantic export is truncated +# +# Ultralytics ends the `-sem-` graph with `Resize -> ArgMax -> Cast`, handing +# back a `[1, 640, 640]` u8 label map. Two reasons that tail is cut here: +# +# 1. **Cost.** The Resize materialises 150 x 640 x 640 x f32 — *246 MB* — and +# ArgMax then reduces across the channel axis, which in NCHW strides +# 409,600 elements per comparison. Measured on one machine it was roughly +# four fifths of total runtime, for work the app throws away. +# 2. **Softness.** ArgMax destroys the per-class scores. The scene tab needs +# them: softmax over the 150 channels, summed within each photographic +# category, gives per-category weights that sum to 1 at every pixel — a +# partition of unity. Feathering those cannot double-grade a boundary, +# where feathering hard labels outward from two adjacent categories does. +# +# The upsample is not information: the graph's true spatial resolution is the +# logit grid (80x80 at imgsz=640), and the app can resample from that itself. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "${HERE}/.." && pwd)" -OUT="${REPO}/core/dr-segment/models" MODEL="${1:-yolo26n-seg}" IMGSZ="${2:-640}" -WORK="$(mktemp -d)" + +# Semantic models are the scene tab's; instance models are the selection path's. +case "${MODEL}" in + *-sem-*|*-sem) OUT="${REPO}/models/scene"; SEMANTIC=1 ;; + *) OUT="${REPO}/models/segment"; SEMANTIC=0 ;; +esac + +# Not `mktemp -d`: the default TMPDIR is `/tmp`, which on most current Linux +# distributions is a tmpfs — RAM, sized at half of physical memory. The venv +# below pulls torch, several gigabytes of it, and installing that into RAM +# either evicts the user's page cache or fails outright with ENOSPC on a +# machine that has hundreds of gigabytes of actual disk free. +WORK="$(mktemp -d -p "${TMPDIR:-/var/tmp}")" trap 'rm -rf "${WORK}"' EXIT echo "==> exporting ${MODEL} at imgsz=${IMGSZ} in ${WORK}" @@ -36,12 +76,13 @@ cd "${WORK}" uv venv --python 3.12 venv VIRTUAL_ENV="${WORK}/venv" uv pip install ultralytics onnx onnxslim -VIRTUAL_ENV="${WORK}/venv" "${WORK}/venv/bin/python" - "${MODEL}" "${IMGSZ}" <<'PY' +VIRTUAL_ENV="${WORK}/venv" "${WORK}/venv/bin/python" - "${MODEL}" "${IMGSZ}" "${SEMANTIC}" <<'PY' import sys, json +import onnx +from onnx import helper from ultralytics import YOLO -name = sys.argv[1] -imgsz = int(sys.argv[2]) +name, imgsz, semantic = sys.argv[1], int(sys.argv[2]), sys.argv[3] == "1" m = YOLO(f"{name}.pt") path = m.export(format="onnx", opset=17, simplify=True, imgsz=imgsz, dynamic=False) print("ONNX:", path) @@ -52,6 +93,25 @@ print("ONNX:", path) with open("classes.json", "w") as f: json.dump([m.names[i] for i in range(len(m.names))], f, indent=1) print("classes:", len(m.names)) + +if semantic: + # Drop `Resize -> ArgMax -> Cast` and expose the classifier's logits. See + # the header for why. Matched by op type rather than by node name so a + # re-export under a different naming scheme still works, and asserted + # rather than assumed so an upstream graph change fails loudly here + # instead of silently shipping a differently-shaped model. + g = onnx.load(path).graph + tail = [n.op_type for n in g.node[-3:]] + assert tail == ["Resize", "ArgMax", "Cast"], f"unexpected graph tail: {tail}" + logits = g.node[-3].input[0] + del g.node[-3:] + del g.output[:] + g.output.extend([helper.make_tensor_value_info(logits, onnx.TensorProto.FLOAT, None)]) + model = onnx.shape_inference.infer_shapes(helper.make_model(g, opset_imports=[helper.make_opsetid("", 17)])) + onnx.checker.check_model(model) + onnx.save(model, path) + shape = [d.dim_value for d in model.graph.output[0].type.tensor_type.shape.dim] + print("truncated to logits:", logits, shape) PY mkdir -p "${OUT}" @@ -61,4 +121,4 @@ cp "${WORK}/classes.json" "${OUT}/${MODEL}.classes.json" echo "==> wrote:" ls -la "${OUT}" echo -echo "Remember: these weights are AGPL-3.0 (see ${OUT}/LICENCE.md)." +echo "Remember: these weights are AGPL-3.0 (see ${REPO}/models/LICENCE.md)." diff --git a/ui/dr-ui/src/lib.rs b/ui/dr-ui/src/lib.rs index aebae15..23a980b 100644 --- a/ui/dr-ui/src/lib.rs +++ b/ui/dr-ui/src/lib.rs @@ -73,6 +73,15 @@ pub use develop::DevelopSession; /// opened — see `library::shared_face_models_dir`. pub use library::shared_face_models_dir; +/// The scene model's three files, wherever this device keeps them. +/// +/// Public for the same reason as the directory above: the pieces that reach for +/// a model are not all inside this crate. Exported ahead of the scene tab that +/// will consume it so that the packaging and unpacking added alongside it have +/// something to be verified against — `assemble-apk.sh` writing files no lookup +/// looks for would be a silent mistake for as long as the tab took to arrive. +pub use library::scene_model; + pub mod launch; pub mod launch_ui; diff --git a/ui/dr-ui/src/library.rs b/ui/dr-ui/src/library.rs index c14796f..ab4562c 100644 --- a/ui/dr-ui/src/library.rs +++ b/ui/dr-ui/src/library.rs @@ -3846,6 +3846,37 @@ pub fn face_models(account: &Account) -> Option<(PathBuf, PathBuf)> { .or_else(|| system_face_models_dirs().into_iter().find_map(pair)) } +/// The scene model, its vocabulary and its category descriptor, if all three +/// are present. +/// +/// All three or none, for the same reason `face_models` insists on its pair: +/// the graph alone decodes to 150 anonymous channels, and a descriptor naming +/// classes a different model does not have is refused by +/// `dr_segment::scene::parse_categories` anyway. Reporting the set missing is +/// more useful than starting and failing at the first inference. +/// +/// Searched in the same three places, most specific first — the account's own +/// directory, the shared one, then wherever a package installed them. Android +/// only ever finds the second, which is where `install_bundled_models` unpacks +/// the APK's copy before any store opens. +/// +/// Unlike the face weights this model *is* in the repository, so a desktop +/// build from a complete checkout has it. Absent means either a checkout +/// without `git lfs pull` or a package that chose not to carry 24 MB, and the +/// scene tab reports itself unavailable rather than the app refusing to run. +pub fn scene_model(account: &Account) -> Option<(PathBuf, PathBuf, PathBuf)> { + fn set(dir: PathBuf) -> Option<(PathBuf, PathBuf, PathBuf)> { + let model = dir.join("yolo26s-sem-ade20k.onnx"); + let classes = dir.join("yolo26s-sem-ade20k.classes.json"); + let categories = dir.join("categories.txt"); + (model.is_file() && classes.is_file() && categories.is_file()) + .then_some((model, classes, categories)) + } + set(face_models_dir(account)) + .or_else(|| set(shared_face_models_dir())) + .or_else(|| system_face_models_dirs().into_iter().find_map(set)) +} + /// Where a *package* may have installed the models. /// /// `$XDG_DATA_DIRS` rather than a hard-coded `/usr/share`, because that is the