use image::GrayImage;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageRegionKind {
Table,
Prose,
Skip,
}
pub trait ImageRegionClassifier: Send + Sync {
fn classify(&self, gray: &GrayImage) -> ImageRegionKind;
}
pub trait TableStructureModel: Send + Sync {
fn refine_table(&self, gray: &GrayImage) -> bool {
let _ = gray;
false
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NullTableStructureModel;
impl TableStructureModel for NullTableStructureModel {}
#[derive(Debug, Clone)]
pub struct ClassifyingRegionRouter<C, M> {
pub classifier: C,
pub table_model: M,
}
impl<C: ImageRegionClassifier, M: TableStructureModel> ClassifyingRegionRouter<C, M> {
pub fn new(classifier: C, table_model: M) -> Self {
Self {
classifier,
table_model,
}
}
pub fn route(&self, gray: &GrayImage) -> (ImageRegionKind, bool) {
let kind = self.classifier.classify(gray);
let model_hit =
matches!(kind, ImageRegionKind::Table) && self.table_model.refine_table(gray);
(kind, model_hit)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HeuristicImageRegionClassifier;
impl ImageRegionClassifier for HeuristicImageRegionClassifier {
fn classify(&self, gray: &GrayImage) -> ImageRegionKind {
#[cfg(not(target_arch = "wasm32"))]
{
use crate::pdf::raster_table_ocr::classify_raster_region;
classify_raster_region(gray)
}
#[cfg(target_arch = "wasm32")]
{
let _ = gray;
ImageRegionKind::Table
}
}
}
#[derive(Debug, Clone)]
pub struct OcrBudget {
deadline: Option<std::time::Instant>,
}
impl OcrBudget {
pub fn from_millis(ms: Option<u64>) -> Self {
let deadline = ms.map(|m| std::time::Instant::now() + std::time::Duration::from_millis(m));
Self { deadline }
}
pub fn unlimited() -> Self {
Self { deadline: None }
}
pub fn exhausted(&self) -> bool {
self.deadline
.is_some_and(|d| std::time::Instant::now() >= d)
}
pub fn allow(&self) -> bool {
!self.exhausted()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AlwaysTable;
impl TableStructureModel for AlwaysTable {
fn refine_table(&self, _: &GrayImage) -> bool {
true
}
}
#[test]
fn router_invokes_table_model_on_table_kind() {
let router = ClassifyingRegionRouter::new(HeuristicImageRegionClassifier, AlwaysTable);
let gray = GrayImage::new(8, 8);
let (kind, hit) = router.route(&gray);
if kind == ImageRegionKind::Table {
assert!(hit);
} else {
assert!(!hit);
}
}
}