From 3ab6fd4c4d52c8ae048f06eace45c0fa5016bea1 Mon Sep 17 00:00:00 2001 From: Nick Franck <46548427+CyMule@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:18:55 -0400 Subject: [PATCH 1/2] fix(layout): normalize detector results at the model boundary --- CHANGELOG.md | 5 +++ .../inference/test_layout.py | 36 +++++++++++++++++++ .../models/test_detectron2onnx.py | 6 ++-- unstructured_inference/__version__.py | 2 +- .../models/unstructuredmodel.py | 5 ++- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d98f0cf9..bf26c891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.6.14 + +### Fixes +- **Normalize object-detection model results**: the object-detection boundary now converts legacy list results to `LayoutElements`, so page-layout routing works consistently across built-in detection models. + ## 1.6.13 ### Fixes diff --git a/test_unstructured_inference/inference/test_layout.py b/test_unstructured_inference/inference/test_layout.py index ae913c78..d7eec940 100644 --- a/test_unstructured_inference/inference/test_layout.py +++ b/test_unstructured_inference/inference/test_layout.py @@ -122,6 +122,42 @@ def test_get_page_elements(monkeypatch, mock_final_layout): assert elements == page.elements_array +class ListPredictingDetectionModel(UnstructuredObjectDetectionModel): + def __init__(self, predictions): + super().__init__() + self.predictions = predictions + + def initialize(self, *args, **kwargs): + pass + + def predict(self, x): + return self.predictions + + +@pytest.mark.parametrize( + ("predictions", "expected_texts"), + [ + ([layoutelement.LayoutElement.from_coords(0, 0, 1, 1, text="detected")], ["detected"]), + ([], []), + ], +) +def test_get_page_elements_with_detection_model_returning_a_list( + mock_image, + predictions, + expected_texts, +): + page = layout.PageLayout( + number=1, + image=mock_image, + detection_model=ListPredictingDetectionModel(predictions), + ) + + result = page.get_elements_with_detection_model(inplace=False) + + assert isinstance(result, layoutelement.LayoutElements) + assert [element.text for element in result.as_list()] == expected_texts + + class MockPool: def map(self, f, xs): return [f(x) for x in xs] diff --git a/test_unstructured_inference/models/test_detectron2onnx.py b/test_unstructured_inference/models/test_detectron2onnx.py index 3be5916f..5230fe9d 100644 --- a/test_unstructured_inference/models/test_detectron2onnx.py +++ b/test_unstructured_inference/models/test_detectron2onnx.py @@ -6,6 +6,7 @@ import unstructured_inference.models.base as models import unstructured_inference.models.detectron2onnx as detectron2 +from unstructured_inference.inference.layoutelement import LayoutElements class MockDetectron2ONNXLayoutModel: @@ -50,7 +51,7 @@ def test_unstructured_detectron_model(): model.model = 1 with patch.object(detectron2.UnstructuredDetectronONNXModel, "predict", return_value=[]): result = model(None) - assert isinstance(result, list) + assert isinstance(result, LayoutElements) assert len(result) == 0 @@ -67,8 +68,9 @@ def test_inference(): image = Image.open(fp) image.load() elements = model(image) + assert isinstance(elements, LayoutElements) assert len(elements) == 1 - element = elements[0] + element = elements.as_list()[0] (x1, y1), _, (x2, y2), _ = element.bbox.coordinates assert hasattr( element, diff --git a/unstructured_inference/__version__.py b/unstructured_inference/__version__.py index 9ae5e1d8..01ca4095 100644 --- a/unstructured_inference/__version__.py +++ b/unstructured_inference/__version__.py @@ -1 +1 @@ -__version__ = "1.6.13" # pragma: no cover +__version__ = "1.6.14" # pragma: no cover diff --git a/unstructured_inference/models/unstructuredmodel.py b/unstructured_inference/models/unstructuredmodel.py index 308216b3..9bb73178 100644 --- a/unstructured_inference/models/unstructuredmodel.py +++ b/unstructured_inference/models/unstructuredmodel.py @@ -61,7 +61,10 @@ def predict(self, x: Image) -> LayoutElements | list[LayoutElement]: def __call__(self, x: Image) -> LayoutElements: """Inference using function call interface.""" - return super().__call__(x) + predictions = super().__call__(x) + if isinstance(predictions, LayoutElements): + return predictions + return LayoutElements.from_list(predictions) @staticmethod def enhance_regions( From e5b7ca3533651ee7104e08c03f4263247b756505 Mon Sep 17 00:00:00 2001 From: Nick Franck <46548427+CyMule@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:08:11 -0400 Subject: [PATCH 2/2] test: assert detected texts via LayoutElements.texts --- test_unstructured_inference/inference/test_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_unstructured_inference/inference/test_layout.py b/test_unstructured_inference/inference/test_layout.py index d7eec940..400057bb 100644 --- a/test_unstructured_inference/inference/test_layout.py +++ b/test_unstructured_inference/inference/test_layout.py @@ -155,7 +155,7 @@ def test_get_page_elements_with_detection_model_returning_a_list( result = page.get_elements_with_detection_model(inplace=False) assert isinstance(result, layoutelement.LayoutElements) - assert [element.text for element in result.as_list()] == expected_texts + assert result.texts.tolist() == expected_texts class MockPool: