visual-cortex-ocr-onnx 0.4.0

PaddleOCR detection+recognition via ONNX Runtime for visual-cortex, with pinned, checksummed model download.
Documentation
//! End-to-end test against the real models. Downloads ~15 MB on first run:
//!   cargo test -p visual-cortex-ocr-onnx --test e2e -- --ignored

use visual_cortex_capture::Frame;
use visual_cortex_ocr_onnx::PaddleOcr;
use visual_cortex_vision::OcrEngine;

#[tokio::test]
#[ignore = "downloads models (~15 MB) and runs real inference"]
async fn recognizes_fixture_text() {
    let png = include_bytes!("fixtures/hp_1234.png");
    let img = image::load_from_memory(png)
        .expect("decode fixture")
        .to_rgba8();
    let (w, h) = img.dimensions();
    // RGBA -> BGRA frame bytes.
    let bgra: Vec<u8> = img
        .pixels()
        .flat_map(|p| [p.0[2], p.0[1], p.0[0], p.0[3]])
        .collect();
    let frame = Frame::new(w, h, bgra).expect("frame");
    let view = frame
        .view(visual_cortex_capture::PxRect { x: 0, y: 0, w, h })
        .expect("view");

    let mut engine = PaddleOcr::new().await.expect("engine (network + cache)");
    let spans = engine.recognize(&view).expect("recognize");

    assert!(!spans.is_empty(), "no text found in fixture");
    let joined = spans
        .iter()
        .map(|s| s.text.as_str())
        .collect::<Vec<_>>()
        .join(" ");
    let normalized = joined.to_uppercase().replace(' ', "");
    assert!(
        normalized.contains("HP") && normalized.contains("1234"),
        "expected 'HP 1234' in output, got: {joined:?}"
    );
    for s in &spans {
        assert!(s.confidence.is_finite());
    }
}