LR-EDIT-OPTICS-PROFILE: restrict Sony correction to validated camera

This commit is contained in:
Elliot Graham committed 2026-10-09 17:25:14 +01:00
1 parent e25909938e
commit 7343c8f85f
8 files changed
+149 -47

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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];
+5 -6
View File
@@ -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,
+52 -35
View File
@@ -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);
}
}
}
}
+35
View File
@@ -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
View File
@@ -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` | |
+43 -2
View File
@@ -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.
+10
View File
@@ -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",