Lossy and lossless image compression in pure Python, running entirely on your own machine. And a second thing worth more than the codec: train your models on the compressed representation instead of on pixels, and the steps run about nine times faster.
Three dependencies. Both trained models ship inside the package.
pip install git+https://github.com/Mubby03/json-camera
There is no service to call, no API key, no account and no telemetry. The
package is torch, numpy and pillow, with
no network code anywhere in it — verified by running a full
encode and decode with every socket blocked. Your images never leave your
computer and it works with the wifi off.
import jsoncam doc = jsoncam.encode("photo.jpg") # learned codec, about 60x jsoncam.decode(doc, "restored.png") doc = jsoncam.encode_lossless("photo.png") # bit exact, keeps alpha jsoncam.decode(doc, "exact.png") # format detected for you
The part worth stealing, and the part with a real caveat attached.
A network does not have to see pixels. The codec's latent grid is 128×14×14 where the image was 3×224×224 — six times fewer values — so every layer downstream of the input does less work. You convert the dataset once and train on it forever after.
import jsoncam from torch.utils.data import DataLoader jsoncam.prepare_dataset("photos/", "train.jcl", size=224) # once, ~27ms/image ds = jsoncam.LatentDataset("train.jcl") # yields (latent, label) dl = DataLoader(ds, batch_size=64, num_workers=4, shuffle=True)
| Pipeline | Input tensor | Values | Throughput | Disk / image |
|---|---|---|---|---|
| Pixels | 3 × 224 × 224 | 150,528 | 50 img/s | 20.1 KB |
| Latents | 128 × 14 × 14 | 25,088 | 462 img/s | 3.1 KB |
| Difference | — | 6.0× fewer | 9.2× faster | 6.5× smaller |
scripts/benchmark_latents.py.| Approach | Test accuracy | Time | Verdict |
|---|---|---|---|
| CNN from scratch, on pixels | 57.5% | 273 s | at chance |
| CNN from scratch, on latents | 52.5% | 179 s | at chance, but faster |
| Pretrained ResNet18, frozen | 92.5% | 87 s | wins outright |
Compressed-domain training is worth it when you were going to train from scratch on a large dataset anyway. If transfer learning is an option for your task, it will almost certainly beat this, and it cannot be combined with it: a pretrained backbone expects 3 channels at 224×224 and a latent is 128 at 14×14.
Paste one of these into Claude Code, Cursor or whatever you use. Each one points the agent at the machine-readable docs first, so it learns the caveats before it writes anything.
AGENTS.md is the full brief: the API, the
measured numbers with their conditions, and a table of requests this library is
the wrong answer to. llms.txt is the
short version. Both are served from the site and live in the repo, so an agent
can fetch either without cloning.
Read https://json-camera.fly.dev/AGENTS.md, then tell me honestly whether json-camera is the right tool for what I am building. Work through its "Should you use it at all" section against my actual project. If WebP, AVIF or transfer learning would serve me better, say so plainly and explain why rather than finding a way to use it. Do not write any code yet.
Read https://json-camera.fly.dev/AGENTS.md. Add json-camera lossless compression to my archival pipeline. Requirements: verify every round trip with np.array_equal and fail loudly if it ever differs, since a lossless codec that is quietly lossy is worse than none. Preserve the alpha channel and the original filename. Warn me if any input is 16-bit or CMYK, because those are silently converted. Compare the output size against PNG and WebP lossless and report all three.
Read https://json-camera.fly.dev/AGENTS.md, especially the five conditions for compressed-domain training. First check my project against all five and tell me if any fail. If they all hold: convert my dataset with jsoncam.prepare_dataset, swap my DataLoader to jsoncam.LatentDataset, and adapt only the input stem of my model to take the latent shape. Then benchmark BOTH accuracy and throughput against the pixel pipeline, because accuracy on latents is unmeasured and speed alone does not tell me whether this was a good idea.
Read https://json-camera.fly.dev/AGENTS.md. I want low-quality image placeholders generated at build time. Note the constraint up front: browsers cannot decode json-camera files, so everything must be decoded during the build and emitted as ordinary images or data URIs, never fetched and decoded client side. Build a script that walks my image directory, produces a tiny placeholder for each, reports the byte size, and compares it against just using a small WebP. If WebP wins, tell me and stop.
Read https://json-camera.fly.dev/AGENTS.md. I want to use json-camera's components on their own rather than the codec as a whole: jsoncam.rans for entropy coding my own symbol stream, jsoncam.entropy for a learned prior, or jsoncam.lossless for the reversible colour transform and MED predictor. Show me each one working standalone with a round-trip assertion, then help me wire the one I need into my own model. JSONCamera is an ordinary nn.Module, so subclassing or replacing it is fine.
Five questions. If any answer is no, the honest advice is don't.
| Condition | Why it matters |
|---|---|
| Training from scratch | No pretrained backbone fits a 128-channel 14×14 input. If you can fine-tune an existing model, do that instead. |
| Large dataset | Conversion costs about 27 ms an image, paid once. Break-even is ~1.4 epochs, so the conversion is cheap — but on 600 images the whole question is moot. |
| Disk or I/O hurts | 6× less data is real money on cloud storage and real time on a network-mounted dataset. If your data already fits in RAM, you gain little. |
| Task tolerates a 16× downsample | Classification, probably fine. Segmentation, detection, OCR, anything needing fine spatial precision — probably not. |
| You can live without augmentation | The finest crop is 16 px, colour jitter is impossible, and even a horizontal flip is not exact in latent space. On small datasets, losing augmentation costs more than 9× speed is worth. |
Places where a small file matters more than a perfect one.
Ship a tiny version inline in the HTML that renders instantly while the real image loads. At very low rates the codec produces a soft but structurally correct picture, which is exactly what a placeholder wants.
Small, numerous, and nobody inspects them at 100%. A few kilobytes each across a large user table adds up, and the quality floor is low.
Catalogue and gallery views load dozens of images at once. The bytes on the wire dominate the experience, not the fidelity of any single tile.
Cold storage for image sets you may want again. Lossless mode keeps them bit exact; lossy keeps them at a fraction of the size if approximate is enough.
The payload is already text, so it drops into a JSON response, a config file, a database text column or a git repository without a separate binary asset or base64 wrapper.
The whole pipeline is readable Python: a range coder, an entropy model, a predictor. Every component imports and runs on its own, which is hard to find in a production codec.
A browser cannot decode these files. The model weights are the format, so decoding needs Python and the checkpoint — which means a server round trip, or decoding ahead of time. For placeholders that is fine, since you generate them during a build. For anything decoded live in a browser, use WebP or AVIF instead. Saying so is more useful than selling you a dead end.
Plain Python. No framework, no registry, no plugin system.
jsoncam.encode(img)out= to write it. Roughly 60× smaller than raw, and beats JPEG at a matched file size on the default model.jsoncam.encode_lossless(img)jsoncam.decode(doc)jsoncam.prepare_dataset(dir, out)ImageFolder layout works unchanged; subdirectories become integer labels.jsoncam.LatentDataset(shard)torch.utils.data.Dataset, so Subset, ConcatDataset, samplers and transform= all apply as normal.jsoncam.psnr / ms_ssim| Component | Import | What it is |
|---|---|---|
| Range coder | jsoncam.rans | Vectorised interleaved rANS, 512 lanes. Works on any symbol stream. |
| Entropy model | jsoncam.entropy | Factorised prior with monotonic per-channel CDFs. |
| Networks | jsoncam.model | JSONCamera is an ordinary nn.Module. Subclass it or replace it. |
| Colour transform | jsoncam.lossless | YCoCg-R, reversible in integers, plus the MED predictor. |
| Metrics | jsoncam.metrics | MS-SSIM with a separable Gaussian. |
codec.encode_image(your_model, img) takes any model of the right shape. The file's fingerprint follows your weights automatically.Stated here rather than discovered by you later.
A file is only decodable by the exact checkpoint that wrote it. Every file carries a fingerprint and decoding refuses on a mismatch. Retrain the codec and old files stop opening.
Decoding needs Python and torch. There is no JavaScript build, so anything client-side needs a server round trip or a build step.
About 1.3 s to encode and 1.7 s to decode a 3 megapixel image on one core. Fine for batch work, too slow for a request path without care.
The network has three input channels. The alpha channel is discarded and the header records it. Use lossless mode, which codes alpha as a fourth plane.
Greyscale becomes three channels, CMYK becomes RGB, and 16-bit is narrowed to 8-bit without comment. Know this before feeding it scientific imagery.
Text carries about 6.1 bits per character in base85, so the file is a quarter larger than the bitstream. On lossless that cancels the win against PNG entirely.
Roughly 4,000 lines of Python and 31 tests, all of it readable.