Layout Postprocessor#
Overview#
LayoutPostprocessor is the component in Docling's PDF pipeline that transforms raw layout model predictions into clean, structured clusters. It runs immediately after LayoutPredictor.predict_batch(), within LayoutModel.predict_layout(), and operates per-page.
The single public entry point is postprocess(), which runs this sequence:
- Filter and remap regular (non-special) clusters
- Assign text cells to clusters; recover orphaned cells as new
TEXTclusters - Iteratively refine bounding boxes and remove intra-type overlaps (up to 3 passes)
- Run cross-type conflict resolution on special clusters (wrappers + pictures)
- Filter full-page pictures and perform wrapper containment assignment
- Run intra-type overlap removal separately for pictures and wrappers
Cluster Taxonomy#
The postprocessor splits incoming Cluster objects (fields: id, label, bbox, confidence, cells, children) into two buckets:
- Regular clusters — everything not in
SPECIAL_TYPES - Special clusters —
WRAPPER_TYPES∪{PICTURE}
WRAPPER_TYPES = {TABLE, FORM, KEY_VALUE_REGION, DOCUMENT_INDEX}. Three separate SpatialClusterIndex instances are built at construction time — one each for regular, picture, and wrapper clusters — so overlap queries never cross type boundaries unintentionally.
Spatial Indexing: SpatialClusterIndex#
SpatialClusterIndex uses an R-tree (rtree.index.Index) for efficient 2D spatial queries.
find_candidates(bbox) returns cluster IDs whose bounding boxes intersect with the query bounding box using the R-tree index. Every overlap relation check_overlap accepts (IoU or containment ratio above a positive threshold) requires a positive intersection area, so the R-tree query already returns a superset of candidates that can pass. check_overlap() then filters to true overlaps using IoU or per-cluster containment (intersection_over_self) against configurable thresholds (default: overlap_threshold=0.8, containment_threshold=0.8).
Overlap Resolution: _remove_overlapping_clusters#
_remove_overlapping_clusters() uses a Union-Find structure to group all mutually overlapping clusters, then selects one winner per group via _select_best_cluster_from_group().
The selection logic in _should_prefer_cluster() applies label-priority rules before falling back to area/confidence thresholds:
| Rule | Condition | Outcome |
|---|---|---|
LIST_ITEM vs TEXT | bboxes within 20% area of each other | prefer LIST_ITEM |
CODE vs any | other bbox ≥80% inside CODE bbox | prefer CODE |
| Area/confidence fallback | area_ratio ≤ threshold AND conf_diff > threshold | reject candidate |
Type-specific OVERLAP_PARAMS control how aggressively clusters are removed:
| Type | area_threshold | conf_threshold |
|---|---|---|
regular | 1.3 | 0.05 |
picture | 2.0 | 0.3 |
wrapper | 2.0 | 0.2 |
Pictures and wrappers require substantially larger area ratios and confidence gaps before a cluster is dropped, making them harder to eliminate.
Cells from eliminated clusters are merged into the winner, then deduplicated .
Wrapper Containment Logic#
During _process_special_clusters(), every regular cluster whose bbox is ≥80% contained within a special cluster's bbox (intersection_over_self > 0.8) becomes a child of that wrapper .
For FORM and KEY_VALUE_REGION only, the wrapper's bbox is then re-fit to the tight union of its children's bounding boxes . TABLE and PICTURE bboxes are not resized. Children are sorted by minimum cell index (PDF print order).
After child assignment, all regular clusters that became children are removed from the top-level cluster list .
Cross-Type Conflict Resolution#
_handle_cross_type_overlaps() runs before containment assignment and enforces two TABLE-wins rules:
-
Wrapper vs TABLE: if any
WRAPPER_TYPEcluster overlaps aTABLEcluster by >90% (intersection_over_self) and its confidence advantage is <0.1, the wrapper is dropped . This means a region predicted as bothKEY_VALUE_REGIONandTABLEwill always surface asTABLE. -
Picture vs TABLE: if a
PICTUREcluster has IoU >0.8 against aTABLE, the picture is dropped . IoU (not containment) is used deliberately so a small genuine figure fully inside a large table is not removed.
Full-page pictures (bbox area >90% of page area) are filtered unconditionally .
Confidence Thresholds and Label Remapping#
CONFIDENCE_THRESHOLDS applies per-label gates: most labels use 0.5; SECTION_HEADER, TITLE, CODE, checkbox types, FORM, KEY_VALUE_REGION, and DOCUMENT_INDEX use 0.45.
After thresholding, LABEL_REMAPPING converts TITLE → SECTION_HEADER, so DocItemLabel.TITLE predictions never appear in the assembled document.
Key Source Files#
| File | Role |
|---|---|
docling/utils/layout_postprocessor.py | LayoutPostprocessor, SpatialClusterIndex, UnionFind |
docling/datamodel/base_models.py | Cluster datamodel |
docling/models/stages/layout/layout_model.py | Instantiation site; wires predictor → postprocessor |