use ndarray::ArrayD;
use crate::config::{Accelerator, Backend};
use crate::error::Result;
#[cfg(any(feature = "ort", feature = "tract", feature = "candle"))]
mod buffer;
#[cfg(feature = "candle")]
mod candle;
#[cfg(feature = "ort")]
mod ort_backend;
#[cfg(feature = "ort")]
mod ort_ep;
mod runtime;
#[cfg(feature = "tract")]
mod tract_backend;
pub use runtime::{OrtRuntimeInfo, RuntimeInfo, runtime_info, runtime_info_for};
pub type Tensor = ArrayD<f32>;
pub trait ModelBackend: Send + Sync {
fn name(&self) -> &str;
fn run(&self, input: Tensor) -> Result<Tensor>;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) enum NetworkKind {
#[default]
Detector,
Recognizer,
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct BackendOptions<'a> {
pub threads: usize,
pub fixed_input: Option<&'a [usize]>,
pub accelerator: Accelerator,
pub network: NetworkKind,
}
pub(crate) fn load_backend(
backend: Backend,
model_bytes: &[u8],
options: BackendOptions<'_>,
) -> Result<Box<dyn ModelBackend>> {
let _ = (
model_bytes,
options.threads,
options.fixed_input,
options.accelerator,
options.network,
);
match backend {
#[cfg(feature = "ort")]
Backend::Ort => Ok(Box::new(ort_backend::OrtBackend::load(model_bytes, options)?)),
#[cfg(feature = "candle")]
Backend::Candle => Ok(Box::new(candle::CandleBackend::load(model_bytes, options)?)),
#[cfg(feature = "tract")]
Backend::Tract => Ok(Box::new(tract_backend::TractBackend::load(
model_bytes,
options.fixed_input,
)?)),
#[cfg(not(all(feature = "ort", feature = "tract", feature = "candle")))]
other => Err(crate::error::OcrError::inference(format!(
"backend {other:?} is not compiled in (enable the matching cargo feature)"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_default_the_model_role_to_the_detector() {
assert_eq!(BackendOptions::default().network, NetworkKind::Detector);
}
#[cfg(not(feature = "candle"))]
#[test]
fn should_report_an_uncompiled_backend_by_name() {
use crate::error::OcrError;
let Err(error) = load_backend(Backend::Candle, &[], BackendOptions::default()) else {
panic!("the candle backend is not compiled in, so loading it must fail");
};
let OcrError::Inference { message, .. } = &error else {
panic!("expected an inference error, got {error:?}");
};
assert!(
message.contains("Candle") && message.contains("not compiled in"),
"message must name the backend and the cause: {message}"
);
}
}