diff --git a/CHANGELOG.md b/CHANGELOG.md index 249b64f1a6..ff1f435510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.22.35 + +### Fixes + +- **Fix `ValueError: Coordinate 'lower' is less than 'upper'` when extracting figure/table images**: `save_elements` assumed `points[0]`/`points[2]` were always the top-left/bottom-right corners in screen orientation. For elements whose coordinates carried (or were converted from) a y-up orientation, that ordering inverted the PIL crop box and raised the error, which was then silently swallowed and the image dropped. The crop box is now derived from the extent (min/max) of all coordinate points, so it is always valid regardless of point ordering. + ## 0.22.34 ### Fixes diff --git a/test_unstructured/partition/pdf_image/test_pdf_image_utils.py b/test_unstructured/partition/pdf_image/test_pdf_image_utils.py index e428d126ad..c0f2c8eacd 100644 --- a/test_unstructured/partition/pdf_image/test_pdf_image_utils.py +++ b/test_unstructured/partition/pdf_image/test_pdf_image_utils.py @@ -249,6 +249,40 @@ def test_save_elements( assert not el.metadata.image_mime_type +def test_save_elements_with_inverted_point_ordering(monkeypatch): + """Regression: points whose ordering puts points[0] below points[2] (as happens when + coordinates come from / were converted from a y-up CARTESIAN system) must not raise + PIL's "Coordinate 'lower' is less than 'upper'" ValueError.""" + filename = example_doc_path("img/layout-parser-paper-fast.jpg") + with tempfile.TemporaryDirectory() as tmpdir: + # points[0] is the bottom-left (larger y) and points[2] the top-right (smaller y), + # i.e. the reverse of the usual ((x1,y1),(x1,y2),(x2,y2),(x2,y1)) screen ordering. + element = Image( + text="Inverted Image", + coordinates=((78, 519), (78, 86), (512, 86), (512, 519)), + coordinate_system=PixelSpace(width=1575, height=1166), + metadata=ElementMetadata(page_number=1), + ) + + pdf_image_utils.save_elements( + elements=[element], + starting_page_number=1, + element_category_to_save=ElementType.IMAGE, + pdf_image_dpi=200, + filename=filename, + is_image=True, + output_dir_path=str(tmpdir), + ) + + expected_image_path = os.path.join(str(tmpdir), "figure-1-1.jpg") + assert os.path.isfile(expected_image_path) + assert element.metadata.image_path == expected_image_path + # The crop box is taken from the extent of all points regardless of their order. + image = PILImg.open(expected_image_path) + assert image.width == 512 - 78 + assert image.height == 519 - 86 + + @pytest.mark.parametrize("storage_enabled", [False, True]) def test_save_elements_with_output_dir_path_none(monkeypatch, storage_enabled): monkeypatch.setenv( diff --git a/unstructured/__version__.py b/unstructured/__version__.py index 59c38a47b1..4781090584 100644 --- a/unstructured/__version__.py +++ b/unstructured/__version__.py @@ -1 +1 @@ -__version__ = "0.22.34" # pragma: no cover +__version__ = "0.22.35" # pragma: no cover diff --git a/unstructured/partition/pdf_image/pdf_image_utils.py b/unstructured/partition/pdf_image/pdf_image_utils.py index b81714a9f7..a4888ac131 100644 --- a/unstructured/partition/pdf_image/pdf_image_utils.py +++ b/unstructured/partition/pdf_image/pdf_image_utils.py @@ -181,8 +181,15 @@ def save_elements( continue points = coordinates.points - x1, y1 = points[0] - x2, y2 = points[2] + # Don't assume a fixed corner ordering: depending on the element's + # coordinate-system orientation, points[0]/points[2] are not reliably the + # top-left/bottom-right in the page image's screen space. Trusting them can + # yield an inverted PIL crop box and raise "Coordinate 'lower' is less than + # 'upper'". Take the extent of all points instead. + xs = [p[0] for p in points] + ys = [p[1] for p in points] + x1, y1 = min(xs), min(ys) + x2, y2 = max(xs), max(ys) h_padding = env_config.EXTRACT_IMAGE_BLOCK_CROP_HORIZONTAL_PAD v_padding = env_config.EXTRACT_IMAGE_BLOCK_CROP_VERTICAL_PAD padded_bbox = cast(