This guide is the deep reference for integrators, administrators, and developers working with Umi-OCR: how the application is built, how its processes talk to each other, the complete HTTP API and command-line surface, configuration files, security model, and the internal architecture that makes it work. End-user task instructions live in the companion User Guide.
Umi-OCR is a portable, fully offline OCR desktop application built on Python + Qt/QML. It bundles an OCR engine (as a swappable plugin), recognizes images / screenshots / scanned PDFs / QR codes, and exposes its functionality over a local HTTP service that doubles as the cross-process command-line bridge.
1. Executive Summary
| Attribute | Value |
|---|---|
| Project | Umi-OCR |
| Author | hiroi-sora |
| License | MIT |
| Version (this snapshot) | 2.1.5 (main branch) |
| Source commit | 83173ef (2025-11-20) |
| Primary platform | Windows 7 x64+ (Linux maintained separately) |
| UI stack | PySide2 (Qt for Python) + QML |
| HTTP framework | Bottle (vendored) on a WSGI reference server |
| Default service port | 1224 (auto-increments if occupied) |
| OCR engine | External plugin (PaddleOCR-json / RapidOCR) |
| Network posture | Offline; loopback-only by default |
What ships in this repository. This repo contains the application logic: the Python backend (UmiOCR-data/py_src/), the QML frontend (UmiOCR-data/qt_res/), the API documentation (docs/), and the i18n tooling (dev-tools/). It does not contain the Python runtime, Qt libraries, or the OCR engine — those are delivered by the separate Windows and Linux runtime repositories, and the engine comes from the plugins repository.
Note
Because the runtime and engine live in other repositories, you will not find dependency manifests (
requirements.txt,pyproject.toml) here. Third-party library versions are recorded inCHANGE_LOG.mdper release (for example, the v2.1.5 Windows build pinsPyMuPDF 1.24.11,Pillow 10.4.0,pynput 1.8.0,psutil,fontTools 4.56.0,zxing-cpp 2.3.0).
2. Architecture
2.1. High-Level Diagram
┌──────────────────────────────────────────────┐
Umi-OCR.exe ───────► │ main.py (Windows/PyStand env bootstrap) │
(launcher) │ • os.MessageBox last-resort error dialog │
│ • set cwd = UmiOCR-data, add site-packages │
│ • add PySide2 plugin path │
└───────────────────────┬──────────────────────┘
│ from py_src.run import main
▼
┌──────────────────────────────────────────────┐
│ py_src/run.py : main() │
│ • umi_about.init() (version/host info) │
│ • pre_configs.readConfigs() (.pre_settings)│
│ • cmd_client.initCmd() ── single-instance ─┼──► if already running:
│ • runQml() │ POST argv → /argv, exit
└───────────────────────┬──────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ QGuiApplication + QQmlApplicationEngine (qt_res/qml/Main.qml) │
│ │
│ QML UI ◄──── qmlRegisterType ────► Python "Connector" QObjects │
│ (tabs, widgets) TagPageConnector, MissionConnector, │
│ PubSubConnector, KeyMouseConnector, │
│ UtilsConnector, GlobalConfigsConnector, │
│ ThemeConnector, ImageConnector, │
│ PluginsConnector, DocPreviewConnector │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Mission │ │ tbpu │ │ ocr/output │ │ plugins_ctrl │ │
│ │ (task queues) │ │ (layout parse)│ │ (txt/pdf/csv…) │ │ (load engines) │ │
│ └──────┬───────┘ └──────────────┘ └─────────────────┘ └───────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ MissionOCR / MissionDOC / MissionQRCode OCR engine plugin (subproc)│
│ e.g. PaddleOCR-json │
└───────────────────────────────┬───────────────────────────────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ Bottle HTTP service (QThreadPool worker thread) │
│ 127.0.0.1:1224 │
│ /argv /api/ocr /api/qrcode /api/doc/* │
└───────────────────────────────────────────────────────┘
2.2. Startup Sequence
Umi-OCR.exe(PyStand on Windows) sets thePYSTANDenvironment variable and runsUmiOCR-data/main.py.main.pyinitializes the runtime environment: installs actypes-basedos.MessageBoxas the last-resort error reporter, redirects stdout/stderr to the console if present, changes the working directory toUmiOCR-data, adds.andsite-packagesto the import path, and registers the PySide2 plugin path. It then callspy_src/run.py:main().run.py:main()addspy_src/importsto the path, callsumi_about.init(app_path)to populate version/host metadata, strips a problematicQMLSCENE_DEVICEenv var if present, reads pre-QML configuration, and runs the single-instance check (initCmd()).- If no other instance owns the lock,
runQml()starts Qt: enables shared OpenGL contexts and high-DPI scaling, initializes the renderer, creates theQGuiApplication, registers every Python connector type withqmlRegisterType, initializes i18n, and loadsqt_res/qml/Main.qml.
2.3. The QML ↔ Python Bridge
The UI is QML; the logic is Python. They are connected by connector classes — QObject subclasses registered with qmlRegisterType() in run.py. QML instantiates them and calls their slots; Python emits signals back. The registered connectors are:
| Connector | Source | Responsibility |
|---|---|---|
TagPageConnector |
tag_pages/tag_pages_connector.py |
Tab/page lifecycle, holds page py/qml objects |
MissionConnector |
mission/mission_connector.py |
Submits and controls OCR task queues |
DocPreviewConnector |
mission/doc_preview_connector.py |
Document preview rendering |
PubSubConnector |
event_bus/pubsub_connector.py |
Publish/subscribe event bus |
KeyMouseConnector |
event_bus/key_mouse/key_mouse_connector.py |
Global keyboard/mouse hooks |
PluginsConnector |
plugins_controller/plugins_connector.py |
OCR engine plugin loading |
UtilsConnector |
utils/utils_connector.py |
Clipboard, file finding, shell open |
GlobalConfigsConnector |
utils/global_configs_connector.py |
Shortcuts, language, OpenGL, log level, web service control |
ThemeConnector |
utils/theme_connector.py |
Theme load/save |
ImageConnector / PixmapProvider |
image_controller/ |
Image processing, QML image provider |
I18n |
utils/i18n_configs.py |
Runtime translation |
2.4. The Mission (Task Queue) Framework
All recognition work flows through a generic task-queue base class, Mission (mission/mission.py), subclassed by MissionOCR, MissionDOC, and MissionQRCode. Key behaviors:
- Queues of queues. Each submission is a mission list (a queue of individual tasks) identified by a UUID. Multiple mission lists can be active at once.
- Scheduling modes.
_schedulingModeis"1111"(round-robin — take the first task from each queue in turn) or"1234"(sequential — finish one queue before starting the next). Round-robin is the default. - Async vs. sync.
addMissionList(msnInfo, msnList)returns immediately with a mission ID and invokes callbacks (onStart,onReady,onGet,onEnd).addMissionWait(argd, msnList)blocks on athreading.Conditionuntil the queue finishes and returns the result list — this is what the synchronous HTTP/api/ocrroute uses. - Lifecycle control.
pauseMissionList,resumeMissionList,stopMissionList,stopAllMissions. State per queue iswaiting → running → stop. - Worker thread. A single async worker (
_taskRun) drains all queues under aQMutex; subclasses overridemsnPreTask(parameter prep) andmsnTask(actual recognition).
2.5. OCR Engine Plugin System
The OCR engine is not built in — it is a plugin loaded at runtime by plugins_controller/plugins_controller.py:
- Plugins live under
./UmiOCR-data/plugins/. The controller adds that directory to the import path and imports every subfolder that contains an__init__.py. - Each plugin must define a
PluginInfodict with at leastgroup(must be one ofPLUGINS_GROUPS, currently["ocr"]) andapi_class, plus optionalglobal_options/local_optionsthat describe its configurable parameters. - After dynamic plugins load,
ocr/api.initOcrPlugins()wires them into the API manager.
This is why parameters like ocr.language, ocr.cls, and ocr.limit_side_len are engine-specific (they belong to the PaddleOCR plugin). Integrators should never hard-code an engine's parameters — query the live option set via the get_options endpoints (see §4) and build requests from that.
2.6. Layout Post-Processing (tbpu)
tbpu ("text block processing unit", ocr/tbpu/) re-orders and merges the engine's raw text blocks into readable paragraphs. The parser registry (ocr/tbpu/__init__.py) maps a string key to a parser class:
| Key | Class | Behavior |
|---|---|---|
none |
ParserNone |
No processing |
multi_para |
MultiPara |
Multi-column, natural paragraphs (default) |
multi_line |
MultiLine |
Multi-column, always wrap |
multi_none |
MultiNone |
Multi-column, no wrap |
single_para |
SinglePara |
Single-column, natural paragraphs |
single_line |
SingleLine |
Single-column, always wrap |
single_none |
SingleNone |
Single-column, no wrap |
single_code |
SingleCode |
Single-column, preserve indentation |
IgnoreArea (also in tbpu/) drops text blocks that fall entirely within user-defined rectangles. Helper algorithms live in tbpu/parser_tools/ (gap_tree.py, line_preprocessing.py, paragraph_parse.py).
2.7. Output Writers
Document/batch results are serialized by writer classes in ocr/output/, selected by key via the Output registry:
| Key | Writer | Output |
|---|---|---|
txt |
output_txt.py |
Text with page/file info |
txtPlain |
output_txt_plain.py |
Plain recognized text |
| (per-file) | output_txt_individual.py |
One text file per input |
csv |
output_csv.py |
CSV table |
jsonl |
output_jsonl.py |
One JSON object per line |
md |
output_md.py |
Markdown |
pdfLayered |
output_pdf_layered.py |
Double-layer searchable PDF |
pdfOneLayer |
output_pdf_one_layer.py |
Single-layer plain-text PDF |
3. Repository Structure
Umi-OCR/
├── README.md / README_en.md / README_ja.md Project overview (multi-language)
├── CHANGE_LOG.md Version history + dependency pins
├── LICENSE MIT
├── docs/
│ ├── README_CLI.md Command-line manual (authoritative)
│ └── http/ HTTP API manual (authoritative)
│ ├── README.md api_ocr.md api_doc.md api_qrcode.md argv.md
│ └── api_doc_demo.py / .html Working API client examples
├── dev-tools/i18n/ Qt Linguist translation toolchain
└── UmiOCR-data/ The application (working directory)
├── main.py Windows/PyStand bootstrap (entry point)
├── about.json Name, version, URLs, translators
└── py_src/
├── run.py App startup, connector registration
├── imports/ umi_about, umi_log, plugin_i18n, call_func
├── utils/ configs, i18n, theme, threads, file finder
├── mission/ task-queue framework (OCR/DOC/QRCode)
├── ocr/
│ ├── api/ OCR plugin API manager
│ ├── tbpu/ layout post-processing parsers
│ └── output/ result writers (txt/csv/pdf/…)
├── server/ HTTP service + CLI bridge (bottle)
├── tag_pages/ per-tab Python logic
├── event_bus/ pub/sub + keyboard/mouse hooks
├── image_controller/ screenshot + image handling
├── plugins_controller/ dynamic plugin loader
└── platform/ win32 / linux platform shims
└── qt_res/qml/ QML UI (Main.qml, TabPages, Widgets, …)
Tip
The two
docs/manuals are the project's own authoritative API references. They are written in Chinese; this guide translates and consolidates them. When the source code and the manual disagree, trust the source — but in this snapshot they are consistent.
4. HTTP API Reference
All endpoints are served by the Bottle app in py_src/server/web_server.py, listening on 127.0.0.1:1224 by default. Responses are JSON strings (non-ASCII characters are escaped to \uXXXX for transport).
Important
The HTTP service must be enabled (it is on by default). The service-related settings appear only when Advanced mode is enabled in the Global Settings tab. To allow LAN clients, switch the host from “local only” to “any available address” — see the Security section before doing so.
4.1. Service Conventions
- CORS: the service sets
Access-Control-Allow-Origin: *and allowsGET, POST, PUT, OPTIONS.OPTIONSpreflights are translated to the requested method. - Request size limit:
BaseRequest.MEMFILE_MAX = 104857600(100 MB per request). - Status codes:
code == 100means success across all endpoints.code == 101means "no text found" (OCR/QR) or "no file uploaded" (doc upload). Other codes indicate errors and carry a human-readabledatastring. - Concurrency: the backend has limited concurrency support — avoid parallel calls. Under sustained heavy load, occasional
Error: connect ECONNREFUSEDresponses can occur; simply retry.
4.2. Base Routes
| Method | Path | Purpose |
|---|---|---|
GET |
/ , /umiocr |
Returns the application full-name string (liveness check) |
POST |
/argv |
Cross-process command-line bridge (localhost only) |
4.3. Image OCR
GET /api/ocr/get_options
Returns the parameter definitions for the currently loaded OCR engine (titles, defaults, types, option lists). Use this to discover valid parameters dynamically rather than hard-coding them.
curl http://127.0.0.1:1224/api/ocr/get_options
Each parameter is described by title, toolTip, default, and type (enum, boolean, text, number, var). For PaddleOCR the set includes ocr.language, ocr.cls, ocr.limit_side_len, tbpu.parser, tbpu.ignoreArea, and data.format.
POST /api/ocr
Recognizes a single Base64-encoded image.
Request body (JSON):
| Field | Required | Description |
|---|---|---|
base64 |
yes | Image as a Base64 string, without any data:image/png;base64, prefix |
options |
no | Parameter dict (see get_options); missing keys take their defaults |
import json, requests
payload = {
"base64": "iVBORw0KGgoAAAAN……",
"options": {
"ocr.language": "models/config_chinese.txt",
"tbpu.parser": "multi_para",
"data.format": "text",
},
}
resp = requests.post(
"http://127.0.0.1:1224/api/ocr",
data=json.dumps(payload),
headers={"Content-Type": "application/json"},
)
print(json.loads(resp.text))
Response (JSON):
| Field | Type | Description |
|---|---|---|
code |
int | 100 success, 101 no text, otherwise failure |
data |
list | string | Result (see below); error string on failure |
time |
double | Recognition time in seconds |
timestamp |
double | Start timestamp (seconds) |
When data.format is dict (default), data is a list of blocks, each with text, score (0–1), box (four clockwise corner [x,y] points starting top-left), and end (the line terminator — empty, space, or \n — derived from layout parsing, so concatenating text + end reconstructs paragraphs). When data.format is text, data is the joined string.
Warning
Some HTTP libraries auto-convert escaped newlines (
\\n) in the response body into real newlines, which then breaks JSON parsing. If you hit this, fetch the raw response string, replace every real newline with\\n, then parse.
Request-validation error codes (/api/ocr): 800 body not JSON, 801 empty body, 802 missing base64, 803 options not a dict, 804 options parsing failed.
4.4. Document OCR (PDF) — Five-Step Workflow
Document recognition (v2.1.4+) is asynchronous: upload → poll → build output → download → clean up. Routes live in server/doc_server.py.
1. POST /api/doc/upload → returns task id
2. POST /api/doc/result → poll until is_done && state=="success"
3. POST /api/doc/download → build target file(s), return download URL
4. GET <download URL> → fetch the file
5. GET /api/doc/clear/<id> → release server resources
GET /api/doc/get_options
Like the OCR options query, plus document-specific keys:
| Key | Default | Type | Meaning |
|---|---|---|---|
tbpu.ignoreRangeStart |
1 |
int | First page the ignore-regions apply to |
tbpu.ignoreRangeEnd |
-1 |
int | Last page (negatives count from the end) |
pageRangeStart |
1 |
int | First page to OCR |
pageRangeEnd |
-1 |
int | Last page to OCR (negatives count from the end) |
pageList |
[] |
var | Explicit page list, e.g. [1,2,5]; takes priority over the range |
password |
"" |
text | Password for encrypted documents |
doc.extractionMode |
"mixed" |
enum | mixed, fullPage, imageOnly, textOnly |
POST /api/doc/upload
Multipart form upload. Fields: file (required) and json (optional options string). Returns {"code": 100, "data": "<task id>"} on success.
Note
On some Linux systems, a non-ASCII
file_namecausescode == 101(file not received). The official client handles this by retrying with an ASCII-onlytemp_namefor the upload part. Allowed extensions are restricted to the document suffixes inDocSuf.
POST /api/doc/result
Poll task status. Body: id (required), is_data (include recognized text, default false), is_unread (only return new entries, default true), format (dict or text). When code == 100 the response also carries processed_count, pages_count, is_done, state (waiting/running/success/failure), and message (only on failure).
POST /api/doc/download
Build the output. Body: id (required), file_types (array — one type returns a single file, several return a zip), ignore_blank (skip blank pages, default true). Valid file_types: pdfLayered, pdfOneLayer, txt, txtPlain, jsonl, csv. Returns a download data URL and name.
Caution
The download URL contains an opaque id that is not the task id. You cannot construct the download URL from the task id — always use the URL returned by
/api/doc/download.
GET /api/doc/clear/<id>
Deletes all of the task's temporary files (including the uploaded original) and aborts it if still running. Tasks auto-clear 24 hours after completion (or after 24 hours of running); leftovers are also purged on next startup. Always clear tasks promptly to free resources.
Document error codes: 201–207 cover doc info extraction, encryption/password, mission submission, output-writer creation, write, save, and zip-packaging failures respectively.
4.5. QR Code / Barcode
POST /api/qrcode serves both reading and generating, distinguished by the body.
Read — body base64 (required) + optional preprocessing.* options (median_filter_size, sharpness_factor, contrast_factor, grayscale, threshold). Response mirrors OCR: a data list where each item has text, format (e.g. QRCode, Code128, …), box, orientation (0 = upright), and score (always 1, for OCR-format compatibility).
Generate — body text (required) + optional options: format (default QRCode), w, h (default 0 = auto-minimum), quiet_zone (default -1 = auto), ec_level (error correction: -1 auto, 1=7%, 0=15%, 3=25%, 2=30%; effective only for Aztec/PDF417/QRCode). Returns the generated image as a JPEG Base64 string in data.
4.6. The /argv Command Bridge
POST /argv accepts a JSON list of command-line arguments and executes them against the running instance, returning the result text. This is how the CLI reaches an already-running Umi-OCR.
// Equivalent to: Umi-OCR.exe --screenshot
fetch("http://127.0.0.1:1224/argv", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(["--screenshot"]),
}).then(r => r.text()).then(console.log);
Important
/argvis restricted to127.0.0.1. The route checksREMOTE_ADDRand returns401for any non-loopback caller, because it can perform sensitive actions (read local images, close the app). This restriction holds even when the rest of the service is exposed to the LAN.
5. Command-Line Interface (Internals)
5.1. How the CLI Works
The command line is not a separate program — it is Umi-OCR.exe invoked with arguments. The flow (server/cmd_client.py):
initCmd()readssys.argv[1:]and checks for--force._isMultiOpen()compares the recordedlast_pid+last_ptime(process create time) in.pre_settingsagainst live processes (viapsutil) to detect whether an instance is already running.- If already running:
_sendCmd(argv)POSTs the argument list tohttp://127.0.0.1:<port>/argvand exits — the running instance does the work. - If not running, with arguments:
_newSend()launches a new process with--force(to avoid recursion), waits up to ~30 s for the service to come up, then forwards the arguments. - If not running, no arguments (or
--force): the process becomes the primary instance, records its pid/ptime, and starts the GUI.
The parser itself (server/cmd_server.py, an argparse.ArgumentParser(prog="Umi-OCR")) and the _Actuator execute commands inside the GUI process.
5.2. Flag Reference
| Flag | Type | Action |
|---|---|---|
--show / --hide / --quit |
store_true | Control the main window |
--screenshot |
store_true | Screenshot OCR (accepts screen= / rect= positional params) |
--clipboard |
store_true | OCR the clipboard image |
--path |
store_true | OCR image path(s)/folder(s) from positional paras |
--qrcode_read |
store_true | Read code(s) from positional path(s) |
--qrcode_create |
store_true | Generate a code from positional "text" "out.jpg" [w] [h] |
--reload |
store_true | Publish <<settingsReload>> to reload .settings (v2.1.5+) |
--all_pages |
store_true | List page templates and open pages |
--add_page / --del_page |
int | Create/close a tab by index |
--all_modules |
store_true | List callable py/qml modules |
--call_py / --call_qml |
str | Target module (prefix match allowed) |
--func |
str | Function to call on the target module |
--thread |
store_true | Run the call on the worker thread to capture a return value (may be unstable) |
--clip |
store_true | Copy result to clipboard |
--output / --> |
str | Write result to file (overwrite) |
--output_append / -->> |
str | Write result to file (append) |
paras |
nargs=* | Positional parameters for the above |
5.3. Advanced Dynamic Invocation
--all_modules, --call_py, --call_qml, --func, and --thread let you call arbitrary functions on any tab module. Module names accept a prefix (e.g. Scr matches ScreenshotOCR_1). Positional paras are auto-coerced to int → float → JSON (list/dict) → string. By default calls run on the main thread asynchronously (no return value); --thread runs them synchronously on the worker thread to capture a return (with a stability caveat). Example — add documents to the Batch Document tab and start it:
umi-ocr --call_qml BatchDOC --func addDocs '[ "C:/Users/My/Desktop/111.epub", "C:/Users/My/Desktop/222.pdf"]'
umi-ocr --call_qml BatchDOC --func docStart
Warning
Synchronous (
--thread) calls run outside the QML main thread and can destabilize or crash the UI. Use them only when you must capture a return value, and prefer the dedicated HTTP API for production automation.
6. Configuration
Umi-OCR keeps configuration in two files, both inside the working directory ./UmiOCR-data/.
6.1. .pre_settings (pre-QML, JSON)
Loaded by utils/pre_configs.py before the QML engine starts, so it holds only the few keys needed early:
| Key | Default | Purpose |
|---|---|---|
i18n |
"" |
Interface language |
opengl |
"" |
Renderer / OpenGL type |
server_port |
1224 |
HTTP service port |
last_pid |
-1 |
PID of the last run (single-instance detection) |
last_ptime |
-1 |
Process create time of the last run |
Written atomically with json.dump (UTF-8, indented). Permission/IO errors are captured and surfaced via getErrorStr() rather than crashing.
6.2. .settings (main, INI)
The main configuration, managed by the QML engine and written whenever you change a setting in the UI (language, theme, fonts, OCR engine, service host, log level, etc.). You may edit it by hand and reload it without restarting via umi-ocr --reload, which publishes the <<settingsReload>> event.
Note
The HTTP
server_portlives in.pre_settings, not.settings, because the service may need to start before the QML config is available. If port1224is occupied, the server auto-increments and persists the new port back to.pre_settings.
6.3. Logging
Logging (imports/umi_log.py) writes entries at or above a configurable level (default ERROR) to Umi-OCR/UmiOCR-data/logs. The level is adjustable in the Global Settings tab. Launching Umi-OCR from a terminal shows real-time logs. Qt's own messages are redirected into this logger via qInstallMessageHandler.
7. Security
Umi-OCR is designed to run on a trusted local machine. Understand these properties before exposing it.
7.1. Network Exposure
- Default: loopback only. The service binds to
127.0.0.1, reachable only from the local machine. /argvis hard-locked to localhost. It checksREMOTE_ADDR == "127.0.0.1"and returns401otherwise, regardless of the host setting — because it can read local files and shut down the app.- LAN exposure is opt-in. Switching the host to “any available address” in Global Settings makes the OCR/QR/doc endpoints reachable from the network.
Caution
When you expose the service to the LAN: there is no authentication on the API endpoints, CORS is wide open (
Access-Control-Allow-Origin: *), and document upload accepts files up to 100 MB. Anyone who can reach the port can submit OCR/document jobs that consume CPU, memory, and disk on your machine. Only enable LAN access on a trusted network, and prefer a firewall rule or reverse proxy with authentication in front of it. The/argvcontrol bridge remains localhost-only, so remote callers cannot drive the CLI.
7.2. File-Path Safety
The document routes guard against path traversal: both upload and download verify with os.path.commonpath that the resolved path stays inside UPLOAD_DIR (./temp_doc), returning an "Unauthorized path" error otherwise. Uploaded filenames are sanitized (illegal characters replaced, length capped at 255).
7.3. Data Handling
- Fully offline. No telemetry, no cloud calls. Recognition happens in the local engine subprocess.
- Temporary files. Uploaded documents and generated outputs live under
./temp_doc/<uuid>/and are deleted on taskclear, after a 24-hour retention window, or at next startup (the upload directory is wiped on init). - Encrypted PDFs. Passwords are passed through to the document engine and are not persisted beyond the task.
7.4. Process & Engine Isolation
The OCR engine runs as a separate subprocess loaded as a plugin. If it crashes, the API returns code: 902 ("failed to pass command to recognizer; subprocess likely crashed") rather than taking down the UI. Restarting Umi-OCR re-spawns the engine.
8. Operations
8.1. Starting and Stopping
- Start: run
Umi-OCR.exe(or the Scoop shim, or the Linux launcher). No service manager is involved — it is a desktop process. - Stop: close the window, press
Escto hide then quit, orumi-ocr --quit.
Warning
If an HTTP client holds an open connection when you close Umi-OCR, the UI thread exits but the network worker thread may linger (the app won't fully close). Ensure clients disconnect, or force-end the process via Task Manager. The custom WSGI server tracks active connections and force-closes them on shutdown, but a client that keeps reconnecting can still hold it open.
8.2. Health Check
curl http://127.0.0.1:1224/
# → the application full-name string if the service is alive
8.3. Choosing / Swapping the OCR Engine
Install engine plugins into ./UmiOCR-data/plugins/ (from the plugins repo) and select one under Global Settings. The two common engines:
- PaddleOCR-json — slightly faster; rich language model libraries.
- RapidOCR — broader hardware/OS compatibility.
8.4. Performance Tuning
- Raise/lower the engine's
ocr.limit_side_len(e.g.960→4320) to trade speed for large-image accuracy. - Avoid concurrent API calls; the backend serializes work through the mission queue and has limited concurrency.
- For overnight batch jobs, enable the post-task shutdown/sleep option in Batch OCR.
- On display glitches, switch the Renderer (OpenGL → ANGLE/software) or disable hardware acceleration.
8.5. Deployment Checklist
- [ ] Extract the release; confirm
Umi-OCR.exelaunches and a tab opens. - [ ] Verify an OCR engine plugin is present and selected (run a Screenshot OCR).
- [ ] Confirm the HTTP service is enabled and
curl http://127.0.0.1:1224/responds. - [ ] If automating, decide loopback-only vs. LAN, and firewall accordingly.
- [ ] Set the log level appropriately and confirm the
logsdirectory is writable. - [ ] For document OCR, verify
./temp_docis writable and has enough free space.
9. Troubleshooting (Operator View)
Error: code: 902 — recognizer subprocess crash
Cause: The OCR engine subprocess died or stopped responding (issue #987; can surface as [Errno 22] Invalid argument).
Resolution: Restart Umi-OCR to re-spawn the engine. Switch engines if it recurs. Reduce image size / batch concurrency on low-memory hosts.
Error: 无法创建输出器 — unable to create output writer (doc code 203)
Cause: The selected output writer could not be instantiated (issue #640) — typically a non-writable output path or a file locked by another process. Resolution: Point output to a writable directory; close any program holding the target file.
Error: width=0,height=0 on screenshot (Linux)
Cause: An empty capture region (issue #1015).
Resolution: Drag a non-empty rectangle; pass a valid rect=x,y,w,h.
Document /result reports the wrong completion state
Cause: Reported as an open issue (#1051) where the doc /result endpoint can return a completion state prematurely.
Resolution: Rely on is_done && state == "success" together before calling /api/doc/download, and poll until both hold.
Error: connect ECONNREFUSED under load
Cause: Backend concurrency limits during sustained, high-volume calls. Resolution: Retry the request; serialize calls instead of issuing them in parallel.
App won't fully exit
Cause: A lingering HTTP client connection keeps the network thread alive. Resolution: Disconnect clients; force-end via Task Manager if needed.
10. Internals & Code Quality
10.1. Conventions
- Connectors bridge QML and Python; each is a
QObjectwith QML-invokable slots. - Mission subclasses override
msnPreTask/msnTask; everything funnels through the shared queue runner withQMutexlocking. - Registries map string keys to classes (
tbpu.Parser,ocr.output.Output, pluginPluginInfo), making parsers/writers/engines pluggable. - Event bus (
event_bus/) uses publish/subscribe for cross-component signals such as<<ScreenshotOcrEnd>>and<<settingsReload>>. - Platform shims (
platform/win32,platform/linux) isolate OS-specific calls (key translation, new-process launch, native APIs).
10.2. Notable Design Choices
- HTTP-as-IPC. Using a local HTTP service for both the public API and the cross-process CLI keeps a single code path for "do this OCR action," whether the caller is the command line, a script, or the UI.
- Vendored Bottle.
server/bottle.pyis bundled so the app has no external web-framework dependency at runtime. - Custom WSGI server.
_WSGIRefServertracks active connections so it can force-close them on shutdown and find a free port if1224is taken.
10.3. Known Issues & Improvement Opportunities (from the issue tracker)
| Theme | Issues | Note |
|---|---|---|
| PaddleOCR v5 / new engines | #1045, #1016, #989 | Demand for newer PaddleOCR (incl. GPU PP-OCRv5, #1053) — handled via the plugin system |
| Table recognition | #87, #445, #1058 | Recognizing tables/structured data is a recurring request |
| Math formula recognition | #254 | Experimental |
| macOS build | #1057 | Not currently provided (Windows + Linux only) |
| Linux autostart | #1050 | Auto-start on Linux requested |
| Engine subprocess robustness | #987 | code: 902 crashes — candidate for supervised restart |
Tip
Most "support more engines / GPU / new models" requests are addressable without changing this repository — they are engine-plugin work in the plugins repository. The plugin contract (
PluginInfowithgroup/api_class/global_options/local_options) is the extension point.
10.4. Contributing
- Code: Build instructions live in the Windows and Linux runtime repositories — clone a runtime, then drop this application code into it.
- Translations: Use Weblate; the
dev-tools/i18n/toolchain (Qt Linguist,lupdate/lreleasewrappers) compiles.ts→.qmfiles underUmiOCR-data/i18n/. - Engine plugins: Implement the
PluginInfocontract in the plugins repository.
11. Quick Reference
11.1. Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | / , /umiocr |
none | Liveness / app name |
| POST | /argv |
localhost-only | Run CLI args against the instance |
| GET | /api/ocr/get_options |
none | OCR parameter schema |
| POST | /api/ocr |
none | Image OCR (Base64) |
| GET | /api/doc/get_options |
none | Document OCR parameter schema |
| POST | /api/doc/upload |
none | Upload document, start task |
| POST | /api/doc/result |
none | Poll task status / results |
| POST | /api/doc/download |
none | Build output, get download URL |
| GET | /api/doc/download/<id>/<name> |
none | Download built file |
| GET | /api/doc/clear/<id> |
none | Clear task + temp files |
| POST | /api/qrcode |
none | Read or generate codes |
11.2. Status Codes
| Code | Meaning |
|---|---|
100 |
Success |
101 |
No text (OCR/QR) / no file uploaded (doc) |
201–207 |
Document task errors (info, password, submit, writer, write, save, zip) |
800–804 |
OCR request validation errors |
902 |
Recognizer subprocess communication failure |
11.3. Key Files & Paths
| Path | Purpose |
|---|---|
UmiOCR-data/main.py |
Entry point (env bootstrap) |
UmiOCR-data/py_src/run.py |
App startup |
UmiOCR-data/.pre_settings |
Pre-QML config (port, language, pid) |
UmiOCR-data/.settings |
Main config (INI, GUI-managed) |
UmiOCR-data/plugins/ |
OCR engine plugins |
UmiOCR-data/logs/ |
Log files |
UmiOCR-data/temp_doc/ |
Document-OCR temp files |
11.4. Defaults
| Setting | Default |
|---|---|
| Service host | 127.0.0.1 (loopback) |
| Service port | 1224 (auto-increment if busy) |
| Request size limit | 100 MB |
| Layout parser | multi_para |
| Document extraction mode | mixed |
| Document download type | pdfLayered |
| Temp file retention | 24 hours |
| Log level | ERROR |