rusto-rs 0.2.0

RustO! - Pure Rust OCR library based on RapidOCR with PaddleOCR engine
use std::time::Instant;

use ndarray::{Array4, ArrayD};

use crate::engine::{EngineError, MnnSession};
use crate::postprocess::{DBPostProcess, TextDetOutput};
use crate::preprocess::DetPreProcess;
use crate::types::DetConfig;

#[cfg(feature = "use-opencv")]
use opencv::{core::Mat, prelude::MatTraitConst};

#[cfg(not(feature = "use-opencv"))]
use crate::image_impl::Mat;

pub struct TextDetector {
    pub cfg: DetConfig,
    pub session: MnnSession,
    pub postprocess: DBPostProcess,
}

impl TextDetector {
    pub fn new(cfg: DetConfig) -> Result<Self, EngineError> {
        let session = MnnSession::from_det_config(&cfg)?;
        let postprocess = DBPostProcess::new(
            cfg.thresh,
            cfg.box_thresh,
            cfg.max_candidates,
            cfg.unclip_ratio,
            cfg.use_dilation,
        );
        Ok(Self {
            cfg,
            session,
            postprocess,
        })
    }

    pub fn run(&mut self, img: &Mat) -> Result<TextDetOutput, EngineError> {
        let start = Instant::now();

        let ori_h = img.rows();
        let ori_w = img.cols();
        let max_wh = ori_h.max(ori_w);

        let limit_side_len = if self.cfg.limit_type == "min" {
            self.cfg.limit_side_len
        } else if max_wh < 960 {
            960
        } else if max_wh < 1500 {
            1500
        } else {
            2000
        };

        let pre = DetPreProcess::new(
            limit_side_len,
            self.cfg.limit_type.clone(),
            self.cfg.mean,
            self.cfg.std,
        );
        let input = pre.run(img)?;
        let input_dyn: ArrayD<f32> = input.into_dyn();
        let preds_dyn = self.session.run(input_dyn)?;
        let preds: Array4<f32> = preds_dyn
            .into_dimensionality()
            .map_err(|_| EngineError::InvalidInputShape)?;
        let (mut boxes, scores) = self.postprocess.process(&preds, ori_h, ori_w)?;
        if boxes.is_empty() {
            return Ok(TextDetOutput::empty());
        }

        self.sorted_boxes(&mut boxes);
        let elapse = start.elapsed().as_secs_f64();

        Ok(TextDetOutput {
            img: None,
            boxes: Some(boxes),
            scores: Some(scores),
            elapse,
        })
    }

    #[cfg(feature = "use-opencv")]
    fn sorted_boxes(&self, dt_boxes: &mut Vec<[opencv::core::Point2f; 4]>) {
        dt_boxes.sort_by(|a, b| {
            let ay = a[0].y as i32;
            let by = b[0].y as i32;
            if ay != by {
                ay.cmp(&by)
            } else {
                let ax = a[0].x as i32;
                let bx = b[0].x as i32;
                ax.cmp(&bx)
            }
        });
    }

    #[cfg(not(feature = "use-opencv"))]
    fn sorted_boxes(&self, dt_boxes: &mut Vec<[crate::image_impl::Point2f; 4]>) {
        dt_boxes.sort_by(|a, b| {
            let ay = a[0].y as i32;
            let by = b[0].y as i32;
            if ay != by {
                ay.cmp(&by)
            } else {
                let ax = a[0].x as i32;
                let bx = b[0].x as i32;
                ax.cmp(&bx)
            }
        });

    }
}