Files
paddlepaddle--paddleocr/tests/unit/test_patch_layout_parsing.py
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

152 lines
5.8 KiB
Python

# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the layout parsing patches (issue #17503).
These tests verify the fixed functions directly without importing the
full paddleocr package (which requires paddlex).
"""
from pathlib import Path
import importlib.util
import numpy as np
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
# Import the patch module directly to avoid triggering the full paddleocr
# import chain which requires paddlex
def _import_patch_module():
"""Import _patch_layout_parsing without triggering paddleocr.__init__."""
spec = importlib.util.spec_from_file_location(
"paddleocr._pipelines._patch_layout_parsing",
REPO_ROOT / "paddleocr" / "_pipelines" / "_patch_layout_parsing.py",
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
_patch_mod = _import_patch_module()
_fixed_calculate_overlap_ratio = _patch_mod._fixed_calculate_overlap_ratio
_fixed_calculate_minimum_enclosing_bbox = (
_patch_mod._fixed_calculate_minimum_enclosing_bbox
)
class TestFixedCalculateOverlapRatio:
"""Tests for the overflow-safe calculate_overlap_ratio."""
def test_normal_overlap(self):
bbox1 = [0, 0, 100, 100]
bbox2 = [50, 50, 150, 150]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union")
# Intersection: 50x50 = 2500
# Union: 10000 + 10000 - 2500 = 17500
expected = 2500.0 / 17500.0
assert abs(ratio - expected) < 1e-6
def test_no_overlap(self):
bbox1 = [0, 0, 50, 50]
bbox2 = [100, 100, 200, 200]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union")
assert ratio == 0.0
def test_complete_overlap(self):
bbox1 = [0, 0, 100, 100]
bbox2 = [0, 0, 100, 100]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union")
assert abs(ratio - 1.0) < 1e-6
def test_large_coordinates_no_overflow(self):
"""Verify no integer overflow with large coordinate values.
This is the primary bug from issue #17503. With int32 arithmetic,
inter_width * inter_height would overflow for large images
(e.g. after document unwarping).
"""
# Coordinates large enough to cause int32 overflow when multiplied
bbox1 = np.array([0, 0, 50000, 50000], dtype=np.int32)
bbox2 = np.array([0, 0, 50000, 50000], dtype=np.int32)
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union")
# Should be 1.0 (identical boxes), not corrupted by overflow
assert abs(ratio - 1.0) < 1e-6
def test_large_coordinates_partial_overlap(self):
"""Verify correct overlap ratio with large coordinates."""
bbox1 = np.array([0, 0, 60000, 60000], dtype=np.int32)
bbox2 = np.array([30000, 30000, 90000, 90000], dtype=np.int32)
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="small")
# Intersection: 30000x30000 = 900_000_000
# Small box area: 60000*60000 = 3_600_000_000
expected = 900_000_000.0 / 3_600_000_000.0
assert abs(ratio - expected) < 1e-6
def test_mode_small(self):
bbox1 = [0, 0, 100, 100]
bbox2 = [0, 0, 200, 200]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="small")
# Intersection = 100*100 = 10000, small area = 10000
assert abs(ratio - 1.0) < 1e-6
def test_mode_large(self):
bbox1 = [0, 0, 100, 100]
bbox2 = [0, 0, 200, 200]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="large")
# Intersection = 100*100 = 10000, large area = 40000
expected = 10000.0 / 40000.0
assert abs(ratio - expected) < 1e-6
def test_zero_area_bbox(self):
bbox1 = [0, 0, 0, 0]
bbox2 = [0, 0, 100, 100]
ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union")
assert ratio == 0.0
def test_invalid_mode_raises(self):
bbox1 = [0, 0, 100, 100]
bbox2 = [50, 50, 150, 150]
with pytest.raises(ValueError, match="Invalid mode"):
_fixed_calculate_overlap_ratio(bbox1, bbox2, mode="invalid")
class TestFixedCalculateMinimumEnclosingBbox:
"""Tests for the empty-safe calculate_minimum_enclosing_bbox."""
def test_single_bbox(self):
result = _fixed_calculate_minimum_enclosing_bbox([[10, 20, 30, 40]])
np.testing.assert_array_equal(result, [10, 20, 30, 40])
def test_multiple_bboxes(self):
bboxes = [[10, 20, 30, 40], [5, 15, 35, 45]]
result = _fixed_calculate_minimum_enclosing_bbox(bboxes)
np.testing.assert_array_equal(result, [5, 15, 35, 45])
def test_empty_list_returns_degenerate_bbox(self):
"""Verify empty list returns a degenerate bbox instead of raising.
This is the secondary fix from issue #17503. The original code
raises ValueError("The list of bounding boxes is empty.").
"""
result = _fixed_calculate_minimum_enclosing_bbox([])
assert result is not None
np.testing.assert_array_equal(result, [0, 0, 0, 0])
def test_none_input_returns_degenerate_bbox(self):
result = _fixed_calculate_minimum_enclosing_bbox(None)
assert result is not None
np.testing.assert_array_equal(result, [0, 0, 0, 0])