mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
LR-EDIT-OPTICS-PROFILE: restrict Sony correction to validated camera
This commit is contained in:
1 parent
e25909938e
commit
7343c8f85f
8 files changed
+149
-47
No files matched your search
+1
-1
@@ -57,7 +57,7 @@ Priorities, in order. Each points at tracker rows in [`docs/parity.md`](docs/par
|
||||
checked for plausible colour; fix per-model bugs (#85).
|
||||
4. **Render fidelity suite** (LR-BEHAV-RENDER-FIDELITY, P1): measure our output against Lightroom on the same CC0 raws
|
||||
(references stay in the local `plan/`), then tune against the numbers.
|
||||
5. **Lens profiles of our own** (LR-EDIT-OPTICS-PROFILE, P1): DNG, Panasonic / Leica RW2 and Sony Bayer ARW
|
||||
5. **Lens profiles of our own** (LR-EDIT-OPTICS-PROFILE, P1): DNG, Panasonic / Leica RW2 and Sony ILCE-7RM4A Bayer ARW
|
||||
embedded distortion are supported; broaden validation and decode remaining vignetting / lateral CA metadata.
|
||||
6. **AI model strategy** (maintainer decision): which permissively licensed models (or our own training) for
|
||||
segmentation masks and denoise. Bayer denoise inference is implemented in pure Rust with an opt-in GPL model; release policy and independently licensed weights remain open. Unblocks M12 and Enhance. Face detection and recognition run on our own checked Rust engine. Object / Describe masks now
|
||||
|
||||
@@ -62,7 +62,7 @@ impl SettingsHashes {
|
||||
}
|
||||
|
||||
/// Bump when the pipeline's output changes, to invalidate cached thumbnails.
|
||||
pub const RENDER_CACHE_VERSION: u64 = 22;
|
||||
pub const RENDER_CACHE_VERSION: u64 = 23;
|
||||
|
||||
/// Thumbnails render at one of these long edges (so window/cell size changes reuse the cache).
|
||||
pub const THUMB_SIZES: [usize; 4] = [128, 256, 384, 512];
|
||||
|
||||
Vendored
+5
-6
@@ -412,11 +412,10 @@ fn sr2_black_levels(cipher: &[u8], order: lightcraft_tiff::ByteOrder) -> Option<
|
||||
(levels.len() == 4 && (64..=4096).contains(&lo) && hi - lo <= 64).then(|| levels.iter().map(|&v| f32::from(v)).sum::<f32>() / 4.0)
|
||||
}
|
||||
|
||||
/// The image area for files without Sony's crop tags (`0x74c7/0x74c8`, written since about 2017): the DNG-style
|
||||
/// default crop when the raw IFD has one, else the maker note's `FullImageSize` (the camera JPEG's size) anchored
|
||||
/// at the top-left. Older bodies store a few columns of padding at the right edge of the raw frame (constant
|
||||
/// values, 8–32 columns on the samples we checked) inside a frame 16–48 pixels wider than `FullImageSize`, so
|
||||
/// the anchored crop removes them while keeping the CFA phase.
|
||||
/// The image area: prefer the standard `DefaultCropOrigin` / `DefaultCropSize`, then Sony's
|
||||
/// crop tags (`0x74c7/0x74c8`), then a plausible maker-note `FullImageSize` anchored at the top-left,
|
||||
/// otherwise the full raw frame. Older bodies store a few padding columns at the right edge;
|
||||
/// the anchored fallback removes those while keeping the CFA phase. All crops are clipped to the frame.
|
||||
fn default_crop(raw: &Ifd, mn: Option<&makernote::MakerNote>, w: usize, h: usize) -> Rect {
|
||||
let full = Rect::new(0, 0, w, h);
|
||||
if let (Some([x, y]), Some([cw, ch])) = (raw.u64s(t::DEFAULT_CROP_ORIGIN).as_deref(), raw.u64s(t::DEFAULT_CROP_SIZE).as_deref())
|
||||
@@ -563,7 +562,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
|
||||
wb_multipliers: if linear_rgb { Some([1.0; 3]) } else { wb },
|
||||
linearized: false,
|
||||
opcodes: OpcodeLists {
|
||||
list3: if linear_rgb { Vec::new() } else { super::arw_lens::distortion(raw, Rect::new(0, 0, w, h), crop).into_iter().collect() },
|
||||
list3: if linear_rgb { Vec::new() } else { super::arw_lens::distortion(&model, raw, Rect::new(0, 0, w, h), crop).into_iter().collect() },
|
||||
..Default::default()
|
||||
},
|
||||
metadata,
|
||||
|
||||
Vendored
+52
-35
@@ -11,9 +11,15 @@ const DISTORTION: u16 = 0x7037;
|
||||
const SAMPLES: usize = 1024;
|
||||
const MAX_ERROR: f64 = 0.0005;
|
||||
|
||||
/// The 16-entry layout only. Unknown layouts and implausible/non-smooth maps are left uncorrected.
|
||||
/// Only independently validated camera models and the 16-entry layout. Unknown models/layouts
|
||||
/// and implausible/non-smooth maps are left uncorrected. See docs/sony-lens-corrections.md.
|
||||
/// A camera setting of Off does not remove the lens data: the user can enable it in the developer.
|
||||
pub(super) fn distortion(ifd: &Ifd, active: Rect, crop: Rect) -> Option<Opcode> {
|
||||
pub(super) fn distortion(model: &str, ifd: &Ifd, active: Rect, crop: Rect) -> Option<Opcode> {
|
||||
// A matching table shape does not establish its scale or radial normalization on another body.
|
||||
// Keep this exact: ILCE-7RM4 and ILCE-7RM4A are distinct models, not interchangeable aliases.
|
||||
if model != "ILCE-7RM4A" {
|
||||
return None;
|
||||
}
|
||||
let Value::SShort(values) = &ifd.get(DISTORTION)?.value else { return None };
|
||||
let [16, table @ ..] = values.as_slice() else { return None };
|
||||
let table: &[i16; 16] = table.try_into().ok()?;
|
||||
@@ -131,7 +137,7 @@ mod tests {
|
||||
#[test]
|
||||
fn matches_independent_sony_reference_geometry() {
|
||||
// On/off feature correspondences from Imaging Edge 4.1 at 600 mm, independently fitted.
|
||||
let Opcode::WarpRectilinear { planes, center } = distortion(&table(&TELE), AREA, AREA).unwrap() else { panic!("warp") };
|
||||
let Opcode::WarpRectilinear { planes, center } = distortion("ILCE-7RM4A", &table(&TELE), AREA, AREA).unwrap() else { panic!("warp") };
|
||||
assert_eq!(center, [0.5, 0.5]);
|
||||
let reference = [0.9760159044, 0.0254891644, -0.0005984764, -0.0010120638];
|
||||
for i in 0..=100 {
|
||||
@@ -145,8 +151,8 @@ mod tests {
|
||||
fn barrel_framing_and_offset_crop_keep_the_same_geometry() {
|
||||
let crop = Rect::new(32, 20, 9504, 6336);
|
||||
let active = Rect::new(0, 0, 9600, 6376);
|
||||
let Opcode::WarpRectilinear { planes: a, .. } = distortion(&table(&WIDE), AREA, AREA).unwrap() else { panic!("warp") };
|
||||
let Opcode::WarpRectilinear { planes: b, center } = distortion(&table(&WIDE), active, crop).unwrap() else { panic!("warp") };
|
||||
let Opcode::WarpRectilinear { planes: a, .. } = distortion("ILCE-7RM4A", &table(&WIDE), AREA, AREA).unwrap() else { panic!("warp") };
|
||||
let Opcode::WarpRectilinear { planes: b, center } = distortion("ILCE-7RM4A", &table(&WIDE), active, crop).unwrap() else { panic!("warp") };
|
||||
assert!(a[0][0] > 1.02 && a[0][0] < 1.03, "barrel correction expands the middle");
|
||||
assert!((center[0] * 9599.0 - 4783.5).abs() < 1e-10);
|
||||
assert!((center[1] * 6375.0 - 3187.5).abs() < 1e-10);
|
||||
@@ -169,41 +175,52 @@ mod tests {
|
||||
ifd(Value::Short(vec![16; 17])),
|
||||
ifd(Value::Double(vec![f64::NAN; 17])),
|
||||
] {
|
||||
assert!(distortion(&bad, AREA, AREA).is_none());
|
||||
assert!(distortion("ILCE-7RM4A", &bad, AREA, AREA).is_none());
|
||||
}
|
||||
assert!(distortion(&table(&TELE), Rect::new(0, 0, 1, 1), AREA).is_none());
|
||||
assert!(distortion(&table(&TELE), AREA, Rect::new(0, 0, 9504, 5346)).is_none());
|
||||
assert!(distortion(&table(&TELE), AREA, Rect::new(99999, 0, 10, 10)).is_none());
|
||||
assert!(distortion("ILCE-7RM4A", &table(&TELE), Rect::new(0, 0, 1, 1), AREA).is_none());
|
||||
assert!(distortion("ILCE-7RM4A", &table(&TELE), AREA, Rect::new(0, 0, 9504, 5346)).is_none());
|
||||
assert!(distortion("ILCE-7RM4A", &table(&TELE), AREA, Rect::new(99999, 0, 10, 10)).is_none());
|
||||
}
|
||||
#[test]
|
||||
fn refuses_unvalidated_models_even_with_a_valid_table() {
|
||||
let valid = table(&TELE);
|
||||
assert!(distortion("ILCE-7RM4A", &valid, AREA, AREA).is_some());
|
||||
for model in ["", "ILCE-7M3", "ILCE-7M4", "ILCE-7RM4", "ILCE-7RM5", "ILCE-9M2", "DSC-RX100M3", "ILCE-7RM4A unknown"] {
|
||||
assert!(distortion(model, &valid, AREA, AREA).is_none(), "{model}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_and_full_decode_preserve_correction_through_dng() {
|
||||
use lightcraft_tiff::{ByteOrder, IfdBuilder, ImageData, TiffWriter, tags as t};
|
||||
for order in [ByteOrder::Little, ByteOrder::Big] {
|
||||
let mut raw = IfdBuilder::new();
|
||||
raw.set(t::MAKE, Value::Ascii("SONY".into()));
|
||||
raw.set(t::MODEL, Value::Ascii("synthetic correction test".into()));
|
||||
raw.set(t::IMAGE_WIDTH, Value::Long(vec![32]));
|
||||
raw.set(t::IMAGE_LENGTH, Value::Long(vec![24]));
|
||||
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
|
||||
raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
|
||||
raw.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA]));
|
||||
raw.set(t::COMPRESSION, Value::Short(vec![1]));
|
||||
raw.set(t::WHITE_LEVEL, Value::Long(vec![16383]));
|
||||
raw.set(0x74c7, Value::Long(vec![0, 2]));
|
||||
raw.set(t::DEFAULT_CROP_ORIGIN, Value::Long(vec![2, 2]));
|
||||
raw.set(t::DEFAULT_CROP_SIZE, Value::Long(vec![30, 20]));
|
||||
raw.set(0x74c8, Value::Long(vec![30, 20]));
|
||||
raw.set(DISTORTION, Value::SShort(std::iter::once(16).chain(TELE).collect()));
|
||||
// Zero pixels have the same byte representation in both orders.
|
||||
raw.set_image(ImageData::Strips { rows_per_strip: 24, strips: vec![vec![0; 32 * 24 * 2]] });
|
||||
let file = TiffWriter { order, ..Default::default() }.write(&[raw]).unwrap();
|
||||
let header = super::super::arw::decode(&file, crate::Mode::Header).unwrap();
|
||||
let full = super::super::arw::decode(&file, crate::Mode::Full).unwrap();
|
||||
assert_eq!(header.info(), full.info());
|
||||
assert_eq!(full.opcodes.list3.len(), 1);
|
||||
assert_eq!(full.crop, Rect::new(2, 2, 30, 20));
|
||||
let dng = crate::write_dng(&full, &Default::default()).unwrap();
|
||||
assert_eq!(crate::decode(&dng).unwrap().opcodes.list3, full.opcodes.list3);
|
||||
for (model, expected) in [("ILCE-7RM4A", 1), ("ILCE-7RM4", 0), ("ILCE-7M3", 0), ("", 0)] {
|
||||
for order in [ByteOrder::Little, ByteOrder::Big] {
|
||||
let mut raw = IfdBuilder::new();
|
||||
raw.set(t::MAKE, Value::Ascii("SONY".into()));
|
||||
raw.set(t::MODEL, Value::Ascii(model.into()));
|
||||
raw.set(t::IMAGE_WIDTH, Value::Long(vec![32]));
|
||||
raw.set(t::IMAGE_LENGTH, Value::Long(vec![24]));
|
||||
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
|
||||
raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
|
||||
raw.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA]));
|
||||
raw.set(t::COMPRESSION, Value::Short(vec![1]));
|
||||
raw.set(t::WHITE_LEVEL, Value::Long(vec![16383]));
|
||||
raw.set(0x74c7, Value::Long(vec![0, 2]));
|
||||
raw.set(t::DEFAULT_CROP_ORIGIN, Value::Long(vec![2, 2]));
|
||||
raw.set(t::DEFAULT_CROP_SIZE, Value::Long(vec![30, 20]));
|
||||
raw.set(0x74c8, Value::Long(vec![30, 20]));
|
||||
raw.set(DISTORTION, Value::SShort(std::iter::once(16).chain(TELE).collect()));
|
||||
// Zero pixels have the same byte representation in both orders.
|
||||
raw.set_image(ImageData::Strips { rows_per_strip: 24, strips: vec![vec![0; 32 * 24 * 2]] });
|
||||
let file = TiffWriter { order, ..Default::default() }.write(&[raw]).unwrap();
|
||||
let header = super::super::arw::decode(&file, crate::Mode::Header).unwrap();
|
||||
let full = super::super::arw::decode(&file, crate::Mode::Full).unwrap();
|
||||
assert_eq!(header.info(), full.info());
|
||||
assert_eq!(full.opcodes.list3.len(), expected, "{model} {order:?}");
|
||||
assert_eq!(full.crop, Rect::new(2, 2, 30, 20));
|
||||
let dng = crate::write_dng(&full, &Default::default()).unwrap();
|
||||
assert_eq!(crate::decode(&dng).unwrap().opcodes.list3, full.opcodes.list3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,3 +610,38 @@ fn corpus_raws_keep_their_container_and_are_not_thumbnail_shells() {
|
||||
}
|
||||
eprintln!("{checked} corpus raws keep their container");
|
||||
}
|
||||
|
||||
/// Matching Sony table dimensions are not evidence of a shared correction model. Exercise the
|
||||
/// actual corpus headers and decoded files as well as the synthetic model-boundary unit tests.
|
||||
#[test]
|
||||
fn sony_embedded_distortion_is_limited_to_validated_models() {
|
||||
let root = corpus_root().join("raw");
|
||||
let mut checked = 0;
|
||||
for (name, model, expected) in [
|
||||
("arw-sony-a7rm4a-compressed.arw", "ILCE-7RM4A", 1),
|
||||
("arw-sony-a9m2-compressed.arw", "ILCE-9M2", 0),
|
||||
("arw-sony-a7m3-compressed.arw", "ILCE-7M3", 0),
|
||||
("arw-sony-a7m3-uncompressed.arw", "ILCE-7M3", 0),
|
||||
("arw-sony-a7m4-14bit.arw", "ILCE-7M4", 0),
|
||||
("arw-sony-a7m4-lossless-l.arw", "ILCE-7M4", 0),
|
||||
("arw-sony-a7m4-lossless-m.arw", "ILCE-7M4", 0),
|
||||
("arw-sony-a7m4-lossless-s.arw", "ILCE-7M4", 0),
|
||||
("arw-sony-a7rm2-12bit-uncompressed.arw", "ILCE-7RM2", 0),
|
||||
("arw-sony-rx100.arw", "DSC-RX100", 0),
|
||||
("arw-sony-rx100m3.arw", "DSC-RX100M3", 0),
|
||||
] {
|
||||
let path = root.join(name);
|
||||
if !path.exists() {
|
||||
eprintln!("skip: {} absent", path.display());
|
||||
continue;
|
||||
}
|
||||
let bytes = std::fs::read(&path).unwrap();
|
||||
let full = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
assert_eq!(probe_info(&bytes).unwrap(), full.info(), "{name}");
|
||||
assert_eq!(full.metadata.model.as_deref(), Some(model), "{name}");
|
||||
assert_eq!(full.opcodes.list3.len(), expected, "{name}: only the validated Sony model gets a distortion warp");
|
||||
eprintln!("{name}: {expected} warp(s); header and full decode agree");
|
||||
checked += 1;
|
||||
}
|
||||
eprintln!("Sony distortion model boundary: {checked} corpus files checked");
|
||||
}
|
||||
+2
-2
@@ -75,7 +75,7 @@ Take the first one nobody is working on.
|
||||
4b. **LR-VIEW-ZOOM** (P0): finish the sharp zoomed views of issue #323: windows for compare, overlays and soft proofing
|
||||
(Before/After has them); check sharpness and the wide-stage margin on real raw photos (only synthetic scenes so far).
|
||||
5. **LR-EDIT-OPTICS-PROFILE** (P1): a lens-profile database of our own (corrections embedded in DNG files and the
|
||||
distortion correction Panasonic / Leica cameras record in RW2 / RWL files work today; issue #256). Sony Bayer
|
||||
distortion correction Panasonic / Leica cameras record in RW2 / RWL files work today; issue #256). Sony ILCE-7RM4A Bayer
|
||||
ARW 16-sample tables in native 3:2 framing now feed the same distortion path; broader coverage and Sony
|
||||
vignetting / lateral CA remain (see `docs/sony-lens-corrections.md`).
|
||||
6. **AI masks and Enhance** (LR-MASK-SUBJECT / SKY / PEOPLE / OBJECTS, LR-EDIT-DETAIL-DENOISE, SUPERRES, LENSBLUR):
|
||||
@@ -274,7 +274,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins.
|
||||
| LR-EDIT-DETAIL-SUPERRES | Super resolution | P2 | ⬜ | | |
|
||||
| LR-EDIT-DETAIL-AISHARPEN | AI sharpen | OOS | 🚫 | | |
|
||||
| LR-EDIT-OPTICS-CA | Remove chromatic aberration | P1 | ✅ | `crates/ui-egui/src/panels/edit.rs` (checkbox), `ctl:optics.caRed`, `ctl:optics.caBlue` | |
|
||||
| LR-EDIT-OPTICS-PROFILE | Lens profile corrections | P1 | 🟡 | `ctl:optics.profileDistortion`, `ctl:optics.profileVignetting`, `crates/pipeline/src/optics.rs`, `crates/raw/src/vendor/rw2.rs`, `crates/engine/src/crs.rs` | uses corrections embedded in the file: DNG `WarpRectilinear` / `FixVignetteRadial`, and the camera's own distortion correction in Panasonic / Leica RW2 / RWL files (tag `0x0119`, read as the equivalent `WarpRectilinear`; on 79 photos from 13 bodies and 11 lenses the corrected render lines up with the camera JPEG within 0.75 px at 1440 px at the corners, from up to 13 % of the half diagonal uncorrected; issue #256); an imported preset's `LensProfileEnable=0` leaves them on, as Lightroom applies built-in corrections regardless (inferred); Sony Bayer ARW raw-IFD distortion tables (`0x7037`, 16-sample layout, native 3:2 framing) use the same warp path, validated on ILCE-7RM4A with FE 24–105 and FE 200–600 lenses (see `docs/sony-lens-corrections.md`); vignetting and lateral CA data of other raw formats (Olympus, Fujifilm, Sony) not read yet; no lens-profile database |
|
||||
| LR-EDIT-OPTICS-PROFILE | Lens profile corrections | P1 | 🟡 | `ctl:optics.profileDistortion`, `ctl:optics.profileVignetting`, `crates/pipeline/src/optics.rs`, `crates/raw/src/vendor/rw2.rs`, `crates/engine/src/crs.rs` | uses corrections embedded in the file: DNG `WarpRectilinear` / `FixVignetteRadial`, and the camera's own distortion correction in Panasonic / Leica RW2 / RWL files (tag `0x0119`, read as the equivalent `WarpRectilinear`; on 79 photos from 13 bodies and 11 lenses the corrected render lines up with the camera JPEG within 0.75 px at 1440 px at the corners, from up to 13 % of the half diagonal uncorrected; issue #256); an imported preset's `LensProfileEnable=0` leaves them on, as Lightroom applies built-in corrections regardless (inferred); Sony ILCE-7RM4A Bayer ARW raw-IFD distortion tables (`0x7037`, 16-sample layout, native 3:2 framing) use the same warp path, enabled only for the independently validated ILCE-7RM4A with FE 24–105 and FE 200–600 lenses; other Sony models remain uncorrected (see `docs/sony-lens-corrections.md`); vignetting and lateral CA data of other raw formats (Olympus, Fujifilm, Sony) not read yet; no lens-profile database |
|
||||
)
|
||||
| LR-EDIT-OPTICS-DEFRINGE | Defringe | P1 | ✅ | `ctl:optics.defringe*` | no fringe eyedropper |
|
||||
| LR-EDIT-OPTICS-MANUAL | Manual distortion / vignetting | P1 | ✅ | `ctl:optics.distortion`, `ctl:optics.vignetting`, `ctl:optics.vignettingMidpoint` | |
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Sony embedded distortion corrections
|
||||
|
||||
LightCraft reads the signed 16-sample distortion table in the raw image IFD (`0x7037`) of Sony ARWs.
|
||||
LightCraft reads the signed 16-sample distortion table in the raw image IFD (`0x7037`) of
|
||||
Sony **ILCE-7RM4A (A7R IVA)** Bayer ARWs. Other camera models are deliberately left uncorrected,
|
||||
even when they contain a table with the same layout.
|
||||
It converts that table into `OpcodeList3` / `WarpRectilinear`, so the existing profile correction control,
|
||||
CPU/GPU optics path, EXIF orientation handling, and DNG export use the same correction.
|
||||
|
||||
This adds **distortion only**. Sony vignetting and lateral chromatic-aberration tables are not decoded.
|
||||
The tested files are Bayer ARWs from the ILCE-7RM4A with the FE 24–105mm F4 G OSS and
|
||||
The enabled and independently validated model is ILCE-7RM4A with the FE 24–105mm F4 G OSS and
|
||||
FE 200–600mm F5.6–6.3 G OSS. Linear YCbCr ARWs and non-3:2 camera crops are excluded pending validation. An aspect crop may
|
||||
retain the full-frame radial normalization; the decoder does not guess that relationship. Older files that carry
|
||||
only encrypted correction metadata, different table lengths, and rejected tables remain uncorrected.
|
||||
@@ -70,3 +72,42 @@ source has loaded, enable lens corrections if they were previously disabled.
|
||||
Tests cover signedness/length rejection, zero and malformed tables, independent 600 mm reference
|
||||
geometry, barrel framing, offset crop normalization, both TIFF byte orders, header/full consistency,
|
||||
and preservation through DNG export. No private photographs or reference TIFFs are committed.
|
||||
|
||||
## Camera coverage and regression boundary
|
||||
|
||||
The model check is exact: `ILCE-7RM4` (A7R IV without the A suffix), `ILCE-7M3`, unknown/missing
|
||||
model names, and every other model remain unsupported by this distortion decoder. Matching tag
|
||||
numbers and table lengths do not establish the scale, interpolation or crop normalization on another
|
||||
body. Support can expand after independent geometry validation; do not add model-name prefix matching.
|
||||
|
||||
The original nine Sony samples listed by `cargo xtask corpus --download` were downloaded from its
|
||||
CC0 URLs. The manifest now also includes CC0 samples 4822 (A7R IVA / 24–105 mm) and 3989
|
||||
(A9 II / 200–600 mm), with their SHA-256 hashes recorded alongside the URLs. All eleven files
|
||||
are checked with `sony_embedded_distortion_is_limited_to_validated_models`:
|
||||
|
||||
| Camera | Corpus samples | Raw-IFD table observed | Expected correction |
|
||||
|---|---|---|---|
|
||||
| ILCE-7RM4A | Compressed (4822) | 16 samples | One distortion warp |
|
||||
| ILCE-9M2 | Compressed (3989) | 16 samples | None |
|
||||
| ILCE-7M3 | Compressed, uncompressed | 16 samples | None |
|
||||
| ILCE-7M4 | 14-bit, lossless L/M/S | 11 samples in the 14-bit file; 16 in L/M/S | None |
|
||||
| ILCE-7RM2 | 12-bit uncompressed | 16 samples | None |
|
||||
| DSC-RX100 | One | No raw-IFD table | None |
|
||||
| DSC-RX100M3 | One | 11 samples | None |
|
||||
|
||||
These are **model-boundary regression checks, not new geometric validation of those models**. In particular,
|
||||
refusing the A7 III tables resolves the unverified corner warp identified in review. The lossless M/S
|
||||
files are linear YCbCr and remain excluded independently of the model check. Header and full decode
|
||||
must agree for every sample. Synthetic tests also verify both TIFF byte orders, exact model matching,
|
||||
standard crop precedence, and that DNG export preserves an accepted warp without adding one to rejected
|
||||
models. The existing independent Sony geometry tests continue to cover the enabled A7R IVA path.
|
||||
Re-exporting the 24, 200 and 600 mm reference files after the model restriction produced byte-identical
|
||||
corrected PNGs to the earlier validated exports. Isolated headless app checks showed the A7R IVA profile
|
||||
controls present and the A7 III profile controls absent, with no notices and completed RAW renders.
|
||||
|
||||
Before enabling another body, compare corrected and uncorrected renders against independent references
|
||||
across multiple lenses/focal lengths and photographs with features near the edges and corners. Record
|
||||
residuals and spatial coverage, and check crop/aspect and compression variants. Embedded JPEGs are
|
||||
useful when the camera actually applied distortion correction; an Off JPEG cannot certify an On warp.
|
||||
Prefer same-renderer Sony On/Off exports when the camera setting or geometry is ambiguous. Table-fit
|
||||
error alone only checks our polynomial approximation, not whether the table's interpretation is correct.
|
||||
@@ -308,6 +308,16 @@ fn cmd_ci() -> Result<(), String> {
|
||||
/// CC0 raw samples from raw.pixls.us (each verified CC0 on the site; files matched by their published SHA-256).
|
||||
/// One per format / compression variant we decode or deliberately report as unsupported (preview only).
|
||||
const RAW_SAMPLES: &[(&str, &str)] = &[
|
||||
// ILCE-7RM4A / CC0-1.0; SHA-256 690c774f1d7bc1db3fa8c2489762743d8e7586e50b6f65d0b6a61cb467972c67
|
||||
(
|
||||
"arw-sony-a7rm4a-compressed.arw",
|
||||
"https://raw.pixls.us/getfile.php/4822/nice/Sony%20-%20ILCE-7RM4A%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW",
|
||||
),
|
||||
// ILCE-9M2 / CC0-1.0; SHA-256 161c2a9da2b5f1e50be6117a0b4da0ce249660b5d7de13568d301c4034716b97
|
||||
(
|
||||
"arw-sony-a9m2-compressed.arw",
|
||||
"https://raw.pixls.us/getfile.php/3989/nice/Sony%20-%20ILCE-9M2%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW",
|
||||
),
|
||||
(
|
||||
"arw-sony-a7m3-compressed.arw",
|
||||
"https://raw.pixls.us/getfile.php/2414/nice/Sony%20-%20ILCE-7M3%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW",
|
||||
|
||||
Reference in new issue
Block a user