use crate::ocr_error::OcrError;
use ndarray::{Array, Array4};
use ort::{
inputs,
session::{builder::GraphOptimizationLevel, Session},
value::Tensor,
};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableType {
Wired,
Wireless,
}
impl std::fmt::Display for TableType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TableType::Wired => write!(f, "wired"),
TableType::Wireless => write!(f, "wireless"),
}
}
}
#[derive(Debug)]
pub struct TableTypeClassifier {
session: Session,
}
impl TableTypeClassifier {
pub fn from_path(model_path: impl AsRef<Path>) -> Result<Self, OcrError> {
let session = Session::builder()?
.with_optimization_level(GraphOptimizationLevel::Level3)?
.commit_from_file(model_path)?;
Ok(Self { session })
}
pub fn classify(&self, image: &image::RgbImage) -> Result<(TableType, f32), OcrError> {
let blob = preprocess_lcnet(image);
let name = self.session.inputs[0].name.clone();
let outputs = self.session.run(
inputs![name => Tensor::from_array(blob)?]?
)?;
let (_, first) = outputs.iter().next()
.ok_or_else(|| OcrError::ModelOutput("TableTypeCls: nessun output".into()))?;
let (_, raw) = crate::compat::tensor_extract_with_shape_f32(&first)?;
let (idx, score) = argmax_f32(&raw);
Ok((if idx == 0 { TableType::Wired } else { TableType::Wireless }, score))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocOrientation {
Deg0,
Deg90,
Deg180,
Deg270,
}
impl DocOrientation {
pub fn degrees(self) -> u32 {
match self {
Self::Deg0 => 0,
Self::Deg90 => 90,
Self::Deg180 => 180,
Self::Deg270 => 270,
}
}
}
impl std::fmt::Display for DocOrientation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}°", self.degrees())
}
}
#[derive(Debug)]
pub struct DocOrientationClassifier {
session: Session,
}
impl DocOrientationClassifier {
pub fn from_path(model_path: impl AsRef<Path>) -> Result<Self, OcrError> {
let session = Session::builder()?
.with_optimization_level(GraphOptimizationLevel::Level3)?
.commit_from_file(model_path)?;
Ok(Self { session })
}
pub fn classify(&self, image: &image::RgbImage) -> Result<(DocOrientation, f32), OcrError> {
let blob = preprocess_lcnet_sq224(image);
let name = self.session.inputs[0].name.clone();
let outputs = self.session.run(
inputs![name => Tensor::from_array(blob)?]?
)?;
let (_, first) = outputs.iter().next()
.ok_or_else(|| OcrError::ModelOutput("DocOriCls: nessun output".into()))?;
let (_, raw) = crate::compat::tensor_extract_with_shape_f32(&first)?;
let (idx, score) = argmax_f32(&raw);
let orient = match idx {
0 => DocOrientation::Deg0,
1 => DocOrientation::Deg90,
2 => DocOrientation::Deg180,
_ => DocOrientation::Deg270,
};
Ok((orient, score))
}
}
fn preprocess_lcnet(image: &image::RgbImage) -> Array4<f32> {
preprocess_lcnet_wh(image, 192, 48)
}
fn preprocess_lcnet_sq224(image: &image::RgbImage) -> Array4<f32> {
preprocess_lcnet_wh(image, 224, 224)
}
fn preprocess_lcnet_wh(image: &image::RgbImage, w: u32, h: u32) -> Array4<f32> {
let resized = image::imageops::resize(image, w, h, image::imageops::FilterType::Triangle);
let mean = [0.485f32, 0.456, 0.406];
let std = [0.229f32, 0.224, 0.225];
let mut blob: Array4<f32> = Array::zeros((1, 3, h as usize, w as usize));
for y in 0..h as usize {
for x in 0..w as usize {
let p = resized.get_pixel(x as u32, y as u32);
for c in 0..3usize {
blob[[0, c, y, x]] = (p[c] as f32 / 255.0 - mean[c]) / std[c];
}
}
}
blob
}
fn argmax_f32(logits: &[f32]) -> (usize, f32) {
logits.iter().copied().enumerate()
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or((0, 0.0))
}