A command line tool that deliberately degrades document images for OCR robustness testing. It can produce low-resolution 1-bit pages that look like they came through a fax machine, paper that is partly crumpled or creased so the text collapses, low-quality JPEG re-encodes, sensor noise, uneven lighting, and any chain of these.
| Original | fax (204x98 dpi, 1-bit, streaks) |
|---|---|
![]() |
![]() |
wrinkle (part of the page crumpled) |
fold (sharp creases) |
|---|---|
![]() |
![]() |
preset crumpled-fax |
lowres → wrinkle → fold → fax --mode fine |
|---|---|
![]() |
![]() |
Every preset at a glance (imagedown sample.png --suite out/):
uv tool install git+https://github.com/mshk/imagedown-cli
imagedown --helpOr clone it for development:
git clone https://github.com/mshk/imagedown-cli.git
cd imagedown-cli
uv sync
uv run imagedown --helpRequires Python 3.10+. Dependencies are Pillow, NumPy and click.
Effects are sub-commands and are applied left to right.
# Fax look: 204x98 dpi, 1-bit, horizontal streaks, slight skew
imagedown scan.png fax
# -> scan_degraded.png
# Halve the resolution, crumple, crease, then fax at "fine" resolution
imagedown scan.png -o out.png --seed 1 lowres --scale 0.5 wrinkle --strength 15 fold --count 1 fax --mode fine
# Process a whole directory
imagedown scans/ -o degraded/ preset crumpled-fax
# Generate every preset (handy for building an OCR test set)
imagedown scan.png --suite variants/
imagedown --list-presets--seed makes the random effects (noise, lighting, fax, wrinkle, fold)
reproducible. Each of those also accepts its own --seed to override the base seed.
| Command | What it does | Main options |
|---|---|---|
lowres |
Downscale, then upscale back (resolution loss) | --scale, --resample nearest|bilinear|bicubic |
blur |
Gaussian blur (out of focus) | --radius |
noise |
Gaussian noise plus salt-and-pepper speckle | --amount, --salt-pepper |
jpeg |
Low-quality JPEG re-encoding (block artifacts) | --quality, --passes |
rotate |
Small rotation (skewed feed) | --angle |
ink |
Ink bleed (positive spread), thinning (negative), or fading | --spread, --fade, --threshold |
lighting |
Uneven illumination and vignette (phone photo) | --strength, --cells, --vignette |
fax |
Fax transmission: 204x98/196 dpi, binarization, streaks, dropped lines, skew, speckle | --mode standard|fine|superfine, --source-dpi, --threshold, --dither, --streaks, --dropouts, --skew, --speckle |
wrinkle |
Crumple part of the page (warp + shading) | --strength, --cells, --coverage, --light |
fold |
Sharp fold lines; text near the crease gets squeezed and broken | --count, --depth, --width, --light |
preset |
Apply a named recipe (see --list-presets) |
NAME |
Run imagedown <command> --help for the full option list of any effect.
A standard ITU-T T.4 fax scans at 204 dpi horizontally and 98 dpi vertically
(196 dpi in fine mode) and binarizes the page to 1 bit. Pass the assumed
resolution of your input with --source-dpi; the image is downscaled
anisotropically by that ratio, thresholded (or dithered with --dither),
decorated with streaks and dropped lines in fax pixel space, then scaled back
with nearest-neighbour so the receiving machine's blocky dots are preserved.
Both build a height map of the paper surface: ridged multi-octave noise for
wrinkle, exponential ridges along random lines for fold. The gradient of
that map becomes a displacement field that re-samples the image (squeezing and
stretching glyphs), and the slope under a random light direction produces
Lambert-style shading so raised paper is lit and valleys fall into shadow.
--coverage limits wrinkle to a soft random blob, leaving the rest of the
document readable.
uv run pytest
uv run python scripts/make_sample.py samples/sample.png # synthetic invoice for trying things out
uv run imagedown samples/sample.png --suite samples/suite --seed 1
uv run python scripts/contact_sheet.py samples/suite samples/contact_sheet.pngMIT License. See LICENSE.






