LR-IMP-CAMERA-COVERAGE: Nikon D90 NEFs drop their filler columns (no pink band)

The D90 writes 4352 x 2868 samples whose last 42 columns (from 4310) are
filler at a flat ~254, above its dark image samples (black 0). The
masked-column test only trims columns near zero, so they stayed in the
image and developed as a pink band on the right. Measured on 10 of 550
NEFs from one body: the camera's 4288 x 2848 JPEG sits at (10, 10), and
green saturates at 3840-3851 while red and blue run on to 4095, so one
white level of 3830 keeps clipped highlights white. The D90 joins the D4
in MEASURED_BODIES; other bodies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Menno
2026-10-13 00:25:33 +02:00
co-authored by Claude Opus 5.5
parent 701f78ea2e
commit 60639aa677
3 changed files with 35 additions and 7 deletions
+28 -2
View File
@@ -74,8 +74,15 @@ struct MeasuredBody {
/// 4928 × 3280 JPEG sits at (8, 6). Without this area the optical black shows as purple stripes on both sides.
/// Green saturates at 15546–15672 while red and blue run on to 15687–16383, so the data's own top (~16300) leaves
/// clipped highlights magenta; one level of 15520 for every channel turns them white and keeps the white balance.
const MEASURED_BODIES: &[MeasuredBody] =
&[MeasuredBody { model: "NIKON D4", size: (4992, 3292), bits: 14, crop: (8, 6, 4928, 3280), white: 15520.0 }];
/// * **NIKON D90** (550 compressed 12-bit NEFs from one body, 10 measured): the data is 4352 × 2868 and its last 42
/// columns (from 4310) are filler at a flat ~254 whatever the exposure, above the dark image samples (black 0), so
/// the masked-column test keeps them and they develop as a pink band on the right. The camera's 4288 × 2848 JPEG
/// sits at (10, 10). Green saturates at 3840–3851 while red and blue run on to 4095; one level of 3830 keeps
/// clipped highlights white.
const MEASURED_BODIES: &[MeasuredBody] = &[
MeasuredBody { model: "NIKON D4", size: (4992, 3292), bits: 14, crop: (8, 6, 4928, 3280), white: 15520.0 },
MeasuredBody { model: "NIKON D90", size: (4352, 2868), bits: 12, crop: (10, 10, 4288, 2848), white: 3830.0 },
];
fn measured_body(model: &str, w: usize, h: usize, bits: u32) -> Option<&'static MeasuredBody> {
MEASURED_BODIES.iter().find(|b| b.model == model.trim() && b.size == (w, h) && b.bits == bits)
@@ -398,6 +405,25 @@ mod tests {
}
}
/// The D90's last 42 columns are filler at a flat ~254, above its dark image samples: without the measured
/// area they stay in the image and develop as a pink band on the right.
#[test]
fn measured_d90_drops_the_filler_columns() {
let (w, h) = (4352usize, 2868usize);
let mut px = vec![40u16; w * h];
for row in px.chunks_mut(w) {
row[4310..].fill(254);
}
let mut nef = nef(1, 12, vec![px.iter().flat_map(|v| v.to_le_bytes()).collect()], w as u32, h as u32, h as u32);
let at = nef.windows(10).position(|b| b == b"NIKON TEST").unwrap();
nef[at..at + 10].copy_from_slice(b"NIKON D90 ");
let d90 = crate::decode(&nef).unwrap();
assert_eq!(d90.crop, Rect::new(10, 10, 4288, 2848));
assert!(d90.crop.x + d90.crop.width <= 4310, "the filler columns are outside the image");
assert_eq!(d90.white, vec![3830.0]);
assert_eq!((d90.metadata.width, d90.metadata.height), (Some(4288), Some(2848)));
}
/// Issue #708: the active width is judged from the first [`MASK_ROWS`] rows in both modes, so the header probe
/// (which reads only those rows' strips) and the full decode agree whether the masked columns are there or not.
#[test]
File diff suppressed because one or more lines are too long
+4 -3
View File
@@ -1,6 +1,6 @@
# RAW parity: cameras, formats, demosaic, colour and lens profiles
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (EOS R5 Mark II decoded and verified: CRX QP-map escape, odd-height planes) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Nikon D90: filler columns no longer develop as a pink band; EOS R5 Mark II decoded and verified: CRX QP-map escape, odd-height planes) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 (Camera Raw 18.7)
Raw processing is the deepest single dimension of Lightroom parity: a photographer whose camera
does not decode, or decodes with the wrong colour, cannot switch. This document measures it model by
@@ -118,7 +118,7 @@ checked, and Canon, the largest bucket, is the weakest of the big four.
| CR3 | HEIF / HDR PQ | ⬜ | HEVC tracks recognised, not decoded |
| CR3 | dual-pixel raw | ⬜ | never substituted |
| CRW | all | ⬜ | preview |
| NEF | uncompressed, lossless, lossy type 1 / 2, lossy after split | ✅ | D5100, D7000, D7500, Z f corpus; ~60 NEFs by hand; #633 |
| NEF | uncompressed, lossless, lossy type 1 / 2, lossy after split | ✅ | D5100, D7000, D7500, Z f corpus; ~60 NEFs by hand; #633; D4 and D90 image area and white level measured against the camera JPEG |
| NEF | packed 14-bit stored under a compressed tag (Z 6II uncompressed) | ⬜ | #769; fix in PR #671 (not merged) |
| NEF | High Efficiency / HE★ (JPEG XS) | ⬜ | named, preview (#193); needs a JPEG XS decoder |
| ARW | uncompressed, cRAW (ARW2), lossless LJ92 (L / M / S) | ✅ | 86 bodies (#535); M/S black level 1024 |
@@ -172,7 +172,7 @@ sample files. A pointer for contributors, not legal advice.
| Monochrome / LinearRaw | ✅ | handled, no demosaic | ✅ |
| Pixel shift (Sony ARQ, Pentax, OM high-res, Nikon) | ✅ (Sony, Pentax, Nikon) | — | ⬜ |
| Black and white levels | per model, calibrated | read from each file (masked columns, maker notes, SR2SubIFD, CMP1) | 🟡 per-body bugs keep surfacing: A450–A700 SR2 black level and M/S ARW pedestal (#535, fixed); 5D Mark IV CR2 renders dark (#455, open); white-level pile-up in phone DNGs (#548, fixed) |
| Crop masks and aspect | ✅ | DefaultCrop, Sony recorded size, Canon AspectInfo, Panasonic in-camera crops, Nikon CropArea | 🟡 7RM3 / 7RM4 crop origin 8–32 px off (cosmetic) |
| Crop masks and aspect | ✅ | DefaultCrop, Sony recorded size, Canon AspectInfo, Panasonic in-camera crops, Nikon CropArea, measured Nikon bodies without it (D4, D90) | 🟡 7RM3 / 7RM4 crop origin 8–32 px off (cosmetic) |
| Lens metadata | ✅ read for profile selection | EXIF lens fields read; used only for the in-file corrections below | 🟡 |
| Highlight reconstruction | ✅ | neutral clip, chromaticity diffusion (`highlight.rs`) | 🟡 tuned by eye |
| AI denoise | ✅ Bayer and X-Trans, model bundled | Bayer only, user-installed weights, pure-Rust CPU / GPU | 🟡 no X-Trans, no shippable weights |
@@ -266,6 +266,7 @@ Beyond decoding:
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | Nikon D90 image area (4288 × 2848 at 10, 10) and white level (3830) measured on 10 of 550 NEFs from one body: its 42 filler columns no longer develop as a pink band |
| 2026-10-10 | minor | Compressed ORF: pointer to the two Olympus patents that describe the method, as a clean-room starting point |
| 2026-10-11 | minor | EOS R5 Mark II verified (CRX QP-map first literal and limited-length escape, odd-height Bayer planes; 4 CC0 files pinned, QP maps of 33,665 private files): 235 verified, 942 unverified; use-weighted 85% (42% on verified bodies); decoding 77%, RAW readiness unchanged at ~56% |
| 2026-10-10 (later) | major | Model-by-model measurement against Adobe's published list (1,446 models: 234 verified, 943 unverified, 233 preview, 36 unsupported); `docs/raw/` data and re-measure script; use-weighted share 84% (40% on verified bodies); variant matrix; colour and lens sections with scores; ranked next cameras; RAW readiness ~56% from written parts |