Turn photos of homework / test pages into clean, grouped, print-ready A4 documents. Built for the recurring "my kid's homework was shared as WhatsApp photos" workflow — especially Romanian 4th-grade material.
It leans on the OCR engine that already ships with Windows (Windows.Media.Ocr
via winsdk), so there is no Tesseract /
external binary to install. On top of that it adds deskewing, upscaled region
re-OCR and an A4 print builder (markdown → HTML → PDF via headless Edge/Chrome).
Why this exists: the AI assistant that drove this work cannot view images. Reading the photos therefore had to go through an OS OCR step. This repo keeps that whole pipeline reusable.
- Windows OCR, not Tesseract.
winsdkgivesro-ROanden-USrecognisers out of the box.owl/tesseractinstalls on Windows are a pain; this just works. - Deskew first. Phone photos are rotated a few degrees; Hough-line deskew markedly improves line reconstruction.
- Region re-OCR. When an option/answer isn't read, crop that fraction of the page and try 4 preprocessing variants × 2 languages.
- One file per test, then one combined print file. Markdown is the source of truth; the HTML/PDF are generated.
- Print is the easy path; handwriting needs a fallback.
Windows.Media.Ocrrecognizes print only, and the offline handwriting options tried (TrOCR, EasyOCR) were not good enough on cursive.hwocr hwris the escape hatch: it sends a cropped, upscaled photo to a cloud vision model (OpenRouter) and asks for a verbatim transcript. It is optional — it needs network + a key. When a single zoom level comes back empty, pass--zooms <px...>(and optionally--models <ids...>) to walk a zoom ladder and stop at the first non-empty transcript.
- Windows 10/11
- Python 3.9–3.12 (tested on 3.11)
- The Romanian language pack (for
ro-ROOCR). If missing: Settings → Time & Language → Language & region → add Română → Language options → Optical character recognition. - Optional, for handwriting (
hwocr hwr): an internet connection and an OpenRouter API key. No extra Python dependency — it usesurllib.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
pip install -e .
# or, to get the `hwocr` command on PATH:
pip install -e .
# If you're in Git Bash/cmder (bash), you can also use the launcher:
./scripts/hwocr.sh --help# 0) what OCR languages does this machine have?
python -m hwocr langs
# 1) OCR a whole folder (deskew + upscale), save one .txt per photo
python -m hwocr ocr "D:\poze\tema" --out "D:\poze\tema\_ocr"
# same, with coordinates and layout JSON (helps locate a bad line)
python -m hwocr ocr "D:\poze\tema" --out "D:\poze\tema\_ocr" --boxes --json
# 2) re-read one stubborn region (fractions of the page: left top right bottom)
python -m hwocr region "D:\poze\tema\pag63.jpg" -r 0.05 0.20 0.60 0.30 --factor 5
# 2b) embed the exact unreadable regions as images (from a crops.json map)
python -m hwocr crops "D:\poze\tema\crops.json" --out "D:\poze\tema"
# 2c) HANDWRITING? Windows OCR is print-only -> use a (free) vision model.
# crop to the ink first, then rotate/upscale as needed
python -m hwocr hwr "D:\poze\tema\manuscris.jpg" --rotate 90 --out "D:\poze\tema\_hwr"
python -m hwocr hwr --list-models # current free vision models (no key needed)
# retry at increasing zoom until the model returns text (first non-empty wins):
python -m hwocr hwr "D:\poze\tema\tabla.jpg" --zooms 1400 2000 2600 3400
# ... or try several models in order: --models idA idB ...
# (Bash/Git Bash/cmder) equivalent:
# ./scripts/hwocr.sh hwr "D:/poze/tema/manuscris.jpg" --rotate 90 --out "D:/poze/tema/_hwr"
# 3) build the printable bundle from the hand-cleaned markdown
python -m hwocr build TEST_1.md TEST_2.md TEST_3.md --out TOATE_TESTELE_print
# -> TOATE_TESTELE_print.md / .html / .pdf (A4, one test per page)
# (if none of --md/--html/--pdf are given, all three are written by default)build defaults to writing markdown + HTML + PDF. Add --md, --html
and/or --pdf to pick specific outputs. The PDF export reuses Edge or Chrome
in headless mode and stamps "Pagina X din Y" at the bottom of every page
(pass --no-page-numbers to skip that).
hwocr ocr <photos> --out <dir>— get a first raw pass.- Read the
.txts, fix the obvious character errors, group them by test (not by photo), and write one cleanTEST_*.mdper test. - For anything unreadable,
hwocr region ...on that fraction of the page. hwocr crops crops.json --out .— cut the unreadable strips intocrops/*.pngand reference them from the md () next to the warning text.hwocr build ...— one combined A4 PDF to print.
See docs/WORKFLOW.md for the field notes and the exact
conventions that worked, and examples/ for a skeleton.
hwocr/
engine.py # Windows.Media.Ocr + deskew + region re-OCR (print)
handwriting.py # `hwr`: handwriting via an optional cloud vision model
build.py # markdown -> A4 markdown/html/pdf
crops.py # `crops`: cut unreadable regions from a JSON map
cli.py # `hwocr` command line
docs/
WORKFLOW.md # how to actually do a session, plus gotchas
HANDWRITING.md # handwriting findings + the OCR experiments behind them
- OCR is not perfect on perspective-distorted or low-light photos. Anything
uncertain should be flagged in the output (the generated sheets use an
asterisk
*+ a legend rather than silently guessing), and such warnings are rendered at 1.25× size, bold + red so they are easy to spot and correct. The unreadable photo strip itself can be embedded next to the warning as an image viahwocr crops+ aline in the markdown. - Handwriting goes to the cloud.
hwocr hwruploads the (cropped) image to OpenRouter, so don't use it on material you may not send off — and keep the API key out of the repo (seedocs/HANDWRITING.md). - Only the tooling is meant to be committed here. Do not commit copyrighted
textbook/homework content — keep generated sheets out of the repo
(see
.gitignore).
MIT — see LICENSE.