parse-bench 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parse_bench/__init__.py +3 -0
- parse_bench/analysis/__init__.py +6 -0
- parse_bench/analysis/aggregation_report.py +582 -0
- parse_bench/analysis/cli.py +472 -0
- parse_bench/analysis/comparison.py +382 -0
- parse_bench/analysis/comparison_core.py +357 -0
- parse_bench/analysis/comparison_report.py +2066 -0
- parse_bench/analysis/detailed_report.py +2254 -0
- parse_bench/analysis/leaderboard_report.py +852 -0
- parse_bench/analysis/metric_definitions.py +771 -0
- parse_bench/cli.py +267 -0
- parse_bench/data/__init__.py +1 -0
- parse_bench/data/cli.py +118 -0
- parse_bench/data/download.py +127 -0
- parse_bench/evaluation/__init__.py +11 -0
- parse_bench/evaluation/cli.py +435 -0
- parse_bench/evaluation/evaluators/__init__.py +17 -0
- parse_bench/evaluation/evaluators/base.py +34 -0
- parse_bench/evaluation/evaluators/extract.py +429 -0
- parse_bench/evaluation/evaluators/layoutdet.py +1682 -0
- parse_bench/evaluation/evaluators/parse.py +1353 -0
- parse_bench/evaluation/evaluators/qa.py +199 -0
- parse_bench/evaluation/layout_adapters/__init__.py +21 -0
- parse_bench/evaluation/layout_adapters/adapters.py +3180 -0
- parse_bench/evaluation/layout_adapters/base.py +105 -0
- parse_bench/evaluation/layout_adapters/registry.py +109 -0
- parse_bench/evaluation/layout_label_mappers/__init__.py +22 -0
- parse_bench/evaluation/layout_label_mappers/base.py +66 -0
- parse_bench/evaluation/layout_label_mappers/mappers.py +332 -0
- parse_bench/evaluation/layout_label_mappers/projection.py +74 -0
- parse_bench/evaluation/layout_label_mappers/registry.py +119 -0
- parse_bench/evaluation/metric_aggregation.py +56 -0
- parse_bench/evaluation/metrics/__init__.py +5 -0
- parse_bench/evaluation/metrics/attribution/__init__.py +35 -0
- parse_bench/evaluation/metrics/attribution/constants.py +12 -0
- parse_bench/evaluation/metrics/attribution/core.py +1108 -0
- parse_bench/evaluation/metrics/attribution/evaluate.py +446 -0
- parse_bench/evaluation/metrics/attribution/geometry.py +161 -0
- parse_bench/evaluation/metrics/attribution/text_utils.py +233 -0
- parse_bench/evaluation/metrics/base.py +33 -0
- parse_bench/evaluation/metrics/downstream/__init__.py +0 -0
- parse_bench/evaluation/metrics/extract/__init__.py +29 -0
- parse_bench/evaluation/metrics/extract/json_subset_match.py +473 -0
- parse_bench/evaluation/metrics/extract/json_subset_match_metric.py +81 -0
- parse_bench/evaluation/metrics/extract/list_unwrap.py +340 -0
- parse_bench/evaluation/metrics/extract/rule_based_metric.py +90 -0
- parse_bench/evaluation/metrics/extract/test_rules.py +409 -0
- parse_bench/evaluation/metrics/extract/test_types.py +11 -0
- parse_bench/evaluation/metrics/field_grounding/__init__.py +21 -0
- parse_bench/evaluation/metrics/field_grounding/core.py +437 -0
- parse_bench/evaluation/metrics/field_grounding/extract_adapter.py +1224 -0
- parse_bench/evaluation/metrics/field_grounding/parse_adapter.py +697 -0
- parse_bench/evaluation/metrics/field_grounding/rule_filters.py +19 -0
- parse_bench/evaluation/metrics/field_grounding/value_compare.py +190 -0
- parse_bench/evaluation/metrics/layoutdet/__init__.py +17 -0
- parse_bench/evaluation/metrics/layoutdet/classification_utils.py +300 -0
- parse_bench/evaluation/metrics/layoutdet/iou.py +76 -0
- parse_bench/evaluation/metrics/parse/__init__.py +5 -0
- parse_bench/evaluation/metrics/parse/_vendor_grits_reference.py +531 -0
- parse_bench/evaluation/metrics/parse/cross_page_table_consistency.py +165 -0
- parse_bench/evaluation/metrics/parse/emphasis_spans.py +242 -0
- parse_bench/evaluation/metrics/parse/fast_tree_edit.py +282 -0
- parse_bench/evaluation/metrics/parse/grits_metric.py +1125 -0
- parse_bench/evaluation/metrics/parse/grits_reference_metric.py +142 -0
- parse_bench/evaluation/metrics/parse/header_accuracy_metric.py +1662 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/__init__.py +51 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/base.py +125 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/config.py +44 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/postprocess.py +322 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/strategy_judge.py +541 -0
- parse_bench/evaluation/metrics/parse/mermaid_graph.py +682 -0
- parse_bench/evaluation/metrics/parse/rule_based_judge_metric.py +56 -0
- parse_bench/evaluation/metrics/parse/rule_based_metric.py +434 -0
- parse_bench/evaluation/metrics/parse/rules_bag.py +1161 -0
- parse_bench/evaluation/metrics/parse/rules_base.py +751 -0
- parse_bench/evaluation/metrics/parse/rules_chart.py +1556 -0
- parse_bench/evaluation/metrics/parse/rules_diagram.py +591 -0
- parse_bench/evaluation/metrics/parse/rules_form.py +2274 -0
- parse_bench/evaluation/metrics/parse/rules_formatting.py +1500 -0
- parse_bench/evaluation/metrics/parse/rules_heading.py +228 -0
- parse_bench/evaluation/metrics/parse/rules_list.py +226 -0
- parse_bench/evaluation/metrics/parse/rules_page_decoration.py +276 -0
- parse_bench/evaluation/metrics/parse/rules_table.py +1666 -0
- parse_bench/evaluation/metrics/parse/rules_text.py +340 -0
- parse_bench/evaluation/metrics/parse/rules_watermark.py +105 -0
- parse_bench/evaluation/metrics/parse/structural_consistency_metric.py +251 -0
- parse_bench/evaluation/metrics/parse/table_extraction.py +152 -0
- parse_bench/evaluation/metrics/parse/table_merging.py +195 -0
- parse_bench/evaluation/metrics/parse/table_pairing.py +87 -0
- parse_bench/evaluation/metrics/parse/table_parsing.py +955 -0
- parse_bench/evaluation/metrics/parse/table_record_match_metric.py +1453 -0
- parse_bench/evaluation/metrics/parse/table_splitting.py +301 -0
- parse_bench/evaluation/metrics/parse/table_title_stripping.py +530 -0
- parse_bench/evaluation/metrics/parse/teds_metric.py +600 -0
- parse_bench/evaluation/metrics/parse/test_rules.py +120 -0
- parse_bench/evaluation/metrics/parse/test_types.py +103 -0
- parse_bench/evaluation/metrics/parse/text_content_projection.py +175 -0
- parse_bench/evaluation/metrics/parse/text_similarity_metric.py +61 -0
- parse_bench/evaluation/metrics/parse/utils.py +885 -0
- parse_bench/evaluation/metrics/qa/__init__.py +5 -0
- parse_bench/evaluation/metrics/qa/answer_comparison.py +380 -0
- parse_bench/evaluation/qa/__init__.py +5 -0
- parse_bench/evaluation/qa/llm_service.py +335 -0
- parse_bench/evaluation/reports/__init__.py +8 -0
- parse_bench/evaluation/reports/csv.py +64 -0
- parse_bench/evaluation/reports/html.py +338 -0
- parse_bench/evaluation/reports/markdown.py +98 -0
- parse_bench/evaluation/reports/rule_csv.py +22 -0
- parse_bench/evaluation/runner.py +1864 -0
- parse_bench/evaluation/stats.py +104 -0
- parse_bench/extensions.py +72 -0
- parse_bench/inference/__init__.py +33 -0
- parse_bench/inference/chunkr_layout_extraction.py +160 -0
- parse_bench/inference/cli.py +484 -0
- parse_bench/inference/layout_extraction.py +422 -0
- parse_bench/inference/pipelines/__init__.py +59 -0
- parse_bench/inference/pipelines/extract.py +39 -0
- parse_bench/inference/pipelines/layout.py +142 -0
- parse_bench/inference/pipelines/parse.py +2603 -0
- parse_bench/inference/pipelines.py +0 -0
- parse_bench/inference/providers/__init__.py +28 -0
- parse_bench/inference/providers/base.py +196 -0
- parse_bench/inference/providers/cancellation.py +137 -0
- parse_bench/inference/providers/extract/__init__.py +22 -0
- parse_bench/inference/providers/extract/citations.py +549 -0
- parse_bench/inference/providers/extract/extend.py +851 -0
- parse_bench/inference/providers/extract/llamaextract_v2_api.py +583 -0
- parse_bench/inference/providers/layoutdet/__init__.py +25 -0
- parse_bench/inference/providers/layoutdet/adapters.py +946 -0
- parse_bench/inference/providers/layoutdet/base.py +203 -0
- parse_bench/inference/providers/layoutdet/chandra.py +449 -0
- parse_bench/inference/providers/layoutdet/docling.py +125 -0
- parse_bench/inference/providers/layoutdet/dots_ocr.py +606 -0
- parse_bench/inference/providers/layoutdet/layout_v3.py +137 -0
- parse_bench/inference/providers/layoutdet/layout_v3_byoc.py +204 -0
- parse_bench/inference/providers/layoutdet/paddle.py +117 -0
- parse_bench/inference/providers/layoutdet/qwen3vl.py +360 -0
- parse_bench/inference/providers/layoutdet/surya.py +250 -0
- parse_bench/inference/providers/layoutdet/yolo.py +109 -0
- parse_bench/inference/providers/parse/__init__.py +64 -0
- parse_bench/inference/providers/parse/_docling_common.py +233 -0
- parse_bench/inference/providers/parse/_layout_utils.py +611 -0
- parse_bench/inference/providers/parse/amazon_nova.py +515 -0
- parse_bench/inference/providers/parse/anthropic.py +882 -0
- parse_bench/inference/providers/parse/azure_document_intelligence.py +700 -0
- parse_bench/inference/providers/parse/chandra2.py +633 -0
- parse_bench/inference/providers/parse/chunkr.py +268 -0
- parse_bench/inference/providers/parse/databricks_ai_parse.py +724 -0
- parse_bench/inference/providers/parse/datalab.py +370 -0
- parse_bench/inference/providers/parse/deepseekocr2.py +382 -0
- parse_bench/inference/providers/parse/docling.py +281 -0
- parse_bench/inference/providers/parse/docling_serve.py +289 -0
- parse_bench/inference/providers/parse/dots_ocr.py +574 -0
- parse_bench/inference/providers/parse/extend_parse.py +710 -0
- parse_bench/inference/providers/parse/falconocr.py +436 -0
- parse_bench/inference/providers/parse/florin_parser_nano.py +559 -0
- parse_bench/inference/providers/parse/gemma4.py +472 -0
- parse_bench/inference/providers/parse/glm_zai.py +229 -0
- parse_bench/inference/providers/parse/google.py +1125 -0
- parse_bench/inference/providers/parse/google_agentic_vision.py +819 -0
- parse_bench/inference/providers/parse/google_docai.py +776 -0
- parse_bench/inference/providers/parse/google_docai_layout_normalization.py +573 -0
- parse_bench/inference/providers/parse/granite_vision.py +515 -0
- parse_bench/inference/providers/parse/infinity_parser2.py +704 -0
- parse_bench/inference/providers/parse/kdl_frontier_nano.py +3327 -0
- parse_bench/inference/providers/parse/landingai.py +452 -0
- parse_bench/inference/providers/parse/liteparse.py +350 -0
- parse_bench/inference/providers/parse/llamaparse.py +677 -0
- parse_bench/inference/providers/parse/llamaparse_v2_normalization.py +1013 -0
- parse_bench/inference/providers/parse/markitdown.py +138 -0
- parse_bench/inference/providers/parse/mineru25.py +405 -0
- parse_bench/inference/providers/parse/mineru2605pro.py +432 -0
- parse_bench/inference/providers/parse/mineru_diffusion.py +371 -0
- parse_bench/inference/providers/parse/mistral_ocr.py +546 -0
- parse_bench/inference/providers/parse/nemotron_omni.py +473 -0
- parse_bench/inference/providers/parse/oi_parser.py +222 -0
- parse_bench/inference/providers/parse/openai.py +740 -0
- parse_bench/inference/providers/parse/opendataloader.py +152 -0
- parse_bench/inference/providers/parse/paddleocr.py +624 -0
- parse_bench/inference/providers/parse/pdf_inspector.py +142 -0
- parse_bench/inference/providers/parse/pulse.py +785 -0
- parse_bench/inference/providers/parse/pymupdf.py +207 -0
- parse_bench/inference/providers/parse/pymupdf4llm.py +356 -0
- parse_bench/inference/providers/parse/pypdf.py +179 -0
- parse_bench/inference/providers/parse/qwen.py +678 -0
- parse_bench/inference/providers/parse/rakedoc_nano.py +70 -0
- parse_bench/inference/providers/parse/reducto.py +546 -0
- parse_bench/inference/providers/parse/surya2.py +372 -0
- parse_bench/inference/providers/parse/tesseract.py +301 -0
- parse_bench/inference/providers/parse/textract.py +694 -0
- parse_bench/inference/providers/parse/unlimitedocr.py +346 -0
- parse_bench/inference/providers/parse/unstructured.py +485 -0
- parse_bench/inference/providers/parse/warp_ingest.py +199 -0
- parse_bench/inference/providers/registry.py +49 -0
- parse_bench/inference/renormalize.py +170 -0
- parse_bench/inference/runner.py +2023 -0
- parse_bench/layout_label_mapping.py +424 -0
- parse_bench/layout_projection.py +179 -0
- parse_bench/pipeline/__init__.py +1 -0
- parse_bench/pipeline/cli.py +549 -0
- parse_bench/schemas/__init__.py +33 -0
- parse_bench/schemas/evaluation.py +93 -0
- parse_bench/schemas/extract_output.py +36 -0
- parse_bench/schemas/layout_detection_output.py +545 -0
- parse_bench/schemas/layout_ontology.py +315 -0
- parse_bench/schemas/metrics.py +69 -0
- parse_bench/schemas/parse_output.py +152 -0
- parse_bench/schemas/pipeline.py +22 -0
- parse_bench/schemas/pipeline_io.py +106 -0
- parse_bench/schemas/product.py +97 -0
- parse_bench/test_cases/__init__.py +25 -0
- parse_bench/test_cases/bbox_value_strict_comparator.py +880 -0
- parse_bench/test_cases/extract_field_paths.py +164 -0
- parse_bench/test_cases/layout_attribution_generation.py +287 -0
- parse_bench/test_cases/loader.py +652 -0
- parse_bench/test_cases/parse_rule_schemas.py +1071 -0
- parse_bench/test_cases/rule_filters.py +32 -0
- parse_bench/test_cases/rule_ids.py +107 -0
- parse_bench/test_cases/schema.py +427 -0
- parse_bench/utils/__init__.py +15 -0
- parse_bench/utils/gemini_layout_utils.py +670 -0
- parse_bench/utils/text_aggregation.py +100 -0
- parse_bench-1.0.0.dist-info/METADATA +476 -0
- parse_bench-1.0.0.dist-info/RECORD +227 -0
- parse_bench-1.0.0.dist-info/WHEEL +4 -0
- parse_bench-1.0.0.dist-info/entry_points.txt +2 -0
- parse_bench-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Provider for Layout-V3 layout detection with figure classification."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from parse_bench.inference.providers.base import ProviderPermanentError
|
|
6
|
+
from parse_bench.inference.providers.layoutdet.base import HFLayoutDetProvider
|
|
7
|
+
from parse_bench.inference.providers.registry import register_provider
|
|
8
|
+
from parse_bench.schemas.layout_detection_output import (
|
|
9
|
+
LayoutDetectionModel,
|
|
10
|
+
LayoutOutput,
|
|
11
|
+
LayoutPrediction,
|
|
12
|
+
LayoutV3Label,
|
|
13
|
+
)
|
|
14
|
+
from parse_bench.schemas.pipeline_io import InferenceResult, RawInferenceResult
|
|
15
|
+
from parse_bench.schemas.product import ProductType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@register_provider("layout_v3")
|
|
19
|
+
class LayoutV3Provider(HFLayoutDetProvider):
|
|
20
|
+
"""
|
|
21
|
+
Provider for Layout-V3 layout detection model.
|
|
22
|
+
|
|
23
|
+
This provider uses the Layout-V3 model served on HuggingFace
|
|
24
|
+
inference endpoints for detecting document layout regions.
|
|
25
|
+
|
|
26
|
+
Layout-V3 uses RT-DETRv2 with ResNet-50 backbone and automatically
|
|
27
|
+
classifies detected Picture regions into 16 figure categories.
|
|
28
|
+
|
|
29
|
+
Response format:
|
|
30
|
+
{
|
|
31
|
+
"pred_boxes": [[x1, y1, x2, y2], ...],
|
|
32
|
+
"pred_classes": [class_id, ...],
|
|
33
|
+
"pred_labels": ["Picture", "Text", ...],
|
|
34
|
+
"scores": [score, ...],
|
|
35
|
+
"figure_classifications": {
|
|
36
|
+
"0": {
|
|
37
|
+
"figure_class": "bar_chart",
|
|
38
|
+
"figure_class_id": 0,
|
|
39
|
+
"figure_score": 0.89,
|
|
40
|
+
"top_3": [...]
|
|
41
|
+
},
|
|
42
|
+
...
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
endpoint_url = "https://jqkx3k3gn4ciymvi.us-east-1.aws.endpoints.huggingface.cloud"
|
|
48
|
+
model_type = LayoutDetectionModel.LAYOUT_V3
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
provider_name: str,
|
|
53
|
+
base_config: dict[str, Any] | None = None,
|
|
54
|
+
):
|
|
55
|
+
"""Initialize the Layout-V3 layout detection provider."""
|
|
56
|
+
# Allow endpoint_url override from config
|
|
57
|
+
if base_config and "endpoint_url" in base_config:
|
|
58
|
+
self.endpoint_url = base_config["endpoint_url"]
|
|
59
|
+
|
|
60
|
+
super().__init__(provider_name, base_config)
|
|
61
|
+
|
|
62
|
+
def _parse_response(self, response: dict[str, Any]) -> list[LayoutPrediction]:
|
|
63
|
+
"""
|
|
64
|
+
Parse Layout-V3 response into layout predictions.
|
|
65
|
+
|
|
66
|
+
:param response: Raw JSON response with pred_boxes, pred_classes,
|
|
67
|
+
pred_labels, scores, and figure_classifications
|
|
68
|
+
:return: List of unified LayoutPrediction objects
|
|
69
|
+
"""
|
|
70
|
+
predictions: list[LayoutPrediction] = []
|
|
71
|
+
|
|
72
|
+
boxes = response.get("pred_boxes", [])
|
|
73
|
+
classes = response.get("pred_classes", [])
|
|
74
|
+
labels = response.get("pred_labels", [])
|
|
75
|
+
scores = response.get("scores", [])
|
|
76
|
+
figure_classifications = response.get("figure_classifications", {})
|
|
77
|
+
|
|
78
|
+
for idx, (bbox, class_id, label_str, score) in enumerate(zip(boxes, classes, labels, scores, strict=False)):
|
|
79
|
+
# Convert class_id to LayoutV3Label enum
|
|
80
|
+
try:
|
|
81
|
+
label = LayoutV3Label(class_id)
|
|
82
|
+
except ValueError:
|
|
83
|
+
# Unknown label, skip
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
predictions.append(
|
|
87
|
+
LayoutPrediction(
|
|
88
|
+
bbox=bbox,
|
|
89
|
+
score=score,
|
|
90
|
+
label=str(int(label)),
|
|
91
|
+
provider_metadata={
|
|
92
|
+
"label_name": label.name,
|
|
93
|
+
"label_str": label_str,
|
|
94
|
+
"figure_classification": figure_classifications.get(str(idx)),
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return predictions
|
|
100
|
+
|
|
101
|
+
def normalize(self, raw_result: RawInferenceResult) -> InferenceResult:
|
|
102
|
+
"""
|
|
103
|
+
Normalize raw inference result to produce LayoutOutput.
|
|
104
|
+
|
|
105
|
+
:param raw_result: Raw inference result from run_inference()
|
|
106
|
+
:return: Inference result with both raw and normalized outputs
|
|
107
|
+
:raises ProviderError: For any normalization failures
|
|
108
|
+
"""
|
|
109
|
+
if raw_result.product_type != ProductType.LAYOUT_DETECTION:
|
|
110
|
+
raise ProviderPermanentError(
|
|
111
|
+
f"{self.__class__.__name__} only supports LAYOUT_DETECTION product type, got {raw_result.product_type}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Parse the response into raw predictions
|
|
115
|
+
response = raw_result.raw_output.get("response", {})
|
|
116
|
+
raw_predictions = self._parse_response(response)
|
|
117
|
+
|
|
118
|
+
output = LayoutOutput(
|
|
119
|
+
task_type="layout_detection",
|
|
120
|
+
example_id=raw_result.request.example_id,
|
|
121
|
+
pipeline_name=raw_result.pipeline_name,
|
|
122
|
+
model=self.model_type,
|
|
123
|
+
image_width=max(int(raw_result.raw_output.get("image_width", 1)), 1),
|
|
124
|
+
image_height=max(int(raw_result.raw_output.get("image_height", 1)), 1),
|
|
125
|
+
predictions=raw_predictions,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return InferenceResult(
|
|
129
|
+
request=raw_result.request,
|
|
130
|
+
pipeline_name=raw_result.pipeline_name,
|
|
131
|
+
product_type=raw_result.product_type,
|
|
132
|
+
raw_output=raw_result.raw_output,
|
|
133
|
+
output=output,
|
|
134
|
+
started_at=raw_result.started_at,
|
|
135
|
+
completed_at=raw_result.completed_at,
|
|
136
|
+
latency_in_ms=raw_result.latency_in_ms,
|
|
137
|
+
)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Provider for Layout-V3 BYOC (Bring Your Own Cloud) deployments."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
from parse_bench.inference.providers.base import (
|
|
12
|
+
Provider,
|
|
13
|
+
ProviderConfigError,
|
|
14
|
+
ProviderPermanentError,
|
|
15
|
+
ProviderRateLimitError,
|
|
16
|
+
ProviderTransientError,
|
|
17
|
+
)
|
|
18
|
+
from parse_bench.inference.providers.registry import register_provider
|
|
19
|
+
from parse_bench.schemas.layout_detection_output import (
|
|
20
|
+
LayoutDetectionModel,
|
|
21
|
+
LayoutOutput,
|
|
22
|
+
LayoutPrediction,
|
|
23
|
+
LayoutV3Label,
|
|
24
|
+
)
|
|
25
|
+
from parse_bench.schemas.pipeline import PipelineSpec
|
|
26
|
+
from parse_bench.schemas.pipeline_io import (
|
|
27
|
+
InferenceRequest,
|
|
28
|
+
InferenceResult,
|
|
29
|
+
RawInferenceResult,
|
|
30
|
+
)
|
|
31
|
+
from parse_bench.schemas.product import ProductType
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LayoutV3BYOCProvider(Provider):
|
|
35
|
+
"""
|
|
36
|
+
Base provider for Layout-V3 BYOC deployments.
|
|
37
|
+
|
|
38
|
+
Uses multipart form data instead of raw image bytes (HuggingFace style).
|
|
39
|
+
Response format is identical to the HuggingFace endpoint.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
endpoint_url: str = ""
|
|
43
|
+
model_type = LayoutDetectionModel.LAYOUT_V3
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
provider_name: str,
|
|
48
|
+
base_config: dict[str, Any] | None = None,
|
|
49
|
+
):
|
|
50
|
+
super().__init__(provider_name, base_config)
|
|
51
|
+
|
|
52
|
+
# Allow endpoint_url override from config
|
|
53
|
+
if base_config and "endpoint_url" in base_config:
|
|
54
|
+
self.endpoint_url = base_config["endpoint_url"]
|
|
55
|
+
|
|
56
|
+
if not self.endpoint_url:
|
|
57
|
+
raise ProviderConfigError(
|
|
58
|
+
f"endpoint_url is required for {self.__class__.__name__}. Set via config or environment variable."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._timeout = self.base_config.get("timeout", 120)
|
|
62
|
+
|
|
63
|
+
def _call_endpoint(self, image: Image.Image) -> dict[str, Any]:
|
|
64
|
+
"""Call BYOC endpoint with multipart form data."""
|
|
65
|
+
buffer = io.BytesIO()
|
|
66
|
+
image.save(buffer, format="PNG")
|
|
67
|
+
buffer.seek(0)
|
|
68
|
+
|
|
69
|
+
files = {"file": ("image.png", buffer, "image/png")}
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
response = requests.post(
|
|
73
|
+
f"{self.endpoint_url}/predict",
|
|
74
|
+
files=files,
|
|
75
|
+
timeout=self._timeout,
|
|
76
|
+
)
|
|
77
|
+
response.raise_for_status()
|
|
78
|
+
return response.json() # type: ignore[no-any-return]
|
|
79
|
+
|
|
80
|
+
except requests.exceptions.Timeout as e:
|
|
81
|
+
raise ProviderTransientError(f"Request timed out: {e}") from e
|
|
82
|
+
except requests.exceptions.ConnectionError as e:
|
|
83
|
+
raise ProviderTransientError(f"Connection error: {e}") from e
|
|
84
|
+
except requests.exceptions.HTTPError as e:
|
|
85
|
+
status_code = e.response.status_code if e.response else None
|
|
86
|
+
if status_code == 429:
|
|
87
|
+
raise ProviderRateLimitError(f"Rate limit exceeded: {e}") from e
|
|
88
|
+
elif status_code and 500 <= status_code < 600:
|
|
89
|
+
raise ProviderTransientError(f"Server error ({status_code}): {e}") from e
|
|
90
|
+
elif status_code and 400 <= status_code < 500:
|
|
91
|
+
raise ProviderPermanentError(f"Client error ({status_code}): {e}") from e
|
|
92
|
+
else:
|
|
93
|
+
raise ProviderPermanentError(f"HTTP error: {e}") from e
|
|
94
|
+
|
|
95
|
+
def _parse_response(self, response: dict[str, Any]) -> list[LayoutPrediction]:
|
|
96
|
+
"""Parse Layout-V3 response (same format as HF endpoint)."""
|
|
97
|
+
predictions: list[LayoutPrediction] = []
|
|
98
|
+
|
|
99
|
+
boxes = response.get("pred_boxes", [])
|
|
100
|
+
classes = response.get("pred_classes", [])
|
|
101
|
+
labels = response.get("pred_labels", [])
|
|
102
|
+
scores = response.get("scores", [])
|
|
103
|
+
figure_classifications = response.get("figure_classifications", {})
|
|
104
|
+
|
|
105
|
+
for idx, (bbox, class_id, label_str, score) in enumerate(zip(boxes, classes, labels, scores, strict=False)):
|
|
106
|
+
try:
|
|
107
|
+
label = LayoutV3Label(class_id)
|
|
108
|
+
except ValueError:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
predictions.append(
|
|
112
|
+
LayoutPrediction(
|
|
113
|
+
bbox=bbox,
|
|
114
|
+
score=score,
|
|
115
|
+
label=str(int(label)),
|
|
116
|
+
provider_metadata={
|
|
117
|
+
"label_name": label.name,
|
|
118
|
+
"label_str": label_str,
|
|
119
|
+
"figure_classification": figure_classifications.get(str(idx)),
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return predictions
|
|
125
|
+
|
|
126
|
+
def run_inference(self, pipeline: PipelineSpec, request: InferenceRequest) -> RawInferenceResult:
|
|
127
|
+
"""Run layout detection inference."""
|
|
128
|
+
if request.product_type != ProductType.LAYOUT_DETECTION:
|
|
129
|
+
raise ProviderPermanentError(f"{self.__class__.__name__} only supports LAYOUT_DETECTION")
|
|
130
|
+
|
|
131
|
+
started_at = datetime.now()
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
image = Image.open(request.source_file_path)
|
|
135
|
+
if image.mode not in ("RGB", "RGBA"):
|
|
136
|
+
image = image.convert("RGB") # type: ignore[assignment]
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise ProviderPermanentError(f"Failed to load image: {e}") from e
|
|
139
|
+
|
|
140
|
+
image_width, image_height = image.size
|
|
141
|
+
raw_response = self._call_endpoint(image)
|
|
142
|
+
|
|
143
|
+
completed_at = datetime.now()
|
|
144
|
+
latency_ms = int((completed_at - started_at).total_seconds() * 1000)
|
|
145
|
+
|
|
146
|
+
raw_output = {
|
|
147
|
+
"response": raw_response,
|
|
148
|
+
"image_width": image_width,
|
|
149
|
+
"image_height": image_height,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return RawInferenceResult(
|
|
153
|
+
request=request,
|
|
154
|
+
pipeline=pipeline,
|
|
155
|
+
pipeline_name=pipeline.pipeline_name,
|
|
156
|
+
product_type=request.product_type,
|
|
157
|
+
raw_output=raw_output,
|
|
158
|
+
started_at=started_at,
|
|
159
|
+
completed_at=completed_at,
|
|
160
|
+
latency_in_ms=latency_ms,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def normalize(self, raw_result: RawInferenceResult) -> InferenceResult:
|
|
164
|
+
"""Normalize raw inference result (identical to LayoutV3Provider)."""
|
|
165
|
+
if raw_result.product_type != ProductType.LAYOUT_DETECTION:
|
|
166
|
+
raise ProviderPermanentError(f"{self.__class__.__name__} only supports LAYOUT_DETECTION")
|
|
167
|
+
|
|
168
|
+
response = raw_result.raw_output.get("response", {})
|
|
169
|
+
raw_predictions = self._parse_response(response)
|
|
170
|
+
|
|
171
|
+
output = LayoutOutput(
|
|
172
|
+
task_type="layout_detection",
|
|
173
|
+
example_id=raw_result.request.example_id,
|
|
174
|
+
pipeline_name=raw_result.pipeline_name,
|
|
175
|
+
model=self.model_type,
|
|
176
|
+
image_width=max(int(raw_result.raw_output.get("image_width", 1)), 1),
|
|
177
|
+
image_height=max(int(raw_result.raw_output.get("image_height", 1)), 1),
|
|
178
|
+
predictions=raw_predictions,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return InferenceResult(
|
|
182
|
+
request=raw_result.request,
|
|
183
|
+
pipeline_name=raw_result.pipeline_name,
|
|
184
|
+
product_type=raw_result.product_type,
|
|
185
|
+
raw_output=raw_result.raw_output,
|
|
186
|
+
output=output,
|
|
187
|
+
started_at=raw_result.started_at,
|
|
188
|
+
completed_at=raw_result.completed_at,
|
|
189
|
+
latency_in_ms=raw_result.latency_in_ms,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@register_provider("layout_v3_byoc_cpu")
|
|
194
|
+
class LayoutV3BYOCCPUProvider(LayoutV3BYOCProvider):
|
|
195
|
+
"""Layout-V3 BYOC provider for CPU deployments."""
|
|
196
|
+
|
|
197
|
+
endpoint_url = os.getenv("LAYOUT_V3_BYOC_CPU_URL", "http://localhost:8001")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@register_provider("layout_v3_byoc_gpu")
|
|
201
|
+
class LayoutV3BYOCGPUProvider(LayoutV3BYOCProvider):
|
|
202
|
+
"""Layout-V3 BYOC provider for GPU deployments."""
|
|
203
|
+
|
|
204
|
+
endpoint_url = os.getenv("LAYOUT_V3_BYOC_GPU_URL", "http://localhost:8002")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Provider for Paddle PP-DocLayout layout detection."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from parse_bench.inference.providers.base import ProviderPermanentError
|
|
6
|
+
from parse_bench.inference.providers.layoutdet.base import HFLayoutDetProvider
|
|
7
|
+
from parse_bench.inference.providers.registry import register_provider
|
|
8
|
+
from parse_bench.schemas.layout_detection_output import (
|
|
9
|
+
PPDOCLAYOUT_STR_TO_LABEL,
|
|
10
|
+
LayoutDetectionModel,
|
|
11
|
+
LayoutOutput,
|
|
12
|
+
LayoutPrediction,
|
|
13
|
+
)
|
|
14
|
+
from parse_bench.schemas.pipeline_io import InferenceResult, RawInferenceResult
|
|
15
|
+
from parse_bench.schemas.product import ProductType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@register_provider("paddle_layout")
|
|
19
|
+
class PaddleLayoutProvider(HFLayoutDetProvider):
|
|
20
|
+
"""
|
|
21
|
+
Provider for Paddle PP-DocLayout layout detection model.
|
|
22
|
+
|
|
23
|
+
This provider uses the Paddle PP-DocLayout model served on HuggingFace
|
|
24
|
+
inference endpoints for detecting document layout regions.
|
|
25
|
+
|
|
26
|
+
Response format:
|
|
27
|
+
{
|
|
28
|
+
"predictions": [
|
|
29
|
+
{"coordinate": [x1, y1, x2, y2], "label": "text", "score": 0.95},
|
|
30
|
+
...
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
endpoint_url = "https://l0tv2kvqu4t5sa03.us-east-1.aws.endpoints.huggingface.cloud"
|
|
36
|
+
model_type = LayoutDetectionModel.PPDOCLAYOUT_PLUS_L
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
provider_name: str,
|
|
41
|
+
base_config: dict[str, Any] | None = None,
|
|
42
|
+
):
|
|
43
|
+
"""Initialize the Paddle layout detection provider."""
|
|
44
|
+
super().__init__(provider_name, base_config)
|
|
45
|
+
|
|
46
|
+
def _parse_response(self, response: dict[str, Any]) -> list[LayoutPrediction]:
|
|
47
|
+
"""
|
|
48
|
+
Parse Paddle PP-DocLayout response into layout predictions.
|
|
49
|
+
|
|
50
|
+
:param response: Raw JSON response with predictions list
|
|
51
|
+
:return: List of unified LayoutPrediction objects
|
|
52
|
+
"""
|
|
53
|
+
predictions: list[LayoutPrediction] = []
|
|
54
|
+
|
|
55
|
+
items = response.get("predictions", [])
|
|
56
|
+
|
|
57
|
+
for item in items:
|
|
58
|
+
# Get bbox - Paddle uses "coordinate" key
|
|
59
|
+
bbox = item.get("coordinate", item.get("bbox", []))
|
|
60
|
+
|
|
61
|
+
# Get string label and convert to enum
|
|
62
|
+
label_str = item.get("label", "")
|
|
63
|
+
label = PPDOCLAYOUT_STR_TO_LABEL.get(label_str)
|
|
64
|
+
if label is None:
|
|
65
|
+
# Unknown label, skip
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
score = item.get("score", 0.0)
|
|
69
|
+
|
|
70
|
+
predictions.append(
|
|
71
|
+
LayoutPrediction(
|
|
72
|
+
bbox=bbox,
|
|
73
|
+
score=score,
|
|
74
|
+
label=str(int(label)),
|
|
75
|
+
provider_metadata={"label_name": label.name},
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return predictions
|
|
80
|
+
|
|
81
|
+
def normalize(self, raw_result: RawInferenceResult) -> InferenceResult:
|
|
82
|
+
"""
|
|
83
|
+
Normalize raw inference result to produce LayoutOutput.
|
|
84
|
+
|
|
85
|
+
:param raw_result: Raw inference result from run_inference()
|
|
86
|
+
:return: Inference result with both raw and normalized outputs
|
|
87
|
+
:raises ProviderError: For any normalization failures
|
|
88
|
+
"""
|
|
89
|
+
if raw_result.product_type != ProductType.LAYOUT_DETECTION:
|
|
90
|
+
raise ProviderPermanentError(
|
|
91
|
+
f"{self.__class__.__name__} only supports LAYOUT_DETECTION product type, got {raw_result.product_type}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Parse the response into raw predictions
|
|
95
|
+
response = raw_result.raw_output.get("response", {})
|
|
96
|
+
raw_predictions = self._parse_response(response)
|
|
97
|
+
|
|
98
|
+
output = LayoutOutput(
|
|
99
|
+
task_type="layout_detection",
|
|
100
|
+
example_id=raw_result.request.example_id,
|
|
101
|
+
pipeline_name=raw_result.pipeline_name,
|
|
102
|
+
model=self.model_type,
|
|
103
|
+
image_width=max(int(raw_result.raw_output.get("image_width", 1)), 1),
|
|
104
|
+
image_height=max(int(raw_result.raw_output.get("image_height", 1)), 1),
|
|
105
|
+
predictions=raw_predictions,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return InferenceResult(
|
|
109
|
+
request=raw_result.request,
|
|
110
|
+
pipeline_name=raw_result.pipeline_name,
|
|
111
|
+
product_type=raw_result.product_type,
|
|
112
|
+
raw_output=raw_result.raw_output,
|
|
113
|
+
output=output,
|
|
114
|
+
started_at=raw_result.started_at,
|
|
115
|
+
completed_at=raw_result.completed_at,
|
|
116
|
+
latency_in_ms=raw_result.latency_in_ms,
|
|
117
|
+
)
|