jpeg_quality_estimate — IMGFORENSICS compression op

• Data kinds: image2d → table

• Call: import fullseye as fs; fs.ledger.jpeg_quality_estimate(image, max_step: 'int' = 64, n_coeff: 'int' = 21) -> 'dict' (to call the implementation directly, import imgforensics; imgforensics.jpeg_quality_estimate(image, max_step: 'int' = 64, n_coeff: 'int' = 21) -> 'dict'; from the registry, opsimgforensics.get("jpeg_quality_estimate"))

• GPU: none (CPU reference implementation only)

Usage

Blind estimation of the quantisation table and JPEG quality from a decoded image. table.

Instead of reading the DQT of the file, estimate it from pixels only (that's the point, Even if the image is resaved/pasted to PNG, the originally JPEG quality N'' is displayed. visible). Take an 8x8 block DCT and estimate the comb spacing for each coefficient to (Fan & de Queiroz 2003), from a family of standard tables created using IJG's public scaling rules. Choose the quality that best suits you.

Return (dict):

jpeg_compressed Judging by how many n_coeff coefficients were combed

bool. This is not proof that it is a JPEG (caveats below)

quality Estimated quality 1..100 (None if there is no comb) table Estimated quantization step of (8, 8) (0 = not estimated) n_quantized raw number of combed coefficients excluding DC n_tested = n_coeff - 1 fit_error Mean absolute difference from the selected quality standard table caveats What this number cannot say

Actual measurement (tests/test_imgforensics.py::test_jpeg_quality_estimate_recovers_quality, Encode 256x256 1/f^1.6 image with Pillow → decode and estimate):

======= ========= =============== ============ True Q estimate Q combed coefficient fit_error ======= ========= =============== ============ 95 95 11/20 0.091 90 90 19/20 0.579 80 80 13/20 1.308 70 71 12/20 2.083 60 60 9/20 2.111 50 52 7/20 1.857 40 40 6/20 3.333 30 None 4/20 — ======= ========= =============== ============

It is a theoretical limit that it collapses on the low quality side. As the quality decreases, the high frequency coefficient becomes 0. quantized, and the comb reading material itself disappears from the image (the column of combed coefficients'' is How it disappears). At quality 30, there is not enough material and does not return an estimate(quality=None). The design policy of this op is not to force an answer here.

and jpeg_compressed=False / quality=None for uncompressed PNG. (Confirmed with 4 seeds, fixed in the same test). The best thing you can do is not just say “quality 100”. In this case, "uncompressed" and "virtually undegraded JPEG" will be the same answer.

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• image_forensics_audit — py -3.11 examples/image_forensics_audit.py

Ops the type connects to (they accept table as input)

evidence_quantile

Same category (compression)

error_level_map · jpeg_ghost_map · jpeg_ghost_quality


*Provenance: imgforensics.py — IMGFORENSICS operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.