paddleocr

    PaddleOCR glue for MoonBit: JPEG/PNG/BMP decode, DBNet boxes, CTC decode, and native ONNX Runtime inference.

    ocr
    paddleocr
    onnx
    image
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    11 days ago
    Downloads
    35

    #paddleocr

    CI

    V1GreenSummer/paddleocr 0.1.0,Apache-2.0。把 PaddleOCR 的检测模型和识别模型接到 MoonBit:mizchi/image 解码图片,纯 MoonBit 做预处理和后处理,HuLunTunTao/moon-ort 在本机 CPU 上调用 ONNX Runtime。

    CXREN/moonocr 是零依赖的纯 MoonBit 引擎,字符表只有数字和英文字母。这个包走另一条路:字符表、检测和识别都由外部 ONNX 模型决定,所以可以读中文。包本身不内置权重。

    在一张 780×300 的 PNG 上(Noto Sans CJK SC 写的三行字),PP-OCRv4 中文检测 / 识别模型读出:

    中国人民共和国 你好,世界。 MoonBit OCR 2026

    对应检测框的分数是 0.994、0.994、0.990。第三行原文就是 MoonBit OCR 2026。空格来自识别模型多出来的那一个空格类,Engine::load 会把它补进字典。

    #安装

    moon add V1GreenSummer/paddleocr@0.1.0

    根包用于 wasm 侧的解码、预处理和后处理:

    ///|
    import {
    "V1GreenSummer/paddleocr",
    }

    native 侧推荐只 import 单入口包:

    ///|
    import {
    "V1GreenSummer/paddleocr/ocr",
    }

    #最小可运行示例

    不需要 ONNX Runtime 或模型:

    moon run examples/ctc

    输出:

    你好

    它用 Charset + ctc_greedy 走一遍 CTC 解码。完整图片 OCR 见“命令行”。

    #流水线

    JPEG / PNG / BMP │ mizchi/image ▼ RGBA │ ├─ 检测:长边 ≤ 960,两边对齐 32,NCHW BGR,ImageNet 归一化 │ moon-ort → DB 概率图 │ 四连通域 → 最小面积矩形 → unclip → 透视裁正 │ └─ 裁正后的横条,高度 48,均值和标准差都是 0.5 moon-ort → CTC 贪心解码 ▼ 文本行

    1. sniff_raster 看文件头,decode_raster 解成 RGBA8。JPEG 只接受 baseline。
    2. 检测输入把长边限制在 limit_side_len(默认 960),短边按比例缩放,再把两边舍入到 32 的倍数,最小 32。双线性插值。像素先除以 255,再按 OpenCV 的 BGR 顺序做 ImageNet 归一化:通道 0 是蓝,均值 (0.485, 0.456, 0.406),标准差 (0.229, 0.224, 0.225)。
    3. 检测模型的第 0 个输出当成单通道概率图。db_quads 做四连通域,用像素块的凸包求最小面积矩形。短边小于 min_size(3)或平均分低于 box_thresh(0.6)的块丢掉。剩下的矩形按 area * unclip_ratio / perimeter 沿边外扩(unclip_ratio 默认 1.5,前景阈值 0.3)。db_regions 用一次连通域同时给出两种框;db_boxes 仍保留轴对齐外接矩形,给只需要正框的调用方。
    4. 四边形按检测图到原图的比例映回去。Rgba::warp 做透视裁切,把四条边拉成横条。高宽比至少 1.5 的竖条再逆时针转 90°。外接矩形用来分行:后一个框与当前行在竖直方向的重叠不少于较短高度的一半时并进同一行,行内按 x 排序。同一行的文字直接拼接,中间不插入空格。
    5. 识别输入把裁块缩到高度 48(模型若把高度轴标成定长,就用那个高度)。宽度按宽高比向上取整,默认不超过 320。模型的宽度轴若是定长,右侧用归一化后的 0 补齐。识别归一化是 (x - 0.5) / 0.5,三个通道相同,因此 RGB 与 BGR 在这一步等价。
    6. CTC 类别 0 是 blank,不出现在字典里。类别 k 对应字典第 k - 1 行。连续相同的类别折成一个字。Charset::from_text 按文件原样解析;Charset::ppocr 在末尾没有空格时补上一行,对应官方 use_space_char。字典里没有的类别被丢掉。ctc_decode 把保留下来的字符的概率取平均,低于 rec_thresh(默认 0.5)的框不进入结果。

    ///|
    test {
    let (width, height) = @paddleocr.limit_side_size(1000, 500, limit=960)
    inspect(width, content="960")
    inspect(height, content="480")
    let charset = @paddleocr.Charset::from_text("你\n好\n")
    let logits : Array[Float] = [0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
    inspect(
    @paddleocr.ctc_greedy(logits, time=2, classes=3, charset~),
    content="你好",
    )
    }

    1000×500 的长边被收到 960,短边成 480,两边都是 32 的倍数。字典文件末尾的换行不会多出一个空类别。

    #目标平台

    代码wasmnative
    解码、缩放、归一化、连通域、分行、CTC可以单独测可以
    engine 里的 ONNX 会话、ocr 单入口包,以及 cmd/ocr不编译Linux x86_64 CPU

    moon-ort 0.1.1 用 dlopen 加载 ONNX Runtime,要求 API 正好是 1.30.0。没有 WASM、JavaScript 或 GPU 后端。模块的 preferred_target 仍是 wasm,因为根包的前后处理可以在 wasm 上复用;真正认字要 moon run --target native。

    #仓库

    moon.mod 模块 V1GreenSummer/paddleocr decode.mbt / rgba.mbt PNG、JPEG、BMP → RGBA preprocess.mbt 检测 / 识别张量 detect.mbt 概率图 → 框 → 行 ctc.mbt / page.mbt 字典、CTC、Page error.mbt OcrError engine/ native:Engine、EngineOptions、Backend 和批处理 ocr/ native 单入口:重导出 Engine 与结果/几何类型 examples/ctc/ 不依赖模型的 CTC 最小示例 cmd/ocr/ native 可执行文件

    依赖:mizchi/image@0.4.3、mizchi/zlib@0.4.10、HuLunTunTao/moon-ort@0.1.1、moonbitlang/x@0.5.5。直接写上 zlib 0.4.10,是因为 image 0.4.3 钉住的 zlib 0.4.8 还依赖一份当前工具链拒绝的 moon.mod.json。

    #第三方依赖与许可证

    本项目代码以 Apache-2.0 发布(见根目录 LICENSE)。主要依赖和数据处理如下:

    组件许可证说明
    mizchi/image 0.4.3Apache-2.0PNG/JPEG/BMP 解码与缩放
    mizchi/zlib 0.4.10Apache-2.0mizchi/image 的 zlib 依赖
    HuLunTunTao/moon-ort 0.1.1MITONNX Runtime 的 MoonBit 绑定
    moonbitlang/x 0.5.5Apache-2.0文件读写
    ONNX Runtime 1.30.0MIT运行时共享库,不随仓库分发
    PaddleOCR / RapidOCR 模型Apache-2.0上游模型权重,不随仓库分发

    检测/识别模型来自 PaddleOCR,ONNX 转换和模型列表由 RapidOCR 提供。samples/page.png 是本项目生成的测试图,随项目以 Apache-2.0 发布。

    #准备运行时和模型

    推理需要三样东西,它们都不进仓库(models/ 和 runtime/ 已忽略):

    • libonnxruntime.so.1.30.0。版本必须是 1.30.0,否则 moon-ort 报 API 不匹配。推荐从官方 release 下载 Linux x64 包:

      https://github.com/microsoft/onnxruntime/releases/download/v1.30.0/onnxruntime-linux-x64-1.30.0.tgz

      该 tarball 的 SHA256 是 a5ed5a3cac51fbb2e90da632ae43d19212faaa20e76484e62bcb7c23ddb3b3fd(与 moon-ort CI 使用的一致)。解压后使用 lib/libonnxruntime.so.1.30.0。也可以从 Python 包 onnxruntime==1.30.0 的 manylinux wheel 提取;本项目验证时使用的文件 SHA256 是 c902c70b3003c0e99fada202f37478c515ae9bba7944c2b2abd0017bae0c82ed。
    • PP-OCRv4 中文检测、识别 ONNX。模型来自 PaddleOCR,RapidOCR 提供 ONNX 转换和模型列表。验证用的是:

      文件SHA256
      ch_PP-OCRv4_det_infer.onnxd2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9
      ch_PP-OCRv4_rec_infer.onnx48fc40f24f6d2a207a2b1091d3437eb3cc3eb6b676dc3ef9c37384005483683b

    • ppocr_keys_v1.txt:一行一个字符。文件本身通常不含空格。Engine::load 用 Charset::ppocr 读它,并在末尾补上 PP-OCR 的空格类。已经以空格结尾的字典不会再补第二次。验证时使用的字典 SHA256 是 28b2362ad4ab2dc38769aa72feb535e3a9ddb3fd2a7585a05920e6393b1dc7f7。

    用环境变量或 --library 指出共享库:

    export MOON_ORT_LIBRARY=/path/to/libonnxruntime.so.1.30.0

    加载时 ONNX Runtime 会向 stderr 打印一批更高 API 版本不可用的探测信息。会话仍然建立在 1.30.0 上。

    #命令行

    moon run --target native cmd/ocr -- page.png \ --det ch_PP-OCRv4_det_infer.onnx \ --rec ch_PP-OCRv4_rec_infer.onnx \ --dict ppocr_keys_v1.txt \ --boxes

    参数作用
    位置参数待识别的 JPEG、PNG 或 BMP
    --det --rec --dict检测模型、识别模型、字典,三者都要
    --libraryONNX Runtime 共享库;缺省时读 MOON_ORT_LIBRARY
    --limit检测长边上限,默认 960
    --boxes每个框一行:x,y,width,height<TAB>检测分<TAB>识别置信度<TAB>text

    不加 --boxes 时,Page 的每一行阅读顺序文本用换行接起来。坐标是四边形的外接矩形。上面那张样图在 --boxes 下是:

    63,59,352,46 0.9941148446743899 0.9998255542346409 中国人民共和国 57,134,280,53 0.9936801729464608 0.9986610412597656 你好,世界。 60,219,444,41 0.989933144244093 0.9717656522989273 MoonBit OCR 2026

    #在代码里调用

    native 侧推荐只 import 单入口包:

    ///|
    import {
    "V1GreenSummer/paddleocr/ocr",
    }

    应用侧保持一个 Engine,换图片时不必重新读字典或再加载 ONNX Runtime。with_engine 在成功和报错时都会关闭引擎;需要手动管理时用 load + close。load 读字典、补空格类,并在没传 library 时使用 MOON_ORT_LIBRARY。recognize_file 负责读图和解码。RecognizeOptions::standard 是 PP-OCRv4 的阈值;with_* 可以改检测长边、DB 阈值、min_size、识别宽度和置信度。EngineOptions::standard 控制 ONNX Runtime 线程数、图优化和识别批大小。

    let page = @ocr.with_engine(
    det=det_path,
    rec=rec_path,
    dict=dict_path,
    engine => {
    engine.recognize_file(image_path)
    },
    )
    println(page.render())

    with_engine 返回回调的值;出错时同样会关闭引擎。线程数和批大小用 EngineOptions 设置:

    ///|
    let settings = @ocr.EngineOptions::standard()
    .with_intra_op_threads(4)
    .with_rec_batch_size(8)

    ///|
    let engine = @ocr.Engine::load(
    det=det_path,
    rec=rec_path,
    dict=dict_path,
    options=settings,
    )

    需要分步控制时:

    let engine = @ocr.Engine::load(det=det_path, rec=rec_path, dict=dict_path)
    let quads = engine.detect(image) // 只检测
    let page = engine.recognize_quads(image, quads) // 改框后识别
    let raw = engine.recognize_regions(crops) // 只识别已有裁块
    engine.close()

    recognize_regions 返回原始 CtcText,不会按 rec_thresh 过滤;recognize_quads 和 recognize 会过滤空文本和低置信度 span。Engine::detect 返回原图坐标下的旋转四边形。recognize_quads 会按阅读顺序分组、透视裁正,并在模型 batch 维可动态时把同宽 crop 合并推理。固定 batch 维为 1 的模型逐条识别;固定 batch > 1 的模型不在当前批处理路径内。

    Page 保留每个 span 的 quad、bounds、det_score、rec_confidence;Page::spans() 展平所有 span,Page::to_json_string() 输出 JSON。内存里已有像素时用 recognize 或 recognize_bytes。省略 options 时用 standard。底层的 Engine::open 仍接受已经解析好的 Charset,不改字典内容。

    native 包读每个模型的第 0 个输入和第 0 个输出,并要求它们是 float32。检测图的秩至少为 2,前面各维的乘积必须是 1,因此 [1,1,H,W]、[1,H,W]、[H,W] 都可以。识别输出是 [T,C] 或 [1,T,C];动态 batch 模型可以是 [N,T,C]。

    根包可以单独使用:

    函数作用
    sniff_raster / decode_raster识别容器并解成 Rgba
    Rgba::from_rgba / Rgba::crop / Rgba::warp校验、裁剪,或把四边形透视成横条
    limit_side_size / prepare_det / prepare_rec检测缩放与两路归一化张量
    RecognizeOptions::standard / with_*检测长边、DB 阈值、min_size、识别宽度和置信度
    db_quads / min_area_rect / unclip_quad概率图上的旋转矩形
    db_regions / db_boxes / map_det_box / group_lines一次连通域给出两种框,或轴对齐框与阅读顺序
    Charset::from_text / Charset::ppocr / ctc_decode字典、CTC 和置信度
    Page::text / Page::render / Page::spans / Page::to_json_string阅读顺序文本、带框表格、展平 span 或 JSON

    native 侧额外提供:

    函数作用
    Engine::detect / Engine::recognize_quads / Engine::recognize_regions分步检测、改框后识别、只识别裁块
    EngineOptions::standard / with_*线程数、图优化、执行模式和识别批大小
    Engine::with_scope / with_engine成功或报错时自动 close
    Backend / recognize_with / recognize_quads_with用自定义检测/识别函数替换 ONNX 后端,便于测试

    错误是 OcrError,构造器是 image、model、runtime、io、config、usage。子错误变体在包外是只读的,调用方用这些构造器。

    #边界

    • 识别用的是最小面积矩形外扩后的四边形,再透视裁正。--boxes 打印的是这个四边形的外接矩形。弯曲的一行仍会被收成一个矩形,邻行贴得很近时也会粘在一起。
    • 竖排文字会转成横条。整行倒转 180° 时还没有方向分类模型,识别会按倒着的顺序读。
    • 识别是每步取最大类别的 CTC,没有词典束搜索。同宽 crop 在动态 batch 模型上会合并成一次 session 调用。置信度低于 rec_thresh(默认 0.5)的框被丢掉;recognize_regions 不做这一步过滤。
    • 能认哪些字取决于加载的识别模型和字典。load 只为 PP-OCR 补末尾空格类;别的模型继续用 Engine::open 和 Charset::from_text。
    • 同一行里的多个框直接拼接,框与框之间不另插空格。空格只来自模型预测到的空格类。
    • 识别批处理只在模型 batch 维是动态时启用;固定 batch 维为 1 的模型逐条识别。
    • JPEG 只支持 baseline。自然场景的效果取决于检测模型;阈值沿用 Paddle 的默认值。
    • riantr/tesseract 是一个 JSON 数据库,和 OCR 无关。

    #测试

    moon test moon test --target native

    wasm 测试覆盖解码、预处理、连通域、最小面积矩形、透视裁切、CTC、空格类、Page::render、Page JSON 和 Options builders。native 另外检查:不存在的运行时库返回 OcrError::Runtime,缺字典时在打开运行时之前返回 OcrError::Io,无效 EngineOptions 返回 OcrError::Config,并用 fake Backend 检查分步流水线。

    CI 配置在 .github/workflows/ci.yml,运行 moon fmt --check、moon check --target all --deny-warn、moon test --deny-warn、moon test --target native --deny-warn 和 moon build --target native --deny-warn。真实 ONNX Runtime 推理需要外部模型和共享库,不在 CI 中下载。

    OcrError

    pub suberror OcrError {
    Image(String)
    Model(String)
    Runtime(String)
    Io(String)
    Config(String)
    Usage(String)
    }

    Failure from decoding, tensor geometry, configuration, or an ONNX Runtime call.
    impl Show for OcrError

    OcrError::config

    fn OcrError::config(message : String) -> OcrError

    Invalid options or missing model files.

    OcrError::image

    fn OcrError::image(message : String) -> OcrError

    Image decoding or geometry failure.

    OcrError::io

    fn OcrError::io(message : String) -> OcrError

    File or stream failure.

    OcrError::model

    fn OcrError::model(message : String) -> OcrError

    Model contract failure.

    OcrError::output

    fn OcrError::output(self : OcrError, logger : &Logger) -> Unit

    OcrError::runtime

    fn OcrError::runtime(message : String) -> OcrError

    Runtime failure.

    OcrError::to_string

    fn OcrError::to_string(self : OcrError) -> String

    OcrError::usage

    fn OcrError::usage(message : String) -> OcrError

    Command-line failure.

    Charset

    pub struct Charset {
    chars : Array[String]
    } derive(Eq,
    Debug
    )

    PP-OCR character dictionary. Class 0 is the CTC blank and is not stored. Class k is line k - 1.

    Charset::equal

    fn Charset::equal(Charset, Charset) -> Bool

    Charset::from_text

    fn Charset::from_text(text : String) -> Charset

    Parse a dictionary file. A trailing newline does not add an empty class.

    Charset::glyph

    fn Charset::glyph(self : Charset, class : Int) -> String?

    Map a CTC class to its character. Blank and out-of-range classes are None.

    Charset::not_equal

    fn Charset::not_equal(x : Charset, y : Charset) -> Bool

    Charset::ppocr

    fn Charset::ppocr(text : String) -> Charset

    Dictionary plus the trailing space class used by PP-OCR (use_space_char).

    A trailing newline still does not add an empty class. A dictionary that already ends with a space is left unchanged.

    Example

    test {
    let charset = @paddleocr.Charset::ppocr("你\n好\n")
    debug_inspect(charset.glyph(3), content="Some(\" \")")
    }

    Charset::to_repr

    ChwTensor

    pub(all) struct ChwTensor {
    height : Int
    width : Int
    values : Array[Float]
    } derive(Eq,
    Debug
    )

    NCHW float tensor without the leading batch axis. values is channel-major RGB, length 3 * height * width.

    ChwTensor::equal

    fn ChwTensor::equal(ChwTensor, ChwTensor) -> Bool

    ChwTensor::not_equal

    fn ChwTensor::not_equal(x : ChwTensor, y : ChwTensor) -> Bool

    CtcText

    pub(all) struct CtcText {
    text : String
    confidence : Double
    } derive(Eq, ToJson,
    Debug
    )

    Greedy CTC text and the mean probability of the kept characters.

    CtcText::equal

    fn CtcText::equal(CtcText, CtcText) -> Bool

    CtcText::not_equal

    fn CtcText::not_equal(x : CtcText, y : CtcText) -> Bool

    CtcText::to_json

    fn CtcText::to_json(CtcText) -> Json

    CtcText::to_repr

    DetBox

    pub(all) struct DetBox {
    x : Double
    y : Double
    width : Double
    height : Double
    score : Double
    } derive(Eq, ToJson,
    Debug
    )

    One text region on the detection probability map, before it is scaled back to the source image.

    DetBox::equal

    fn DetBox::equal(DetBox, DetBox) -> Bool

    DetBox::not_equal

    fn DetBox::not_equal(x : DetBox, y : DetBox) -> Bool

    DetBox::to_json

    fn DetBox::to_json(DetBox) -> Json

    DetBox::to_repr

    DetQuad

    pub(all) struct DetQuad {
    top_left : Point
    top_right : Point
    bottom_right : Point
    bottom_left : Point
    score : Double
    } derive(Eq, ToJson,
    Debug
    )

    Text region as an ordered quadrilateral: top-left, top-right, bottom-right, bottom-left. score is the mean detection probability of the component.

    DetQuad::bounds

    fn DetQuad::bounds(self : DetQuad, width : Int, height : Int) -> GlyphBox?

    Pixel bounds of the quadrilateral, clamped to the image.

    DetQuad::equal

    fn DetQuad::equal(DetQuad, DetQuad) -> Bool

    DetQuad::not_equal

    fn DetQuad::not_equal(x : DetQuad, y : DetQuad) -> Bool

    DetQuad::place

    fn DetQuad::place(self : DetQuad, width : Int, height : Int) -> PlacedText?

    Pair a source-space quadrilateral with its clamped pixel bounds.

    DetQuad::scale

    fn DetQuad::scale(self : DetQuad, scale_x : Double, scale_y : Double) -> DetQuad

    Multiply both axes. Used to map detection-map coordinates onto the source.

    DetQuad::to_json

    fn DetQuad::to_json(DetQuad) -> Json

    DetQuad::to_repr

    DetRegions

    pub(all) struct DetRegions {
    boxes : Array[DetBox]
    quads : Array[DetQuad]
    } derive(Eq, ToJson,
    Debug
    )

    Axis-aligned and rotated views of the same probability map, computed in one connected-component pass.

    DetRegions::equal

    fn DetRegions::equal(DetRegions, DetRegions) -> Bool

    DetRegions::not_equal

    fn DetRegions::not_equal(x : DetRegions, y : DetRegions) -> Bool

    DetRegions::to_json

    fn DetRegions::to_json(DetRegions) -> Json

    DetTensor

    pub(all) struct DetTensor {
    tensor : ChwTensor
    ratio_w : Double
    ratio_h : Double
    } derive(Eq,
    Debug
    )

    Detection input plus the scale from the resized map back to the source.

    DetTensor::equal

    fn DetTensor::equal(DetTensor, DetTensor) -> Bool

    DetTensor::not_equal

    fn DetTensor::not_equal(x : DetTensor, y : DetTensor) -> Bool

    GlyphBox

    pub(all) struct GlyphBox {
    x : Int
    y : Int
    width : Int
    height : Int
    score : Double
    } derive(Eq, ToJson,
    Debug
    )

    Axis-aligned text region in source pixels.

    GlyphBox::equal

    fn GlyphBox::equal(GlyphBox, GlyphBox) -> Bool

    GlyphBox::not_equal

    fn GlyphBox::not_equal(x : GlyphBox, y : GlyphBox) -> Bool

    GlyphBox::to_json

    fn GlyphBox::to_json(GlyphBox) -> Json

    GlyphBox::to_repr

    Page

    pub(all) struct Page {
    lines : Array[TextLine]
    } derive(Eq,
    Debug
    )

    A page of reading lines.
    impl Show for Page
    impl ToJson for Page

    Page::equal

    fn Page::equal(Page, Page) -> Bool

    Page::from_lines

    fn Page::from_lines(lines : Array[TextLine]) -> Page

    Build a page from lines that are already in reading order.

    Page::not_equal

    fn Page::not_equal(x : Page, y : Page) -> Bool

    Page::output

    fn Page::output(self : Page, logger : &Logger) -> Unit

    Page::render

    fn Page::render(self : Page, boxes? : Bool) -> String

    text when boxes is false. Otherwise one span per row: x,y,width,height<TAB>det_score<TAB>rec_confidence<TAB>text.

    Example

    test {
    let quad : @paddleocr.DetQuad = {
    top_left: { x: 1.0, y: 2.0, },
    top_right: { x: 4.0, y: 2.0, },
    bottom_right: { x: 4.0, y: 6.0, },
    bottom_left: { x: 1.0, y: 6.0, },
    score: 0.5,
    }
    let page = @paddleocr.Page::from_lines([
    @paddleocr.TextLine::from_spans([
    {
    quad,
    bounds: { x: 1, y: 2, width: 3, height: 4, score: 0.5, },
    text: "你好",
    det_score: 0.5,
    rec_confidence: 0.9,
    },
    ]),
    ])
    inspect(page.render(), content="你好")
    inspect(page.render(boxes=true), content="1,2,3,4\t0.5\t0.9\t你好")
    }

    Page::spans

    fn Page::spans(self : Page) -> Array[TextSpan]

    All spans in reading order.

    Page::text

    fn Page::text(self : Page) -> String

    Text of the page, with one reading line per row.

    Page::to_json

    fn Page::to_json(self : Page) -> Json

    Page::to_json_string

    fn Page::to_json_string(self : Page) -> String

    Serialize the page to a compact JSON string.

    Page::to_repr

    Page::to_string

    fn Page::to_string(self : Page) -> String

    PlacedText

    pub(all) struct PlacedText {
    quad : DetQuad
    bounds : GlyphBox
    } derive(Eq, ToJson,
    Debug
    )

    A quadrilateral placed on the source image, plus its pixel bounds.

    PlacedText::equal

    fn PlacedText::equal(PlacedText, PlacedText) -> Bool

    PlacedText::not_equal

    fn PlacedText::not_equal(x : PlacedText, y : PlacedText) -> Bool

    PlacedText::to_json

    fn PlacedText::to_json(PlacedText) -> Json

    Point

    pub(all) struct Point {
    x : Double
    y : Double
    } derive(Eq, ToJson,
    Debug
    )

    A point in image coordinates. Y grows downward.

    Point::equal

    fn Point::equal(Point, Point) -> Bool

    Point::not_equal

    fn Point::not_equal(x : Point, y : Point) -> Bool

    Point::to_json

    fn Point::to_json(Point) -> Json

    Point::to_repr

    RasterKind

    pub enum RasterKind {
    Png
    Jpeg
    Bmp
    } derive(Eq,
    Debug
    )

    Raster container recognized by the decoder.

    RasterKind::equal

    fn RasterKind::equal(RasterKind, RasterKind) -> Bool

    RasterKind::not_equal

    fn RasterKind::not_equal(x : RasterKind, y : RasterKind) -> Bool

    RecognizeOptions

    pub(all) struct RecognizeOptions {
    limit_side_len : Int
    det_thresh : Double
    box_thresh : Double
    unclip_ratio : Double
    min_size : Int
    rec_max_width : Int
    rec_thresh : Double
    } derive(Eq, ToJson,
    Debug
    )

    Detection size and DB thresholds for one call. standard matches PP-OCRv4.

    RecognizeOptions::default

    RecognizeOptions::equal

    RecognizeOptions::not_equal

    fn RecognizeOptions::not_equal(x : RecognizeOptions, y : RecognizeOptions) -> Bool

    RecognizeOptions::standard

    PP-OCRv4 defaults: long side 960, threshold 0.3, box threshold 0.6, unclip 1.5, minimum component side 3, recognition width 320, recognition confidence 0.5.

    Example

    test {
    let options = @paddleocr.RecognizeOptions::standard().with_limit(640)
    inspect(options.limit_side_len, content="640")
    inspect(options.box_thresh, content="0.6")
    inspect(options.min_size, content="3")
    }

    RecognizeOptions::to_json

    RecognizeOptions::with_box_thresh

    fn RecognizeOptions::with_box_thresh(self : RecognizeOptions, box_thresh : Double) -> RecognizeOptions

    Same options with a different DB box threshold.

    RecognizeOptions::with_det_thresh

    fn RecognizeOptions::with_det_thresh(self : RecognizeOptions, det_thresh : Double) -> RecognizeOptions

    Same options with a different DB foreground threshold.

    RecognizeOptions::with_limit

    fn RecognizeOptions::with_limit(self : RecognizeOptions, limit_side_len : Int) -> RecognizeOptions

    Same options with a different detection long-side limit.

    RecognizeOptions::with_min_size

    fn RecognizeOptions::with_min_size(self : RecognizeOptions, min_size : Int) -> RecognizeOptions

    Same options with a different minimum component side.

    RecognizeOptions::with_rec_max_width

    fn RecognizeOptions::with_rec_max_width(self : RecognizeOptions, rec_max_width : Int) -> RecognizeOptions

    Same options with a different recognition width cap.

    RecognizeOptions::with_rec_thresh

    fn RecognizeOptions::with_rec_thresh(self : RecognizeOptions, rec_thresh : Double) -> RecognizeOptions

    Same options with a different recognition confidence threshold.

    RecognizeOptions::with_unclip_ratio

    fn RecognizeOptions::with_unclip_ratio(self : RecognizeOptions, unclip_ratio : Double) -> RecognizeOptions

    Same options with a different DB unclip ratio.

    Rgba

    pub(all) struct Rgba {
    width : Int
    height : Int
    pixels : Bytes
    } derive(Eq,
    Debug
    )

    8-bit RGBA image, row-major, top-left origin. Alpha is kept and ignored by the recognition normalizer.

    Rgba::crop

    fn Rgba::crop(self : Rgba, x : Int, y : Int, width : Int, height : Int) -> Rgba raise OcrError

    Crop a rectangle and clamp it to the image. The result is still RGBA.

    Rgba::equal

    fn Rgba::equal(Rgba, Rgba) -> Bool

    Rgba::from_rgba

    fn Rgba::from_rgba(width : Int, height : Int, pixels : Bytes) -> Rgba raise OcrError

    Build an image from packed RGBA bytes.

    Example

    test {
    let image = @paddleocr.Rgba::from_rgba(1, 1, [
    b'\x01', b'\x02', b'\x03', b'\xff',
    ])
    inspect(image.width, content="1")
    inspect(image.height, content="1")
    }

    Rgba::not_equal

    fn Rgba::not_equal(x : Rgba, y : Rgba) -> Bool

    Rgba::to_repr

    Rgba::warp

    fn Rgba::warp(self : Rgba, quad : DetQuad) -> Rgba raise OcrError

    Perspective-crop quad into a horizontal strip.

    A strip at least 1.5 times taller than it is wide is rotated a quarter turn counter-clockwise, which is how PP-OCR turns vertical text sideways.

    TextLine

    pub(all) struct TextLine {
    spans : Array[TextSpan]
    } derive(Eq,
    Debug
    )

    Spans that share a reading line, already ordered left to right. text is computed from spans, so the two cannot disagree.
    impl ToJson for TextLine

    TextLine::equal

    fn TextLine::equal(TextLine, TextLine) -> Bool

    TextLine::from_spans

    fn TextLine::from_spans(spans : Array[TextSpan]) -> TextLine

    Build a line from spans that are already in left-to-right order.

    TextLine::not_equal

    fn TextLine::not_equal(x : TextLine, y : TextLine) -> Bool

    TextLine::text

    fn TextLine::text(self : TextLine) -> String

    Concatenate the span texts without inserting separators.

    TextLine::to_json

    fn TextLine::to_json(self : TextLine) -> Json

    TextLine::to_repr

    TextSpan

    pub(all) struct TextSpan {
    quad : DetQuad
    bounds : GlyphBox
    text : String
    det_score : Double
    rec_confidence : Double
    } derive(Eq, ToJson,
    Debug
    )

    One recognized text region. quad keeps the rotated geometry, bounds is the axis-aligned rectangle used by Page::render, and the two scores keep detection and recognition confidence separate.

    TextSpan::equal

    fn TextSpan::equal(TextSpan, TextSpan) -> Bool

    TextSpan::not_equal

    fn TextSpan::not_equal(x : TextSpan, y : TextSpan) -> Bool

    TextSpan::to_json

    fn TextSpan::to_json(TextSpan) -> Json

    TextSpan::to_repr

    ctc_decode

    fn ctc_decode(logits : ArrayView[Float], time~ : Int, classes~ : Int, charset~ : Charset) -> CtcText raise OcrError

    Greedy CTC decode with a confidence in 0.0..=1.0 when the row is a probability distribution or a softmax of logits.

    ctc_greedy

    fn ctc_greedy(logits : ArrayView[Float], time~ : Int, classes~ : Int, charset~ : Charset) -> String raise OcrError

    Greedy CTC decode. logits is row-major [time, classes].

    Confidence is the mean score of the emitted characters. A row that already sums to one is treated as probabilities; otherwise the score is a softmax.

    Example

    test {
    let charset = @paddleocr.Charset::from_text("a\nb\n")
    let logits : Array[Float] = [
    0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
    ]
    inspect(
    @paddleocr.ctc_greedy(logits, time=4, classes=3, charset~),
    content="ab",
    )
    }

    db_boxes

    fn db_boxes(prob : Array[Float], height~ : Int, width~ : Int, thresh? : Double, box_thresh? : Double, unclip_ratio? : Double, min_size? : Int) -> Array[DetBox] raise OcrError

    Connected-component boxes for a DBNet probability map.

    The map is row-major. Components below min_size or box_thresh are dropped. The remaining rectangle is expanded by the DB unclip distance area * unclip_ratio / perimeter and clipped to the map.

    db_quads

    fn db_quads(prob : Array[Float], height~ : Int, width~ : Int, thresh? : Double, box_thresh? : Double, unclip_ratio? : Double, min_size? : Int) -> Array[DetQuad] raise OcrError

    Connected components as minimum-area rectangles, expanded by the DB unclip distance. Short sides below min_size and means below box_thresh are dropped. Corners are ordered from the top-left.

    db_regions

    fn db_regions(prob : Array[Float], height~ : Int, width~ : Int, thresh? : Double, box_thresh? : Double, unclip_ratio? : Double, min_size? : Int) -> DetRegions raise OcrError

    Compute axis-aligned boxes and minimum-area quads in one pass.

    The two arrays use the same component scan, but each keeps its own min_size rule: boxes check the bounding rectangle, quads check the fitted rectangle.

    decode_raster

    fn decode_raster(bytes : Bytes) -> Rgba raise OcrError

    Decode a PNG, baseline JPEG, or BMP image into RGBA.

    group_lines

    fn group_lines(boxes : Array[GlyphBox]) -> Array[Array[GlyphBox]]

    Group boxes into reading lines. Lines run top to bottom, and boxes inside a line run left to right.

    group_placed

    fn group_placed(items : Array[PlacedText]) -> Array[Array[PlacedText]]

    Group placed quadrilaterals with the same reading-order rule as group_lines.

    limit_side_size

    fn limit_side_size(width : Int, height : Int, limit~ : Int) -> (Int, Int) raise OcrError

    Resize size used by PP-OCR detection: limit the long side, then round both sides to a multiple of 32.

    Example

    test {
    let (width, height) = @paddleocr.limit_side_size(10, 20, limit=960)
    inspect(width, content="32")
    inspect(height, content="32")
    }

    map_det_box

    fn map_det_box(box : DetBox, ratio_w~ : Double, ratio_h~ : Double, width~ : Int, height~ : Int) -> GlyphBox?

    Scale a detection box back to source pixels.

    min_area_rect

    fn min_area_rect(points : Array[Point]) -> DetQuad raise OcrError

    Smallest-area rectangle around points, ordered clockwise from the top-left in image coordinates.

    Example

    test {
    let quad = @paddleocr.min_area_rect([
    { x: 0.0, y: 0.0, },
    { x: 2.0, y: 0.0, },
    { x: 2.0, y: 1.0, },
    { x: 0.0, y: 1.0, },
    ])
    inspect(quad.top_left.x, content="0")
    inspect(quad.top_right.x, content="2")
    inspect(quad.bottom_left.y, content="1")
    }

    order_corners

    fn order_corners(corners : Array[Point], score? : Double) -> DetQuad raise OcrError

    Order four corners as top-left, top-right, bottom-right, bottom-left.

    prepare_det

    fn prepare_det(image : Rgba, limit_side_len? : Int) -> DetTensor raise OcrError

    Build the PP-OCR detection tensor. ImageNet mean and std, NCHW BGR.

    prepare_rec

    fn prepare_rec(image : Rgba, height? : Int, max_width? : Int, fixed_width? : Int) -> ChwTensor raise OcrError

    Build the PP-OCR recognition tensor. Height defaults to 48. A missing fixed_width keeps the aspect width, capped by max_width. A fixed width pads the normalized tensor on the right with zeros.

    sniff_raster

    fn sniff_raster(bytes : Bytes) -> RasterKind raise OcrError

    Identify PNG, JPEG, or BMP bytes.

    Example

    test {
    debug_inspect(
    @paddleocr.sniff_raster([b'\xff', b'\xd8', b'\xff']),
    content="Jpeg",
    )
    }

    unclip_quad

    fn unclip_quad(quad : DetQuad, ratio~ : Double) -> DetQuad raise OcrError

    Expand a convex quadrilateral by the DB distance area * ratio / perimeter.