use redact_paperasse_recognizers::{default_registry, Recognizer};
use crate::types::{BoundingBox, DetectionSource, Entity, ExtractedDocument, Span};
fn union_bbox(
word_boxes: &[crate::types::WordBox],
start: usize,
end: usize,
) -> Option<BoundingBox> {
let mut overlapping = word_boxes
.iter()
.filter(|wb| wb.span.start < end && start < wb.span.end)
.map(|wb| wb.bbox);
let first = overlapping.next()?;
let (page, mut x0, mut y0, mut x1, mut y1) = (
first.page,
first.x,
first.y,
first.x + first.width,
first.y + first.height,
);
for b in overlapping.filter(|b| b.page == page) {
x0 = x0.min(b.x);
y0 = y0.min(b.y);
x1 = x1.max(b.x + b.width);
y1 = y1.max(b.y + b.height);
}
Some(BoundingBox {
page,
x: x0,
y: y0,
width: x1 - x0,
height: y1 - y0,
})
}
pub struct TierA {
recognizers: Vec<Box<dyn Recognizer>>,
}
impl Default for TierA {
fn default() -> Self {
Self {
recognizers: default_registry(),
}
}
}
impl TierA {
pub fn analyze(
&self,
doc: &ExtractedDocument,
entities: Option<&[String]>,
score_threshold: Option<f32>,
) -> Vec<Entity> {
let mut out = Vec::new();
for recognizer in &self.recognizers {
if let Some(wanted) = entities {
if !wanted.iter().any(|e| e == recognizer.entity_type()) {
continue;
}
}
for m in recognizer.analyze(&doc.text) {
if let Some(threshold) = score_threshold {
if m.score < threshold {
continue;
}
}
let bbox = union_bbox(&doc.word_boxes, m.start, m.end);
out.push(Entity {
entity_type: recognizer.entity_type().to_string(),
span: Span {
start: m.start,
end: m.end,
},
score: m.score,
bbox,
source: DetectionSource::TierA,
});
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::WordBox;
fn word(start: usize, end: usize, page: u32, x: f32) -> WordBox {
WordBox {
span: Span { start, end },
bbox: BoundingBox {
page,
x,
y: 100.0,
width: 10.0,
height: 12.0,
},
}
}
#[test]
fn single_token_match_uses_its_own_box() {
let boxes = vec![word(0, 5, 1, 20.0)];
let bbox = union_bbox(&boxes, 0, 5).unwrap();
assert_eq!(bbox.x, 20.0);
assert_eq!(bbox.width, 10.0);
}
#[test]
fn multi_token_match_unions_every_overlapping_box() {
let boxes = vec![
word(0, 1, 1, 0.0), word(2, 4, 1, 15.0), word(5, 7, 1, 30.0), ];
let bbox = union_bbox(&boxes, 0, 7).unwrap();
assert_eq!(bbox.page, 1);
assert_eq!(bbox.x, 0.0);
assert_eq!(bbox.x + bbox.width, 40.0); }
#[test]
fn no_overlapping_box_returns_none() {
let boxes = vec![word(100, 105, 1, 0.0)];
assert!(union_bbox(&boxes, 0, 5).is_none());
}
#[test]
fn ignores_boxes_on_a_different_page_than_the_first_match() {
let boxes = vec![word(0, 3, 1, 0.0), word(3, 6, 2, 999.0)];
let bbox = union_bbox(&boxes, 0, 6).unwrap();
assert_eq!(bbox.page, 1);
assert_eq!(bbox.x, 0.0); }
}