Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 34 additions & 0 deletions test_unstructured/partition/pdf_image/test_pdf_image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion unstructured/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.22.34" # pragma: no cover
__version__ = "0.22.35" # pragma: no cover
11 changes: 9 additions & 2 deletions unstructured/partition/pdf_image/pdf_image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading