NC Imago · Docs

Sony ARW6 (Compressed RAW 2)

Imago decodes the raw sensor data of Sony's Compressed and Compressed (HQ) ARW files (TIFF Compression 32766, SonyRawFileType 6, "ARW 6.0") written by the A7R VI (ILCE-7RM6) in pure Rust. The shared RAW decoder serves import, desktop previews, editing, CLI rendering, web and export. Before this decoder these files failed the full decode and silently rendered from their embedded JPEG. The ILCE-7M5 (A7 V) writes the same codec; no sample of it was available, so it is unverified.

Decode timings: see the end of this document (measured in the performance pass).

Format and implementation

The codec is Sony's use of SMPTE RDD 34 (LLVC, Low Latency Video Codec). crates/raw/src/llvc.rs is the codec core (bit reader, variable-length decoder, dequantiser, inverse 5/3 wavelet); crates/raw/src/vendor/arw6.rs is the Sony container, geometry and colour reconstruction; crates/raw/src/vendor/arw6_curve.rs is the companding curve; arw.rs dispatches Compression 32766 to it. A test-only encoder (arw6_testenc.rs) builds synthetic tiles for round-trip tests.

Container

Entropy coding and dequantisation

Frame geometry

Colour reconstruction

With plane row j, column x (x +- 1 clamped at the edges), in 12-bit code units:

Output range (companding curve)

Codes map through one monotone curve, the same for every file and both modes. It is not stored in the file (below); it is our own measurement. Output values are in 2 x 14-bit DN: black 2 x 512 = 1024, the 14-bit white 16383 maps to 32766.

How it was measured

Each of the four scenes of the CC0 set (APS-C and full frame, landscape and portrait) has a lossless (LJ92) file and a Compressed and an HQ file, shot handheld one after another. Seven Compressed / HQ files share shutter, aperture and ISO with their lossless sibling. The eighth, APSC_land RAW-C (1/500 s against 1/1000 s), was left out: its brightness ratio to the lossless file is 1.37 rather than 2 and drifts with level, so the light changed between the shots.

Uncertainty: 95 % intervals of a cluster bootstrap over the 7 captures (40 replicates), as a share of F - 1024 and in codes, and the largest deviation of any subset or method fit (Compressed only, HQ only, greens, red + blue, APS-C, full frame, quantile method):

Codes Bootstrap 95 % In codes Largest subset / method deviation
1408-1536 -0.42..+0.35 % -1.6..+2.0 0.75 %
1536-2048 -0.65..+0.58 % -3.0..+2.8 0.80 %
2048-2560 -0.55..+0.57 % -3.1..+3.3 0.78 %
2560-3072 -0.59..+0.50 % -3.1..+3.7 0.64 %
3072-3584 -0.76..+0.54 % -3.5..+4.9 0.77 % (quantile)
3584-3840 -0.84..+0.57 % -3.7..+5.5 0.90 % (quantile)
3840-3977 -0.88..+0.59 % -3.8..+5.8 0.97 % (quantile)
3977-4095 -0.91..+0.62 % -4.0..+5.9 1.03 %, extrapolated

The lossless files clip at the 14-bit white, so codes above about 3977 rest on the formula alone. The camera's exact integer table can't be recovered: integer knot codes, the encoder's rounding and a few codes around the knee are below the measurement's resolution.

Evidence for the structure

Scale

The x2 unit is measured, not assumed:

Not stored in the file

Every IFD of all 12 files was dumped and compared across modes:

Verification against the oracle

After the derivation was frozen, the derived table was compared with the black-box oracle's output curve (below), as a check only, with no tuning. They are identical through code 1422 (both are the identity through 1408), then within -1.0 % to +0.25 % of (value - black); the largest difference is 75 (0.2 %) at code 4093. Ours minus the oracle's: -5..+1 for codes 1408-1536, -13..-2 for 1536-2048, -33..-12 for 2048-3072, -34..+40 for 3072-3977, +1..+75 for 3977-4095.

Verification

Method: LibRaw master (commit d1f0dd95, 2026-10-08, run as unprocessed_raw, 16-bit output before any processing) was used only as a black-box oracle: it was built and run, and its source was never read. A Python reference model of the format (the Phase 0 reference model, local, not in the repo) was written from RDD 34 and from the oracle's behaviour on real and synthetic input, then reproduced the oracle exactly on every tile of the 8 Compressed / HQ files below. A test-only encoder, validated byte for byte against real tiles, probed the oracle with synthetic coefficients (dequantiser, phases, edge mapping). The Rust decoder reproduced the Phase 0 reference sums on all 8 files. Its output then went through a table read off the oracle's output; that table was strictly increasing, so these exact matches prove the 12-bit codes before the curve identical to the oracle's. The table has since been replaced by our own measured curve (Output range), so the output values now differ from the oracle's by the amounts in Verification against the oracle.

Geometry was checked against each file's embedded full-resolution JPEG at the DefaultCropOrigin: our output aligns at offset 0 on full frame and APS-C.

LibRaw's APS-C bug

LibRaw assumes the full-frame grid (s = 0) for every tile. On the APS-C crop that drops the first three plane rows, leaves the last three unfed, and shifts its whole output up by 6 sensor rows; its first rows and last rows are also wrong. Our decoder differs from LibRaw there on purpose. To compare against LibRaw on APS-C files, our row y+6 equals LibRaw's row y for 12 <= y < h - 24 (LibRaw's rows 0-11 and the last 18 rows of the shifted overlap differ); on full frame compare directly: the 12-bit codes match over the whole frame.

Corpus

File Source path (RawDB, ILCE-7RM6) Size (bytes) SHA-256 Fetched by
arw6-sony-ilce7rm6-apsc-land-hq.arw arw6/ILCE-7RM6_APSC_land_RAW-C-HD.ARW 29 720 576 b60a7e752d2a8102ccbe6eebcfdbf533c6b0f2545739090873e9e8eb32cc0637 cargo xtask corpus --download
arw6-sony-ilce7rm6-apsc-land-c.arw arw6/ILCE-7RM6_APSC_land_RAW-C.ARW 30 900 224 1c803304e63ec508b2b5f9d59d4a7e30ed279315b35fa4c5d75f959bed62198b curl -L
arw6-sony-ilce7rm6-apsc-port-hq.arw arw6/ILCE-7RM6_APSC_port_RAW-C-HD.ARW 28 758 016 0b6d782571e86868670f613de9fa1f1364623b24fbfd21b704b2c516d7a1c1cb curl -L
arw6-sony-ilce7rm6-apsc-port-c.arw arw6/ILCE-7RM6_APSC_port_RAW-C.ARW 29 044 736 c41674a485ffcf6d873e20e545ae62936192d94e95be3d53548e93bc543ecfd4 curl -L
arw6-sony-ilce7rm6-ff-land-hq.arw arw6/ILCE-7RM6_FF_land_RAW-C-HD.ARW 63 451 136 559fa26501d7813e56afecde2aea3d9965a99228162b194752d986193abb172c curl -L
arw6-sony-ilce7rm6-ff-land-c.arw arw6/ILCE-7RM6_FF_land_RAW-C.ARW 63 844 352 d900d6cbe838be864269ad54de4a33fcac7eb3a3d4f91009739f770b8f85a23b cargo xtask corpus --download
arw6-sony-ilce7rm6-ff-port-hq.arw arw6/ILCE-7RM6_FF_port_RAW-C-HD.ARW 62 640 128 81c709633cb8f6340e612c3f53bb6ec6b0cccfe5b713c67efb1e977ece9e266d curl -L
arw6-sony-ilce7rm6-ff-port-c.arw arw6/ILCE-7RM6_FF_port_RAW-C.ARW 63 168 512 f4448485629c3f82ff1955fd046404350e9a1f09b346038574b89c074d7c50fd curl -L
arw-sony-ilce7rm6-apsc-land-lossless.arw raw_modes/ILCE-7RM6_APSC_land_RAW.ARW 44 167 168 7aaf0b3941509d7364a6f03844783155ebc912d246d1c456e5cdb4693ec09fbc curl -L
arw-sony-ilce7rm6-apsc-port-lossless.arw raw_modes/ILCE-7RM6_APSC_port_RAW.ARW 43 692 032 0f011933ee96525a39710daa50b59d39702b4ec5934fa0206742e2128c55f366 curl -L
arw-sony-ilce7rm6-ff-land-lossless.arw raw_modes/ILCE-7RM6_FF_land_RAW.ARW 100 851 712 5cb3c870bc961cf6b079d56c860d0cdf11acd1afd04e5fb8dbe041bcfd2d6201 curl -L
arw-sony-ilce7rm6-ff-port-lossless.arw raw_modes/ILCE-7RM6_FF_port_RAW.ARW 100 884 480 b1a4026aa6adbaa27973b8b3426d8b4aa0c1cae1845cf5102359327d3b069691 curl -L

These 12 files are CC0 1.0. RawDB's record of the set, https://rawdb.dnglab.org/api/sets/Sony/ILCE-7RM6 (the data behind https://rawdb.dnglab.org/sets/Sony/ILCE-7RM6, accessed 2026-10-10), says "license":"CC0 1.0" for the set (uploaded by abbradar, 2026-08-29) and again for each of the 12 files, next to the SHA-256 values in the table.

They live in the gitignored corpus. cargo xtask corpus --download fetches two of them, one per geometry and compression mode (full frame Compressed, APS-C HQ), and checks each against its SHA-256, pinned in RAW_SAMPLES (xtask/src/main.rs). Fetch the other ten by hand: the server answers 303 to a signed object URL, so curl -L -o corpus/raw/<file> https://rawdb.dnglab.org/api/download/Sony/ILCE-7RM6/<source path>, then compare with the SHA-256 above. cargo test -p imago-raw corpus_sony -- --nocapture checks the decoder's sensor sum and position-weighted sum of the two pinned files and skips absent files. The sums are regression values from Imago's decoder with the derived curve; the 12-bit codes behind them were verified against the oracle in Phase 0.

Nine further full-frame HQ photos from the maintainer's A7R VI were compared with the oracle locally; they are private and never committed or published. On all 9 (ISO 100-12800, incl. two near-black night shots) the 12-bit codes matched the oracle over the full frame (0 mismatching pixels of 66.8 MP each).

Unconditional synthetic tests (encode -> decode round trips for both geometries, the RDD 34 worked example, the dequantiser, hostile and truncated input) run without the corpus.

Coverage

Coverage Status
ILCE-7RM6 Compressed and HQ, full frame, landscape and portrait 4 files, 12-bit codes exact against the oracle; output through our measured curve
ILCE-7RM6 Compressed and HQ, APS-C crop, landscape and portrait 4 files, 12-bit codes exact against the Phase 0 model (interior-exact against LibRaw after the 6-row shift; LibRaw's own edge rows differ)
ILCE-7RM6 lossless (existing LJ92 path) 4 files decode; not in the corpus test
Private A7R VI HQ photos 9 files, 12-bit codes exact over the full frame against the oracle (local)

Not covered

Sources

Timings

Raw decode only, 4 CPUs, release build, best of 3 wall-clock (2026-10-10). Method: a small throwaway Rust program linking imago-raw reads the file into memory, then times imago_raw::decode alone with std::time::Instant (no render, no JPEG encode), three decodes in one process; the best is reported. Files were already in the page cache.

File Size Sensor Decode
ILCE-7RM6 FF, Compressed (HQ) 63.5 MB 10016x6672 0.45 s
ILCE-7RM6 FF, Compressed 63.8 MB 10016x6672 0.51 s
ILCE-7RM6 APS-C, Compressed (HQ) 29.7 MB 6592x4372 0.35 s
ILCE-7RM6 APS-C, Compressed 30.9 MB 6592x4372 0.34 s

The 1.0 s budget for the full-frame file is met. Parallelised: rayon over the tiles, over the 13 streams of each tile and over its 3 components, and the colour reconstruction over rows.

2026-10-12 performance pass (output unchanged, bit for bit): the vertical wavelet pass lifts whole rows instead of gathering each column, 0^n 1 codes are counted with one leading_zeros, and the colour reconstruction reads rows as slices, in parallel. Same method, best of 5, the previous and the new decoder run alternately on the same machine (4 CPUs, which was busier than on 2026-10-10), best of two rounds:

File Before After
ILCE-7RM6 FF, Compressed (HQ) 0.56 s 0.46 s
ILCE-7RM6 FF, Compressed 0.54 s 0.38 s
ILCE-7RM6 APS-C, Compressed (HQ) 0.34 s 0.23 s
ILCE-7RM6 APS-C, Compressed 0.35 s 0.24 s

2026-10-13 (output unchanged, bit for bit): the two halves of each stream, which are independent bit streams, decode in parallel; the vertical wavelet pass runs its rows in parallel; the horizontal pass lifts each row in place (scratch space reused per thread), rows in parallel, instead of allocating two vectors per row. Same method, best of 3, the previous and the new decoder run alternately, on another machine (Ryzen 7 9800X3D, a 4-CPU cgroup quota shared with other builds); CPU is the process CPU time of the decode (all threads):

File Before After CPU before CPU after
ILCE-7RM6 FF, Compressed (HQ) 0.60 s 0.48 s 1.14 s 0.87 s
ILCE-7RM6 FF, Compressed 0.62 s 0.48 s 1.14 s 0.89 s
ILCE-7RM6 APS-C, Compressed (HQ) 0.34 s 0.23 s 0.50 s 0.40 s
ILCE-7RM6 APS-C, Compressed 0.31 s 0.24 s 0.51 s 0.42 s