Spaces:
Build error
title: PageParse
emoji: π
colorFrom: blue
colorTo: green
sdk: docker
app_port: 7860
PageParse
Any handwritten page β clean structured data. On CPU. Fully offline.
PageParse reads a scanned or photographed page of handwritten notes entirely on your own device and turns it into structured, schema-validated records in a local database β no GPU, no CUDA, no cloud. Point it at a handwritten page; it cleans the image, recognises the ink on CPU, maps the messy text to a defined JSON schema, and saves it to SQLite. It keeps working with the Wi-Fi switched off.
Built for The CPU-First Hackathon β "Build AI that runs anywhere."
Table of Contents
- The Idea
- Why It Matters
- How It Works
- Architecture
- Model & Runtime (CPU-first)
- Output Schema
- Data Model (SQLite)
- Tech Stack
- Project Structure
- Getting Started
- Usage
- Offline-First by Design
- Quality Gates (CI/CD)
- Judging Criteria Mapping
- Roadmap
- Team & Work Division
- Contributing
- License
The Idea
People still write on paper β to-do lists, planners, field surveys, recipe cards, clinic notes. That ink almost never makes it into a system you can search, sort, or query. PageParse bridges paper and database, entirely on-device.
Drop in a scan or photo of a handwritten page. PageParse:
- reads the handwriting on CPU,
- maps it to a defined, relational schema using a small local language model, and
- stores clean structured rows in SQLite you can query and export β
all while completely air-gapped.
The reference build is anchored to one note type β a handwritten to-do / planner page β because the schema is tight and objectively measurable. The same engine generalises to survey forms, recipe cards, and meeting notes with only a new schema and grammar.
Why It Matters
GPUs are scarce, costly, and online. Most computing isn't. PageParse is a demonstration that the CPU is enough, and that a genuinely useful app keeps working when the network doesn't β in clinics, fields, classrooms, and anywhere connectivity is unreliable.
It also tackles the hackathon's core mission head-on: handwriting is about as unstructured as input gets, and PageParse maps it to a strict, structured schema. Handwriting recognition is a hard, real problem β and that difficulty is exactly what makes the result worth scoring.
How It Works
PageParse follows the required Ingestion β Processing β Transformation β Storage workflow, with every inference step on CPU and offline.
| Stage | What happens | Runs on |
|---|---|---|
| 1. Ingestion | Load a handwritten page (JPG / PNG / PDF). | local |
| 2. Processing | OpenCV cleanup: grayscale, deskew, adaptive threshold, denoise. | CPU |
| 3. Recognition | On-device OCR converts ink β raw text (handwriting tier + printed fallback). | CPU |
| 4. Transformation | A small language model maps raw text β JSON, grammar-constrained to the schema. | CPU |
| 5. Storage | Validated rows persisted to SQLite; optional local semantic search. | local |
Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββ
β PageParse β
β (100% on-device, CPU) β
ββββββββββββββββββββββββββββββββββββββββββββββββ
scan / photo OpenCV ONNX / Tesseract llama.cpp + GBNF SQLite
JPG Β· PNG Β· PDF ββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββ
ββββββββββββββββΆβ IngestionββββββββΆβ Preprocessing ββββΆβ OCR (on CPU) ββββΆβ TransformββββΆβ Storage β
β β clean β deskewΒ·binarizeβ β TrOCR / Surya /β β SLMβJSON β β + vec β
ββββββββββββ denoiseββββββββββββββββββ β Tesseract β β validatedβ ββββββββββββ
ββββββββββββββββββ ββββββββββββββββββ
β
βββββββββββΌββββββββββ
β query Β· search β
β export Β· review β
βββββββββββββββββββββ
Model & Runtime (CPU-first)
Declared explicitly per the rules. No GPU or CUDA anywhere in the inference path. All weights are fetched once and cached locally, so runtime needs no network.
| Stage | Model | Runtime | Precision |
|---|---|---|---|
| Handwriting OCR | TrOCR-small (handwritten) / Surya OCR 2 | ONNX Runtime (CPU EP) | INT8 |
| Printed fallback | Tesseract LSTM | Tesseract (CPU) | β |
| Text β schema (SLM) | Qwen2.5-1.5B-Instruct | llama.cpp | Q4_K_M |
| Semantic search | all-MiniLM-L6-v2 | ONNX Runtime (CPU EP) | INT8 |
Schema validity is guaranteed by llama.cpp GBNF grammar-constrained decoding: the model is structurally prevented from emitting anything that is not valid against the PageParse schema, then re-validated with Pydantic.
Output Schema
The structured target that unstructured handwriting is mapped to:
{
"source_page": "string (filename)",
"captured_date": "YYYY-MM-DD",
"tasks": [
{
"task": "string",
"due_date": "YYYY-MM-DD | null",
"priority": "high | medium | low",
"category": "string | null",
"status": "todo | done",
"ocr_confidence": 0.0
}
]
}
Data Model (SQLite)
CREATE TABLE pages (
id INTEGER PRIMARY KEY,
filename TEXT,
captured_date TEXT,
raw_ocr_text TEXT,
created_at TEXT
);
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
page_id INTEGER REFERENCES pages(id),
task TEXT NOT NULL,
due_date TEXT,
priority TEXT CHECK (priority IN ('high', 'medium', 'low')),
category TEXT,
status TEXT DEFAULT 'todo',
ocr_confidence REAL
);
Tech Stack
Language & core
- Python 3.11
Computer vision / OCR
- OpenCV β image preprocessing
- ONNX Runtime (CPU EP) β TrOCR / Surya handwriting OCR
- Tesseract β printed-text fallback
AI / structuring
- llama.cpp (
llama-cpp-python) β Qwen2.5-1.5B Q4_K_M GGUF inference - GBNF grammar β guaranteed-valid JSON
- Pydantic β schema validation
Storage & retrieval
- SQLite β structured persistence
- sqlite-vec + all-MiniLM-L6-v2 (ONNX) β optional semantic search
Interface
- Typer β CLI (primary)
- FastAPI + PWA β web interface
Observability
- psutil β live CPU & memory telemetry
Tooling & quality
- Ruff, mypy, Bandit, pip-audit, detect-secrets, pytest, gitlint, yamllint, markdownlint, REUSE
- pre-commit, GitLab CI (self-hosted Docker runner)
License
- AGPL-3.0-or-later
Project Structure
pageparse/
βββ src/
β βββ pageparse/
β βββ __init__.py
β βββ ingest.py # load image / PDF input
β βββ preprocess.py # OpenCV cleanup pipeline
β βββ ocr/
β β βββ __init__.py
β β βββ handwriting.py # TrOCR / Surya (ONNX)
β β βββ printed.py # Tesseract fallback
β βββ extract.py # SLM + GBNF β JSON
β βββ schema.py # Pydantic models
β βββ store.py # SQLite persistence
β βββ search.py # optional semantic search
β βββ telemetry.py # psutil CPU/RAM panel
β βββ config.py # paths, model settings, airgap flag
β βββ cli.py # Typer CLI entry point
β βββ web.py # FastAPI + PWA
βββ grammars/
β βββ task.gbnf # schema-constraining grammar
βββ models/ # bundled GGUF / ONNX weights (gitignored)
βββ scripts/
β βββ fetch_models.py # one-time online model download
β βββ init_db.py # initialise SQLite
βββ samples/ # real handwritten demo pages
βββ tests/
β βββ test_preprocess.py
β βββ test_extract.py
β βββ test_schema.py
β βββ test_store.py
βββ web/
β βββ static/ # PWA assets, service worker
β βββ templates/
βββ docs/
β βββ architecture.md
βββ .gitlab-ci.yml
βββ .pre-commit-config.yaml
βββ .gitignore
βββ .secrets.baseline
βββ pyproject.toml
βββ CONTRIBUTING.md
βββ CHANGELOG.md
βββ LICENSE # AGPL-3.0
βββ README.md
Getting Started
Prerequisites
- Python 3.11+
- Tesseract OCR β
apt install tesseract-ocr/brew install tesseract - ~3 GB free disk for bundled models
Installation
# Clone
git clone https://code.swecha.org/centurions/pageparse.git
cd pageparse
# Environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install (with dev tooling)
pip install -e ".[dev]"
# Fetch models ONCE while online β cached locally thereafter
python scripts/fetch_models.py
# Initialise the local database
python scripts/init_db.py
After fetch_models.py runs once, disconnect from the network entirely β
everything below works offline.
Usage
CLI
# Process a single handwritten page
pageparse process samples/todo_page_01.jpg
# Process a whole folder
pageparse process samples/ --batch
# Force air-gapped mode (any outbound call fails loudly)
pageparse process samples/todo_page_01.jpg --airgap
# Query and search
pageparse list --priority high
pageparse search "supplier"
# Export
pageparse export --format json > out.json
Web (PWA)
pageparse serve # http://localhost:8000
Upload a page, watch the queued β processing β saved status, and view the structured result. Installable as a PWA for offline use.
Offline-First by Design
The core feature works with no cloud calls, and the demo proves it:
- Air-gap toggle β a UI switch and
--airgapflag assert zero network access; any accidental outbound call fails loudly rather than silently. - Status badge β every page shows
queued β processing β saved, making caching and graceful handling observable. - Graceful degradation β low-confidence OCR lines are flagged for review instead of crashing; pages queue and process as the CPU frees up.
- Live resource panel β a psutil readout of CPU % and RAM during inference, so the footprint is measured, not guessed.
Demo procedure: enable airplane mode / pull the Ethernet, then run a full ingestion β extraction β storage cycle on a real handwritten page.
Quality Gates (CI/CD)
All checks run locally via pre-commit and in CI on a self-hosted GitLab
runner (Docker executor). These are real checks β no stub jobs, no exit 0.
| # | Check | Tool |
|---|---|---|
| 1 | Lint | Ruff |
| 2 | Format | Ruff format |
| 3 | Type-check | mypy |
| 4 | Security (SAST) | Bandit |
| 5 | Dependency CVEs | pip-audit |
| 6 | Secret scan | detect-secrets |
| 7 | Unit tests | pytest |
| 8 | Semantic commits | gitlint |
| 9 | CI YAML lint | yamllint |
| 10 | Docs lint | markdownlint |
| 11 | License compliance | REUSE |
| 12 | Smoke build | app boots headless |
pre-commit run --all-files
pytest
Conventional Commits are enforced (feat:, fix:, chore:, β¦).
Judging Criteria Mapping
| Criterion | How PageParse addresses it |
|---|---|
| Model performance | OCR confidence + extraction accuracy reported on real pages. |
| Resource efficiency | Live psutil CPU/RAM panel during inference. |
| Offline resiliency | Air-gap toggle; full cycle demoed with the network off. |
| Schema alignment | GBNF grammar guarantees valid JSON every run. |
| Graceful handling | Queued processing + flagged low-confidence lines + caching. |
Roadmap
- MVP: scanned to-do page β validated JSON β SQLite (offline, CPU)
- Printed-text fallback path
- Web PWA with air-gap toggle and status badge
- Live CPU/RAM telemetry panel
- Semantic search over extracted notes (
sqlite-vec) - Additional note types: survey forms, recipe cards, meeting notes
- Indic-script handwriting support
Contributing
See CONTRIBUTING.md. Branch from main, keep commits
Conventional, ensure pre-commit run --all-files and pytest pass, then open a
merge request. All contributions are licensed under AGPL-3.0-or-later.
License
Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE.
AGPL is chosen deliberately: as strong copyleft, it closes the network/SaaS loophole that plain GPL leaves open, ensuring anyone who runs a modified PageParse as a network service must also share their source.