Metadata-Version: 2.4
Name: fastpluggy-ocr-service
Version: 0.4.4
Summary: Reusable local OCR backend (PaddleOCR, CPU) for FastPluggy
Author: FastPluggy Team
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: FastPluggy>=0.4.33
Requires-Dist: paddleocr<4,>=3.0
Requires-Dist: paddlepaddle>=3.0
Requires-Dist: paddlex[ocr]<3.8,>=3.7
Requires-Dist: pillow
Requires-Dist: pypdfium2>=4
Requires-Dist: opencv-python-headless>=4.8
Requires-Dist: numpy
Provides-Extra: tests
Requires-Dist: pytest>=7.0; extra == "tests"
Requires-Dist: pytest-cov>=4.0; extra == "tests"
Requires-Dist: httpx; extra == "tests"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"

# OCR Service

[![pipeline status](https://gitlab.ggcorp.fr/open/fastpluggy/private_plugins/ocr_service/badges/main/pipeline.svg)](https://gitlab.ggcorp.fr/open/fastpluggy/private_plugins/ocr_service/-/commits/main)
[![coverage report](https://gitlab.ggcorp.fr/open/fastpluggy/private_plugins/ocr_service/badges/main/coverage.svg)](https://gitlab.ggcorp.fr/open/fastpluggy/private_plugins/ocr_service/-/commits/main)

A reusable **local CPU OCR primitive** for FastPluggy. Image/PDF in →
`{text, blocks[], language}` out (plus layout/tables in `structured` mode).

Runs **PaddleOCR on CPU**, so it never touches the contended GPU. It's a low-level
primitive other plugins (`docs_processor`, `upload-bills`, the brain app, the Immich
sidecar) `depends_on` instead of each re-rolling OCR — *not* a document manager
(that stays `docs_processor`'s job, with its Qwen2.5-VL "understand" tier).

Design & rationale: [`docs/design.md`](docs/design.md). The KB's
`ai-ml/ocr-document-understanding.md` keeps the cross-engine landscape (OCR vs VLM, the
`docs_processor` ↔ `ocr_service` split) and links here. Issue #31 / brain-app PR #1.

## UI

Three admin pages under the **OCR Service** menu (on top of the JSON API):

- **OCR Service** (`/ocr_service/`) — recent-request history; each row links to its detail page.
- **Test OCR** (`/ocr_service/upload`) — drag-and-drop an image/PDF, pick `mode`/`lang`/`preprocess`,
  run it through the API, and jump to the result.
- **Detail** (`/ocr_service/task/{id}`) — the source document next to its OCR result: recognised text,
  per-block table with confidence, bounding-box overlay (images), and layout regions + tables in
  `structured` mode. Needs `persist_uploads` on (the default) and the stored source still on disk.

## API

`POST /ocr_service/ocr` — multipart form:

| field | default | notes |
|-------|---------|-------|
| `file` | — | image (png/jpg/jpeg/webp/bmp/tiff) **or** PDF |
| `mode` | `text` | `text` / `structured` (layout regions + table HTML) / `vision` (see below) |
| `lang` | plugin setting (`fr`) | PaddleOCR language code (ignored in `vision` mode) |
| `preprocess` | plugin setting (off) | OpenCV deskew/denoise/contrast (helps on photos/scans) |

### `mode=vision` — vision-LLM engine (opt-in)

Routes the image through a **vision-LLM on the homelab Ollama** (via the optional
`ollama_connector` plugin) instead of PaddleOCR — better on messy/handwritten/contextual
docs. Trade-offs: it uses the **shared GPU** (slower, contends with other Ollama consumers),
so the CPU `text`/`structured` modes stay the default. A VLM returns free-form text with
**no bounding boxes or confidence**, so the result is a single block (`bbox: []`, `conf: 1.0`)
and `layout: null`. If `ollama_connector` (or Ollama) is unavailable the request returns
**503** — `text`/`structured` are unaffected. Model + prompt are plugin settings
(`vision_model`, default `qwen2.5vl:latest`; `vision_prompt`). The task records
`engine="ollama-vlm"` and `model_version=<model tag>`.

Response (`OcrResult`):

```json
{
  "text": "…",
  "blocks": [{"bbox": [[x,y],…], "text": "…", "conf": 0.98, "page": 0}],
  "language": "fr",
  "layout": null,            // {regions:[…], tables:[{bbox,html,page}]} in structured mode
  "task_id": 42
}
```

PDFs are rasterised page-by-page (pypdfium2) and OCR'd; `blocks[].page` carries the
0-based page index. Each request is recorded as an `OcrTask` row (visible on the
plugin's admin page).

## Installation

```bash
pip install -e ".[tests]"
```

PaddleOCR/PaddlePaddle (CPU), pypdfium2 and OpenCV are pulled in as dependencies.

## Development

```bash
pytest tests/
```
