DEV Community

AsyncMonk
AsyncMonk

Posted on

OCR said done and returned half the page, so count lines before trusting it

I keep circling the idea of adding "upload a scanned PDF, get the text back" to one of my small tool sites, and the failure that worries me is not a crash. A crash shows up in the logs. What worries me is an OCR step that finishes cleanly and hands back text with a chunk of the page missing. On 2026-11-04 I ran a small test that produced exactly that: a scanned Chinese page tilted by 5° went through a browser-side OCR engine, roughly half of its 14 lines came back, and the status still said processing was complete. No warning, no error.

The setup was cheap on purpose. Two public-domain pages went in as image-only PDFs: the 1986 note to the Chinese simplified character list, a government document on the Internet Archive (557 characters), and page 8 of H. G. Wells' The Time Machine, 1895 edition, from Wikimedia Commons (1,002 characters). The engines were the text recognition built into macOS (the Vision framework, accurate mode), Tesseract 5.5.3 with English only, since that machine had no Chinese pack, and a browser-side PP-OCR engine: the PDF content extraction in ImgIng https://imging.ai/ , on a Mac with an Apple M4 in a desktop browser. Errors are character-level edit distance, so one extra, missing or wrong character counts as one. I also counted missing characters ignoring order. These are single runs on two clean single-column pages, with tilt and noise added by a script, so they say nothing about any engine's accuracy in general.

Straight pages were fine and tilted ones were not

On the straight Chinese page macOS made 5 errors and the browser engine on its default tier made 2. On English, macOS made 0, the browser engine 1 (the page number) and Tesseract 11. At 1° the Chinese page still came back with 1 error. From 3° the line order got scrambled and two Chinese lines were gone. At 5° the Chinese output was missing 275 of 557 characters and the English one 115 of 1,002. Over 0 to 5°, macOS stayed between 4 and 8 errors on Chinese. When I denoised, straightened and binarized the noisy 3° page before feeding it in, the browser engine was back to 2 errors on both pages. The engine handles a straight page well; nothing tells you when the page isn't straight.

Wrong output still looks like output

Half a page of clean text reads fine, so neither a user skimming it nor a search index has a reason to complain. Other failures in the test had the same shape. On its largest model tier the browser engine repeated a chunk inside 13 of the 14 Chinese lines (114 edits, 113 extra characters), and the default tier did better on this page. The English page at 5° with only a 3×3 median filter was later rerun with a second page-segmentation setting: Tesseract's default layout analysis had cut the page into 64 non-empty lines instead of 28 and scored 34.03%, while --psm 6 on the same input scored 96.51%. That was a layout split, not the filter. The macOS fast mode produced 526 edits on the 557-character Chinese page, my guess being that the mode doesn't handle Chinese. None of these raised an error.

Cheap checks before trusting the text

User uploads have no ground truth, so each check compares the output with something the page image or the request gives you. The first is a line count: count horizontal ink bands with a row projection and compare with non-empty output lines. Both bad cases are far off, about half of 14 in one and 64 against 28 in the other. I haven't built this, and it's the part I trust least, because tables, two columns and running headers will confuse a band count and both test pages were one column.

A rough characters-per-line figure for the language should catch that repetition, and so should looking for any run of eight or more characters that appears twice in one line. A script check is even cheaper: if the user picked Chinese and most of the output isn't CJK, the fast-mode result would get flagged. I haven't run either of these against the outputs yet.

One check runs before OCR. On the 3° page a projection-based skew estimate came back as 3.3°. The browser engine was fine at 1° and broke at 3°, so above 1° I'd rotate first. It fixes the problem instead of only flagging it, which makes it the one I'd ship first.

None of these counts sees order. On the English page at 3° almost every character survived, but each paragraph's first line landed after the line that should follow it. Comparing the output's first words with a quick OCR of the top band is the closest idea I have, and I don't know yet whether it's reliable enough to be worth the extra pass.

Top comments (0)