Skip to main content
HomeProjectsMathscribe

MathScribe — Handwritten Maths to LaTeX in Under a Second

Published May 18, 2026
Updated Jul 30, 2026
1 minutes read

Why I built this

I take a lot of maths notes by hand and then need them in LaTeX. Retyping an integral is the single most annoying tax on writing anything up. So: point the camera at the formula, get the LaTeX, paste it.

Live at yolomolo.vercel.app. Built for AIxYOU's build-a-web-app track and then kept going because I actually use it.

Two tiers

ModeProviderSpeedNotes
/convert (default)DeepInfra olmOCR, server-side key2–6 sLower token budget, no setup
/enhanceYour own key (DeepInfra / Parasail / Cirrascale)5–15 sHigher token budget, key stored in an httpOnly cookie

The default tier exists so the thing is usable by someone who has never held an API key. The enhanced tier exists because my own handwriting is bad enough to need it.

Decisions

Frontend
  • Next.js 14
  • MathJax
  • NextAuth v5
  • getUserMedia
Model
  • olmOCR-2-7B
  • QLoRA fine-tune
  • pix2tex (self-host path)
Backend
  • FastAPI
  • LRU cache
  • Docker

Single-platform on Vercel. Production needs no GPU host. The backend/ FastAPI + pix2tex stack is there for local development and self-hosting, not for the deployed app. Keeping one deployment target is what made this finishable in a weekend.

Auto-capture every two seconds. Not a shutter button. If you have to press something, you have to hold the phone still, and holding the phone still while pressing something is exactly when the frame blurs.

An MD5-keyed LRU cache of the last 64 results. A camera pointed at a static page sends the same frame repeatedly. Hashing before inferring turned a continuous stream of identical calls into one.

Google SSO with an email allowlist. No open registration. I am paying for the inference.

The fine-tune

The QLoRA path is the part I care about most. Base olmOCR handles printed formulae well and gets lost on handwriting with unusual \alpha shapes and my particular way of writing a summation. Fine-tuning on custom math-OCR data closed most of that gap, and gave me the end-to-end loop I actually wanted from this project: collect failures, label, train, evaluate, deploy, watch it fail differently.

That loop — not the app — is the thing I was really building.