Desktop · 2026
PixelOCR
Turns a phone into an overhead book scanner, driven from Windows over USB.
4 Components
Desktop
2026
Key decisions
- The transport is wired USB ADB — scrcpy mirrors the phone while adb forward tunnels the companion WebSocket — so there are no dropped frames and no battery anxiety through a 400-page book.
- Capture locks exposure and focus, because auto-exposure drifting between page 40 and page 41 is what ruins a batch.
- The dewarper fits cubic curves to text baselines instead of shipping a model-based dewarping network, because it recovers most of the OCR loss on lightly curled pages with no GPU and no model weights in the base install.
- OCR stays a separate FastAPI sidecar with Tesseract as the default engine and Surya or PaddleOCR as optional extras, so heavyweight ML dependencies never have to be linked into the Windows app.
The cheapest good scanner you already own
Flatbed scanners are slow and cruel to bindings. Document cameras are expensive. A modern phone has a better sensor than either, mounted overhead — and no software that treats it as a scanner rather than a camera roll.
PixelOCR is that software, in three runtime parts that each do one job, plus a PowerShell harness that proves them on real hardware.
The transport
The primary operator path is USB ADB. scrcpy mirrors and controls the
phone, and the WinUI host embeds that mirror as a Win32 child window, so the
operator frames, focuses and triggers from the machine they are already
working at, watching a live preview at full size rather than crouching over a
handset. adb forward tunnels the companion WebSocket between the two:
the Android app binds a Ktor server to 127.0.0.1:38473, the host forwards a
local port to it, and the message schema is versioned in
schemas/companion-protocol.schema.json with protocol tests on both ends.
Wired means no dropped frames and no battery anxiety through a 400-page book.
The three components
android/ — Kotlin, Jetpack Compose and CameraX. The CameraController
locks exposure and focus, because auto-exposure drifting between page 40 and
page 41 is what ruins a batch, and a Camera2 RAW_SENSOR path writes DNG when
the sensor allows it. A PerceptionFrameAnalyzer and an ML Kit on-device OCR
preview feed a readiness band in the viewfinder, so framing and legibility
problems surface before capture rather than after a 400-page run.
pc-client/ — C# on .NET 8. PixelOCR.WinUI is the primary host (Windows
App SDK 2.1, Win2D, CommunityToolkit MVVM), PixelOCR.Presentation holds the
UI-neutral MVVM state, and PixelOCR.Core is headless: AdbService, the
companion client, the sidecar manager, and the project model. The older WPF
host stays buildable as a migration safety rail. This is where the operator
actually lives.
ocr-sidecar/ — Python 3.11 and FastAPI, a seven-stage pipeline:
preprocess (page-quad detection, dual-page split, page-curl dewarp, deskew,
illumination normalization), language autodetect, OCR, layout analysis so
columns, headers and footnotes survive as structure rather than collapsing
into a single stream of text, text normalization, optional LLM cleanup, and
export to searchable PDF, Markdown or plain text. Tesseract is the default
engine; Surya and PaddleOCR are install-time extras, as is the LLM cleanup.
Keeping the sidecar a separate service means the Python ML stack never has to
be linked into the Windows app, and the pipeline can be restarted or swapped
without touching the host. The fourth component is the harness: 25 PowerShell
scripts under scripts/ run the verification and release gates — device smoke
tests over USB, WinUI smoke evidence, release readiness and sign-off — so a
release is something the machines record, not something the operator asserts.