edgeparse_core/pdf/
image_region.rs1use image::GrayImage;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ImageRegionKind {
12 Table,
14 Prose,
16 Skip,
18}
19
20pub trait ImageRegionClassifier: Send + Sync {
22 fn classify(&self, gray: &GrayImage) -> ImageRegionKind;
24}
25
26pub trait TableStructureModel: Send + Sync {
32 fn refine_table(&self, gray: &GrayImage) -> bool {
35 let _ = gray;
36 false
37 }
38}
39
40#[derive(Debug, Default, Clone, Copy)]
42pub struct NullTableStructureModel;
43
44impl TableStructureModel for NullTableStructureModel {}
45
46#[derive(Debug, Clone)]
48pub struct ClassifyingRegionRouter<C, M> {
49 pub classifier: C,
51 pub table_model: M,
53}
54
55impl<C: ImageRegionClassifier, M: TableStructureModel> ClassifyingRegionRouter<C, M> {
56 pub fn new(classifier: C, table_model: M) -> Self {
58 Self {
59 classifier,
60 table_model,
61 }
62 }
63
64 pub fn route(&self, gray: &GrayImage) -> (ImageRegionKind, bool) {
66 let kind = self.classifier.classify(gray);
67 let model_hit =
68 matches!(kind, ImageRegionKind::Table) && self.table_model.refine_table(gray);
69 (kind, model_hit)
70 }
71}
72
73#[derive(Debug, Default, Clone, Copy)]
76pub struct HeuristicImageRegionClassifier;
77
78impl ImageRegionClassifier for HeuristicImageRegionClassifier {
79 fn classify(&self, gray: &GrayImage) -> ImageRegionKind {
80 #[cfg(not(target_arch = "wasm32"))]
81 {
82 use crate::pdf::raster_table_ocr::classify_raster_region;
83 classify_raster_region(gray)
84 }
85 #[cfg(target_arch = "wasm32")]
86 {
87 let _ = gray;
88 ImageRegionKind::Table
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct OcrBudget {
96 deadline: Option<std::time::Instant>,
97}
98
99impl OcrBudget {
100 pub fn from_millis(ms: Option<u64>) -> Self {
102 let deadline = ms.map(|m| std::time::Instant::now() + std::time::Duration::from_millis(m));
103 Self { deadline }
104 }
105
106 pub fn unlimited() -> Self {
108 Self { deadline: None }
109 }
110
111 pub fn exhausted(&self) -> bool {
113 self.deadline
114 .is_some_and(|d| std::time::Instant::now() >= d)
115 }
116
117 pub fn allow(&self) -> bool {
119 !self.exhausted()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 struct AlwaysTable;
128 impl TableStructureModel for AlwaysTable {
129 fn refine_table(&self, _: &GrayImage) -> bool {
130 true
131 }
132 }
133
134 #[test]
135 fn router_invokes_table_model_on_table_kind() {
136 let router = ClassifyingRegionRouter::new(HeuristicImageRegionClassifier, AlwaysTable);
137 let gray = GrayImage::new(8, 8);
138 let (kind, hit) = router.route(&gray);
139 if kind == ImageRegionKind::Table {
141 assert!(hit);
142 } else {
143 assert!(!hit);
144 }
145 }
146}