Environment
- paddlex: 3.7.2
- paddleocr: 3.7.0
- paddlepaddle-gpu: 3.3.0
- Base commit:
ffb64904 (2026-06-25)
- Layout model:
PP-DocLayout_plus-L architecture, fine-tuned on a custom 11-category label set (not the stock ~20-category scheme)
Summary
When PPStructureV3 is configured with a custom-trained layout_detection_model_dir whose category scheme differs from the stock model's, res["parsing_res_list"] silently drops any layout box that is geometrically nested inside another detected box (e.g. a caption/headline overlaid directly on a photo -- common in newspaper/magazine layouts). Across our 47-image validation set, 46 of 47 images were affected, in some cases collapsing a 12-box page down to 1.
Critically, res["layout_det_res"]["boxes"] (the raw detection output) is complete and correct on every single one of those 47 images -- matching a standalone create_model("PP-DocLayout_plus-L", model_dir=...).predict() call exactly, same boxes, same confidence scores. The loss happens specifically in the structured-parsing assembly step (get_layout_parsing_res -> get_layout_parsing_objects -> sort_layout_parsing_blocks -> xycut_enhanced).
Root cause (as far as we traced it)
Two separate config dictionaries in this pipeline are keyed by the stock label scheme, and silently misbehave for a custom one:
PPStructureV3's own exposed layout_threshold / layout_merge_bboxes_mode constructor kwargs are dicts keyed by class ID (0-19), tuned for the stock category order (e.g. 0: "large" # paragraph_title, 7: "large" # formula). A custom model with a different 11-class scheme at the same IDs silently inherits whatever stock class happens to share that ID -- e.g. our class 7, "picture", inherited the threshold/merge-mode meant for stock class 7, "formula". This part is fixable by explicitly overriding both dicts for all of a custom model's class IDs.
- Deeper in the pipeline,
layout_parsing/layout_objects.py's LayoutRegion.init_region_info_from_layout and pipeline_v2.py's reading-order assignment both consult BLOCK_LABEL_MAP, which is keyed by label name (strings like "paragraph_title", "image", "header"). None of a custom model's label strings match any stock name, so every custom-labeled block (including ones that should be recognized as vision_labels, e.g. our "picture") falls through to a generic bucket. get_layout_parsing_res's order_index assignment loop also only assigns an order to labels in BLOCK_LABEL_MAP["visualize_index_labels"] -- a custom-labeled block never gets one. We could not find any constructor-level override for this second part.
The combination appears to make xycut_enhanced's recursive X/Y-cut region-splitting (which assumes non-overlapping blocks) unable to reconcile genuinely nested boxes for a custom label scheme, and it silently drops the nested one rather than erroring.
Minimal repro
from paddleocr import PPStructureV3
pipeline = PPStructureV3(
layout_detection_model_name="PP-DocLayout_plus-L",
layout_detection_model_dir="<path to a model fine-tuned on a non-stock label set>",
use_doc_orientation_classify=False,
use_doc_unwarping=False,
layout_threshold={i: 0.5 for i in range(N)}, # N = your model's class count
layout_merge_bboxes_mode={i: "union" for i in range(N)},
)
res = list(pipeline.predict("<image with overlapping/nested regions, e.g. caption on a photo>"))[0]
print(len(res["layout_det_res"]["boxes"])) # correct, complete
print(len(res["parsing_res_list"])) # incomplete -- fewer than the line above
Impact
Any user fine-tuning PP-DocLayout_plus-L (or presumably other layout models used by PPStructureV3) on a custom category scheme that includes classes which can legitimately contain other classes (e.g. an image/photo class with overlaid text) will silently lose most of their page content from parsing_res_list, with no error or warning -- layout_det_res looking correct makes this easy to miss. In our validation, this meant 46/47 pages returning drastically incomplete structured output while looking like they'd worked.
Verified workaround
Replacing the body of get_layout_parsing_res (in paddlex/inference/pipelines/layout_parsing/pipeline_v2.py) with a simple geometric assignment -- every OCR line to its smallest-area enclosing layout_det_res box, sorted top-to-bottom/left-to-right for reading order -- fully resolves this for our dataset (0/47 mismatches after the change vs. 46/47 before). This bypasses standardized_data/get_layout_parsing_objects/sort_layout_parsing_blocks/xycut_enhanced entirely, so it also drops table/seal/chart/formula special-casing, which our model doesn't use -- not proposing this as the upstream fix, just confirming where the problem lives and that a label-scheme-agnostic replacement resolves it completely.
Possible fix directions (not proposing a specific patch -- happy to help test one)
- Have
BLOCK_LABEL_MAP-dependent logic fall back to a sensible default (rather than silently excluding) for label strings it doesn't recognize, and/or expose it as a user-overridable mapping the same way layout_threshold/layout_merge_bboxes_mode already are.
- Have
xycut_enhanced (or the region-matching step feeding it) explicitly detect containment/nesting and represent it (e.g. via LayoutBlock.child_blocks, which already exists as a field but isn't populated by this path) rather than dropping the contained block.
- At minimum, log a warning when a detected
layout_det_res box doesn't make it into the final parsing_res_list, so this failure mode isn't silent.
Happy to provide the full fine-tuned model / dataset details or a minimal repro image if useful.
Environment
ffb64904(2026-06-25)PP-DocLayout_plus-Larchitecture, fine-tuned on a custom 11-category label set (not the stock ~20-category scheme)Summary
When
PPStructureV3is configured with a custom-trainedlayout_detection_model_dirwhose category scheme differs from the stock model's,res["parsing_res_list"]silently drops any layout box that is geometrically nested inside another detected box (e.g. a caption/headline overlaid directly on a photo -- common in newspaper/magazine layouts). Across our 47-image validation set, 46 of 47 images were affected, in some cases collapsing a 12-box page down to 1.Critically,
res["layout_det_res"]["boxes"](the raw detection output) is complete and correct on every single one of those 47 images -- matching a standalonecreate_model("PP-DocLayout_plus-L", model_dir=...).predict()call exactly, same boxes, same confidence scores. The loss happens specifically in the structured-parsing assembly step (get_layout_parsing_res->get_layout_parsing_objects->sort_layout_parsing_blocks->xycut_enhanced).Root cause (as far as we traced it)
Two separate config dictionaries in this pipeline are keyed by the stock label scheme, and silently misbehave for a custom one:
PPStructureV3's own exposedlayout_threshold/layout_merge_bboxes_modeconstructor kwargs are dicts keyed by class ID (0-19), tuned for the stock category order (e.g.0: "large" # paragraph_title,7: "large" # formula). A custom model with a different 11-class scheme at the same IDs silently inherits whatever stock class happens to share that ID -- e.g. our class 7,"picture", inherited the threshold/merge-mode meant for stock class 7,"formula". This part is fixable by explicitly overriding both dicts for all of a custom model's class IDs.layout_parsing/layout_objects.py'sLayoutRegion.init_region_info_from_layoutandpipeline_v2.py's reading-order assignment both consultBLOCK_LABEL_MAP, which is keyed by label name (strings like"paragraph_title","image","header"). None of a custom model's label strings match any stock name, so every custom-labeled block (including ones that should be recognized asvision_labels, e.g. our"picture") falls through to a generic bucket.get_layout_parsing_res'sorder_indexassignment loop also only assigns an order to labels inBLOCK_LABEL_MAP["visualize_index_labels"]-- a custom-labeled block never gets one. We could not find any constructor-level override for this second part.The combination appears to make
xycut_enhanced's recursive X/Y-cut region-splitting (which assumes non-overlapping blocks) unable to reconcile genuinely nested boxes for a custom label scheme, and it silently drops the nested one rather than erroring.Minimal repro
Impact
Any user fine-tuning
PP-DocLayout_plus-L(or presumably other layout models used byPPStructureV3) on a custom category scheme that includes classes which can legitimately contain other classes (e.g. an image/photo class with overlaid text) will silently lose most of their page content fromparsing_res_list, with no error or warning --layout_det_reslooking correct makes this easy to miss. In our validation, this meant 46/47 pages returning drastically incomplete structured output while looking like they'd worked.Verified workaround
Replacing the body of
get_layout_parsing_res(inpaddlex/inference/pipelines/layout_parsing/pipeline_v2.py) with a simple geometric assignment -- every OCR line to its smallest-area enclosinglayout_det_resbox, sorted top-to-bottom/left-to-right for reading order -- fully resolves this for our dataset (0/47 mismatches after the change vs. 46/47 before). This bypassesstandardized_data/get_layout_parsing_objects/sort_layout_parsing_blocks/xycut_enhancedentirely, so it also drops table/seal/chart/formula special-casing, which our model doesn't use -- not proposing this as the upstream fix, just confirming where the problem lives and that a label-scheme-agnostic replacement resolves it completely.Possible fix directions (not proposing a specific patch -- happy to help test one)
BLOCK_LABEL_MAP-dependent logic fall back to a sensible default (rather than silently excluding) for label strings it doesn't recognize, and/or expose it as a user-overridable mapping the same waylayout_threshold/layout_merge_bboxes_modealready are.xycut_enhanced(or the region-matching step feeding it) explicitly detect containment/nesting and represent it (e.g. viaLayoutBlock.child_blocks, which already exists as a field but isn't populated by this path) rather than dropping the contained block.layout_det_resbox doesn't make it into the finalparsing_res_list, so this failure mode isn't silent.Happy to provide the full fine-tuned model / dataset details or a minimal repro image if useful.