Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/paddleocr-vl-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@happyvertical/ocr": minor
---

Add the `paddleocr-vl` provider for a served PaddleOCR-VL `/layout-parsing` pipeline. It sends PNG and JPEG unchanged, converts TIFF (every page), WebP, GIF, BMP and raw pixels to PNG, and is selected only by name, never by `auto`.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The `@happyvertical/ocr` package provides a unified Node.js interface for Optica

## Features

- **Multi-Provider Support**: Unified API for Tesseract.js, ONNX-based OCR engines (PaddleOCR PP-OCRv4), HappyVertical AI-backed vision OCR, and served Unlimited-OCR endpoints
- **Multi-Provider Support**: Unified API for Tesseract.js, ONNX-based OCR engines (PaddleOCR PP-OCRv4), HappyVertical AI-backed vision OCR, served Unlimited-OCR endpoints, and served PaddleOCR-VL layout parsing
- **Intelligent Fallback**: Automatic provider selection and fallback when primary providers fail
- **Node-First Runtime**: Optimized for Node.js packages and server-side workflows
- **Environment Detection**: Selects compatible OCR providers based on runtime environment
Expand Down Expand Up @@ -298,6 +298,35 @@ PY

With the `sglang` backend, the provider sends `gundam` image mode for one image and `base` image mode for multi-image/multi-page requests by default. Override with `HAVE_OCR_UNLIMITED_IMAGE_MODE=base` or `gundam` when needed.

### PaddleOCR-VL Provider (Node.js, Served Layout Parsing)

The `paddleocr-vl` provider sends images to a [PaddleOCR-VL](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) `/layout-parsing` service: the full pipeline, with layout detection and the VL recognizer. `@happyvertical/pdf` reads PDFs through the same service (its `paddleocr` reader provider), so images and PDFs can share one model.

```typescript
import { getOCR } from '@happyvertical/ocr';

const ocr = getOCR({
provider: 'paddleocr-vl',
providerConfig: {
'paddleocr-vl': { baseUrl: 'http://paddleocr-vl:8080' },
},
});
const { text } = await ocr.performOCR([{ data: scanBytes }]);
```

- **Explicit only.** Images leave the process, so `auto` never selects this provider. Name it (`provider: 'paddleocr-vl'` or `HAVE_OCR_PROVIDER=paddleocr-vl`).
- **One request per page.** PNG and JPEG are sent unchanged. TIFF, WebP, GIF, BMP and raw pixel buffers are converted to PNG with Sharp first, and a multi-page TIFF (typical scanner output) is sent page by page, in order. Sharp loads only when a conversion is needed. Unknown or undecodable input is refused before any request. Remote image URLs are refused, never fetched.
- **Result.** `text` is the service's Markdown, with tables as HTML, and pages are joined by a blank line. `detections` has one entry per page. `confidence` is 100 for any text, because the service reports no score.
- **No retries.** A dropped connection can leave a GPU parse running. Error messages never include the service's response or document text.
- **Timeout.** The default is 65 minutes, which covers an on-demand cold start. `OCROptions.timeout` (and so `HAVE_OCR_TIMEOUT`) is ignored by this provider, because a limit sized for local engines would cut off a cold start. Set the provider's own `timeout` instead.
- `checkDependencies()` checks configuration only. A proxy `/health` success does not mean the model is loaded.

| Setting | Environment variable (first found wins) | Default |
| --- | --- | --- |
| `baseUrl` | `HAVE_OCR_PADDLEOCR_URL`, `HAVE_PDF_PADDLEOCR_URL` | none (provider unavailable) |
| `apiKey` | `HAVE_OCR_PADDLEOCR_API_KEY`, `HAVE_PDF_PADDLEOCR_API_KEY` | none |
| `timeout` | `HAVE_OCR_PADDLEOCR_TIMEOUT` | `3900000` (65 min) |

## Advanced Usage

### Multi-Language OCR
Expand Down
2 changes: 2 additions & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ result.detections?.forEach(detection => {
## Classes

- [LiteLLMProvider](classes/LiteLLMProvider.md)
- [PaddleOCRVLProvider](classes/PaddleOCRVLProvider.md)
- [UnlimitedOCRProvider](classes/UnlimitedOCRProvider.md)
- [OCRFactory](classes/OCRFactory.md)
- [OCRError](classes/OCRError.md)
Expand All @@ -82,6 +83,7 @@ result.detections?.forEach(detection => {

- [OAuth2Config](interfaces/OAuth2Config.md)
- [LiteLLMProviderConfig](interfaces/LiteLLMProviderConfig.md)
- [PaddleOCRVLProviderConfig](interfaces/PaddleOCRVLProviderConfig.md)
- [UnlimitedOCRProviderConfig](interfaces/UnlimitedOCRProviderConfig.md)
- [OCROptions](interfaces/OCROptions.md)
- [OCRImage](interfaces/OCRImage.md)
Expand Down
20 changes: 10 additions & 10 deletions docs/api/classes/OCRFactory.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const factory = new OCRFactory(); // Uses env vars

> **getBestProvider**(): `Promise`\<[`OCRProvider`](../interfaces/OCRProvider.md) \| `null`\>

Defined in: [src/shared/factory.ts:314](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L314)
Defined in: [src/shared/factory.ts:331](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L331)

Get the best available OCR provider for the current environment.

Expand Down Expand Up @@ -158,7 +158,7 @@ if (provider) {

> **performOCR**(`images`, `options?`): `Promise`\<[`OCRResult`](../interfaces/OCRResult.md)\>

Defined in: [src/shared/factory.ts:461](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L461)
Defined in: [src/shared/factory.ts:479](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L479)

Perform OCR processing on one or more images.

Expand Down Expand Up @@ -245,7 +245,7 @@ try {

> **getProvidersInfo**(): `Promise`\<[`OCRProviderInfo`](../interfaces/OCRProviderInfo.md)[]\>

Defined in: [src/shared/factory.ts:557](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L557)
Defined in: [src/shared/factory.ts:575](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L575)

Get detailed information about all OCR providers.

Expand Down Expand Up @@ -280,7 +280,7 @@ providers.forEach(provider => {

> **isOCRAvailable**(): `Promise`\<`boolean`\>

Defined in: [src/shared/factory.ts:609](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L609)
Defined in: [src/shared/factory.ts:627](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L627)

Check if OCR functionality is available in the current environment.

Expand Down Expand Up @@ -309,7 +309,7 @@ if (await factory.isOCRAvailable()) {

> **getSupportedLanguages**(): `Promise`\<`string`[]\>

Defined in: [src/shared/factory.ts:634](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L634)
Defined in: [src/shared/factory.ts:652](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L652)

Get array of supported language codes from the best available provider.

Expand Down Expand Up @@ -341,7 +341,7 @@ const result = await factory.performOCR(images, {

> **cleanup**(): `Promise`\<`void`\>

Defined in: [src/shared/factory.ts:671](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L671)
Defined in: [src/shared/factory.ts:689](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L689)

Clean up all OCR providers and release their resources.

Expand Down Expand Up @@ -382,7 +382,7 @@ process.on('SIGINT', async () => {

> **addProvider**(`name`, `provider`): `void`

Defined in: [src/shared/factory.ts:709](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L709)
Defined in: [src/shared/factory.ts:727](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L727)

Add a custom OCR provider to the factory.

Expand Down Expand Up @@ -429,7 +429,7 @@ const customFactory = new OCRFactory({ provider: 'custom' });

> **removeProvider**(`name`): `Promise`\<`void`\>

Defined in: [src/shared/factory.ts:727](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L727)
Defined in: [src/shared/factory.ts:745](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L745)

Remove an OCR provider from the factory.

Expand Down Expand Up @@ -461,7 +461,7 @@ await factory.removeProvider('custom');

> **getAvailableProviderNames**(): `string`[]

Defined in: [src/shared/factory.ts:752](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L752)
Defined in: [src/shared/factory.ts:770](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L770)

Get array of provider names that have been loaded in the current environment.

Expand Down Expand Up @@ -490,7 +490,7 @@ console.log('Loaded providers:', providerNames);

> **getEnvironment**(): [`OCREnvironment`](../type-aliases/OCREnvironment.md)

Defined in: [src/shared/factory.ts:771](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L771)
Defined in: [src/shared/factory.ts:789](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L789)

Get the detected runtime environment.

Expand Down
156 changes: 156 additions & 0 deletions docs/api/classes/PaddleOCRVLProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
[**@happyvertical/ocr**](../README.md)

***

[@happyvertical/ocr](../README.md) / PaddleOCRVLProvider

# Class: PaddleOCRVLProvider

Defined in: [src/node/paddleocr-vl.ts:125](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L125)

OCR provider for a PaddleOCR-VL `/layout-parsing` service.

Each image page is one request (`fileType: 1`); results are joined in
order. PNG and JPEG are sent unchanged; TIFF (every page), WebP, GIF, BMP
and raw pixel buffers are converted to PNG with `sharp` first. Requests are
not retried: a dropped connection can leave a GPU parse running.

## Implements

- [`OCRProvider`](../interfaces/OCRProvider.md)

## Constructors

### Constructor

> **new PaddleOCRVLProvider**(`config?`): `PaddleOCRVLProvider`

Defined in: [src/node/paddleocr-vl.ts:132](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L132)

#### Parameters

##### config?

[`PaddleOCRVLProviderConfig`](../interfaces/PaddleOCRVLProviderConfig.md) = `{}`

#### Returns

`PaddleOCRVLProvider`

## Properties

### name

> `readonly` **name**: `"paddleocr-vl"` = `'paddleocr-vl'`

Defined in: [src/node/paddleocr-vl.ts:126](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L126)

Provider name identifier

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`name`](../interfaces/OCRProvider.md#name)

## Methods

### performOCR()

> **performOCR**(`images`, `_options?`): `Promise`\<[`OCRResult`](../interfaces/OCRResult.md)\>

Defined in: [src/node/paddleocr-vl.ts:153](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L153)

`options` carries nothing this service uses: it needs no language hint,
and `options.timeout` is ignored because the factory fills it from
`HAVE_OCR_TIMEOUT`, a limit sized for local engines that would cut off a
cold start. Use the provider's own `timeout`.

#### Parameters

##### images

[`OCRImage`](../interfaces/OCRImage.md)[]

##### \_options?

[`OCROptions`](../interfaces/OCROptions.md)

#### Returns

`Promise`\<[`OCRResult`](../interfaces/OCRResult.md)\>

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`performOCR`](../interfaces/OCRProvider.md#performocr)

***

### checkDependencies()

> **checkDependencies**(): `Promise`\<[`DependencyCheckResult`](../interfaces/DependencyCheckResult.md)\>

Defined in: [src/node/paddleocr-vl.ts:199](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L199)

Configuration only. The service is not probed: a proxy `/health` success
does not mean the model is loaded.

#### Returns

`Promise`\<[`DependencyCheckResult`](../interfaces/DependencyCheckResult.md)\>

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`checkDependencies`](../interfaces/OCRProvider.md#checkdependencies)

***

### checkCapabilities()

> **checkCapabilities**(): `Promise`\<[`OCRCapabilities`](../interfaces/OCRCapabilities.md)\>

Defined in: [src/node/paddleocr-vl.ts:227](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L227)

Get provider capabilities

#### Returns

`Promise`\<[`OCRCapabilities`](../interfaces/OCRCapabilities.md)\>

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`checkCapabilities`](../interfaces/OCRProvider.md#checkcapabilities)

***

### getSupportedLanguages()

> **getSupportedLanguages**(): `string`[]

Defined in: [src/node/paddleocr-vl.ts:252](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L252)

PaddleOCR-VL recognizes 100+ languages and needs no language hint.

#### Returns

`string`[]

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`getSupportedLanguages`](../interfaces/OCRProvider.md#getsupportedlanguages)

***

### cleanup()

> **cleanup**(): `Promise`\<`void`\>

Defined in: [src/node/paddleocr-vl.ts:256](https://github.com/happyvertical/ocr/blob/main/src/node/paddleocr-vl.ts#L256)

Clean up provider resources (optional)

#### Returns

`Promise`\<`void`\>

#### Implementation of

[`OCRProvider`](../interfaces/OCRProvider.md).[`cleanup`](../interfaces/OCRProvider.md#cleanup)
2 changes: 1 addition & 1 deletion docs/api/functions/getAvailableProviders.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **getAvailableProviders**(): `Promise`\<`string`[]\>

Defined in: [src/shared/factory.ts:915](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L915)
Defined in: [src/shared/factory.ts:933](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L933)

Get list of OCR provider names available in the current environment.

Expand Down
2 changes: 1 addition & 1 deletion docs/api/functions/getOCR.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **getOCR**(`options?`): [`OCRFactory`](../classes/OCRFactory.md)

Defined in: [src/shared/factory.ts:851](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L851)
Defined in: [src/shared/factory.ts:869](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L869)

Get or create an OCR factory instance with automatic provider selection.

Expand Down
2 changes: 1 addition & 1 deletion docs/api/functions/getProviderInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **getProviderInfo**(`providerName`): `Promise`\<[`OCRProviderInfo`](../interfaces/OCRProviderInfo.md) \| `null`\>

Defined in: [src/shared/factory.ts:989](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L989)
Defined in: [src/shared/factory.ts:1007](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L1007)

Get detailed information about a specific OCR provider.

Expand Down
2 changes: 1 addition & 1 deletion docs/api/functions/isProviderAvailable.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **isProviderAvailable**(`providerName`): `Promise`\<`boolean`\>

Defined in: [src/shared/factory.ts:943](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L943)
Defined in: [src/shared/factory.ts:961](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L961)

Check if a specific OCR provider is available and ready to use.

Expand Down
2 changes: 1 addition & 1 deletion docs/api/functions/resetOCRFactory.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **resetOCRFactory**(): `void`

Defined in: [src/shared/factory.ts:885](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L885)
Defined in: [src/shared/factory.ts:903](https://github.com/happyvertical/ocr/blob/main/src/shared/factory.ts#L903)

Reset the global OCR factory instance.

Expand Down
2 changes: 1 addition & 1 deletion docs/api/interfaces/OCRFactoryOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ OCR factory configuration options

Defined in: [src/shared/types.ts:203](https://github.com/happyvertical/ocr/blob/main/src/shared/types.ts#L203)

Primary provider to use ('auto', 'tesseract', 'onnx', 'litellm', 'unlimited-ocr', etc.)
Primary provider to use ('auto', 'tesseract', 'onnx', 'litellm', 'unlimited-ocr', 'paddleocr-vl', etc.)

***

Expand Down
Loading