Find hot photosites without repairing them, and measure a sensor's aging
The hot-pixel pass could only repair: it returned how many photosites it changed and threw away which. find_hot_pixels runs the same pass and returns them as sensor coordinates, leaving the frame alone, so a sensor's defects can be tracked across frames. sensor_scan prints each frame's candidates, and with --probe reads a list of coordinates back out of every frame. Run over 53 6D raws from 2015 to 2026, it found 32 persistent defects, 2 in 2015 and 32 by 2026, and showed what a defect map has to account for: a frame that does not flag a photosite proves nothing unless its neighbourhood is dark, and the 6D hides some of its defects itself above ISO 5000. docs/dev/sensor-health.md records the findings and the design they argue for.
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
//! List each RAW file's hot and dead photosite candidates (docs/dev/sensor-health.md).
|
||||||
|
//!
|
||||||
|
//! Reads paths on stdin and prints one JSON line per file: its capture
|
||||||
|
//! conditions and every photosite [`Demosaicer::find_hot_pixels`] flags, as
|
||||||
|
//! `[x, y, value, hot]` in sensor coordinates. Which candidates are defects
|
||||||
|
//! is a question across frames, so this answers nothing on its own.
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! find ~/Pictures -name '*.CR2' | cargo run --release -p dr-gpu --example sensor_scan
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::io::{BufRead, Write};
|
||||||
|
|
||||||
|
use dr_gpu::{Demosaicer, GpuContext};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Some(list) = std::env::args().skip_while(|a| a != "--probe").nth(1) {
|
||||||
|
return probe(&list);
|
||||||
|
}
|
||||||
|
let ctx = pollster::block_on(GpuContext::new_headless()).expect("a GPU for the hot-pixel pass");
|
||||||
|
let demosaicer = Demosaicer::new(&ctx).expect("demosaicer");
|
||||||
|
for line in std::io::stdin().lock().lines() {
|
||||||
|
let path = line.expect("stdin");
|
||||||
|
match scan(&path, &demosaicer) {
|
||||||
|
Ok(json) => println!("{json}"),
|
||||||
|
Err(e) => eprintln!("fail\t{path}\t{e}"),
|
||||||
|
}
|
||||||
|
std::io::stdout().flush().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan(path: &str, demosaicer: &Demosaicer) -> Result<String, String> {
|
||||||
|
let bytes = std::fs::read(path).map_err(|e| e.to_string())?;
|
||||||
|
let raw = dr_decode::decode(&bytes).map_err(|e| e.to_string())?;
|
||||||
|
let meta = dr_decode::metadata(&bytes).map_err(|e| e.to_string())?;
|
||||||
|
let sites = demosaicer
|
||||||
|
.find_hot_pixels(&raw)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let opt = |v: Option<f64>| v.map_or("null".to_string(), |v| v.to_string());
|
||||||
|
let list: Vec<String> = sites
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
let v = raw.data[(s.y * raw.width + s.x) as usize];
|
||||||
|
format!("[{},{},{},{}]", s.x, s.y, v, u8::from(s.hot))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(format!(
|
||||||
|
"{{\"path\":{:?},\"model\":{:?},\"captured\":{},\"iso\":{},\"shutter\":{},\"white\":{},\"black\":{:?},\"crop\":[{},{},{},{}],\"sites\":[{}]}}",
|
||||||
|
path,
|
||||||
|
format!("{} {}", raw.make, raw.model),
|
||||||
|
meta.captured_at.map_or("null".to_string(), |t| t.to_string()),
|
||||||
|
opt(meta.iso.map(f64::from)),
|
||||||
|
opt(meta.shutter.map(f64::from)),
|
||||||
|
raw.white_level,
|
||||||
|
raw.black_level,
|
||||||
|
raw.crop.x,
|
||||||
|
raw.crop.y,
|
||||||
|
raw.crop.width,
|
||||||
|
raw.crop.height,
|
||||||
|
list.join(","),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--probe COORDS`: for each path on stdin, each `x y` line of COORDS as
|
||||||
|
/// `[value, same-colour neighbour max, median]` over black, on the CPU. A
|
||||||
|
/// probe asks whether a photosite stood out in a frame where it would have
|
||||||
|
/// been visible, which the scan's verdict cannot say: a frame that does not
|
||||||
|
/// flag a defect may only have been too bright around it.
|
||||||
|
fn probe(list: &str) {
|
||||||
|
let coords: Vec<(u32, u32)> = std::fs::read_to_string(list)
|
||||||
|
.expect("coords")
|
||||||
|
.lines()
|
||||||
|
.filter_map(|l| {
|
||||||
|
let mut it = l.split_whitespace().map(|v| v.parse().ok());
|
||||||
|
Some((it.next()??, it.next()??))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for line in std::io::stdin().lock().lines() {
|
||||||
|
let path = line.expect("stdin");
|
||||||
|
let Ok(bytes) = std::fs::read(&path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let (Ok(raw), Ok(meta)) = (dr_decode::decode(&bytes), dr_decode::metadata(&bytes)) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let w = raw.width as i64;
|
||||||
|
let at = |x: i64, y: i64| {
|
||||||
|
let cell = (((y - raw.crop.y as i64) & 1) * 2 + ((x - raw.crop.x as i64) & 1)) as usize;
|
||||||
|
raw.data[(y * w + x) as usize].saturating_sub(raw.black_level[cell])
|
||||||
|
};
|
||||||
|
let rows: Vec<String> = coords
|
||||||
|
.iter()
|
||||||
|
.map(|&(x, y)| {
|
||||||
|
let (x, y) = (x as i64, y as i64);
|
||||||
|
let mut n: Vec<u16> = Vec::new();
|
||||||
|
for dy in [-2i64, 0, 2] {
|
||||||
|
for dx in [-2i64, 0, 2] {
|
||||||
|
if (dx, dy) != (0, 0) {
|
||||||
|
n.push(at(x + dx, y + dy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.sort_unstable();
|
||||||
|
format!("[{},{},{}]", at(x, y), n[n.len() - 1], n[n.len() / 2])
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
println!(
|
||||||
|
"{{\"path\":{:?},\"captured\":{},\"iso\":{},\"shutter\":{},\"range\":{},\"p\":[{}]}}",
|
||||||
|
path,
|
||||||
|
meta.captured_at.unwrap_or(0),
|
||||||
|
meta.iso.unwrap_or(0),
|
||||||
|
meta.shutter.unwrap_or(0.0),
|
||||||
|
raw.white_level - raw.black_level[0],
|
||||||
|
rows.join(","),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1071,6 +1071,46 @@ impl Demosaicer {
|
|||||||
if raw.samples_per_pixel != 1 {
|
if raw.samples_per_pixel != 1 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
let words = self.hot_pixel_words(raw)?;
|
||||||
|
let mut changed = 0;
|
||||||
|
for (i, v) in raw.data.iter_mut().enumerate() {
|
||||||
|
let new = unpack_sample(&words, i);
|
||||||
|
changed += usize::from(new != *v);
|
||||||
|
*v = new;
|
||||||
|
}
|
||||||
|
Ok(changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The photosites [`Self::repair_hot_pixels`] would replace, in sensor
|
||||||
|
/// coordinates, without replacing them.
|
||||||
|
///
|
||||||
|
/// For the sensor health record (docs/dev/sensor-health.md): one frame's
|
||||||
|
/// verdict is a candidate list, not a defect map — a single photosite of a
|
||||||
|
/// star that passes both tests reads the same as a hot one. Which of them
|
||||||
|
/// is the sensor is decided across frames, by who keeps coming back.
|
||||||
|
pub fn find_hot_pixels(&self, raw: &RawImage) -> Result<Vec<Photosite>, GpuError> {
|
||||||
|
if raw.samples_per_pixel != 1 {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
let words = self.hot_pixel_words(raw)?;
|
||||||
|
let stride = raw.width.max(1);
|
||||||
|
Ok(raw
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, &v)| {
|
||||||
|
let new = unpack_sample(&words, i);
|
||||||
|
(new != v).then(|| Photosite {
|
||||||
|
x: i as u32 % stride,
|
||||||
|
y: i as u32 / stride,
|
||||||
|
hot: new < v,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The hot-pixel pass over `raw`, read back as packed words.
|
||||||
|
fn hot_pixel_words(&self, raw: &RawImage) -> Result<Vec<u32>, GpuError> {
|
||||||
let (width, height) = (raw.crop.width.max(1), raw.crop.height.max(1));
|
let (width, height) = (raw.crop.width.max(1), raw.crop.height.max(1));
|
||||||
let xtrans_tile = raw
|
let xtrans_tile = raw
|
||||||
.cfa_pattern
|
.cfa_pattern
|
||||||
@@ -1122,18 +1162,30 @@ impl Demosaicer {
|
|||||||
.map_err(|e| GpuError::Readback(e.to_string()))?;
|
.map_err(|e| GpuError::Readback(e.to_string()))?;
|
||||||
let words: Vec<u32> = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec();
|
let words: Vec<u32> = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec();
|
||||||
readback.unmap();
|
readback.unmap();
|
||||||
|
Ok(words)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One photosite the hot-pixel pass judged defective.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Photosite {
|
||||||
|
/// Sensor coordinates: the full readout, masked border included.
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
/// Read far above its neighbourhood; otherwise far below (dead).
|
||||||
|
pub hot: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample `i` of a readout packed by [`pack_samples`].
|
||||||
|
fn unpack_sample(words: &[u32], i: usize) -> u16 {
|
||||||
|
let w = words[i / 2];
|
||||||
|
(if i.is_multiple_of(2) {
|
||||||
|
w & 0xFFFF
|
||||||
|
} else {
|
||||||
|
w >> 16
|
||||||
|
}) as u16
|
||||||
|
}
|
||||||
|
|
||||||
const IDENTITY_3X3: [f32; 9] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
|
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.
|
/// Pack u16 samples two per u32, little-endian within the word.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub use adjust::AdjustPass;
|
|||||||
// rather than an implementation detail: a detail pass is guaranteed linear,
|
// rather than an implementation detail: a detail pass is guaranteed linear,
|
||||||
// unclipped, full internal precision (FR-DEV-2), and anyone reasoning about
|
// unclipped, full internal precision (FR-DEV-2), and anyone reasoning about
|
||||||
// VRAM at 24 MP needs to know what an intermediate costs.
|
// VRAM at 24 MP needs to know what an intermediate costs.
|
||||||
pub use demosaic::{DemosaicedImage, Demosaicer};
|
pub use demosaic::{DemosaicedImage, Demosaicer, Photosite};
|
||||||
pub use detail::INTERMEDIATE_FORMAT as DETAIL_INTERMEDIATE_FORMAT;
|
pub use detail::INTERMEDIATE_FORMAT as DETAIL_INTERMEDIATE_FORMAT;
|
||||||
pub use error::GpuError;
|
pub use error::GpuError;
|
||||||
pub use focus::{FocusPeakPass, FocusPeaking, PeakColour, PeakSensitivity};
|
pub use focus::{FocusPeakPass, FocusPeaking, PeakColour, PeakSensitivity};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! see of a defect it missed is the coloured cross the demosaic makes of it.
|
//! see of a defect it missed is the coloured cross the demosaic makes of it.
|
||||||
|
|
||||||
use dr_decode::{CfaPattern, CropRect, RawImage};
|
use dr_decode::{CfaPattern, CropRect, RawImage};
|
||||||
use dr_gpu::{AdjustPass, Demosaicer, GpuContext};
|
use dr_gpu::{AdjustPass, Demosaicer, GpuContext, Photosite};
|
||||||
use dr_pipeline::EditGraph;
|
use dr_pipeline::EditGraph;
|
||||||
|
|
||||||
const SIZE: u32 = 36;
|
const SIZE: u32 = 36;
|
||||||
@@ -171,3 +171,43 @@ fn the_repaired_mosaic_reads_back_with_only_the_defect_changed() {
|
|||||||
let others = (0..raw.data.len()).filter(|&i| i != at);
|
let others = (0..raw.data.len()).filter(|&i| i != at);
|
||||||
assert!(others.into_iter().all(|i| raw.data[i] == before.data[i]));
|
assert!(others.into_iter().all(|i| raw.data[i] == before.data[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finding without repairing (docs/dev/sensor-health.md): the same verdict as
|
||||||
|
/// the repair, as sensor coordinates, with the frame left as it was. The
|
||||||
|
/// sensor health record builds on this, so it must name exactly the
|
||||||
|
/// photosites the repair would change — the hot one and the dead one, and
|
||||||
|
/// not the star.
|
||||||
|
#[test]
|
||||||
|
fn finding_names_what_the_repair_would_change_and_changes_nothing() {
|
||||||
|
let Some(ctx) = ctx() else {
|
||||||
|
eprintln!("no GPU adapter; skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let d = Demosaicer::new(&ctx).expect("demosaicer");
|
||||||
|
let mut set = vec![(MIDDLE, MIDDLE, WHITE), (9, 25, 0)];
|
||||||
|
for dy in 0..3 {
|
||||||
|
for dx in 0..3 {
|
||||||
|
set.push((4 + dx, 4 + dy, WHITE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let raw = frame(CfaPattern::Rggb, 1600, &set);
|
||||||
|
let mut found = d.find_hot_pixels(&raw).expect("find");
|
||||||
|
found.sort_by_key(|p| (p.y, p.x));
|
||||||
|
assert_eq!(
|
||||||
|
found,
|
||||||
|
vec![
|
||||||
|
Photosite {
|
||||||
|
x: MIDDLE,
|
||||||
|
y: MIDDLE,
|
||||||
|
hot: true
|
||||||
|
},
|
||||||
|
Photosite {
|
||||||
|
x: 9,
|
||||||
|
y: 25,
|
||||||
|
hot: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let mut repaired = raw.clone();
|
||||||
|
assert_eq!(d.repair_hot_pixels(&mut repaired).expect("repair"), 2);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# DarkRoom — Sensor health: a dated defect map per body
|
||||||
|
|
||||||
|
**Status:** Spike · 2026-10-04 · not built
|
||||||
|
**Companion to:** [requirements.md](requirements.md) FR-RAW-3, [catalog.md](catalog.md)
|
||||||
|
|
||||||
|
A sensor gains defective photosites as it ages, and a photosite that has gone bad does not recover.
|
||||||
|
This records, per camera body, which photosites are defective and since when, so that the library
|
||||||
|
can show how a sensor has aged and the hot-pixel repair can fix the defects a body is known to have
|
||||||
|
rather than only those that stand out in the frame at hand.
|
||||||
|
|
||||||
|
What exists is the measuring tool: `Demosaicer::find_hot_pixels` (the photosites the repair pass
|
||||||
|
would replace, without replacing them) and `core/dr-gpu/examples/sensor_scan.rs`, which prints them
|
||||||
|
per frame and, with `--probe`, reads a list of coordinates back out of each frame. The rest of this
|
||||||
|
document is what a spike with them on the 6D found, and the design it argues for.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What is wanted
|
||||||
|
|
||||||
|
- **Settings → Bodies**, one entry per body, with a graph of the defective share over time in two
|
||||||
|
series: photosites (the raw mosaic) and 2×2 cells holding at least one defective photosite (what
|
||||||
|
reaches a pixel of the output).
|
||||||
|
- **A dated defect map** per body, synced with the library like any other catalog data, and
|
||||||
|
cumulative: an entry is never removed.
|
||||||
|
- **The repair reads the map** whose date is nearest the frame's, and fixes every defect the body had
|
||||||
|
by then, whether or not it stands out in that frame.
|
||||||
|
- One body per model for now: the catalog stores `make model`, not a body serial.
|
||||||
|
|
||||||
|
## 2. What the spike found (6D, 2026-10-03)
|
||||||
|
|
||||||
|
53 CR2s, up to four per quarter at the highest ISO of a day, 2015 to 2026. The library holds almost
|
||||||
|
no 6D raws from 2016–2022, so onsets in that span are dated to the span, not the year. `sensor_scan`
|
||||||
|
ran at 0.8 s per frame, decode included.
|
||||||
|
|
||||||
|
**Persistence separates the sensor from the scene.** 4,179 photosites were flagged at least once;
|
||||||
|
3,554 on one day only (stars, glints, noise). A defect is a photosite that keeps coming back.
|
||||||
|
|
||||||
|
**A frame that does not flag a photosite is not evidence it was clean.** The repair's test is
|
||||||
|
relative to the neighbourhood, so a defect in a lit area does not stand out. Confirmed defects were
|
||||||
|
flagged in a median 20 % of the frames after their first sighting. Only a frame whose neighbourhood
|
||||||
|
at that photosite is dark counts, either way.
|
||||||
|
|
||||||
|
**The 6D hides some defects itself at high ISO.** (2517, 3172) reads 8,000–13,000 over neighbours
|
||||||
|
near 200 at ISO 2000–5000, and does not stand out at all at ISO 6400–12800 (82 over 149 on
|
||||||
|
2025-03-15). The camera appears to map out photosites it knows at those gains. So evidence for this
|
||||||
|
body comes from ISO ≤ 5000; the cut-off must be learned per body, not fixed.
|
||||||
|
|
||||||
|
**Long exposures light everything.** A 9.8 s frame saturated every candidate; it confirms, it does
|
||||||
|
not date.
|
||||||
|
|
||||||
|
**The curve.** Counting a photosite as defective from the first frame where it stands out, provided
|
||||||
|
it stands out in at least 60 % of the observable frames after that (32 defects; 31 with a clean
|
||||||
|
observable frame before onset to bracket it):
|
||||||
|
|
||||||
|
| Year | Defects | Share of photosites |
|
||||||
|
|---|---|---|
|
||||||
|
| 2015 | 2 | 0.1 ppm |
|
||||||
|
| 2016–2021 | 2 | 0.1 ppm |
|
||||||
|
| 2022 | 7 | 0.3 ppm |
|
||||||
|
| 2023 | 26 | 1.3 ppm |
|
||||||
|
| 2026 | 32 | 1.6 ppm |
|
||||||
|
|
||||||
|
(2517, 3172) is the shape every entry should have: clean at ISO 800–1000 in 2015 and at ISO 100–200
|
||||||
|
in 2016, then 338 over 72 at ISO 100 on 2022-08-13 and in every comparable frame since.
|
||||||
|
|
||||||
|
Weak defects exist too, about twice their neighbours ((1814, 3039)); the blind repair misses them in
|
||||||
|
most frames. A known map would catch them.
|
||||||
|
|
||||||
|
## 3. Design it argues for
|
||||||
|
|
||||||
|
- **Evidence per frame, per known photosite**: observable (dark neighbourhood, ISO inside the
|
||||||
|
body's band) and, if so, lit or clean. Not just the frame's flagged list.
|
||||||
|
- **A map entry is a bracket**: last clean observation, first lit observation, strength, kind. Onset
|
||||||
|
lies between the two; the graph plots it at the first, and can show the bracket.
|
||||||
|
- **The repair**: every entry whose first lit date is on or before the frame's capture date; for an
|
||||||
|
entry whose bracket contains the date, probe the photosite in the frame itself.
|
||||||
|
- **Storage and sync**: catalog tables created on first use (as `albums` does), so no schema bump
|
||||||
|
breaks an older peer. They travel in the snapshot by default. Merge is a set union of photosites
|
||||||
|
per body, the earlier first-lit and the later last-clean winning, which makes it commutative and
|
||||||
|
keeps the map cumulative.
|
||||||
|
- **Work**: a sample, not the library. Frames are picked for what they can reveal (dark, mid ISO,
|
||||||
|
long exposures), a few per body per month, and after the first pass only new imports are read.
|
||||||
@@ -9,7 +9,7 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
|
|||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Source files scanned | 500 |
|
| Source files scanned | 501 |
|
||||||
| TRACES tags found | 2105 |
|
| TRACES tags found | 2105 |
|
||||||
| Requirements defined | 193 |
|
| Requirements defined | 193 |
|
||||||
| Requirements deferred (post-v1) | 24 |
|
| Requirements deferred (post-v1) | 24 |
|
||||||
@@ -149,9 +149,9 @@ _None._
|
|||||||
| FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:35`](../../apps/darkroom-desktop/src/main.rs#L35) |
|
| FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:35`](../../apps/darkroom-desktop/src/main.rs#L35) |
|
||||||
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:266`](../../core/dr-decode/src/lib.rs#L266), [`core/dr-types/src/lib.rs:135`](../../core/dr-types/src/lib.rs#L135), [`core/dr-types/src/lib.rs:206`](../../core/dr-types/src/lib.rs#L206) |
|
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:266`](../../core/dr-decode/src/lib.rs#L266), [`core/dr-types/src/lib.rs:135`](../../core/dr-types/src/lib.rs#L135), [`core/dr-types/src/lib.rs:206`](../../core/dr-types/src/lib.rs#L206) |
|
||||||
| 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:185`](../../ui/dr-ui/src/decoder_seam.rs#L185), [`ui/dr-ui/src/decoder_seam.rs:1`](../../ui/dr-ui/src/decoder_seam.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:216`](../../ui/dr-ui/src/decoder_seam.rs#L216), [`ui/dr-ui/src/decoder_seam.rs:257`](../../ui/dr-ui/src/decoder_seam.rs#L257), [`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:322`](../../ui/dr-ui/src/library/sweep.rs#L322), [`ui/dr-ui/src/library/sweep.rs:654`](../../ui/dr-ui/src/library/sweep.rs#L654), [`ui/dr-ui/src/library/thumbnails_gen.rs:109`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L109), [`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-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:185`](../../ui/dr-ui/src/decoder_seam.rs#L185), [`ui/dr-ui/src/decoder_seam.rs:1`](../../ui/dr-ui/src/decoder_seam.rs#L1), [`ui/dr-ui/src/decoder_seam.rs:216`](../../ui/dr-ui/src/decoder_seam.rs#L216), [`ui/dr-ui/src/decoder_seam.rs:257`](../../ui/dr-ui/src/decoder_seam.rs#L257), [`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:322`](../../ui/dr-ui/src/library/sweep.rs#L322), [`ui/dr-ui/src/library/sweep.rs:654`](../../ui/dr-ui/src/library/sweep.rs#L654), [`ui/dr-ui/src/library/thumbnails_gen.rs:109`](../../ui/dr-ui/src/library/thumbnails_gen.rs#L109), [`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:162`](../../core/dr-decode/src/lib.rs#L162), [`core/dr-decode/src/lib.rs:564`](../../core/dr-decode/src/lib.rs#L564), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1063`](../../core/dr-gpu/src/demosaic.rs#L1063), [`core/dr-gpu/src/demosaic.rs:1409`](../../core/dr-gpu/src/demosaic.rs#L1409), [`core/dr-gpu/src/demosaic.rs:868`](../../core/dr-gpu/src/demosaic.rs#L868), [`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:162`](../../core/dr-decode/src/lib.rs#L162), [`core/dr-decode/src/lib.rs:564`](../../core/dr-decode/src/lib.rs#L564), [`core/dr-decode/src/locate.rs:1435`](../../core/dr-decode/src/locate.rs#L1435), [`core/dr-gpu/src/demosaic.rs:1063`](../../core/dr-gpu/src/demosaic.rs#L1063), [`core/dr-gpu/src/demosaic.rs:1461`](../../core/dr-gpu/src/demosaic.rs#L1461), [`core/dr-gpu/src/demosaic.rs:868`](../../core/dr-gpu/src/demosaic.rs#L868), [`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-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:190`](../../core/dr-decode/src/lib.rs#L190), [`core/dr-gpu/src/demosaic.rs:1204`](../../core/dr-gpu/src/demosaic.rs#L1204), [`core/dr-gpu/src/demosaic.rs:1283`](../../core/dr-gpu/src/demosaic.rs#L1283), [`core/dr-gpu/src/demosaic.rs:1407`](../../core/dr-gpu/src/demosaic.rs#L1407), [`core/dr-gpu/src/demosaic.rs:34`](../../core/dr-gpu/src/demosaic.rs#L34) |
|
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:190`](../../core/dr-decode/src/lib.rs#L190), [`core/dr-gpu/src/demosaic.rs:1256`](../../core/dr-gpu/src/demosaic.rs#L1256), [`core/dr-gpu/src/demosaic.rs:1335`](../../core/dr-gpu/src/demosaic.rs#L1335), [`core/dr-gpu/src/demosaic.rs:1459`](../../core/dr-gpu/src/demosaic.rs#L1459), [`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:284`](../../ui/dr-ui/src/develop_ui.rs#L284), [`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:3387`](../../ui/dr-ui/src/lib.rs#L3387), [`ui/dr-ui/src/lib.rs:3406`](../../ui/dr-ui/src/lib.rs#L3406), [`ui/dr-ui/src/lib.rs:3433`](../../ui/dr-ui/src/lib.rs#L3433), [`ui/dr-ui/src/lib.rs:3484`](../../ui/dr-ui/src/lib.rs#L3484), [`ui/dr-ui/src/lib.rs:3517`](../../ui/dr-ui/src/lib.rs#L3517), [`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-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:284`](../../ui/dr-ui/src/develop_ui.rs#L284), [`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:3387`](../../ui/dr-ui/src/lib.rs#L3387), [`ui/dr-ui/src/lib.rs:3406`](../../ui/dr-ui/src/lib.rs#L3406), [`ui/dr-ui/src/lib.rs:3433`](../../ui/dr-ui/src/lib.rs#L3433), [`ui/dr-ui/src/lib.rs:3484`](../../ui/dr-ui/src/lib.rs#L3484), [`ui/dr-ui/src/lib.rs:3517`](../../ui/dr-ui/src/lib.rs#L3517), [`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:1164`](../../ui/dr-ui/src/library_ui/window.rs#L1164), [`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-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:1164`](../../ui/dr-ui/src/library_ui/window.rs#L1164), [`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) |
|
| 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) |
|
||||||
|
|||||||
Reference in New Issue
Block a user