Desktop  ·  2026

PixelOCR

Turns a phone into an overhead book scanner, driven from Windows over USB.

4 Components
844 Tests
7 Pipeline stages
3 OCR engines

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.