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.
Files changed (227) hide show
  1. parse_bench/__init__.py +3 -0
  2. parse_bench/analysis/__init__.py +6 -0
  3. parse_bench/analysis/aggregation_report.py +582 -0
  4. parse_bench/analysis/cli.py +472 -0
  5. parse_bench/analysis/comparison.py +382 -0
  6. parse_bench/analysis/comparison_core.py +357 -0
  7. parse_bench/analysis/comparison_report.py +2066 -0
  8. parse_bench/analysis/detailed_report.py +2254 -0
  9. parse_bench/analysis/leaderboard_report.py +852 -0
  10. parse_bench/analysis/metric_definitions.py +771 -0
  11. parse_bench/cli.py +267 -0
  12. parse_bench/data/__init__.py +1 -0
  13. parse_bench/data/cli.py +118 -0
  14. parse_bench/data/download.py +127 -0
  15. parse_bench/evaluation/__init__.py +11 -0
  16. parse_bench/evaluation/cli.py +435 -0
  17. parse_bench/evaluation/evaluators/__init__.py +17 -0
  18. parse_bench/evaluation/evaluators/base.py +34 -0
  19. parse_bench/evaluation/evaluators/extract.py +429 -0
  20. parse_bench/evaluation/evaluators/layoutdet.py +1682 -0
  21. parse_bench/evaluation/evaluators/parse.py +1353 -0
  22. parse_bench/evaluation/evaluators/qa.py +199 -0
  23. parse_bench/evaluation/layout_adapters/__init__.py +21 -0
  24. parse_bench/evaluation/layout_adapters/adapters.py +3180 -0
  25. parse_bench/evaluation/layout_adapters/base.py +105 -0
  26. parse_bench/evaluation/layout_adapters/registry.py +109 -0
  27. parse_bench/evaluation/layout_label_mappers/__init__.py +22 -0
  28. parse_bench/evaluation/layout_label_mappers/base.py +66 -0
  29. parse_bench/evaluation/layout_label_mappers/mappers.py +332 -0
  30. parse_bench/evaluation/layout_label_mappers/projection.py +74 -0
  31. parse_bench/evaluation/layout_label_mappers/registry.py +119 -0
  32. parse_bench/evaluation/metric_aggregation.py +56 -0
  33. parse_bench/evaluation/metrics/__init__.py +5 -0
  34. parse_bench/evaluation/metrics/attribution/__init__.py +35 -0
  35. parse_bench/evaluation/metrics/attribution/constants.py +12 -0
  36. parse_bench/evaluation/metrics/attribution/core.py +1108 -0
  37. parse_bench/evaluation/metrics/attribution/evaluate.py +446 -0
  38. parse_bench/evaluation/metrics/attribution/geometry.py +161 -0
  39. parse_bench/evaluation/metrics/attribution/text_utils.py +233 -0
  40. parse_bench/evaluation/metrics/base.py +33 -0
  41. parse_bench/evaluation/metrics/downstream/__init__.py +0 -0
  42. parse_bench/evaluation/metrics/extract/__init__.py +29 -0
  43. parse_bench/evaluation/metrics/extract/json_subset_match.py +473 -0
  44. parse_bench/evaluation/metrics/extract/json_subset_match_metric.py +81 -0
  45. parse_bench/evaluation/metrics/extract/list_unwrap.py +340 -0
  46. parse_bench/evaluation/metrics/extract/rule_based_metric.py +90 -0
  47. parse_bench/evaluation/metrics/extract/test_rules.py +409 -0
  48. parse_bench/evaluation/metrics/extract/test_types.py +11 -0
  49. parse_bench/evaluation/metrics/field_grounding/__init__.py +21 -0
  50. parse_bench/evaluation/metrics/field_grounding/core.py +437 -0
  51. parse_bench/evaluation/metrics/field_grounding/extract_adapter.py +1224 -0
  52. parse_bench/evaluation/metrics/field_grounding/parse_adapter.py +697 -0
  53. parse_bench/evaluation/metrics/field_grounding/rule_filters.py +19 -0
  54. parse_bench/evaluation/metrics/field_grounding/value_compare.py +190 -0
  55. parse_bench/evaluation/metrics/layoutdet/__init__.py +17 -0
  56. parse_bench/evaluation/metrics/layoutdet/classification_utils.py +300 -0
  57. parse_bench/evaluation/metrics/layoutdet/iou.py +76 -0
  58. parse_bench/evaluation/metrics/parse/__init__.py +5 -0
  59. parse_bench/evaluation/metrics/parse/_vendor_grits_reference.py +531 -0
  60. parse_bench/evaluation/metrics/parse/cross_page_table_consistency.py +165 -0
  61. parse_bench/evaluation/metrics/parse/emphasis_spans.py +242 -0
  62. parse_bench/evaluation/metrics/parse/fast_tree_edit.py +282 -0
  63. parse_bench/evaluation/metrics/parse/grits_metric.py +1125 -0
  64. parse_bench/evaluation/metrics/parse/grits_reference_metric.py +142 -0
  65. parse_bench/evaluation/metrics/parse/header_accuracy_metric.py +1662 -0
  66. parse_bench/evaluation/metrics/parse/llm_normalization/__init__.py +51 -0
  67. parse_bench/evaluation/metrics/parse/llm_normalization/base.py +125 -0
  68. parse_bench/evaluation/metrics/parse/llm_normalization/config.py +44 -0
  69. parse_bench/evaluation/metrics/parse/llm_normalization/postprocess.py +322 -0
  70. parse_bench/evaluation/metrics/parse/llm_normalization/strategy_judge.py +541 -0
  71. parse_bench/evaluation/metrics/parse/mermaid_graph.py +682 -0
  72. parse_bench/evaluation/metrics/parse/rule_based_judge_metric.py +56 -0
  73. parse_bench/evaluation/metrics/parse/rule_based_metric.py +434 -0
  74. parse_bench/evaluation/metrics/parse/rules_bag.py +1161 -0
  75. parse_bench/evaluation/metrics/parse/rules_base.py +751 -0
  76. parse_bench/evaluation/metrics/parse/rules_chart.py +1556 -0
  77. parse_bench/evaluation/metrics/parse/rules_diagram.py +591 -0
  78. parse_bench/evaluation/metrics/parse/rules_form.py +2274 -0
  79. parse_bench/evaluation/metrics/parse/rules_formatting.py +1500 -0
  80. parse_bench/evaluation/metrics/parse/rules_heading.py +228 -0
  81. parse_bench/evaluation/metrics/parse/rules_list.py +226 -0
  82. parse_bench/evaluation/metrics/parse/rules_page_decoration.py +276 -0
  83. parse_bench/evaluation/metrics/parse/rules_table.py +1666 -0
  84. parse_bench/evaluation/metrics/parse/rules_text.py +340 -0
  85. parse_bench/evaluation/metrics/parse/rules_watermark.py +105 -0
  86. parse_bench/evaluation/metrics/parse/structural_consistency_metric.py +251 -0
  87. parse_bench/evaluation/metrics/parse/table_extraction.py +152 -0
  88. parse_bench/evaluation/metrics/parse/table_merging.py +195 -0
  89. parse_bench/evaluation/metrics/parse/table_pairing.py +87 -0
  90. parse_bench/evaluation/metrics/parse/table_parsing.py +955 -0
  91. parse_bench/evaluation/metrics/parse/table_record_match_metric.py +1453 -0
  92. parse_bench/evaluation/metrics/parse/table_splitting.py +301 -0
  93. parse_bench/evaluation/metrics/parse/table_title_stripping.py +530 -0
  94. parse_bench/evaluation/metrics/parse/teds_metric.py +600 -0
  95. parse_bench/evaluation/metrics/parse/test_rules.py +120 -0
  96. parse_bench/evaluation/metrics/parse/test_types.py +103 -0
  97. parse_bench/evaluation/metrics/parse/text_content_projection.py +175 -0
  98. parse_bench/evaluation/metrics/parse/text_similarity_metric.py +61 -0
  99. parse_bench/evaluation/metrics/parse/utils.py +885 -0
  100. parse_bench/evaluation/metrics/qa/__init__.py +5 -0
  101. parse_bench/evaluation/metrics/qa/answer_comparison.py +380 -0
  102. parse_bench/evaluation/qa/__init__.py +5 -0
  103. parse_bench/evaluation/qa/llm_service.py +335 -0
  104. parse_bench/evaluation/reports/__init__.py +8 -0
  105. parse_bench/evaluation/reports/csv.py +64 -0
  106. parse_bench/evaluation/reports/html.py +338 -0
  107. parse_bench/evaluation/reports/markdown.py +98 -0
  108. parse_bench/evaluation/reports/rule_csv.py +22 -0
  109. parse_bench/evaluation/runner.py +1864 -0
  110. parse_bench/evaluation/stats.py +104 -0
  111. parse_bench/extensions.py +72 -0
  112. parse_bench/inference/__init__.py +33 -0
  113. parse_bench/inference/chunkr_layout_extraction.py +160 -0
  114. parse_bench/inference/cli.py +484 -0
  115. parse_bench/inference/layout_extraction.py +422 -0
  116. parse_bench/inference/pipelines/__init__.py +59 -0
  117. parse_bench/inference/pipelines/extract.py +39 -0
  118. parse_bench/inference/pipelines/layout.py +142 -0
  119. parse_bench/inference/pipelines/parse.py +2603 -0
  120. parse_bench/inference/pipelines.py +0 -0
  121. parse_bench/inference/providers/__init__.py +28 -0
  122. parse_bench/inference/providers/base.py +196 -0
  123. parse_bench/inference/providers/cancellation.py +137 -0
  124. parse_bench/inference/providers/extract/__init__.py +22 -0
  125. parse_bench/inference/providers/extract/citations.py +549 -0
  126. parse_bench/inference/providers/extract/extend.py +851 -0
  127. parse_bench/inference/providers/extract/llamaextract_v2_api.py +583 -0
  128. parse_bench/inference/providers/layoutdet/__init__.py +25 -0
  129. parse_bench/inference/providers/layoutdet/adapters.py +946 -0
  130. parse_bench/inference/providers/layoutdet/base.py +203 -0
  131. parse_bench/inference/providers/layoutdet/chandra.py +449 -0
  132. parse_bench/inference/providers/layoutdet/docling.py +125 -0
  133. parse_bench/inference/providers/layoutdet/dots_ocr.py +606 -0
  134. parse_bench/inference/providers/layoutdet/layout_v3.py +137 -0
  135. parse_bench/inference/providers/layoutdet/layout_v3_byoc.py +204 -0
  136. parse_bench/inference/providers/layoutdet/paddle.py +117 -0
  137. parse_bench/inference/providers/layoutdet/qwen3vl.py +360 -0
  138. parse_bench/inference/providers/layoutdet/surya.py +250 -0
  139. parse_bench/inference/providers/layoutdet/yolo.py +109 -0
  140. parse_bench/inference/providers/parse/__init__.py +64 -0
  141. parse_bench/inference/providers/parse/_docling_common.py +233 -0
  142. parse_bench/inference/providers/parse/_layout_utils.py +611 -0
  143. parse_bench/inference/providers/parse/amazon_nova.py +515 -0
  144. parse_bench/inference/providers/parse/anthropic.py +882 -0
  145. parse_bench/inference/providers/parse/azure_document_intelligence.py +700 -0
  146. parse_bench/inference/providers/parse/chandra2.py +633 -0
  147. parse_bench/inference/providers/parse/chunkr.py +268 -0
  148. parse_bench/inference/providers/parse/databricks_ai_parse.py +724 -0
  149. parse_bench/inference/providers/parse/datalab.py +370 -0
  150. parse_bench/inference/providers/parse/deepseekocr2.py +382 -0
  151. parse_bench/inference/providers/parse/docling.py +281 -0
  152. parse_bench/inference/providers/parse/docling_serve.py +289 -0
  153. parse_bench/inference/providers/parse/dots_ocr.py +574 -0
  154. parse_bench/inference/providers/parse/extend_parse.py +710 -0
  155. parse_bench/inference/providers/parse/falconocr.py +436 -0
  156. parse_bench/inference/providers/parse/florin_parser_nano.py +559 -0
  157. parse_bench/inference/providers/parse/gemma4.py +472 -0
  158. parse_bench/inference/providers/parse/glm_zai.py +229 -0
  159. parse_bench/inference/providers/parse/google.py +1125 -0
  160. parse_bench/inference/providers/parse/google_agentic_vision.py +819 -0
  161. parse_bench/inference/providers/parse/google_docai.py +776 -0
  162. parse_bench/inference/providers/parse/google_docai_layout_normalization.py +573 -0
  163. parse_bench/inference/providers/parse/granite_vision.py +515 -0
  164. parse_bench/inference/providers/parse/infinity_parser2.py +704 -0
  165. parse_bench/inference/providers/parse/kdl_frontier_nano.py +3327 -0
  166. parse_bench/inference/providers/parse/landingai.py +452 -0
  167. parse_bench/inference/providers/parse/liteparse.py +350 -0
  168. parse_bench/inference/providers/parse/llamaparse.py +677 -0
  169. parse_bench/inference/providers/parse/llamaparse_v2_normalization.py +1013 -0
  170. parse_bench/inference/providers/parse/markitdown.py +138 -0
  171. parse_bench/inference/providers/parse/mineru25.py +405 -0
  172. parse_bench/inference/providers/parse/mineru2605pro.py +432 -0
  173. parse_bench/inference/providers/parse/mineru_diffusion.py +371 -0
  174. parse_bench/inference/providers/parse/mistral_ocr.py +546 -0
  175. parse_bench/inference/providers/parse/nemotron_omni.py +473 -0
  176. parse_bench/inference/providers/parse/oi_parser.py +222 -0
  177. parse_bench/inference/providers/parse/openai.py +740 -0
  178. parse_bench/inference/providers/parse/opendataloader.py +152 -0
  179. parse_bench/inference/providers/parse/paddleocr.py +624 -0
  180. parse_bench/inference/providers/parse/pdf_inspector.py +142 -0
  181. parse_bench/inference/providers/parse/pulse.py +785 -0
  182. parse_bench/inference/providers/parse/pymupdf.py +207 -0
  183. parse_bench/inference/providers/parse/pymupdf4llm.py +356 -0
  184. parse_bench/inference/providers/parse/pypdf.py +179 -0
  185. parse_bench/inference/providers/parse/qwen.py +678 -0
  186. parse_bench/inference/providers/parse/rakedoc_nano.py +70 -0
  187. parse_bench/inference/providers/parse/reducto.py +546 -0
  188. parse_bench/inference/providers/parse/surya2.py +372 -0
  189. parse_bench/inference/providers/parse/tesseract.py +301 -0
  190. parse_bench/inference/providers/parse/textract.py +694 -0
  191. parse_bench/inference/providers/parse/unlimitedocr.py +346 -0
  192. parse_bench/inference/providers/parse/unstructured.py +485 -0
  193. parse_bench/inference/providers/parse/warp_ingest.py +199 -0
  194. parse_bench/inference/providers/registry.py +49 -0
  195. parse_bench/inference/renormalize.py +170 -0
  196. parse_bench/inference/runner.py +2023 -0
  197. parse_bench/layout_label_mapping.py +424 -0
  198. parse_bench/layout_projection.py +179 -0
  199. parse_bench/pipeline/__init__.py +1 -0
  200. parse_bench/pipeline/cli.py +549 -0
  201. parse_bench/schemas/__init__.py +33 -0
  202. parse_bench/schemas/evaluation.py +93 -0
  203. parse_bench/schemas/extract_output.py +36 -0
  204. parse_bench/schemas/layout_detection_output.py +545 -0
  205. parse_bench/schemas/layout_ontology.py +315 -0
  206. parse_bench/schemas/metrics.py +69 -0
  207. parse_bench/schemas/parse_output.py +152 -0
  208. parse_bench/schemas/pipeline.py +22 -0
  209. parse_bench/schemas/pipeline_io.py +106 -0
  210. parse_bench/schemas/product.py +97 -0
  211. parse_bench/test_cases/__init__.py +25 -0
  212. parse_bench/test_cases/bbox_value_strict_comparator.py +880 -0
  213. parse_bench/test_cases/extract_field_paths.py +164 -0
  214. parse_bench/test_cases/layout_attribution_generation.py +287 -0
  215. parse_bench/test_cases/loader.py +652 -0
  216. parse_bench/test_cases/parse_rule_schemas.py +1071 -0
  217. parse_bench/test_cases/rule_filters.py +32 -0
  218. parse_bench/test_cases/rule_ids.py +107 -0
  219. parse_bench/test_cases/schema.py +427 -0
  220. parse_bench/utils/__init__.py +15 -0
  221. parse_bench/utils/gemini_layout_utils.py +670 -0
  222. parse_bench/utils/text_aggregation.py +100 -0
  223. parse_bench-1.0.0.dist-info/METADATA +476 -0
  224. parse_bench-1.0.0.dist-info/RECORD +227 -0
  225. parse_bench-1.0.0.dist-info/WHEEL +4 -0
  226. parse_bench-1.0.0.dist-info/entry_points.txt +2 -0
  227. 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
+ )