A single-file lossless image codec (.tso).
The one thing it does that other codecs don't: given only the decoded pixels of an image that used to be a JPEG, it recovers a quantized-DCT representation that reproduces those pixels exactly, and stores that instead. The original .jpg is not needed.
Everything is bit-for-bit lossless. If a mode cannot guarantee an exact reconstruction, it is not used.
Four paths, picked automatically from the input:
| Input | What Tsiro does | Honest verdict |
|---|---|---|
| Pixels of an image that was once a JPEG (4:4:4 or 4:2:0) | Recovers the DCT lattice from the pixels alone, then codes it | This is the point of the project. 2-6× smaller than JPEG XL |
A camera RAW (.dng, .cr2, .nef, .arw, .orf, …) |
De-interleaves the Bayer mosaic into 4 color planes, MED prediction, JPEG-LS gradient contexts, adaptive binary range coder | Beats JPEG XL and JPEG 2000 on every sensor tested |
A JPEG file (.jpg) |
Re-encodes its quantized DCT coefficients, one static rANS model per coefficient position, DC predicted with a 2D MED | 18% smaller than the .jpg, but JPEG XL does this better. Included for completeness |
| Any other image | Adaptive quadtree + MED predictor + wedgelet + context-adaptive rANS | Beats PNG. Loses to WebP and JPEG XL. A fallback, not a contender |
NASA photo, 8256×5504, 4:4:4 source, losslessly compressing the decoded pixels:
| Codec | Size |
|---|---|
| PNG | 20.27 MB |
| WebP lossless | 14.18 MB |
| JPEG XL lossless (best effort) | 13.63 MB |
| Tsiro | 2.26 MB |
Same effect on a clean source (a demosaiced RAW crop, never a JPEG before, re-encoded at several qualities, 1024×1024), for both chroma layouts:
| JPEG quality | JPEG XL | Tsiro | JPEG XL | Tsiro |
|---|---|---|---|---|
| 4:4:4 | 4:4:4 | 4:2:0 | 4:2:0 | |
| 60 | 44 KB | 20 KB | 55 KB | 15 KB |
| 75 | 110 KB | 29 KB | 68 KB | 23 KB |
| 85 | 363 KB | 75 KB | 189 KB | 51 KB |
| 90 | 531 KB | 94 KB | 332 KB | 58 KB |
| 95 | 649 KB | 262 KB | 452 KB | 205 KB |
| 98 | 880 KB | 608 KB | 637 KB | 550 KB |
4:2:0 is what most JPEGs in the wild use. Recovering it requires inverting libjpeg's fancy chroma upsampling on top of everything else; Tsiro clones that bit-exactly too.
The comparison this invites, answered honestly. If you still have the .jpg file, JPEG XL's lossless JPEG transcoding beats Tsiro outright:
| Size | Reconstructs | |
|---|---|---|
original test.jpg |
2.63 MB | |
JPEG XL (jpegxl_encode_jpeg) |
1.94 MB | the original file, byte for byte |
| Tsiro, JPEG-file mode | 2.15 MB | pixels + EXIF |
| Tsiro, from pixels only | 2.26 MB | pixels |
So Tsiro is not the best JPEG recompressor. Lepton, Brunsli and JPEG XL solved that, better. Tsiro's contribution is that it needs no .jpg at all: it works from pixels, where every general-purpose codec falls back to 13+ MB.
Sony α7R V, 14-bit, 9566×6374 (121.95 MB raw) · download original ↗
| Codec | Size |
|---|---|
| JPEG XL lossless (effort 7) | 76.11 MB |
| JPEG 2000 lossless | 66.66 MB |
| Tsiro | 62.84 MB |
Olympus E-M5 Mark II, 16-bit high-res, 9274×6926 (128.46 MB raw) · download original ↗
| Codec | Size |
|---|---|
| JPEG XL lossless (effort 7) | 43.66 MB |
| JPEG 2000 lossless | 37.64 MB |
| Tsiro | 36.12 MB |
A Canon 14-bit DNG gives the same picture (19.19 MB vs 20.77 JPEG 2000, 23.86 JPEG XL). Across three cameras: 4-8% better than JPEG 2000, 17-20% better than JPEG XL. All bit-for-bit lossless. Sample RAWs are CC0 from raw.pixls.us.
The generic fallback, on a 2048×2048 crop of a demosaiced Canon DNG:
| Codec | Size |
|---|---|
| PNG | 4.50 MB |
| Tsiro | 3.96 MB |
| WebP lossless | 3.76 MB |
| JPEG XL lossless | 3.60 MB |
It loses. Stated plainly because it is the truth: the quadtree/MED pipeline is a reasonable predictive coder, not a JPEG XL competitor.
- At extreme quality (q98+) the advantage shrinks: quantization steps near 1 carry almost as much information as the pixels themselves. Tsiro still beats JPEG XL there, but not by much.
- The DCT recovery requires image dimensions that are multiples of 8 (multiples of 16 for 4:2:0). Otherwise it declines.
- Other chroma layouts (4:2:2, 4:1:1) are not yet handled; 4:4:4 and 4:2:0 cover the vast majority of real files.
- When it declines, output is still lossless. It just gets bigger.
- The large factor applies only to pixels whose history includes a JPEG. It is a structural exploit, not a general compression advance. On a never-compressed image Tsiro loses to JPEG XL.
- On camera RAW there is no such factor, and there cannot be. The low bits of sensor data are essentially random: bits 0 through 3 have a marginal frequency of 0.5000. Tsiro emits 6.85 bits per pixel on the Canon, and at least ~4 of those look irreducible. Every codec clusters near the same entropy floor. A percentage win there is the whole game.
- None of the components are new: quadtrees, wedgelets, MED/LOCO-I prediction, ANS, JPEG-LS contexts, JPEG recompression. Recovering the DCT lattice from pixels alone is the part I have not found in a mainstream codec.
- The wedgelet is nearly vestigial: on real photographs the rate-distortion decision picks it for about 0.1% of quadtree leaves. MED wins 96.7% of them.
- Tsiro does not recover the original coefficients, only a quantized-DCT representation that decodes to exactly the input pixels. That is all losslessness requires.
If a tool promises multi-× lossless compression of a real RAW, it is either wrong or not lossless.
python tsiro.py photo.jpg # JPEG recompression (from the .jpg)
python tsiro.py photo.jpg pixel # force pixel mode (DCT-from-pixels recovery)
python tsiro.py image.png # generic / ex-JPEG pixels
python tsiro.py shot.dng # camera RAW (Bayer mosaic)Each run compresses to <name>.tso, decompresses, and verifies the reconstruction is bit-for-bit identical before printing any number, alongside PNG / WebP / JPEG XL / JPEG 2000 baselines.
Programmatic API:
import tsiro, numpy as np
blob = tsiro.compress_pixels(img_rgb) # np.uint8 HxWx3
img = tsiro.decompress_any(blob)
blob = tsiro.compress_raw(mosaic_u16) # np.uint16 HxW Bayer mosaic
mosaic = tsiro.decompress_raw(blob)DCT recovery from pixels. Estimates the quantization tables from the pixel data alone: it first tries the standard IJG tables (quality 1-100, which cover most real files), validating each entry against the observed coefficient lattice, and falls back to a per-position lattice estimator (trimmed mean-squared distance to the nearest multiple, noise-floor corrected, largest lattice among near-ties) for custom tables. Coefficients that are always zero are forced to zero; anything unreliable falls back to a step of 1, which stores the coefficient exactly. Underestimating a step costs bits, never fidelity.
It clones libjpeg's integer IDCT (jidctint), its fixed-point YCbCr conversion, and its fancy h2v2 chroma upsampler bit-exactly in numba, and inverts the color transform by candidate search rather than by float rounding. For 4:2:0 sources it first inverts the chroma upsampling (iterative linear solve, then greedy integer polish against the exact integer upsampler). Then, per 8×8 block (per 16×16 MCU in 4:2:0), it flips borderline coefficients and re-decodes through the full chain until the region reproduces the exact input pixels. Whatever still mismatches is stored as an explicit patch list, so the result is bit-exact by construction rather than by convergence. If no lattice is found, the mode declines and the generic coder runs.
RAW. Bayer de-interleave into 4 planes, so the predictor never crosses colors. That single choice is why JPEG-LS scores 30 MB on the raw mosaic and 19 MB per-plane. Then MED prediction, sign-folded 3-gradient JPEG-LS contexts, median bias correction per context, and an adaptive binary range coder.
Entropy coders, both written from scratch:
- A static byte-wise rANS, verified by exact roundtrip on randomized skewed distributions.
- An adaptive binary range coder (LZMA-style, capped-unary magnitude class, bypass mantissa bits). On synthetic data it lands at 3.918 bits/symbol against an entropy of 3.884, about 0.9% above the floor, and it transmits no frequency tables.
Pixel mode. Reversible YCoCg-R color transform, adaptive quadtree, and a per-leaf rate-distortion choice between a flat mean, a 32-orientation wedgelet, and a MED predictor. Residuals are coded losslessly in all cases, so the choice affects size only, never fidelity.
pip install numpy==2.2.6 numba pillow jpeglib rawpy imagecodecsnumpy, numba, pillow and jpeglib are required to import the module. rawpy is needed only for RAW files and imagecodecs only for the benchmark comparisons; both are imported lazily.
Tsiro was designed and built by Daxlia with the assistance of AI coding tools. Every number in this README was measured on real files, not estimated. Every run enforces losslessness by a full bit-exact roundtrip, including odd image dimensions.
Apache License 2.0 (SPDX: Apache-2.0), see LICENSE and NOTICE.
Use it, modify it, ship it commercially. Apache 2.0 carries an explicit patent grant, which matters in a field with as much patent history as image compression.
Daxlia, 2026.