use crate::layout::types::{LayoutClass, LayoutDetection};
fn class_threshold(class: LayoutClass) -> f32 {
match class {
LayoutClass::SectionHeader
| LayoutClass::Title
| LayoutClass::Code
| LayoutClass::Form
| LayoutClass::KeyValueRegion => 0.45,
_ => 0.50,
}
}
pub fn apply_heuristics(detections: &mut Vec<LayoutDetection>, page_width: f32, page_height: f32) {
detections.retain(|d| d.confidence >= class_threshold(d.class));
detections.retain(|d| {
if d.class == LayoutClass::Picture {
d.bbox.page_coverage(page_width, page_height) < 0.9
} else {
true
}
});
for d in detections.iter_mut() {
if matches!(d.class, LayoutClass::Table | LayoutClass::Picture)
&& d.bbox.page_coverage(page_width, page_height) < 0.03
&& d.confidence < 0.7
{
d.class = LayoutClass::Text;
}
}
for _ in 0..3 {
let prev_len = detections.len();
resolve_overlaps(detections);
if detections.len() == prev_len {
break;
}
}
resolve_kvr_table_overlap(detections);
}
fn resolve_overlaps(detections: &mut Vec<LayoutDetection>) {
let n = detections.len();
let mut remove = vec![false; n];
for i in 0..n {
if remove[i] {
continue;
}
for j in (i + 1)..n {
if remove[j] {
continue;
}
let iou = detections[i].bbox.iou(&detections[j].bbox);
let containment_i_of_j = detections[i].bbox.containment_of(&detections[j].bbox);
let containment_j_of_i = detections[j].bbox.containment_of(&detections[i].bbox);
if iou < 0.8 && containment_i_of_j < 0.8 && containment_j_of_i < 0.8 {
continue;
}
let remove_idx = pick_removal(&detections[i], &detections[j], containment_i_of_j);
if remove_idx == 0 {
remove[i] = true;
} else {
remove[j] = true;
}
}
}
let mut idx = 0;
detections.retain(|_| {
let k = !remove[idx];
idx += 1;
k
});
}
fn pick_removal(a: &LayoutDetection, b: &LayoutDetection, containment_a_of_b: f32) -> usize {
if a.class == LayoutClass::ListItem && b.class == LayoutClass::Text {
let area_ratio = a.bbox.area() / b.bbox.area().max(1e-6);
if (0.8..=1.2).contains(&area_ratio) {
return 1; }
}
if b.class == LayoutClass::ListItem && a.class == LayoutClass::Text {
let area_ratio = b.bbox.area() / a.bbox.area().max(1e-6);
if (0.8..=1.2).contains(&area_ratio) {
return 0; }
}
if a.class == LayoutClass::Code && containment_a_of_b > 0.8 {
return 1; }
if b.class == LayoutClass::Code {
let containment_b_of_a = b.bbox.containment_of(&a.bbox);
if containment_b_of_a > 0.8 {
return 0; }
}
if a.class == LayoutClass::Text
&& matches!(b.class, LayoutClass::Table | LayoutClass::Picture)
&& a.confidence >= b.confidence
{
return 1; }
if b.class == LayoutClass::Text
&& matches!(a.class, LayoutClass::Table | LayoutClass::Picture)
&& b.confidence >= a.confidence
{
return 0; }
if a.confidence >= b.confidence { 1 } else { 0 }
}
fn resolve_kvr_table_overlap(detections: &mut Vec<LayoutDetection>) {
let n = detections.len();
let mut remove = vec![false; n];
for i in 0..n {
if remove[i] || detections[i].class != LayoutClass::KeyValueRegion {
continue;
}
for j in 0..n {
if i == j || remove[j] || detections[j].class != LayoutClass::Table {
continue;
}
let overlap = detections[j].bbox.containment_of(&detections[i].bbox);
let conf_diff = (detections[i].confidence - detections[j].confidence).abs();
if overlap > 0.9 && conf_diff < 0.1 {
remove[i] = true;
break;
}
}
}
let mut idx = 0;
detections.retain(|_| {
let k = !remove[idx];
idx += 1;
k
});
}