Spaces:
Sleeping
Sleeping
| """Tests for the ViewerProjection builder and overlay generation.""" | |
| from __future__ import annotations | |
| import json | |
| from typing import TYPE_CHECKING | |
| import pytest | |
| from src.app.domain.models import ( | |
| AltoReadiness, | |
| CanonicalDocument, | |
| EvidenceType, | |
| ExportEligibility, | |
| Geometry, | |
| GeometryStatus, | |
| NonTextRegion, | |
| OverlayLevel, | |
| Page, | |
| PageXmlReadiness, | |
| Provenance, | |
| RawProviderPayload, | |
| ReadinessLevel, | |
| Source, | |
| TextLine, | |
| TextRegion, | |
| Word, | |
| ) | |
| from src.app.domain.models.geometry import GeometryContext | |
| from src.app.domain.models.status import BlockRole, InputType, NonTextKind | |
| from src.app.normalization.pipeline import normalize | |
| from src.app.viewer.overlays import ( | |
| line_to_overlay, | |
| region_to_overlay, | |
| word_to_inspection, | |
| word_to_overlay, | |
| ) | |
| from src.app.viewer.projection_builder import build_all_projections, build_projection | |
| if TYPE_CHECKING: | |
| from pathlib import Path | |
| def _prov() -> Provenance: | |
| return Provenance( | |
| provider="test", adapter="v1", source_ref="$", | |
| evidence_type=EvidenceType.PROVIDER_NATIVE, | |
| ) | |
| def _geo(x: float = 10, y: float = 10, w: float = 100, h: float = 30) -> Geometry: | |
| return Geometry(bbox=(x, y, w, h), status=GeometryStatus.EXACT) | |
| def _simple_doc() -> CanonicalDocument: | |
| return CanonicalDocument( | |
| document_id="viewer_test", | |
| source=Source(input_type=InputType.IMAGE, filename="test.png"), | |
| pages=[Page( | |
| id="p1", page_index=0, width=2480, height=3508, | |
| alto_readiness=AltoReadiness(level=ReadinessLevel.FULL), | |
| page_readiness=PageXmlReadiness(level=ReadinessLevel.FULL), | |
| reading_order=["tb1"], | |
| text_regions=[TextRegion( | |
| id="tb1", role=BlockRole.BODY, | |
| geometry=_geo(100, 200, 1200, 900), provenance=_prov(), lang="fra", | |
| lines=[ | |
| TextLine(id="tl1", geometry=_geo(110, 220, 1100, 42), | |
| provenance=_prov(), lang="fra", | |
| words=[ | |
| Word(id="w1", text="Bonjour", geometry=_geo(110, 220, 90, 40), | |
| provenance=_prov(), confidence=0.96, lang="fra"), | |
| Word(id="w2", text="monde", geometry=_geo(220, 220, 80, 40), | |
| provenance=_prov(), confidence=0.94, lang="fra"), | |
| ]), | |
| TextLine(id="tl2", geometry=_geo(110, 280, 1100, 42), | |
| provenance=_prov(), | |
| words=[ | |
| Word(id="w3", text="Test", geometry=_geo(110, 280, 60, 40), | |
| provenance=_prov(), confidence=0.90), | |
| ]), | |
| ], | |
| )], | |
| non_text_regions=[NonTextRegion( | |
| id="ntr1", kind=NonTextKind.ILLUSTRATION, | |
| geometry=_geo(1500, 200, 400, 300), provenance=_prov(), | |
| )], | |
| )], | |
| ) | |
| # -- Overlay unit tests ------------------------------------------------------- | |
| class TestOverlayGeneration: | |
| def test_word_overlay(self) -> None: | |
| w = Word(id="w1", text="Hello", geometry=_geo(10, 20, 50, 30), | |
| provenance=_prov(), confidence=0.95, lang="eng") | |
| ov = word_to_overlay(w) | |
| assert ov.id == "w1" | |
| assert ov.level == OverlayLevel.WORD | |
| assert ov.text == "Hello" | |
| assert ov.bbox == (10, 20, 50, 30) | |
| assert ov.confidence == 0.95 | |
| assert ov.provenance_type == EvidenceType.PROVIDER_NATIVE | |
| assert ov.geometry_status == GeometryStatus.EXACT | |
| def test_line_overlay(self) -> None: | |
| line = TextLine(id="tl1", geometry=_geo(10, 20, 200, 30), | |
| provenance=_prov(), | |
| words=[Word(id="w1", text="Hello", geometry=_geo(), | |
| provenance=_prov())]) | |
| ov = line_to_overlay(line) | |
| assert ov.id == "tl1" | |
| assert ov.level == OverlayLevel.LINE | |
| assert ov.text == "Hello" | |
| def test_region_overlay(self) -> None: | |
| r = TextRegion(id="tb1", role=BlockRole.HEADING, | |
| geometry=_geo(0, 0, 500, 100), provenance=_prov(), | |
| lines=[TextLine(id="tl1", geometry=_geo(), | |
| provenance=_prov(), | |
| words=[Word(id="w1", text="Title", | |
| geometry=_geo(), | |
| provenance=_prov())])]) | |
| ov = region_to_overlay(r) | |
| assert ov.id == "tb1" | |
| assert ov.level == OverlayLevel.BLOCK | |
| assert ov.label == "heading" | |
| def test_word_inspection(self) -> None: | |
| w = Word(id="w1", text="Hello", geometry=_geo(10, 20, 50, 30), | |
| provenance=_prov(), confidence=0.95, lang="eng") | |
| insp = word_to_inspection(w) | |
| assert insp.id == "w1" | |
| assert insp.text == "Hello" | |
| assert insp.lang == "eng" | |
| assert insp.provenance_provider == "test" | |
| # -- Projection builder ------------------------------------------------------- | |
| class TestProjectionBuilder: | |
| def test_build_projection(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert vp.image_ref == "test.png" | |
| assert vp.image_width == 2480 | |
| assert vp.image_height == 3508 | |
| def test_block_overlays(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert len(vp.block_overlays) == 1 | |
| assert vp.block_overlays[0].id == "tb1" | |
| def test_line_overlays(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert len(vp.line_overlays) == 2 | |
| def test_word_overlays(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert len(vp.word_overlays) == 3 | |
| word_ids = {o.id for o in vp.word_overlays} | |
| assert word_ids == {"w1", "w2", "w3"} | |
| def test_non_text_overlays(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert len(vp.non_text_overlays) == 1 | |
| assert vp.non_text_overlays[0].id == "ntr1" | |
| def test_inspection_index(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| # Should contain all blocks, lines, and words | |
| assert "tb1" in vp.inspection_index | |
| assert "tl1" in vp.inspection_index | |
| assert "tl2" in vp.inspection_index | |
| assert "w1" in vp.inspection_index | |
| assert "w2" in vp.inspection_index | |
| assert "w3" in vp.inspection_index | |
| def test_inspection_data_content(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| w1 = vp.inspection_index["w1"] | |
| assert w1.text == "Bonjour" | |
| assert w1.confidence == 0.96 | |
| assert w1.lang == "fra" | |
| assert w1.provenance_provider == "test" | |
| def test_export_status_default(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| assert vp.export_status.alto_export == ReadinessLevel.NONE | |
| def test_export_status_custom(self) -> None: | |
| doc = _simple_doc() | |
| elig = ExportEligibility( | |
| alto_export=ReadinessLevel.FULL, | |
| page_export=ReadinessLevel.FULL, | |
| viewer_render=ReadinessLevel.FULL, | |
| ) | |
| vp = build_projection(doc, export_status=elig) | |
| assert vp.export_status.alto_export == ReadinessLevel.FULL | |
| def test_page_index_out_of_range(self) -> None: | |
| doc = _simple_doc() | |
| with pytest.raises(ValueError, match="out of range"): | |
| build_projection(doc, page_index=5) | |
| def test_build_all_projections(self) -> None: | |
| doc = _simple_doc() | |
| projections = build_all_projections(doc) | |
| assert len(projections) == 1 | |
| assert projections[0].image_width == 2480 | |
| def test_json_roundtrip(self) -> None: | |
| doc = _simple_doc() | |
| vp = build_projection(doc) | |
| data = vp.model_dump(mode="json") | |
| # Verify it's fully serializable | |
| import json | |
| json_str = json.dumps(data) | |
| restored = json.loads(json_str) | |
| assert restored["image_width"] == 2480 | |
| assert len(restored["word_overlays"]) == 3 | |
| assert "w1" in restored["inspection_index"] | |
| class TestProjectionFromPipeline: | |
| """Integration: PaddleOCR raw → normalize → build_projection.""" | |
| def test_from_paddle(self, fixtures_dir: Path) -> None: | |
| with open(fixtures_dir / "paddle_ocr_sample.json") as f: | |
| payload = json.load(f) | |
| raw = RawProviderPayload( | |
| provider_id="paddleocr", adapter_id="v1", runtime_type="local", | |
| payload=payload, image_width=2480, image_height=3508, | |
| ) | |
| geo_ctx = GeometryContext(source_width=2480, source_height=3508) | |
| doc = normalize(raw, family="word_box_json", geometry_context=geo_ctx, | |
| document_id="vp_test", source_filename="page.png") | |
| vp = build_projection(doc) | |
| assert vp.image_ref == "page.png" | |
| assert len(vp.word_overlays) == 5 | |
| assert vp.word_overlays[0].text == "Bonjour" | |
| assert len(vp.inspection_index) > 0 | |