feat(layout): relative valley column detection for justified text (#15)
* feat(layout): relative valley column detection for justified text Add fallback column detection using relative valley analysis for PDFs with justified text where item widths extend past gutter boundaries. The absolute valley detector fails on these layouts because gutter bins are at ~40% of peak (well above the 15% noise threshold). The relative valley detector smooths the histogram with a 5-bin moving average, finds local minima where contrast < 0.60 of surrounding peaks, and validates with peak balance >= 0.40. Limited to single best valley (max 2 columns) and requires >= 100 items per page. Tested on IRS Publication 17 (2002), a 289-page 2-column justified text document: column detection went from ~40 pages to 165 pages. 190 passed, 0 regressions across 191 eval PDFs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(layout): tighten relative valley thresholds to reduce false positives Reduce PEAK_WINDOW from 40 to 25 bins (50pt) so valleys are only validated against nearby peaks, not distant ones. Add MIN_PEAK_HEIGHT of 20 (smoothed) to reject sparse pages where histogram peaks are too low to indicate dense two-column text. Previous thresholds caused 13 regressions across the eval suite by splitting tables, TOCs, checklists, and forms. Now: 188 passed, 0 regressions (2 minor metadata-only diffs on IRS P17 and 9978293). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(layout): skip relative valley detection on pages with tables Table column gaps in the histogram look identical to text column gutters but the table pipeline already handles reading order for those pages. Pass page_has_table flag through detect_columns to suppress the relative valley fallback on pages where tables were detected. This eliminates all remaining regressions from relative valley detection: 190 passed, 0 regressions across 191 eval PDFs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(layout): prose density validation for relative valley detection Add columns_have_prose() to validate relative valley column splits. Checks that both sides of a proposed split contain paragraph-like content (fill ratio >= 40%, avg items/line <= 3.5) before committing to a column split. Combined with the table-page guard, this prevents false column splits on financial statements, forms, and tabular layouts where long labels or dot leaders fill the column width. Also tightens find_relative_valleys() thresholds (PEAK_WINDOW 40->25, MIN_PEAK_HEIGHT 5->20) to reduce false positive valley candidates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3a15235244
commit
95aac6a7cd
@@ -913,7 +913,7 @@ mod tests {
|
||||
items.push(make_item("Left text here", 72.0, y, 200.0));
|
||||
items.push(make_item("Right text here", 350.0, y, 200.0));
|
||||
}
|
||||
let cols = detect_columns(&items, 1);
|
||||
let cols = detect_columns(&items, 1, false);
|
||||
assert_eq!(cols.len(), 2, "Expected 2 columns, got {:?}", cols);
|
||||
assert!(cols[0].x_min < cols[1].x_min);
|
||||
}
|
||||
@@ -928,7 +928,7 @@ mod tests {
|
||||
items.push(make_item("Col two", 220.0, y, 140.0));
|
||||
items.push(make_item("Col three", 390.0, y, 140.0));
|
||||
}
|
||||
let cols = detect_columns(&items, 1);
|
||||
let cols = detect_columns(&items, 1, false);
|
||||
assert_eq!(cols.len(), 3, "Expected 3 columns, got {:?}", cols);
|
||||
}
|
||||
|
||||
@@ -946,7 +946,7 @@ mod tests {
|
||||
let y = 700.0 - (i as f32) * 14.0;
|
||||
items.push(make_item("wide", 72.0, y, 320.0));
|
||||
}
|
||||
let cols = detect_columns(&items, 1);
|
||||
let cols = detect_columns(&items, 1, false);
|
||||
assert!(
|
||||
cols.len() >= 2,
|
||||
"Width bleed should not prevent column detection, got {:?}",
|
||||
@@ -967,7 +967,7 @@ mod tests {
|
||||
468.0,
|
||||
));
|
||||
}
|
||||
let cols = detect_columns(&items, 1);
|
||||
let cols = detect_columns(&items, 1, false);
|
||||
assert!(
|
||||
cols.len() <= 1,
|
||||
"Full-width text should not be split into columns, got {:?}",
|
||||
|
||||
Reference in New Issue
Block a user