diff --git a/core/dr-decode/examples/mosaic_dump.rs b/core/dr-gpu/examples/mosaic_dump.rs similarity index 72% rename from core/dr-decode/examples/mosaic_dump.rs rename to core/dr-gpu/examples/mosaic_dump.rs index 0c1114a..9206135 100644 --- a/core/dr-decode/examples/mosaic_dump.rs +++ b/core/dr-gpu/examples/mosaic_dump.rs @@ -5,27 +5,37 @@ //! network trained on one phase runs on another and paints moiré everywhere //! (denoise.md §4.4). So it reads this, not LibRaw. //! +//! The photosites are those the demosaic reads: hot and dead ones repaired by +//! the app's own pass ([`Demosaicer::repair_hot_pixels`], the same shader +//! `run` dispatches), because the learned stage replaces the demosaic and +//! takes its input (denoise.md §2). `--unrepaired` skips it. +//! //! Reads `inputoutput-prefix` lines on stdin and writes, per line, //! `prefix.npy` (the whole readout, masked border included, `u16`, row-major) //! and `prefix.json` (what `decode` and `metadata` say about it). The border -//! is kept because its optically black photosites are a dark frame for free: -//! read noise and row noise at that ISO. +//! is kept, and the repair never touches it, because its optically black +//! photosites are a dark frame for free: read noise and row noise at that ISO. //! //! ```sh //! printf 'IMG_0001.CR2\tout/IMG_0001\n' | -//! cargo run --release -p dr-decode --example mosaic_dump +//! cargo run --release -p dr-gpu --example mosaic_dump //! ``` use std::io::{BufRead, Write}; +use dr_gpu::{Demosaicer, GpuContext}; + fn main() { + let repair = !std::env::args().any(|a| a == "--unrepaired"); + let ctx = pollster::block_on(GpuContext::new_headless()).expect("a GPU for the hot-pixel pass"); + let demosaicer = Demosaicer::new(&ctx).expect("demosaicer"); let mut failed = 0; for line in std::io::stdin().lock().lines() { let line = line.expect("stdin"); let Some((input, prefix)) = line.split_once('\t') else { continue; }; - match dump(input, prefix) { + match dump(input, prefix, repair.then_some(&demosaicer)) { Ok(()) => println!("ok\t{input}"), Err(e) => { failed += 1; @@ -37,12 +47,16 @@ fn main() { std::process::exit(if failed > 0 { 1 } else { 0 }); } -fn dump(input: &str, prefix: &str) -> Result<(), String> { +fn dump(input: &str, prefix: &str, repair: Option<&Demosaicer>) -> Result<(), String> { let bytes = std::fs::read(input).map_err(|e| e.to_string())?; - let raw = dr_decode::decode(&bytes).map_err(|e| e.to_string())?; + let mut raw = dr_decode::decode(&bytes).map_err(|e| e.to_string())?; if raw.samples_per_pixel != 1 { return Err("linear DNG: no photosites".into()); } + let repaired = match repair { + Some(d) => d.repair_hot_pixels(&mut raw).map_err(|e| e.to_string())? as i64, + None => -1, + }; let meta = dr_decode::metadata(&bytes).map_err(|e| e.to_string())?; let mut npy = Vec::with_capacity(raw.data.len() * 2 + 128); @@ -73,7 +87,8 @@ fn dump(input: &str, prefix: &str) -> Result<(), String> { "\"width\": {}, \"height\": {}, ", "\"crop\": [{}, {}, {}, {}], \"cfa\": {:?}, ", "\"black\": {:?}, \"white\": {}, \"wb\": {:?}, \"cam_to_srgb\": {}, ", - "\"iso\": {}, \"shutter\": {}, \"aperture\": {}, \"captured_at\": {}}}\n" + "\"iso\": {}, \"shutter\": {}, \"aperture\": {}, \"captured_at\": {}, ", + "\"hot_repaired\": {}}}\n" ), input, raw.make, @@ -94,6 +109,7 @@ fn dump(input: &str, prefix: &str) -> Result<(), String> { opt(meta.aperture), meta.captured_at .map_or("null".to_string(), |v| v.to_string()), + repaired, ); std::fs::write(format!("{prefix}.json"), json).map_err(|e| e.to_string()) } diff --git a/core/dr-gpu/src/demosaic.rs b/core/dr-gpu/src/demosaic.rs index 5c87004..ec245c0 100644 --- a/core/dr-gpu/src/demosaic.rs +++ b/core/dr-gpu/src/demosaic.rs @@ -761,62 +761,14 @@ impl Demosaicer { // TRACES: FR-RAW-3 // The mosaic the demosaic actually reads: the readout with its hot and - // dead photosites repaired. A second buffer rather than in place, - // because every photosite's verdict reads its neighbours' originals. - let repaired = self.ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("raw-repaired"), - size: raw_buf.size(), - usage: wgpu::BufferUsages::STORAGE, - mapped_at_creation: false, - }); - let words = packed.len() as u32; - let groups = words.div_ceil(HOT_PIXEL_GROUP).max(1); - // A 24 MP readout is 190,000 workgroups, past the 65,535 one - // dispatch dimension may hold, so the grid folds into rows. - let groups_x = groups.min( - self.ctx - .device - .limits() - .max_compute_workgroups_per_dimension, - ); - let groups_y = groups.div_ceil(groups_x); - let hot_params = hot_pixel_params( + // dead photosites repaired. + let hot = self.hot_pass( raw, (width, height), - words, - groups_x * HOT_PIXEL_GROUP, + &raw_buf, + packed.len() as u32, xtrans_tile, ); - let hot_params_buf = - self.ctx - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("hot-pixel-params"), - contents: bytemuck::bytes_of(&hot_params), - usage: wgpu::BufferUsages::UNIFORM, - }); - let hot_bind_group = self - .ctx - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("hot-pixel-bg"), - layout: &self.hot_pixel_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: raw_buf.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: hot_params_buf.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: repaired.as_entire_binding(), - }, - ], - }); - let params_buf = self .ctx .device @@ -855,7 +807,7 @@ impl Demosaicer { entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: repaired.as_entire_binding(), + resource: hot.repaired.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, @@ -877,15 +829,7 @@ impl Demosaicer { // Two passes in one submission. wgpu orders a storage write in one // pass before a read of the same buffer in the next, so the demosaic // sees every repair. - { - let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("hot-pixel-pass"), - timestamp_writes: None, - }); - pass.set_pipeline(&self.hot_pixel_pipeline); - pass.set_bind_group(0, &hot_bind_group, &[]); - pass.dispatch_workgroups(groups_x, groups_y, 1); - } + self.record_hot_pass(&mut enc, &hot); { let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("demosaic-pass"), @@ -921,6 +865,169 @@ impl Demosaicer { } } +/// The hot-pixel pass's resources for one frame, ready to record. +struct HotPass { + repaired: wgpu::Buffer, + bind_group: wgpu::BindGroup, + groups: (u32, u32), +} + +impl Demosaicer { + /// Buffers and bindings for the hot and dead photosite repair of `raw`, + /// whose packed samples are in `raw_buf`. + fn hot_pass( + &self, + raw: &RawImage, + (width, height): (u32, u32), + raw_buf: &wgpu::Buffer, + words: u32, + xtrans_tile: Option<[u32; 4]>, + ) -> HotPass { + // A second buffer rather than in place, because every photosite's + // verdict reads its neighbours' originals. + let repaired = self.ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("raw-repaired"), + size: raw_buf.size(), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let groups = words.div_ceil(HOT_PIXEL_GROUP).max(1); + // A 24 MP readout is 190,000 workgroups, past the 65,535 one + // dispatch dimension may hold, so the grid folds into rows. + let groups_x = groups.min( + self.ctx + .device + .limits() + .max_compute_workgroups_per_dimension, + ); + let groups_y = groups.div_ceil(groups_x); + let hot_params = hot_pixel_params( + raw, + (width, height), + words, + groups_x * HOT_PIXEL_GROUP, + xtrans_tile, + ); + let hot_params_buf = + self.ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("hot-pixel-params"), + contents: bytemuck::bytes_of(&hot_params), + usage: wgpu::BufferUsages::UNIFORM, + }); + let bind_group = self + .ctx + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("hot-pixel-bg"), + layout: &self.hot_pixel_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: raw_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: hot_params_buf.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: repaired.as_entire_binding(), + }, + ], + }); + HotPass { + repaired, + bind_group, + groups: (groups_x, groups_y), + } + } + + fn record_hot_pass(&self, enc: &mut wgpu::CommandEncoder, hot: &HotPass) { + let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("hot-pixel-pass"), + timestamp_writes: None, + }); + pass.set_pipeline(&self.hot_pixel_pipeline); + pass.set_bind_group(0, &hot.bind_group, &[]); + pass.dispatch_workgroups(hot.groups.0, hot.groups.1, 1); + } + + /// TRACES: FR-RAW-3 | FR-DEV-3g + /// Repair `raw`'s hot and dead photosites in place, exactly as [`Self::run`] + /// does before it demosaics, and return how many changed. + /// + /// For the learned demosaic (denoise.md §2), which reads the same repaired + /// mosaic the classical one does: its training data and its input in the + /// app must have been through this one pass, not a lookalike. + pub fn repair_hot_pixels(&self, raw: &mut RawImage) -> Result { + if raw.samples_per_pixel != 1 { + return Ok(0); + } + let (width, height) = (raw.crop.width.max(1), raw.crop.height.max(1)); + let xtrans_tile = raw + .cfa_pattern + .is_xtrans() + .then(|| xtrans_params_for(raw, width, height).tile); + let packed = pack_samples(&raw.data); + let raw_buf = self + .ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("raw-samples"), + contents: bytemuck::cast_slice(&packed), + usage: wgpu::BufferUsages::STORAGE, + }); + let hot = self.hot_pass( + raw, + (width, height), + &raw_buf, + packed.len() as u32, + xtrans_tile, + ); + let readback = self.ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("raw-repaired-readback"), + size: hot.repaired.size(), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + let mut enc = self + .ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("hot-pixel-encoder"), + }); + self.record_hot_pass(&mut enc, &hot); + enc.copy_buffer_to_buffer(&hot.repaired, 0, &readback, 0, hot.repaired.size()); + self.ctx.queue.submit(Some(enc.finish())); + + let slice = readback.slice(..); + let (tx, rx) = std::sync::mpsc::channel(); + slice.map_async(wgpu::MapMode::Read, move |r| { + let _ = tx.send(r); + }); + self.ctx + .device + .poll(wgpu::PollType::wait_indefinitely()) + .map_err(|e| GpuError::Readback(e.to_string()))?; + rx.recv() + .map_err(|e| GpuError::Readback(e.to_string()))? + .map_err(|e| GpuError::Readback(e.to_string()))?; + let words: Vec = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec(); + readback.unmap(); + + let mut changed = 0; + for (i, v) in raw.data.iter_mut().enumerate() { + let w = words[i / 2]; + let new = if i % 2 == 0 { w & 0xFFFF } else { w >> 16 } as u16; + changed += usize::from(new != *v); + *v = new; + } + Ok(changed) + } +} + const IDENTITY_3X3: [f32; 9] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]; /// Pack u16 samples two per u32, little-endian within the word. diff --git a/core/dr-gpu/tests/hot_pixels.rs b/core/dr-gpu/tests/hot_pixels.rs index 46aba71..39b8aa5 100644 --- a/core/dr-gpu/tests/hot_pixels.rs +++ b/core/dr-gpu/tests/hot_pixels.rs @@ -140,3 +140,33 @@ fn a_hot_photosite_on_x_trans_is_invisible() { let diff = worst(&clean, &hot); assert!(diff <= 1, "a hot X-Trans photosite still shows, by {diff}"); } + +/// The repair alone, read back (FR-DEV-3g): the learned demosaic takes the +/// mosaic this pass leaves, so it must be the same pass and nothing more — +/// the hot photosite replaced, a real highlight and every other photosite +/// untouched. +#[test] +fn the_repaired_mosaic_reads_back_with_only_the_defect_changed() { + let Some(ctx) = ctx() else { + eprintln!("no GPU adapter; skipping"); + return; + }; + let d = Demosaicer::new(&ctx).expect("demosaicer"); + let mut star = vec![(MIDDLE, MIDDLE, WHITE)]; + for dy in 0..3 { + for dx in 0..3 { + star.push((4 + dx, 4 + dy, WHITE)); + } + } + let before = frame(CfaPattern::Rggb, 40, &star); + let mut raw = before.clone(); + let changed = d.repair_hot_pixels(&mut raw).expect("repair"); + assert_eq!(changed, 1, "only the lone hot photosite should change"); + let at = (MIDDLE * SIZE + MIDDLE) as usize; + assert_eq!( + raw.data[at], 40, + "repaired to its brightest same-colour neighbour" + ); + let others = (0..raw.data.len()).filter(|&i| i != at); + assert!(others.into_iter().all(|i| raw.data[i] == before.data[i])); +} diff --git a/docs/dev/traceability.md b/docs/dev/traceability.md index 0c24a03..91d78a6 100644 --- a/docs/dev/traceability.md +++ b/docs/dev/traceability.md @@ -10,17 +10,17 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| | Source files scanned | 487 | -| TRACES tags found | 2033 | +| TRACES tags found | 2034 | | Requirements defined | 193 | | Requirements deferred (post-v1) | 24 | -| Requirements covered | 165 | -| **Coverage** | **85.5%** (165/193) | +| Requirements covered | 166 | +| **Coverage** | **86.0%** (166/193) | ### By type | Type | Covered | Defined | |---|---|---| -| FR | 125 | 135 | +| FR | 126 | 135 | | NFR | 35 | 51 | | R | 5 | 7 | @@ -81,6 +81,7 @@ _None._ | FR-DEV-3d | [`core/dr-gpu/src/adjust.rs:1107`](../../core/dr-gpu/src/adjust.rs#L1107), [`core/dr-gpu/src/adjust.rs:112`](../../core/dr-gpu/src/adjust.rs#L112), [`core/dr-gpu/src/adjust.rs:1429`](../../core/dr-gpu/src/adjust.rs#L1429), [`core/dr-gpu/src/adjust.rs:1523`](../../core/dr-gpu/src/adjust.rs#L1523), [`core/dr-gpu/src/adjust.rs:82`](../../core/dr-gpu/src/adjust.rs#L82), [`core/dr-gpu/tests/capture_sharpen.rs:433`](../../core/dr-gpu/tests/capture_sharpen.rs#L433), [`core/dr-gpu/tests/detail_stage.rs:241`](../../core/dr-gpu/tests/detail_stage.rs#L241), [`core/dr-gpu/tests/local_contrast.rs:557`](../../core/dr-gpu/tests/local_contrast.rs#L557), [`core/dr-gpu/tests/noise_reduction.rs:555`](../../core/dr-gpu/tests/noise_reduction.rs#L555), [`core/dr-pipeline/src/framing.rs:481`](../../core/dr-pipeline/src/framing.rs#L481), [`core/dr-pipeline/src/graph.rs:1044`](../../core/dr-pipeline/src/graph.rs#L1044), [`core/dr-pipeline/src/operation.rs:32`](../../core/dr-pipeline/src/operation.rs#L32), [`core/dr-pipeline/src/operation.rs:505`](../../core/dr-pipeline/src/operation.rs#L505), [`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/dcp.rs:126`](../../core/dr-decode/src/dcp.rs#L126), [`core/dr-decode/src/dcp.rs:157`](../../core/dr-decode/src/dcp.rs#L157), [`core/dr-decode/src/dcp.rs:176`](../../core/dr-decode/src/dcp.rs#L176), [`core/dr-decode/src/dcp.rs:199`](../../core/dr-decode/src/dcp.rs#L199), [`core/dr-decode/src/dcp.rs:1`](../../core/dr-decode/src/dcp.rs#L1), [`core/dr-decode/src/dcp.rs:230`](../../core/dr-decode/src/dcp.rs#L230), [`core/dr-decode/src/dcp.rs:403`](../../core/dr-decode/src/dcp.rs#L403), [`core/dr-decode/src/dcp.rs:454`](../../core/dr-decode/src/dcp.rs#L454), [`core/dr-decode/src/dcp.rs:465`](../../core/dr-decode/src/dcp.rs#L465), [`core/dr-decode/src/dcp.rs:498`](../../core/dr-decode/src/dcp.rs#L498), [`core/dr-decode/src/dcp.rs:513`](../../core/dr-decode/src/dcp.rs#L513), [`core/dr-decode/src/dcp.rs:523`](../../core/dr-decode/src/dcp.rs#L523), [`core/dr-decode/src/dcp.rs:74`](../../core/dr-decode/src/dcp.rs#L74), [`core/dr-decode/src/lib.rs:126`](../../core/dr-decode/src/lib.rs#L126), [`core/dr-decode/src/lib.rs:141`](../../core/dr-decode/src/lib.rs#L141), [`core/dr-decode/src/lib.rs:584`](../../core/dr-decode/src/lib.rs#L584), [`core/dr-decode/src/lib.rs:780`](../../core/dr-decode/src/lib.rs#L780), [`core/dr-decode/src/lib.rs:820`](../../core/dr-decode/src/lib.rs#L820), [`core/dr-decode/src/profile.rs:104`](../../core/dr-decode/src/profile.rs#L104), [`core/dr-decode/src/profile.rs:153`](../../core/dr-decode/src/profile.rs#L153), [`core/dr-decode/src/profile.rs:1`](../../core/dr-decode/src/profile.rs#L1), [`core/dr-decode/src/profile.rs:237`](../../core/dr-decode/src/profile.rs#L237), [`core/dr-decode/src/profile.rs:288`](../../core/dr-decode/src/profile.rs#L288), [`core/dr-decode/src/profile.rs:345`](../../core/dr-decode/src/profile.rs#L345), [`core/dr-decode/src/profile.rs:475`](../../core/dr-decode/src/profile.rs#L475), [`core/dr-decode/src/profile.rs:509`](../../core/dr-decode/src/profile.rs#L509), [`core/dr-decode/src/profile.rs:680`](../../core/dr-decode/src/profile.rs#L680), [`core/dr-gpu/src/adjust.rs:523`](../../core/dr-gpu/src/adjust.rs#L523), [`core/dr-gpu/src/adjust.rs:536`](../../core/dr-gpu/src/adjust.rs#L536), [`core/dr-gpu/src/adjust.rs:74`](../../core/dr-gpu/src/adjust.rs#L74), [`core/dr-gpu/src/demosaic.rs:110`](../../core/dr-gpu/src/demosaic.rs#L110), [`core/dr-gpu/src/demosaic.rs:215`](../../core/dr-gpu/src/demosaic.rs#L215), [`core/dr-gpu/tests/camera_profile.rs:1`](../../core/dr-gpu/tests/camera_profile.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:279`](../../core/dr-pipeline/src/declared/decl.rs#L279), [`core/dr-pipeline/src/operation.rs:2459`](../../core/dr-pipeline/src/operation.rs#L2459), [`core/dr-pipeline/src/operation.rs:2483`](../../core/dr-pipeline/src/operation.rs#L2483), [`core/dr-pipeline/src/operation.rs:273`](../../core/dr-pipeline/src/operation.rs#L273), [`core/dr-pipeline/src/operation.rs:349`](../../core/dr-pipeline/src/operation.rs#L349), [`core/dr-pipeline/src/operation.rs:369`](../../core/dr-pipeline/src/operation.rs#L369), [`core/dr-pipeline/src/operation.rs:443`](../../core/dr-pipeline/src/operation.rs#L443), [`core/dr-pipeline/src/operation.rs:463`](../../core/dr-pipeline/src/operation.rs#L463), [`core/dr-pipeline/src/ops/camera_profile.rs:1`](../../core/dr-pipeline/src/ops/camera_profile.rs#L1), [`core/dr-pipeline/src/ops/camera_profile.rs:355`](../../core/dr-pipeline/src/ops/camera_profile.rs#L355), [`core/dr-pipeline/src/ops/camera_profile.rs:381`](../../core/dr-pipeline/src/ops/camera_profile.rs#L381), [`core/dr-pipeline/src/view.rs:227`](../../core/dr-pipeline/src/view.rs#L227), [`core/dr-types/src/hue_sat.rs:1`](../../core/dr-types/src/hue_sat.rs#L1), [`ui/dr-ui/src/develop/session.rs:590`](../../ui/dr-ui/src/develop/session.rs#L590), [`ui/dr-ui/src/develop/session.rs:603`](../../ui/dr-ui/src/develop/session.rs#L603), [`ui/dr-ui/src/develop/session.rs:638`](../../ui/dr-ui/src/develop/session.rs#L638), [`ui/dr-ui/src/develop/session.rs:647`](../../ui/dr-ui/src/develop/session.rs#L647), [`ui/dr-ui/src/develop/session.rs:73`](../../ui/dr-ui/src/develop/session.rs#L73), [`ui/dr-ui/src/develop/session.rs:838`](../../ui/dr-ui/src/develop/session.rs#L838), [`ui/dr-ui/src/develop/session.rs:881`](../../ui/dr-ui/src/develop/session.rs#L881), [`ui/dr-ui/src/lib.rs:1205`](../../ui/dr-ui/src/lib.rs#L1205), [`ui/dr-ui/src/lib.rs:1503`](../../ui/dr-ui/src/lib.rs#L1503), [`ui/dr-ui/src/lib.rs:1521`](../../ui/dr-ui/src/lib.rs#L1521), [`ui/dr-ui/src/lib.rs:327`](../../ui/dr-ui/src/lib.rs#L327), [`ui/dr-ui/ui/develop.slint:386`](../../ui/dr-ui/ui/develop.slint#L386), [`ui/dr-ui/ui/develop.slint:395`](../../ui/dr-ui/ui/develop.slint#L395) | | FR-DEV-3f | [`core/dr-film/src/bake.rs:161`](../../core/dr-film/src/bake.rs#L161), [`core/dr-film/src/bake.rs:384`](../../core/dr-film/src/bake.rs#L384), [`core/dr-film/src/bake.rs:52`](../../core/dr-film/src/bake.rs#L52), [`core/dr-film/src/bake.rs:662`](../../core/dr-film/src/bake.rs#L662), [`core/dr-film/src/bake.rs:689`](../../core/dr-film/src/bake.rs#L689), [`core/dr-film/src/bake.rs:710`](../../core/dr-film/src/bake.rs#L710), [`core/dr-film/src/bake.rs:747`](../../core/dr-film/src/bake.rs#L747), [`core/dr-film/src/bake.rs:95`](../../core/dr-film/src/bake.rs#L95), [`core/dr-film/src/boolean_grain.rs:1`](../../core/dr-film/src/boolean_grain.rs#L1), [`core/dr-film/src/boolean_grain.rs:78`](../../core/dr-film/src/boolean_grain.rs#L78), [`core/dr-film/src/grain.rs:140`](../../core/dr-film/src/grain.rs#L140), [`core/dr-film/src/grain.rs:1`](../../core/dr-film/src/grain.rs#L1), [`core/dr-film/src/grain.rs:302`](../../core/dr-film/src/grain.rs#L302), [`core/dr-film/src/grain.rs:79`](../../core/dr-film/src/grain.rs#L79), [`core/dr-film/src/lib.rs:160`](../../core/dr-film/src/lib.rs#L160), [`core/dr-film/src/lib.rs:1`](../../core/dr-film/src/lib.rs#L1), [`core/dr-film/src/profile.rs:100`](../../core/dr-film/src/profile.rs#L100), [`core/dr-film/src/profile.rs:142`](../../core/dr-film/src/profile.rs#L142), [`core/dr-film/src/profile.rs:182`](../../core/dr-film/src/profile.rs#L182), [`core/dr-film/src/profile.rs:259`](../../core/dr-film/src/profile.rs#L259), [`core/dr-film/src/profile.rs:502`](../../core/dr-film/src/profile.rs#L502), [`core/dr-film/src/profile.rs:73`](../../core/dr-film/src/profile.rs#L73), [`core/dr-gpu/src/adjust.rs:152`](../../core/dr-gpu/src/adjust.rs#L152), [`core/dr-gpu/src/adjust.rs:422`](../../core/dr-gpu/src/adjust.rs#L422), [`core/dr-gpu/src/adjust.rs:632`](../../core/dr-gpu/src/adjust.rs#L632), [`core/dr-gpu/src/adjust.rs:67`](../../core/dr-gpu/src/adjust.rs#L67), [`core/dr-gpu/src/adjust.rs:765`](../../core/dr-gpu/src/adjust.rs#L765), [`core/dr-gpu/tests/film_sim.rs:1`](../../core/dr-gpu/tests/film_sim.rs#L1), [`core/dr-gpu/tests/film_sim.rs:214`](../../core/dr-gpu/tests/film_sim.rs#L214), [`core/dr-gpu/tests/film_sim.rs:348`](../../core/dr-gpu/tests/film_sim.rs#L348), [`core/dr-gpu/tests/film_sim.rs:387`](../../core/dr-gpu/tests/film_sim.rs#L387), [`core/dr-gpu/tests/film_sim.rs:416`](../../core/dr-gpu/tests/film_sim.rs#L416), [`core/dr-gpu/tests/film_sim.rs:456`](../../core/dr-gpu/tests/film_sim.rs#L456), [`core/dr-pipeline/src/graph.rs:110`](../../core/dr-pipeline/src/graph.rs#L110), [`core/dr-pipeline/src/graph.rs:175`](../../core/dr-pipeline/src/graph.rs#L175), [`core/dr-pipeline/src/graph.rs:624`](../../core/dr-pipeline/src/graph.rs#L624), [`core/dr-pipeline/src/mask.rs:2452`](../../core/dr-pipeline/src/mask.rs#L2452), [`core/dr-pipeline/src/mask.rs:2485`](../../core/dr-pipeline/src/mask.rs#L2485), [`core/dr-pipeline/src/operation.rs:1086`](../../core/dr-pipeline/src/operation.rs#L1086), [`core/dr-pipeline/src/operation.rs:1436`](../../core/dr-pipeline/src/operation.rs#L1436), [`core/dr-pipeline/src/operation.rs:1670`](../../core/dr-pipeline/src/operation.rs#L1670), [`core/dr-pipeline/src/operation.rs:1698`](../../core/dr-pipeline/src/operation.rs#L1698), [`core/dr-pipeline/src/operation.rs:1750`](../../core/dr-pipeline/src/operation.rs#L1750), [`core/dr-pipeline/src/operation.rs:2377`](../../core/dr-pipeline/src/operation.rs#L2377), [`core/dr-pipeline/src/operation.rs:309`](../../core/dr-pipeline/src/operation.rs#L309), [`core/dr-pipeline/src/operation.rs:323`](../../core/dr-pipeline/src/operation.rs#L323), [`core/dr-pipeline/src/operation.rs:334`](../../core/dr-pipeline/src/operation.rs#L334), [`core/dr-pipeline/src/operation.rs:349`](../../core/dr-pipeline/src/operation.rs#L349), [`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:134`](../../core/dr-pipeline/src/ops/film_sim.rs#L134), [`core/dr-pipeline/src/ops/film_sim.rs:186`](../../core/dr-pipeline/src/ops/film_sim.rs#L186), [`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:233`](../../core/dr-pipeline/src/ops/film_sim.rs#L233), [`core/dr-pipeline/src/ops/film_sim.rs:315`](../../core/dr-pipeline/src/ops/film_sim.rs#L315), [`core/dr-pipeline/src/ops/film_sim.rs:329`](../../core/dr-pipeline/src/ops/film_sim.rs#L329), [`core/dr-pipeline/src/ops/film_sim.rs:438`](../../core/dr-pipeline/src/ops/film_sim.rs#L438), [`core/dr-pipeline/src/ops/film_sim.rs:48`](../../core/dr-pipeline/src/ops/film_sim.rs#L48), [`core/dr-pipeline/src/ops/film_sim.rs:736`](../../core/dr-pipeline/src/ops/film_sim.rs#L736), [`core/dr-pipeline/src/preset.rs:1293`](../../core/dr-pipeline/src/preset.rs#L1293), [`core/dr-pipeline/src/preset.rs:1303`](../../core/dr-pipeline/src/preset.rs#L1303), [`core/dr-pipeline/src/preset.rs:1312`](../../core/dr-pipeline/src/preset.rs#L1312), [`core/dr-pipeline/src/preset.rs:1328`](../../core/dr-pipeline/src/preset.rs#L1328), [`core/dr-pipeline/src/preset.rs:1340`](../../core/dr-pipeline/src/preset.rs#L1340), [`core/dr-pipeline/src/preset.rs:1356`](../../core/dr-pipeline/src/preset.rs#L1356), [`core/dr-pipeline/src/preset.rs:1529`](../../core/dr-pipeline/src/preset.rs#L1529), [`core/dr-pipeline/src/preset.rs:1544`](../../core/dr-pipeline/src/preset.rs#L1544), [`core/dr-pipeline/src/preset.rs:248`](../../core/dr-pipeline/src/preset.rs#L248), [`core/dr-pipeline/src/preset.rs:689`](../../core/dr-pipeline/src/preset.rs#L689), [`core/dr-pipeline/src/preset.rs:778`](../../core/dr-pipeline/src/preset.rs#L778), [`core/dr-pipeline/src/sidecar.rs:1077`](../../core/dr-pipeline/src/sidecar.rs#L1077), [`core/dr-pipeline/src/sidecar.rs:133`](../../core/dr-pipeline/src/sidecar.rs#L133), [`core/dr-pipeline/src/sidecar.rs:217`](../../core/dr-pipeline/src/sidecar.rs#L217), [`core/dr-pipeline/src/sidecar.rs:2724`](../../core/dr-pipeline/src/sidecar.rs#L2724), [`core/dr-pipeline/src/sidecar.rs:2800`](../../core/dr-pipeline/src/sidecar.rs#L2800), [`core/dr-pipeline/src/sidecar.rs:595`](../../core/dr-pipeline/src/sidecar.rs#L595), [`core/dr-pipeline/src/sidecar.rs:898`](../../core/dr-pipeline/src/sidecar.rs#L898), [`core/dr-pipeline/src/state.rs:100`](../../core/dr-pipeline/src/state.rs#L100), [`core/dr-pipeline/src/state.rs:115`](../../core/dr-pipeline/src/state.rs#L115), [`core/dr-pipeline/src/state.rs:60`](../../core/dr-pipeline/src/state.rs#L60), [`ui/dr-ui/src/develop/render.rs:1046`](../../ui/dr-ui/src/develop/render.rs#L1046), [`ui/dr-ui/src/develop/render.rs:1072`](../../ui/dr-ui/src/develop/render.rs#L1072), [`ui/dr-ui/src/develop/render.rs:1395`](../../ui/dr-ui/src/develop/render.rs#L1395), [`ui/dr-ui/src/develop/render.rs:1435`](../../ui/dr-ui/src/develop/render.rs#L1435), [`ui/dr-ui/src/develop/render.rs:1470`](../../ui/dr-ui/src/develop/render.rs#L1470), [`ui/dr-ui/src/develop/render.rs:823`](../../ui/dr-ui/src/develop/render.rs#L823), [`ui/dr-ui/src/develop/render.rs:840`](../../ui/dr-ui/src/develop/render.rs#L840), [`ui/dr-ui/src/develop/render.rs:852`](../../ui/dr-ui/src/develop/render.rs#L852), [`ui/dr-ui/src/develop/render.rs:890`](../../ui/dr-ui/src/develop/render.rs#L890), [`ui/dr-ui/src/develop/render.rs:927`](../../ui/dr-ui/src/develop/render.rs#L927), [`ui/dr-ui/src/develop/render.rs:938`](../../ui/dr-ui/src/develop/render.rs#L938), [`ui/dr-ui/src/develop/render.rs:995`](../../ui/dr-ui/src/develop/render.rs#L995), [`ui/dr-ui/src/develop/session.rs:717`](../../ui/dr-ui/src/develop/session.rs#L717), [`ui/dr-ui/src/develop/session.rs:737`](../../ui/dr-ui/src/develop/session.rs#L737), [`ui/dr-ui/src/develop/tabs.rs:80`](../../ui/dr-ui/src/develop/tabs.rs#L80), [`ui/dr-ui/src/develop_ui.rs:454`](../../ui/dr-ui/src/develop_ui.rs#L454), [`ui/dr-ui/src/lib.rs:878`](../../ui/dr-ui/src/lib.rs#L878), [`ui/dr-ui/src/lib.rs:943`](../../ui/dr-ui/src/lib.rs#L943), [`ui/dr-ui/src/library/sidecar.rs:325`](../../ui/dr-ui/src/library/sidecar.rs#L325), [`ui/dr-ui/src/library/sidecar.rs:50`](../../ui/dr-ui/src/library/sidecar.rs#L50), [`ui/dr-ui/src/library_ui/ratings_keywords.rs:602`](../../ui/dr-ui/src/library_ui/ratings_keywords.rs#L602), [`ui/dr-ui/src/presets.rs:1892`](../../ui/dr-ui/src/presets.rs#L1892), [`ui/dr-ui/src/presets.rs:422`](../../ui/dr-ui/src/presets.rs#L422), [`ui/dr-ui/tests/film_list_reaches_every_stock.rs:1`](../../ui/dr-ui/tests/film_list_reaches_every_stock.rs#L1), [`ui/dr-ui/ui/adjust.slint:1071`](../../ui/dr-ui/ui/adjust.slint#L1071), [`ui/dr-ui/ui/adjust.slint:1081`](../../ui/dr-ui/ui/adjust.slint#L1081), [`ui/dr-ui/ui/adjust.slint:1298`](../../ui/dr-ui/ui/adjust.slint#L1298) | +| FR-DEV-3g | [`core/dr-gpu/src/demosaic.rs:957`](../../core/dr-gpu/src/demosaic.rs#L957) | | FR-DEV-3h | [`core/dr-decode/src/lib.rs:445`](../../core/dr-decode/src/lib.rs#L445), [`core/dr-decode/src/preview.rs:29`](../../core/dr-decode/src/preview.rs#L29), [`core/dr-pipeline/src/framing.rs:1492`](../../core/dr-pipeline/src/framing.rs#L1492), [`core/dr-pipeline/src/framing.rs:499`](../../core/dr-pipeline/src/framing.rs#L499), [`core/dr-pipeline/src/framing.rs:717`](../../core/dr-pipeline/src/framing.rs#L717), [`core/dr-types/src/lib.rs:341`](../../core/dr-types/src/lib.rs#L341), [`core/dr-types/src/lib.rs:449`](../../core/dr-types/src/lib.rs#L449), [`core/dr-types/src/lib.rs:461`](../../core/dr-types/src/lib.rs#L461), [`core/dr-types/src/lib.rs:477`](../../core/dr-types/src/lib.rs#L477), [`ui/dr-ui/src/develop/masks.rs:280`](../../ui/dr-ui/src/develop/masks.rs#L280), [`ui/dr-ui/src/develop/segmentation.rs:114`](../../ui/dr-ui/src/develop/segmentation.rs#L114), [`ui/dr-ui/src/segmentation.rs:499`](../../ui/dr-ui/src/segmentation.rs#L499) | | FR-DEV-3i | [`core/dr-pipeline/src/mask.rs:558`](../../core/dr-pipeline/src/mask.rs#L558), [`core/dr-segment/src/lib.rs:1`](../../core/dr-segment/src/lib.rs#L1), [`ui/dr-ui/src/segmentation.rs:1`](../../ui/dr-ui/src/segmentation.rs#L1) | | FR-DEV-3j | [`core/dr-gpu/src/adjust.rs:1301`](../../core/dr-gpu/src/adjust.rs#L1301), [`core/dr-gpu/src/adjust.rs:1539`](../../core/dr-gpu/src/adjust.rs#L1539), [`core/dr-gpu/src/adjust.rs:3392`](../../core/dr-gpu/src/adjust.rs#L3392), [`core/dr-gpu/tests/scene_referred.rs:144`](../../core/dr-gpu/tests/scene_referred.rs#L144), [`core/dr-gpu/tests/scene_referred.rs:1`](../../core/dr-gpu/tests/scene_referred.rs#L1), [`core/dr-gpu/tests/view_transform.rs:115`](../../core/dr-gpu/tests/view_transform.rs#L115), [`core/dr-gpu/tests/view_transform.rs:153`](../../core/dr-gpu/tests/view_transform.rs#L153), [`core/dr-gpu/tests/view_transform.rs:1`](../../core/dr-gpu/tests/view_transform.rs#L1), [`core/dr-gpu/tests/view_transform.rs:66`](../../core/dr-gpu/tests/view_transform.rs#L66), [`core/dr-gpu/tests/view_transform.rs:89`](../../core/dr-gpu/tests/view_transform.rs#L89), [`core/dr-pipeline/src/operation.rs:1414`](../../core/dr-pipeline/src/operation.rs#L1414), [`core/dr-pipeline/src/operation.rs:2377`](../../core/dr-pipeline/src/operation.rs#L2377), [`core/dr-pipeline/src/operation.rs:2493`](../../core/dr-pipeline/src/operation.rs#L2493), [`core/dr-pipeline/src/operation.rs:2510`](../../core/dr-pipeline/src/operation.rs#L2510), [`core/dr-pipeline/src/operation.rs:2700`](../../core/dr-pipeline/src/operation.rs#L2700), [`core/dr-pipeline/src/operation.rs:2738`](../../core/dr-pipeline/src/operation.rs#L2738), [`core/dr-pipeline/src/operation.rs:580`](../../core/dr-pipeline/src/operation.rs#L580), [`core/dr-pipeline/src/operation.rs:880`](../../core/dr-pipeline/src/operation.rs#L880), [`core/dr-pipeline/src/operation.rs:930`](../../core/dr-pipeline/src/operation.rs#L930), [`core/dr-pipeline/src/ops/film_sim.rs:315`](../../core/dr-pipeline/src/ops/film_sim.rs#L315), [`core/dr-pipeline/src/ops/view_transform.rs:168`](../../core/dr-pipeline/src/ops/view_transform.rs#L168), [`core/dr-pipeline/src/ops/view_transform.rs:1`](../../core/dr-pipeline/src/ops/view_transform.rs#L1), [`core/dr-pipeline/src/view.rs:184`](../../core/dr-pipeline/src/view.rs#L184), [`core/dr-pipeline/src/view.rs:197`](../../core/dr-pipeline/src/view.rs#L197), [`core/dr-pipeline/src/view.rs:1`](../../core/dr-pipeline/src/view.rs#L1), [`core/dr-pipeline/src/view.rs:211`](../../core/dr-pipeline/src/view.rs#L211), [`core/dr-pipeline/src/view.rs:227`](../../core/dr-pipeline/src/view.rs#L227), [`core/dr-pipeline/src/view.rs:241`](../../core/dr-pipeline/src/view.rs#L241), [`core/dr-pipeline/src/view.rs:252`](../../core/dr-pipeline/src/view.rs#L252) | @@ -148,9 +149,9 @@ _None._ | FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:19`](../../apps/darkroom-desktop/src/main.rs#L19) | | FR-RAW-1 | [`core/dr-decode/src/lib.rs:260`](../../core/dr-decode/src/lib.rs#L260), [`core/dr-types/src/lib.rs:134`](../../core/dr-types/src/lib.rs#L134), [`core/dr-types/src/lib.rs:205`](../../core/dr-types/src/lib.rs#L205) | | FR-RAW-2 | [`core/dr-decode/src/decoder.rs:1`](../../core/dr-decode/src/decoder.rs#L1), [`core/dr-decode/src/decoder.rs:26`](../../core/dr-decode/src/decoder.rs#L26), [`core/dr-decode/src/decoder.rs:59`](../../core/dr-decode/src/decoder.rs#L59), [`core/dr-decode/src/decoder.rs:95`](../../core/dr-decode/src/decoder.rs#L95), [`ui/dr-ui/src/decoder_seam.rs:184`](../../ui/dr-ui/src/decoder_seam.rs#L184), [`ui/dr-ui/src/decoder_seam.rs:1`](../../ui/dr-ui/src/decoder_seam.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:215`](../../ui/dr-ui/src/decoder_seam.rs#L215), [`ui/dr-ui/src/decoder_seam.rs:256`](../../ui/dr-ui/src/decoder_seam.rs#L256), [`ui/dr-ui/src/export.rs:1238`](../../ui/dr-ui/src/export.rs#L1238), [`ui/dr-ui/src/export.rs:973`](../../ui/dr-ui/src/export.rs#L973), [`ui/dr-ui/src/import.rs:96`](../../ui/dr-ui/src/import.rs#L96), [`ui/dr-ui/src/library/sweep.rs:301`](../../ui/dr-ui/src/library/sweep.rs#L301), [`ui/dr-ui/src/library/sweep.rs:629`](../../ui/dr-ui/src/library/sweep.rs#L629), [`ui/dr-ui/src/library/thumbnails_gen.rs:101`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L101), [`ui/dr-ui/src/merge.rs:119`](../../ui/dr-ui/src/merge.rs#L119), [`ui/dr-ui/src/repairs.rs:243`](../../ui/dr-ui/src/repairs.rs#L243) | -| FR-RAW-3 | [`core/dr-decode/src/lib.rs:156`](../../core/dr-decode/src/lib.rs#L156), [`core/dr-decode/src/lib.rs:547`](../../core/dr-decode/src/lib.rs#L547), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1196`](../../core/dr-gpu/src/demosaic.rs#L1196), [`core/dr-gpu/src/demosaic.rs:762`](../../core/dr-gpu/src/demosaic.rs#L762), [`core/dr-gpu/tests/hot_pixels.rs:1`](../../core/dr-gpu/tests/hot_pixels.rs#L1) | +| FR-RAW-3 | [`core/dr-decode/src/lib.rs:156`](../../core/dr-decode/src/lib.rs#L156), [`core/dr-decode/src/lib.rs:547`](../../core/dr-decode/src/lib.rs#L547), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1303`](../../core/dr-gpu/src/demosaic.rs#L1303), [`core/dr-gpu/src/demosaic.rs:762`](../../core/dr-gpu/src/demosaic.rs#L762), [`core/dr-gpu/src/demosaic.rs:957`](../../core/dr-gpu/src/demosaic.rs#L957), [`core/dr-gpu/tests/hot_pixels.rs:1`](../../core/dr-gpu/tests/hot_pixels.rs#L1) | | FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../../core/dr-decode/src/error.rs#L1), [`core/dr-decode/src/error.rs:30`](../../core/dr-decode/src/error.rs#L30), [`ui/dr-ui/src/lib.rs:295`](../../ui/dr-ui/src/lib.rs#L295) | -| FR-RAW-5 | [`core/dr-decode/src/lib.rs:184`](../../core/dr-decode/src/lib.rs#L184), [`core/dr-gpu/src/demosaic.rs:1070`](../../core/dr-gpu/src/demosaic.rs#L1070), [`core/dr-gpu/src/demosaic.rs:1194`](../../core/dr-gpu/src/demosaic.rs#L1194), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34), [`core/dr-gpu/src/demosaic.rs:991`](../../core/dr-gpu/src/demosaic.rs#L991) | +| FR-RAW-5 | [`core/dr-decode/src/lib.rs:184`](../../core/dr-decode/src/lib.rs#L184), [`core/dr-gpu/src/demosaic.rs:1098`](../../core/dr-gpu/src/demosaic.rs#L1098), [`core/dr-gpu/src/demosaic.rs:1177`](../../core/dr-gpu/src/demosaic.rs#L1177), [`core/dr-gpu/src/demosaic.rs:1301`](../../core/dr-gpu/src/demosaic.rs#L1301), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34) | | FR-UI-1 | [`core/dr-types/src/settings.rs:1390`](../../core/dr-types/src/settings.rs#L1390), [`core/dr-types/src/settings.rs:492`](../../core/dr-types/src/settings.rs#L492), [`core/dr-types/src/settings.rs:512`](../../core/dr-types/src/settings.rs#L512), [`ui/dr-ui/src/develop_ui.rs:204`](../../ui/dr-ui/src/develop_ui.rs#L204), [`ui/dr-ui/src/lib.rs:126`](../../ui/dr-ui/src/lib.rs#L126), [`ui/dr-ui/src/lib.rs:141`](../../ui/dr-ui/src/lib.rs#L141), [`ui/dr-ui/src/lib.rs:163`](../../ui/dr-ui/src/lib.rs#L163), [`ui/dr-ui/src/lib.rs:3344`](../../ui/dr-ui/src/lib.rs#L3344), [`ui/dr-ui/src/lib.rs:3363`](../../ui/dr-ui/src/lib.rs#L3363), [`ui/dr-ui/src/lib.rs:3390`](../../ui/dr-ui/src/lib.rs#L3390), [`ui/dr-ui/src/lib.rs:3441`](../../ui/dr-ui/src/lib.rs#L3441), [`ui/dr-ui/src/lib.rs:3474`](../../ui/dr-ui/src/lib.rs#L3474), [`ui/dr-ui/src/library_ui/grid.rs:242`](../../ui/dr-ui/src/library_ui/grid.rs#L242), [`ui/dr-ui/src/masks_ui.rs:1608`](../../ui/dr-ui/src/masks_ui.rs#L1608), [`ui/dr-ui/src/settings_ui.rs:143`](../../ui/dr-ui/src/settings_ui.rs#L143), [`ui/dr-ui/ui/adjust.slint:1000`](../../ui/dr-ui/ui/adjust.slint#L1000), [`ui/dr-ui/ui/app.slint:3147`](../../ui/dr-ui/ui/app.slint#L3147), [`ui/dr-ui/ui/app.slint:670`](../../ui/dr-ui/ui/app.slint#L670), [`ui/dr-ui/ui/app.slint:815`](../../ui/dr-ui/ui/app.slint#L815), [`ui/dr-ui/ui/identity.slint:249`](../../ui/dr-ui/ui/identity.slint#L249), [`ui/dr-ui/ui/library.slint:1469`](../../ui/dr-ui/ui/library.slint#L1469), [`ui/dr-ui/ui/settings.slint:129`](../../ui/dr-ui/ui/settings.slint#L129), [`ui/dr-ui/ui/settings.slint:660`](../../ui/dr-ui/ui/settings.slint#L660), [`ui/dr-ui/ui/toolrail.slint:180`](../../ui/dr-ui/ui/toolrail.slint#L180), [`ui/dr-ui/ui/toolrail.slint:92`](../../ui/dr-ui/ui/toolrail.slint#L92), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) | | FR-UI-2 | [`ui/dr-ui/src/collections_ui/controller.rs:45`](../../ui/dr-ui/src/collections_ui/controller.rs#L45), [`ui/dr-ui/src/collections_ui/controller.rs:83`](../../ui/dr-ui/src/collections_ui/controller.rs#L83), [`ui/dr-ui/src/collections_ui/drag.rs:246`](../../ui/dr-ui/src/collections_ui/drag.rs#L246), [`ui/dr-ui/src/collections_ui/drag.rs:256`](../../ui/dr-ui/src/collections_ui/drag.rs#L256), [`ui/dr-ui/src/collections_ui/press.rs:453`](../../ui/dr-ui/src/collections_ui/press.rs#L453), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:121`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L121), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:138`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L138), [`ui/dr-ui/src/collections_ui/wiring_grid.rs:415`](../../ui/dr-ui/src/collections_ui/wiring_grid.rs#L415), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/gestures.rs:1`](../../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:126`](../../ui/dr-ui/src/lib.rs#L126), [`ui/dr-ui/src/library_ui/filter_bar.rs:351`](../../ui/dr-ui/src/library_ui/filter_bar.rs#L351), [`ui/dr-ui/src/library_ui/grid.rs:242`](../../ui/dr-ui/src/library_ui/grid.rs#L242), [`ui/dr-ui/src/library_ui/window.rs:1149`](../../ui/dr-ui/src/library_ui/window.rs#L1149), [`ui/dr-ui/tests/scroll_cue.rs:1`](../../ui/dr-ui/tests/scroll_cue.rs#L1), [`ui/dr-ui/ui/app.slint:137`](../../ui/dr-ui/ui/app.slint#L137), [`ui/dr-ui/ui/app.slint:3545`](../../ui/dr-ui/ui/app.slint#L3545), [`ui/dr-ui/ui/app.slint:799`](../../ui/dr-ui/ui/app.slint#L799), [`ui/dr-ui/ui/collections.slint:1354`](../../ui/dr-ui/ui/collections.slint#L1354), [`ui/dr-ui/ui/controls.slint:626`](../../ui/dr-ui/ui/controls.slint#L626), [`ui/dr-ui/ui/gestures.slint:1`](../../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1356`](../../ui/dr-ui/ui/library.slint#L1356), [`ui/dr-ui/ui/library.slint:1389`](../../ui/dr-ui/ui/library.slint#L1389), [`ui/dr-ui/ui/library.slint:1717`](../../ui/dr-ui/ui/library.slint#L1717), [`ui/dr-ui/ui/library.slint:1724`](../../ui/dr-ui/ui/library.slint#L1724), [`ui/dr-ui/ui/library.slint:1825`](../../ui/dr-ui/ui/library.slint#L1825), [`ui/dr-ui/ui/library.slint:3168`](../../ui/dr-ui/ui/library.slint#L3168), [`ui/dr-ui/ui/library.slint:586`](../../ui/dr-ui/ui/library.slint#L586), [`ui/dr-ui/ui/presets.slint:114`](../../ui/dr-ui/ui/presets.slint#L114), [`ui/dr-ui/ui/settings.slint:143`](../../ui/dr-ui/ui/settings.slint#L143), [`ui/dr-ui/ui/widgets.slint:1117`](../../ui/dr-ui/ui/widgets.slint#L1117) | | FR-UI-3 | [`ui/dr-ui/src/collections_ui/controller.rs:143`](../../ui/dr-ui/src/collections_ui/controller.rs#L143), [`ui/dr-ui/src/collections_ui/controller.rs:348`](../../ui/dr-ui/src/collections_ui/controller.rs#L348), [`ui/dr-ui/src/collections_ui/controller.rs:361`](../../ui/dr-ui/src/collections_ui/controller.rs#L361), [`ui/dr-ui/src/collections_ui/wiring_tree.rs:287`](../../ui/dr-ui/src/collections_ui/wiring_tree.rs#L287), [`ui/dr-ui/src/develop/masks.rs:361`](../../ui/dr-ui/src/develop/masks.rs#L361), [`ui/dr-ui/src/develop/repairs.rs:49`](../../ui/dr-ui/src/develop/repairs.rs#L49), [`ui/dr-ui/src/library_ui/grid.rs:228`](../../ui/dr-ui/src/library_ui/grid.rs#L228), [`ui/dr-ui/src/masks_ui.rs:1700`](../../ui/dr-ui/src/masks_ui.rs#L1700), [`ui/dr-ui/src/masks_ui.rs:1722`](../../ui/dr-ui/src/masks_ui.rs#L1722), [`ui/dr-ui/src/masks_ui.rs:339`](../../ui/dr-ui/src/masks_ui.rs#L339), [`ui/dr-ui/src/spots_ui.rs:19`](../../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:2356`](../../ui/dr-ui/ui/app.slint#L2356), [`ui/dr-ui/ui/collections.slint:1228`](../../ui/dr-ui/ui/collections.slint#L1228), [`ui/dr-ui/ui/collections.slint:4`](../../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:78`](../../ui/dr-ui/ui/collections.slint#L78), [`ui/dr-ui/ui/collections.slint:978`](../../ui/dr-ui/ui/collections.slint#L978), [`ui/dr-ui/ui/masks.slint:1246`](../../ui/dr-ui/ui/masks.slint#L1246), [`ui/dr-ui/ui/widgets.slint:72`](../../ui/dr-ui/ui/widgets.slint#L72) | @@ -231,14 +232,13 @@ Defined in `requirements.md` and marked `(post-v1)` on the defining line. Not in ## Not yet tagged -28 of 193 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built. +27 of 193 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
Show untagged requirements - FR-CAT-14 - FR-CULL-6 - FR-CULL-7 -- FR-DEV-3g - FR-INF-2 - FR-INF-3 - FR-MRG-9