Repository navigation
Recognize text in scans and pictures with Tesseract (OCR, #288) - #429
Conversation
…ress Engine package, language downloads, OCR routes, PDF reader OCR layer, tests. Still to do: preset editor UI, translations, Searchable PDF preset, docs.
The preset editor gets Recognize text (OCR) with a language list for PDF, DOCX, TXT, Markdown and HWPX targets, in all 12 UI languages. Settings schema 33 adds the Searchable PDF preset after Compress PDF. ENGINES.md, THIRD-PARTY-NOTICES.md and the README privacy policy mention Tesseract and its language downloads.
|
@cre8ive-marketing This is a really thoughtful Tesseract integration, especially the handling of mixed born-digital/scanned PDFs and on-demand language data. One OCR backend worth considering as a follow-up is Indra OCR (model, announcement). It is CPU-only, uses ONNX Runtime rather than PyTorch, and can return text/Markdown plus line and block bounding boxes, layout labels, confidence, and reading order. That structured JSON could be especially useful for Filee's editable DOCX/Markdown/HWPX routes rather than treating OCR as plain text only. According to Datalab's current published benchmarks, it reaches about 44 pages/s on 32 CPU cores (~15× Tesseract and ~10× PaddleOCR on their setup), with a 46.7 OmniParseBench text score versus 35.8 for PaddleOCR and 19.3 for Tesseract. The main Latin recognizer is roughly 1.25M parameters; the complete default model set is 7.3M parameters and downloads about 76 MB. I would probably keep Tesseract as the broad, mature baseline in this PR and consider Indra as an optional selectable engine afterward. There are a few packaging/licensing details to resolve first: it currently requires Python 3.10+ plus ONNX Runtime/OpenCV/pypdfium2, its documented tested platforms do not include Windows yet, and the model weights use a modified OpenRAIL-M license with commercial restrictions (the code itself is Apache-2.0). If those constraints fit Filee, it looks like a very promising local CPU backend. |
|
@cre8ive-marketing @KnifeLemon One technical reason Indra could be especially interesting for Filee is that it is not only a faster text recognizer: it already produces a structured page model. That could let Filee create genuinely useful
A possible integration boundary could be: That intermediate One honest limitation to expose in the UI/docs: Indra says simple grid tables work well, but complex tables with merged cells, nested headers or wrapped cell text can still be reconstructed incorrectly. Even with that caveat, its reading order and semantic blocks could make PDF → Markdown one of Filee's strongest conversion paths. |
|
@Perricheno Thank you for the detailed write-up. The structured output (blocks, reading order, confidence) is exactly what an editable DOCX needs, so it's a useful pointer. For Filee, though, I'll keep Tesseract as the only OCR engine, for now and as the default later. Filee's aim is to stay small and to run on every PC, low-end ones included. Indra doesn't fit that yet, for these reasons from your notes:
This PR is built with low-end machines in mind:
If Indra later ships a plain ONNX model with a permissive license that can run from .NET without Python, it would be worth another look as an optional engine. |
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks @cre8ive-marketing, this is thorough: OCR is off by default, Tesseract is offered rather than suggested, the NSIS installer is unpacked and never run, every language is pinned with a hash and downloaded only when used, and pages that already have text stay as they are. A few technical points while we settle how this fits with #372:
- Memory and disk on big scans. All pages are rendered to 300 dpi PNGs before the first Tesseract starts, so a 300-page scan puts several GB of PNGs in the work folder. Then
MaxDegreeOfParallelism = ProcessorCountstarts up to 16 or more Tesseract processes on a desktop CPU. Please render and recognize page by page (delete each PNG once its PDF exists) and cap the parallel runs, for example at 4. - Scans with a stamp. A scanned page that carries a little real text (a page number or a Bates stamp added by the scanner software) counts as a page with text and is skipped. A text-coverage threshold would catch those; fine as a follow-up.
- The README privacy line is changed in English only. We'll do the other eleven READMEs when merging.
- Settings schema 33 is taken by earlier PRs; we'll renumber when merging.
CI for fork runs is waiting for approval; we'll start it.
A page is rendered only when one of at most four runs (fewer on PCs with fewer cores) is free, and its picture is deleted once its PDF exists, so a long scan never has more than four 300 dpi pictures in the work folder.
|
@KnifeLemon Thanks for the careful review, and for the note about #372. Point 1 is fixed in 53f7384:
|
# Conflicts: # docs/ENGINES.md # src/Filee.App/Assets/i18n/ar.json # src/Filee.App/Assets/i18n/en.json # src/Filee.App/Assets/i18n/es.json # src/Filee.App/Assets/i18n/fr.json # src/Filee.App/Assets/i18n/hi.json # src/Filee.App/Assets/i18n/ja.json # src/Filee.App/Assets/i18n/ko.json # src/Filee.App/Assets/i18n/pl.json # src/Filee.App/Assets/i18n/pt-BR.json # src/Filee.App/Assets/i18n/ru.json # src/Filee.App/Assets/i18n/zh-CN.json # src/Filee.App/Assets/i18n/zh-TW.json # src/Filee.Core/Presets/Preset.cs # src/Filee.Core/Settings/AppSettings.cs # src/Filee.Core/Settings/UserDataStore.cs
|
Merged
|
On the macOS arm64 CI runner the compactor task could start only after the workers had finished, so compactions stayed 0 and the test failed although nothing crashed. The loop now runs at least once.
|
The macOS arm64 check failed in |
|
@cre8ive-marketing @smjrifle An update on OCR, since #429 and #372 change the same code and only one approach can go in as it is. We'd like to take #429 as the base: OCR off by default, Tesseract offered with languages downloaded when needed, pages with text left alone and the Searchable PDF preset. Then #372 adds Apple Vision on macOS on top of it, as the engine used there, so Filee gets the macOS built-in recognizer without a separate pipeline. @cre8ive-marketing, I'll go through #429 again now. @smjrifle, I'll comment on #372 with what that means for your branch. |
|
Thanks, that sounds like a good split. I'll keep #429 up to date with |
Review point 2 on KnifeLemon#429. A scanned page whose only text is a little visible text the scanner software added (a page number, a Bates number) counted as a page with text and was skipped. Now a page counts as a scan when a picture covers at least half of it and its visible text covers under 1% of it. An OCR layer (invisible text) always counts as text, so a page recognized before is never recognized again, and a page drawn with little text and no picture under it (a cover, a short note) is left alone.
|
@KnifeLemon Point 2 from your review (scans with a stamp) is now in this PR (e77c457, c7e4071):
All 83 OCR tests pass locally with Tesseract installed. |
main took settings schema 39 for .ps1 (KnifeLemon#474), so the Searchable PDF step moves to v40 (CurrentSchemaVersion = 40); its test upgrades a version 39 library. README: main's one-line engines entry, with the OCR languages.
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks @cre8ive-marketing. Point 1 from last time is done well: a page is rendered only when a run is free, at most four runs go side by side with one thread each, every PNG is deleted once its PDF exists, and a failed page stops the rendering after the running pages end. The stamp rule in e77c457 is a good answer to point 2 too: an OCR layer always counts as text, and a picture over half the page with under 1 % visible text counts as a scan. The merge with main is clean (schema 39, all 14 language files), and the NativeMemoryStressTests fix is right; main has the same flaky loop.
I built c7e4071 here: Core 507/510 (3 skipped as usual), Engines 1610/1621 on a552a14 (8 skipped; the 3 failures are this PC's LibreOffice/H2Orestart timeout, a calibre e-book PDF and a tracing timing test, none touched here), and on c7e4071 the OCR, installer and stress tests 83/99 (the 16 skipped need Tesseract or FFmpeg live-photo tools). The App tests that cover this (preset editor, availability, localization, renders in every language) pass. CI is green on every job, but the runners have no Tesseract, so the real recognition tests are skipped there; we'll run them once with fetch-engines.ps1 -Only 7zip,tesseract before merging.
Two things before we merge:
- The preset's other PDF options are dropped when OCR is on. With "Recognize text" the route to PDF ends at Tesseract (
PlanWithOcradds nothing when the target is PDF), so a PDF → PDF preset that also compresses, writes one file per page or removes metadata silently does none of it.RecognizePdfAsynceven copies Title, Author, Subject and Keywords into the new file when "Remove metadata" is ticked, and the "every page already has text" shortcut copies the file as it is. For pictures, the page size and margin are ignored too (Tesseract makes the page the picture's size). The editor shows all of these next to the OCR box. The simplest fix is to make them exclusive: hide compression, one file per page, "Remove metadata", page size and margin while "Recognize text" is ticked, and clear them inApplywhen OCR is on (plus: don't copy the document information whenRemoveMetadatais set). Running main's PDF → PDF step after the OCR step works too, if the page range is applied in only one of the two steps. A test with OCR + "Remove metadata" and OCR + one file per page would keep it that way. - Paths with non-ASCII characters on Windows. Tesseract gets full paths for the picture, the work folder and
--tessdata-dir. The work folder is under%TEMP%, which contains the user name, and pictures are passed from where they are (스캔.jpginC:\Users\김민수\Documents). Some Windows builds of Tesseract and Leptonica open files through the ANSI code page and fail on such paths. I couldn't check it here (no Tesseract on this PC). Could you try a picture and a scanned PDF with Korean or Cyrillic names, in a folder with such a name? If it fails, running Tesseract withworkingDirectory: scratch(ProcessRunner takes it) and relative names (copy the picture into the scratch folder first,--tessdata-dir tessdata) avoids it whatever the user name is.
Smaller, fine as follow-ups:
- A born-digital page with a full-bleed photo and a short caption (a title slide, a photo page in a brochure) now also counts as a scan, so its real text is rasterized. Counting only text near the top or bottom edge as a stamp (where scanners put page and Bates numbers) would keep those pages as they are.
- Pictures are recognized one at a time:
MaxParallelism => 1gates the whole engine and each run has one thread, so 50 photos go strictly one after another. One sharedSemaphoreSlim(Runs)around every Tesseract process, withMaxParallelism => Runs, would keep the cap at four and let pictures run side by side. - When a PDF job is cancelled while pages are being recognized,
free.WaitAsyncthrows andRecognizePdfAsyncleaves without waiting for the running pages, so the queue can start deleting the work folder while Tesseract still writes into it. Atry/finallythat awaitsruns(ignoring their errors) closes that. - Every preset starts with English as the OCR language. Defaulting it to the UI language where tessdata has one (kor, jpn, …) would save most users a step.
On our side at merge time: the Korean strings engine.tesseract.description, presets.ocr_hint and engines.package.tesseract.description go to 해요체 like the rest of ko.json, the OcrPresets() comment says "settings schema 33" (it's 39), and the privacy line goes into README.fr.md, README.pt-BR.md and README.th.md. A correction to my earlier note: only those three translations have the privacy section, not eleven. This PR goes in first of the open ones, so schema 39 stays as it is.
To get this into the next release, we'll push the remaining changes to your branch ourselves today (maintainer edits are on). Have a look at the commits when they land, and tell us if anything looks off.
|
Merged
|
…#429) 1. With "Recognize text" the editor hides one file per page, compression (with removing metadata), page size and margin, and clears them in Apply: Tesseract makes the PDF, so none of them would happen. A scanned PDF with "Remove metadata" no longer gets the source's title, author, subject and keywords. 2. Tesseract runs in the job's scratch folder and sees only plain relative names there (the picture is copied in as picture.<ext>, the language in tessdata), so a user name or folder in Korean or Cyrillic can't trip a build that opens files through the ANSI code page. - A cancelled or failed PDF waits for the running pages before the work folder can be removed. - The OcrPresets() comment names schema 40.
|
Thanks @KnifeLemon. Both points are in 327224b (and 6dab0c0, format only), so they won't clash with what you push:
From the smaller list:
The other smaller items are still open: stamps only near the edges, pictures running side by side, and the UI language as the default. Schema: #474 ( Tests: all 85 OCR tests pass here with Tesseract installed, along with the preset editor tests. The full suite on the merge commit passed: Core 511, Platform 48, CLI 27, Setup 149, Engines 1619 and App 852, none failed. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…otes - ko: engine.tesseract.description, presets.ocr_hint and engines.package.tesseract.description in the polite style of the rest of ko.json. - README.fr, README.pt-BR and README.th (the translations with the privacy section): the OCR languages pinned in ocr-languages.json, as README.md has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#429 added the Searchable PDF preset as migration 40. Both migrations stay; the PNG/JPEG download aliases become 41 and their tests start from 40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ne.json and th.json: keep both the Replace-the-original strings and the OCR notice from KnifeLemon#429. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main took OCR (KnifeLemon#429), which touches the same PDF options: - Preset.cs: both PdfOptions groups kept (watermark, page numbers, password next to Ocr, OcrLanguage, OcrTargets). - PresetEditorViewModel: both kept in the constructor, the properties and Apply (finishing, then OCR clearing what it doesn't do). - RenderTests: main's file with the two PDF finishing editor tests added back before the OCR one.
KnifeLemon#429 (Tesseract, languages downloaded when used, "Searchable PDF") is the OCR base now, so this branch keeps only the Apple Vision part and plugs it into KnifeLemon#429's OCR step. The own OCR layer (IOcrEngine, OcrEngineRegistry, ImageOcrConverter, TesseractOcrEngine, the Document OCR option, schema 33) is gone. - VisionConverter: the same OCR edges as TesseractConverter (pictures and PDFs -> searchable PDF), registered before it, so on a Mac the route planner takes Vision; unavailable on Windows and Linux. A preset language Vision doesn't read goes to Tesseract when it is installed, and is refused with error.ocr_needs_tesseract otherwise. - AppleVision: the Objective-C runtime bridge from this PR, moved into Filee.Engines: newest revision, languages asked from the Mac, word boxes (boundingBoxForRange), cancel, objc_msgSend_stret on Intel, releases. - OcrPdfWriter: the page picture with invisible text (render mode 3) in a glyphless TrueType font built in code; read like Tesseract's layer. - PDFs share TesseractConverter.RecognizePdfAsync (pages with text copied). - Preset editor: no download size for the languages Vision reads, a macOS hint. - 3 new strings in all 14 languages; docs/ENGINES.md. No settings migration. Co-authored-by: smjrifle <smjrifle@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What does this change?
Adds text recognition (OCR) with Tesseract, as asked in #288 (and #226). Scans and pictures become searchable PDFs, and their text goes into DOCX, TXT, Markdown and HWPX, so a scanned PDF → DOCX gives text you can edit instead of pictures of the pages.
For users
Engine
nsiskind), andTrimTesseractremoves the training programs, manuals and installer files (~160 MB on disk). It is a MinGW build, so it doesn't need the VC++ runtime.apt install tesseract-ocr,brew install tesseract), found like Ghostscript, with the same guidance texts.tessdata_fast4.1.0, pinned inInfrastructure/ocr-languages.json(URL, SHA-256, size), installed byEngineInstaller(newtraineddatakind) intoengines/ocr-languages/<code>.How it works
ConversionEdge.OcrandRoutePlanner.Plan(ocr: true): a route starts with Tesseract's picture/PDF → PDF edge, then takes the cheapest ordinary route from that PDF. A picture Tesseract can't open (HEIC, PSD, …) becomes a PNG first. OCR edges are never part of an ordinary route.Preset.RecognizesTextOfdecides, likeSavesFramesOf; it is used inJobQueue(merges too) andPresetAvailability.TesseractConverterrenders the pages without text at 300 dpi with PDFium. It recognizes several pages at a time withOMP_THREAD_LIMIT=1, then puts the document back together with PDFsharp (pages and document info; bookmarks and form fields don't come along). It passes the job's own copy of the language data with--tessdata-dir, and-c tessedit_create_pdf=1, because thepdfconfig file lives in Tesseract's own tessdata folder.PdfDocumentReader.ReadWithOcrLayer: with OCR on, DOCX, HWPX and Markdown read the invisible layer as the page's text, the scan behind it is left out, and Tesseract's GlyphLessFont is dropped. With OCR off nothing changes: an OCR layer stays invisible and the page stays a picture.Docs:
docs/ENGINES.md(engine table, optional engines, a new "Text recognition (OCR)" section),THIRD-PARTY-NOTICES.md(Tesseract, Leptonica, tessdata_fast), and the README privacy policy, which now names the language downloads. Only the English README is changed; I didn't want to guess the other eleven translations of that line.Settings schema 33, like #351, #393 and #401: this will need renumbering. Happy to do that, or leave it to you.
How was it tested?
dotnet format --verify-no-changesdotnet testwith Tesseract 5.4, FFmpeg 9.0.2 and rhwp 0.8.7: Platform 48, Core 461, Cli 27, Setup 145, Engines 1469, App 807 pass, none fail.OcrRouteTests(Core):RecognizesTextOffor sources and targets.OcrLanguageListTests: all 69 languages pinned (SHA-256, URL) and named; Tesseract offered but not suggested.RenderTests(App): the Searchable PDF preset shows the box and the language list; German into DOCX is saved; a PNG target hides the box and doesn't keep it on.EngineInstallerTests: atraineddatadownload lands in its own folder;TrimTesseractkeepstesseract.exe, the DLLs anddoc/.OcrTests, with real Tesseract 5.4 (skipped when it isn't installed):pwsh build/fetch-engines.ps1 -Only 7zip,tesseractsets up the engine for development.Screenshots (UI changes)
The PDF card of the preset editor gets the "Recognize text (OCR)" box, its hint and, when it's ticked, the language list (from the new render test):
Checklist
src/Filee.App/Assets/i18n/*.jsonfilesTHIRD-PARTY-NOTICES.mdCloses #288.