Skip to main content

Model Sources

Retto supports multiple ways to provide OCR models:

Local Files

Load models from the filesystem

HuggingFace Hub

Automatically download from HF Hub

Embedded Blobs

Compile models into your binary

Model Types

Retto requires three models for full OCR functionality:
det
Model
required
Text Detection Model - Locates text regions in imagesDefault: ch_PP-OCRv4_det_infer.onnx
cls
Model
required
Text Classification Model - Determines text orientationDefault: ch_ppocr_mobile_v2.0_cls_infer.onnx
rec
Model
required
Text Recognition Model - Recognizes characters in text regionsDefault: ch_PP-OCRv4_rec_infer.onnx
Additionally, the recognition model requires a character dictionary file:
rec_dict
Text File
required
Character Dictionary - Maps model outputs to charactersDefault: ppocr_keys_v1.txt

Loading from Local Files

The simplest approach for desktop applications:
use retto_core::prelude::*;

let models = RettoOrtWorkerModelProvider(RettoWorkerModelProvider {
    det: RettoWorkerModelSource::Path("models/ch_PP-OCRv4_det_infer.onnx".into()),
    cls: RettoWorkerModelSource::Path("models/ch_ppocr_mobile_v2.0_cls_infer.onnx".into()),
    rec: RettoWorkerModelSource::Path("models/ch_PP-OCRv4_rec_infer.onnx".into()),
});

let cfg = RettoSessionConfig {
    worker_config: RettoOrtWorkerConfig {
        device: RettoOrtWorkerDevice::CPU,
        models,
    },
    rec_processor_config: RecProcessorConfig {
        character_source: RecCharacterDictProvider::OutSide(
            RettoWorkerModelSource::Path("models/ppocr_keys_v1.txt".into())
        ),
        ..Default::default()
    },
    ..Default::default()
};
If the specified files don’t exist, session creation will fail with a ModelNotFoundError.

Using Default Paths

For convenience, use the built-in defaults:
let models = RettoOrtWorkerModelProvider::from_local_v4_path_default();
This expects models in the current directory with standard names:
  • ch_PP-OCRv4_det_infer.onnx
  • ch_PP-OCRv4_rec_infer.onnx
  • ch_ppocr_mobile_v2.0_cls_infer.onnx

Loading from HuggingFace Hub

Enable the hf-hub feature to automatically download models:
Cargo.toml
[dependencies]
retto-core = { version = "0.1.5", features = ["hf-hub", "backend-ort"] }
Then use the HuggingFace provider:
let models = RettoOrtWorkerModelProvider::from_hf_hub_v4_default();

let cfg = RettoSessionConfig {
    worker_config: RettoOrtWorkerConfig {
        device: RettoOrtWorkerDevice::CPU,
        models,
    },
    ..Default::default()
};
1

Models are downloaded automatically

On first run, models are fetched from pk5ls20/PaddleModel on HuggingFace Hub.
2

Models are cached locally

Subsequent runs reuse the cached models from ~/.cache/huggingface/.
3

Progress is shown

Download progress is displayed via tracing logs.

Custom HuggingFace Repository

Specify a custom repository:
let models = RettoOrtWorkerModelProvider(RettoWorkerModelProvider {
    det: RettoWorkerModelSource::HuggingFace {
        repo: "your-org/your-models".to_string(),
        model: "path/to/det.onnx".to_string(),
    },
    rec: RettoWorkerModelSource::HuggingFace {
        repo: "your-org/your-models".to_string(),
        model: "path/to/rec.onnx".to_string(),
    },
    cls: RettoWorkerModelSource::HuggingFace {
        repo: "your-org/your-models".to_string(),
        model: "path/to/cls.onnx".to_string(),
    },
});

Embedding Models

For WASM or self-contained binaries, embed models at compile time.

Enable Feature Flag

Cargo.toml
[dependencies]
retto-core = { version = "0.1.5", features = ["download-models", "backend-ort-wasm"] }

Build-Time Download

From build.rs, models are downloaded during compilation:
// Models are downloaded to ./models/ during build
// and embedded using include_bytes!

Use Embedded Models

let models = RettoOrtWorkerModelProvider::from_local_v4_blob_default();

let cfg = RettoSessionConfig {
    worker_config: RettoOrtWorkerConfig {
        device: RettoOrtWorkerDevice::CPU,
        models,
    },
    ..Default::default()
};
From ort_worker.rs:87, the implementation uses include_bytes!:
fn from_local_v4_blob_default() -> Self {
    Self(RettoWorkerModelProvider {
        det: RettoWorkerModelSource::Blob(
            include_bytes!("../../models/ch_PP-OCRv4_det_infer.onnx").to_vec(),
        ),
        rec: RettoWorkerModelSource::Blob(
            include_bytes!("../../models/ch_PP-OCRv4_rec_infer.onnx").to_vec(),
        ),
        cls: RettoWorkerModelSource::Blob(
            include_bytes!("../../models/ch_ppocr_mobile_v2.0_cls_infer.onnx").to_vec(),
        ),
    })
}
Embedding models significantly increases binary size (typically ~30-50 MB).

WASM-Specific Loading

For WebAssembly, models can be loaded from JavaScript:
import { Retto } from '@nekoimageland/retto-wasm';

const retto = await Retto.load();

// Option 1: Use embedded models (if built with embed-models feature)
await retto.init();

// Option 2: Load external models
const models = {
  det_model: await fetch('/models/det.onnx').then(r => r.arrayBuffer()),
  cls_model: await fetch('/models/cls.onnx').then(r => r.arrayBuffer()),
  rec_model: await fetch('/models/rec.onnx').then(r => r.arrayBuffer()),
  rec_dict: await fetch('/models/dict.txt').then(r => r.arrayBuffer()),
};

await retto.init(models);
From wasm_lib.rs:80, the WASM interface accepts raw buffers:
pub unsafe extern "C" fn retto_init(
    det_ptr: *const u8,
    det_len: usize,
    cls_ptr: *const u8,
    cls_len: usize,
    rec_ptr: *const u8,
    rec_len: usize,
    rec_dict_ptr: *const u8,
    rec_dict_len: usize,
) {
    let det_model = unsafe { std::slice::from_raw_parts(det_ptr, det_len).to_vec() };
    let cls_model = unsafe { std::slice::from_raw_parts(cls_ptr, cls_len).to_vec() };
    let rec_model = unsafe { std::slice::from_raw_parts(rec_ptr, rec_len).to_vec() };
    let rec_dict = unsafe { std::slice::from_raw_parts(rec_dict_ptr, rec_dict_len).to_vec() };
    
    // Initialize session with blob sources...
}

Model Resolution

From worker.rs:30, the resolution logic:
impl RettoWorkerModelSource {
    pub(crate) fn resolve(self) -> RettoResult<RettoWorkerModelResolvedSource> {
        match self {
            RettoWorkerModelSource::Path(path) => {
                let path = std::path::PathBuf::from(path);
                match path.exists() {
                    true => Ok(RettoWorkerModelResolvedSource::Path(path)),
                    false => Err(RettoError::ModelNotFoundError(
                        path.into_os_string().to_string_lossy().to_string(),
                    )),
                }
            }
            RettoWorkerModelSource::Blob(blob) => match blob.is_empty() {
                true => Err(RettoError::ModelNotFoundError(
                    "Empty model blob!".to_string(),
                )),
                false => Ok(RettoWorkerModelResolvedSource::Blob(blob)),
            },
            RettoWorkerModelSource::HuggingFace { repo, model } => {
                use crate::hf_hub_helper::HfHubHelper;
                let helper = HfHubHelper::new();
                let path = helper.get_model_file(&repo, &model)?;
                Ok(RettoWorkerModelResolvedSource::Path(path))
            }
        }
    }
}

Comparison

Pros:
  • Fast loading
  • No network required
  • Full control over model versions
Cons:
  • Must distribute models separately
  • User must download models
  • Path configuration required
Best for: Desktop applications, servers

Default Behavior

From worker.rs:81, the default provider selection:
fn default_provider() -> Self {
    #[cfg(all(not(target_family = "wasm"), feature = "hf-hub"))]
    return Self::from_hf_hub_v4_default();
    
    #[cfg(all(not(target_family = "wasm"), not(feature = "hf-hub")))]
    return Self::from_local_v4_path_default();
    
    #[cfg(target_family = "wasm")]
    return Self::from_local_v4_blob_default();
}
Using RettoOrtWorkerConfig::default() or RettoOrtWorkerModelProvider::default() will automatically select the appropriate provider based on your build configuration.

Next Steps

Backends

Configure execution backends

Rust Usage

Learn the core Rust API

Build docs developers (and LLMs) love