natural-pdf 0.1.3__py3-none-any.whl → 0.1.5__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 (132) hide show
  1. docs/api/index.md +386 -0
  2. docs/assets/favicon.png +3 -0
  3. docs/assets/favicon.svg +3 -0
  4. docs/assets/javascripts/custom.js +17 -0
  5. docs/assets/logo.svg +3 -0
  6. docs/assets/sample-screen.png +0 -0
  7. docs/assets/social-preview.png +17 -0
  8. docs/assets/social-preview.svg +17 -0
  9. docs/assets/stylesheets/custom.css +65 -0
  10. docs/document-qa/index.ipynb +435 -0
  11. docs/document-qa/index.md +79 -0
  12. docs/element-selection/index.ipynb +915 -0
  13. docs/element-selection/index.md +229 -0
  14. docs/index.md +170 -0
  15. docs/installation/index.md +69 -0
  16. docs/interactive-widget/index.ipynb +962 -0
  17. docs/interactive-widget/index.md +12 -0
  18. docs/layout-analysis/index.ipynb +818 -0
  19. docs/layout-analysis/index.md +185 -0
  20. docs/ocr/index.md +222 -0
  21. docs/pdf-navigation/index.ipynb +314 -0
  22. docs/pdf-navigation/index.md +97 -0
  23. docs/regions/index.ipynb +816 -0
  24. docs/regions/index.md +294 -0
  25. docs/tables/index.ipynb +658 -0
  26. docs/tables/index.md +144 -0
  27. docs/text-analysis/index.ipynb +370 -0
  28. docs/text-analysis/index.md +105 -0
  29. docs/text-extraction/index.ipynb +1478 -0
  30. docs/text-extraction/index.md +292 -0
  31. docs/tutorials/01-loading-and-extraction.ipynb +1696 -0
  32. docs/tutorials/01-loading-and-extraction.md +95 -0
  33. docs/tutorials/02-finding-elements.ipynb +340 -0
  34. docs/tutorials/02-finding-elements.md +149 -0
  35. docs/tutorials/03-extracting-blocks.ipynb +147 -0
  36. docs/tutorials/03-extracting-blocks.md +48 -0
  37. docs/tutorials/04-table-extraction.ipynb +114 -0
  38. docs/tutorials/04-table-extraction.md +50 -0
  39. docs/tutorials/05-excluding-content.ipynb +270 -0
  40. docs/tutorials/05-excluding-content.md +109 -0
  41. docs/tutorials/06-document-qa.ipynb +332 -0
  42. docs/tutorials/06-document-qa.md +91 -0
  43. docs/tutorials/07-layout-analysis.ipynb +260 -0
  44. docs/tutorials/07-layout-analysis.md +66 -0
  45. docs/tutorials/07-working-with-regions.ipynb +409 -0
  46. docs/tutorials/07-working-with-regions.md +151 -0
  47. docs/tutorials/08-spatial-navigation.ipynb +508 -0
  48. docs/tutorials/08-spatial-navigation.md +190 -0
  49. docs/tutorials/09-section-extraction.ipynb +2434 -0
  50. docs/tutorials/09-section-extraction.md +256 -0
  51. docs/tutorials/10-form-field-extraction.ipynb +484 -0
  52. docs/tutorials/10-form-field-extraction.md +201 -0
  53. docs/tutorials/11-enhanced-table-processing.ipynb +54 -0
  54. docs/tutorials/11-enhanced-table-processing.md +9 -0
  55. docs/tutorials/12-ocr-integration.ipynb +586 -0
  56. docs/tutorials/12-ocr-integration.md +188 -0
  57. docs/tutorials/13-semantic-search.ipynb +1888 -0
  58. docs/tutorials/13-semantic-search.md +77 -0
  59. docs/visual-debugging/index.ipynb +2970 -0
  60. docs/visual-debugging/index.md +157 -0
  61. docs/visual-debugging/region.png +0 -0
  62. natural_pdf/__init__.py +39 -20
  63. natural_pdf/analyzers/__init__.py +2 -1
  64. natural_pdf/analyzers/layout/base.py +32 -24
  65. natural_pdf/analyzers/layout/docling.py +131 -72
  66. natural_pdf/analyzers/layout/layout_analyzer.py +156 -113
  67. natural_pdf/analyzers/layout/layout_manager.py +98 -58
  68. natural_pdf/analyzers/layout/layout_options.py +32 -17
  69. natural_pdf/analyzers/layout/paddle.py +152 -95
  70. natural_pdf/analyzers/layout/surya.py +164 -92
  71. natural_pdf/analyzers/layout/tatr.py +149 -84
  72. natural_pdf/analyzers/layout/yolo.py +84 -44
  73. natural_pdf/analyzers/text_options.py +22 -15
  74. natural_pdf/analyzers/text_structure.py +131 -85
  75. natural_pdf/analyzers/utils.py +30 -23
  76. natural_pdf/collections/pdf_collection.py +126 -98
  77. natural_pdf/core/__init__.py +1 -1
  78. natural_pdf/core/element_manager.py +416 -337
  79. natural_pdf/core/highlighting_service.py +268 -196
  80. natural_pdf/core/page.py +910 -516
  81. natural_pdf/core/pdf.py +387 -289
  82. natural_pdf/elements/__init__.py +1 -1
  83. natural_pdf/elements/base.py +302 -214
  84. natural_pdf/elements/collections.py +714 -514
  85. natural_pdf/elements/line.py +39 -36
  86. natural_pdf/elements/rect.py +32 -30
  87. natural_pdf/elements/region.py +854 -883
  88. natural_pdf/elements/text.py +122 -99
  89. natural_pdf/exporters/__init__.py +0 -1
  90. natural_pdf/exporters/searchable_pdf.py +261 -102
  91. natural_pdf/ocr/__init__.py +23 -14
  92. natural_pdf/ocr/engine.py +17 -8
  93. natural_pdf/ocr/engine_easyocr.py +63 -47
  94. natural_pdf/ocr/engine_paddle.py +97 -68
  95. natural_pdf/ocr/engine_surya.py +54 -44
  96. natural_pdf/ocr/ocr_manager.py +88 -62
  97. natural_pdf/ocr/ocr_options.py +16 -10
  98. natural_pdf/qa/__init__.py +1 -1
  99. natural_pdf/qa/document_qa.py +119 -111
  100. natural_pdf/search/__init__.py +37 -31
  101. natural_pdf/search/haystack_search_service.py +312 -189
  102. natural_pdf/search/haystack_utils.py +186 -122
  103. natural_pdf/search/search_options.py +25 -14
  104. natural_pdf/search/search_service_protocol.py +12 -6
  105. natural_pdf/search/searchable_mixin.py +261 -176
  106. natural_pdf/selectors/__init__.py +2 -1
  107. natural_pdf/selectors/parser.py +159 -316
  108. natural_pdf/templates/__init__.py +1 -1
  109. natural_pdf/utils/highlighting.py +8 -2
  110. natural_pdf/utils/reading_order.py +65 -63
  111. natural_pdf/utils/text_extraction.py +195 -0
  112. natural_pdf/utils/visualization.py +70 -61
  113. natural_pdf/widgets/__init__.py +2 -3
  114. natural_pdf/widgets/viewer.py +749 -718
  115. {natural_pdf-0.1.3.dist-info → natural_pdf-0.1.5.dist-info}/METADATA +29 -15
  116. natural_pdf-0.1.5.dist-info/RECORD +134 -0
  117. natural_pdf-0.1.5.dist-info/top_level.txt +5 -0
  118. notebooks/Examples.ipynb +1293 -0
  119. pdfs/.gitkeep +0 -0
  120. pdfs/01-practice.pdf +543 -0
  121. pdfs/0500000US42001.pdf +0 -0
  122. pdfs/0500000US42007.pdf +0 -0
  123. pdfs/2014 Statistics.pdf +0 -0
  124. pdfs/2019 Statistics.pdf +0 -0
  125. pdfs/Atlanta_Public_Schools_GA_sample.pdf +0 -0
  126. pdfs/needs-ocr.pdf +0 -0
  127. tests/test_loading.py +50 -0
  128. tests/test_optional_deps.py +298 -0
  129. natural_pdf-0.1.3.dist-info/RECORD +0 -61
  130. natural_pdf-0.1.3.dist-info/top_level.txt +0 -1
  131. {natural_pdf-0.1.3.dist-info → natural_pdf-0.1.5.dist-info}/WHEEL +0 -0
  132. {natural_pdf-0.1.3.dist-info → natural_pdf-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,15 @@
1
1
  # layout_detector_surya.py
2
- import logging
2
+ import copy
3
3
  import importlib.util
4
+ import logging
4
5
  import os
5
6
  import tempfile
6
- import copy
7
- from typing import List, Dict, Any, Optional, Tuple
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+
8
9
  from PIL import Image
9
10
 
10
11
  from .base import LayoutDetector
11
- from .layout_options import SuryaLayoutOptions, BaseLayoutOptions
12
+ from .layout_options import BaseLayoutOptions, SuryaLayoutOptions
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
@@ -33,42 +34,58 @@ class SuryaLayoutDetector(LayoutDetector):
33
34
  def __init__(self):
34
35
  super().__init__()
35
36
  self.supported_classes = {
36
- 'text', 'pageheader', 'pagefooter', 'sectionheader',
37
- 'table', 'tableofcontents', 'picture', 'caption',
38
- 'heading', 'title', 'list', 'listitem', 'code',
39
- 'textinlinemath', 'mathformula', 'form',
40
- 'table-row', 'table-column'
37
+ "text",
38
+ "pageheader",
39
+ "pagefooter",
40
+ "sectionheader",
41
+ "table",
42
+ "tableofcontents",
43
+ "picture",
44
+ "caption",
45
+ "heading",
46
+ "title",
47
+ "list",
48
+ "listitem",
49
+ "code",
50
+ "textinlinemath",
51
+ "mathformula",
52
+ "form",
53
+ "table-row",
54
+ "table-column",
41
55
  }
42
- self._page_ref = None # To store page reference from options
56
+ self._page_ref = None # To store page reference from options
43
57
 
44
58
  def is_available(self) -> bool:
45
59
  return LayoutPredictor is not None and TableRecPredictor is not None
46
60
 
47
61
  def _get_cache_key(self, options: BaseLayoutOptions) -> str:
48
62
  if not isinstance(options, SuryaLayoutOptions):
49
- options = SuryaLayoutOptions(device=options.device)
50
- device_key = str(options.device).lower() if options.device else 'default_device'
63
+ options = SuryaLayoutOptions(device=options.device)
64
+ device_key = str(options.device).lower() if options.device else "default_device"
51
65
  model_key = options.model_name
52
66
  return f"{self.__class__.__name__}_{device_key}_{model_key}"
53
67
 
54
68
  def _load_model_from_options(self, options: BaseLayoutOptions) -> Dict[str, Any]:
55
69
  if not self.is_available():
56
- raise RuntimeError("Surya dependencies (surya.layout and surya.table_rec) not installed.")
70
+ raise RuntimeError(
71
+ "Surya dependencies (surya.layout and surya.table_rec) not installed."
72
+ )
57
73
  if not isinstance(options, SuryaLayoutOptions):
58
74
  raise TypeError("Incorrect options type provided for Surya model loading.")
59
75
  self.logger.info(f"Loading Surya models (device={options.device})...")
60
76
  models = {}
61
- try:
62
- models['layout'] = LayoutPredictor()
63
- models['table_rec'] = TableRecPredictor()
77
+ try:
78
+ models["layout"] = LayoutPredictor()
79
+ models["table_rec"] = TableRecPredictor()
64
80
  self.logger.info("Surya LayoutPredictor and TableRecPredictor loaded.")
65
81
  return models
66
82
  except Exception as e:
67
83
  self.logger.error(f"Failed to load Surya models: {e}", exc_info=True)
68
84
  raise
69
-
70
- def _expand_bbox(self, bbox: Tuple[float, float, float, float],
71
- padding: int, max_width: int, max_height: int) -> Tuple[int, int, int, int]:
85
+
86
+ def _expand_bbox(
87
+ self, bbox: Tuple[float, float, float, float], padding: int, max_width: int, max_height: int
88
+ ) -> Tuple[int, int, int, int]:
72
89
  """Expand bbox by padding, clamping to max dimensions."""
73
90
  x0, y0, x1, y1 = bbox
74
91
  x0 = max(0, int(x0 - padding))
@@ -83,97 +100,139 @@ class SuryaLayoutDetector(LayoutDetector):
83
100
  raise RuntimeError("Surya dependencies (layout and table_rec) not installed.")
84
101
 
85
102
  if not isinstance(options, SuryaLayoutOptions):
86
- self.logger.warning("Received BaseLayoutOptions, expected SuryaLayoutOptions. Using defaults.")
87
- options = SuryaLayoutOptions(
88
- confidence=options.confidence, classes=options.classes,
89
- exclude_classes=options.exclude_classes, device=options.device,
90
- extra_args=options.extra_args,
91
- recognize_table_structure=True
92
- )
93
-
103
+ self.logger.warning(
104
+ "Received BaseLayoutOptions, expected SuryaLayoutOptions. Using defaults."
105
+ )
106
+ options = SuryaLayoutOptions(
107
+ confidence=options.confidence,
108
+ classes=options.classes,
109
+ exclude_classes=options.exclude_classes,
110
+ device=options.device,
111
+ extra_args=options.extra_args,
112
+ recognize_table_structure=True,
113
+ )
114
+
94
115
  # Extract page reference and scaling factors from extra_args (passed by LayoutAnalyzer)
95
- self._page_ref = options.extra_args.get('_page_ref')
96
- img_scale_x = options.extra_args.get('_img_scale_x')
97
- img_scale_y = options.extra_args.get('_img_scale_y')
98
-
116
+ self._page_ref = options.extra_args.get("_page_ref")
117
+ img_scale_x = options.extra_args.get("_img_scale_x")
118
+ img_scale_y = options.extra_args.get("_img_scale_y")
119
+
99
120
  # We still need this check, otherwise later steps that need these vars will fail
100
- can_do_table_rec = options.recognize_table_structure and self._page_ref and img_scale_x is not None and img_scale_y is not None
121
+ can_do_table_rec = (
122
+ options.recognize_table_structure
123
+ and self._page_ref
124
+ and img_scale_x is not None
125
+ and img_scale_y is not None
126
+ )
101
127
  if options.recognize_table_structure and not can_do_table_rec:
102
- logger.warning("Surya table recognition cannot proceed without page reference and scaling factors. Disabling.")
103
- options.recognize_table_structure = False
128
+ logger.warning(
129
+ "Surya table recognition cannot proceed without page reference and scaling factors. Disabling."
130
+ )
131
+ options.recognize_table_structure = False
104
132
 
105
133
  # Validate classes
106
- if options.classes: self.validate_classes(options.classes)
107
- if options.exclude_classes: self.validate_classes(options.exclude_classes)
134
+ if options.classes:
135
+ self.validate_classes(options.classes)
136
+ if options.exclude_classes:
137
+ self.validate_classes(options.exclude_classes)
108
138
 
109
139
  models = self._get_model(options)
110
- layout_predictor = models['layout']
111
- table_rec_predictor = models['table_rec']
140
+ layout_predictor = models["layout"]
141
+ table_rec_predictor = models["table_rec"]
112
142
 
113
143
  input_image = image.convert("RGB")
114
144
  input_image_list = [input_image]
115
145
 
116
- initial_layout_detections = [] # Detections relative to input_image
146
+ initial_layout_detections = [] # Detections relative to input_image
117
147
  tables_to_process = []
118
-
119
- # --- Initial Layout Detection ---
148
+
149
+ # --- Initial Layout Detection ---
120
150
  self.logger.debug("Running Surya layout prediction...")
121
151
  layout_predictions = layout_predictor(input_image_list)
122
152
  self.logger.debug(f"Surya prediction returned {len(layout_predictions)} results.")
123
- if not layout_predictions: return []
153
+ if not layout_predictions:
154
+ return []
124
155
  prediction = layout_predictions[0]
125
156
 
126
- normalized_classes_req = {self._normalize_class_name(c) for c in options.classes} if options.classes else None
127
- normalized_classes_excl = {self._normalize_class_name(c) for c in options.exclude_classes} if options.exclude_classes else set()
157
+ normalized_classes_req = (
158
+ {self._normalize_class_name(c) for c in options.classes} if options.classes else None
159
+ )
160
+ normalized_classes_excl = (
161
+ {self._normalize_class_name(c) for c in options.exclude_classes}
162
+ if options.exclude_classes
163
+ else set()
164
+ )
128
165
 
129
166
  for layout_box in prediction.bboxes:
130
167
  class_name_orig = layout_box.label
131
168
  normalized_class = self._normalize_class_name(class_name_orig)
132
169
  score = float(layout_box.confidence)
133
170
 
134
- if score < options.confidence: continue
135
- if normalized_classes_req and normalized_class not in normalized_classes_req: continue
136
- if normalized_class in normalized_classes_excl: continue
171
+ if score < options.confidence:
172
+ continue
173
+ if normalized_classes_req and normalized_class not in normalized_classes_req:
174
+ continue
175
+ if normalized_class in normalized_classes_excl:
176
+ continue
137
177
 
138
178
  x_min, y_min, x_max, y_max = map(float, layout_box.bbox)
139
179
  detection_data = {
140
- 'bbox': (x_min, y_min, x_max, y_max),
141
- 'class': class_name_orig,
142
- 'confidence': score,
143
- 'normalized_class': normalized_class,
144
- 'source': 'layout',
145
- 'model': 'surya'
180
+ "bbox": (x_min, y_min, x_max, y_max),
181
+ "class": class_name_orig,
182
+ "confidence": score,
183
+ "normalized_class": normalized_class,
184
+ "source": "layout",
185
+ "model": "surya",
146
186
  }
147
187
  initial_layout_detections.append(detection_data)
148
188
 
149
- if options.recognize_table_structure and normalized_class in ('table', 'tableofcontents'):
189
+ if options.recognize_table_structure and normalized_class in (
190
+ "table",
191
+ "tableofcontents",
192
+ ):
150
193
  tables_to_process.append(detection_data)
151
194
 
152
- self.logger.info(f"Surya initially detected {len(initial_layout_detections)} layout elements matching criteria.")
195
+ self.logger.info(
196
+ f"Surya initially detected {len(initial_layout_detections)} layout elements matching criteria."
197
+ )
153
198
 
154
199
  # --- Table Structure Recognition (Optional) ---
155
200
  if not options.recognize_table_structure or not tables_to_process:
156
- self.logger.debug("Skipping Surya table structure recognition (disabled or no tables found).")
201
+ self.logger.debug(
202
+ "Skipping Surya table structure recognition (disabled or no tables found)."
203
+ )
157
204
  return initial_layout_detections
158
205
 
159
- self.logger.info(f"Attempting Surya table structure recognition for {len(tables_to_process)} tables...")
206
+ self.logger.info(
207
+ f"Attempting Surya table structure recognition for {len(tables_to_process)} tables..."
208
+ )
160
209
  high_res_crops = []
161
- pdf_offsets = [] # Store (pdf_x0, pdf_y0) for each crop
162
-
163
- high_res_dpi = getattr(self._page_ref._parent, '_config', {}).get('surya_table_rec_dpi', 192)
164
- bbox_padding = getattr(self._page_ref._parent, '_config', {}).get('surya_table_bbox_padding', 10)
210
+ pdf_offsets = [] # Store (pdf_x0, pdf_y0) for each crop
211
+
212
+ high_res_dpi = getattr(self._page_ref._parent, "_config", {}).get(
213
+ "surya_table_rec_dpi", 192
214
+ )
215
+ bbox_padding = getattr(self._page_ref._parent, "_config", {}).get(
216
+ "surya_table_bbox_padding", 10
217
+ )
165
218
  pdf_to_highres_scale = high_res_dpi / 72.0
166
-
219
+
167
220
  # Render high-res page ONCE
168
- self.logger.debug(f"Rendering page {self._page_ref.number} at {high_res_dpi} DPI for table recognition...")
169
- high_res_page_image = self._page_ref.to_image(resolution=high_res_dpi, include_highlights=False)
221
+ self.logger.debug(
222
+ f"Rendering page {self._page_ref.number} at {high_res_dpi} DPI for table recognition..."
223
+ )
224
+ high_res_page_image = self._page_ref.to_image(
225
+ resolution=high_res_dpi, include_highlights=False
226
+ )
170
227
  if not high_res_page_image:
171
228
  raise RuntimeError(f"Failed to render page {self._page_ref.number} at high resolution.")
172
- self.logger.debug(f" High-res image size: {high_res_page_image.width}x{high_res_page_image.height}")
229
+ self.logger.debug(
230
+ f" High-res image size: {high_res_page_image.width}x{high_res_page_image.height}"
231
+ )
173
232
 
174
233
  for i, table_detection in enumerate(tables_to_process):
175
- img_x0, img_y0, img_x1, img_y1 = table_detection['bbox']
176
-
234
+ img_x0, img_y0, img_x1, img_y1 = table_detection["bbox"]
235
+
177
236
  # PDF coords
178
237
  pdf_x0 = img_x0 * img_scale_x
179
238
  pdf_y0 = img_y0 * img_scale_y
@@ -183,40 +242,44 @@ class SuryaLayoutDetector(LayoutDetector):
183
242
  pdf_y0 = max(0, pdf_y0)
184
243
  pdf_x1 = min(self._page_ref.width, pdf_x1)
185
244
  pdf_y1 = min(self._page_ref.height, pdf_y1)
186
-
245
+
187
246
  # High-res image coords
188
247
  hr_x0 = pdf_x0 * pdf_to_highres_scale
189
248
  hr_y0 = pdf_y0 * pdf_to_highres_scale
190
249
  hr_x1 = pdf_x1 * pdf_to_highres_scale
191
250
  hr_y1 = pdf_y1 * pdf_to_highres_scale
192
-
251
+
193
252
  # Expand high-res bbox
194
253
  hr_x0_exp, hr_y0_exp, hr_x1_exp, hr_y1_exp = self._expand_bbox(
195
- (hr_x0, hr_y0, hr_x1, hr_y1),
196
- padding=bbox_padding,
197
- max_width=high_res_page_image.width,
198
- max_height=high_res_page_image.height
254
+ (hr_x0, hr_y0, hr_x1, hr_y1),
255
+ padding=bbox_padding,
256
+ max_width=high_res_page_image.width,
257
+ max_height=high_res_page_image.height,
199
258
  )
200
259
 
201
260
  crop = high_res_page_image.crop((hr_x0_exp, hr_y0_exp, hr_x1_exp, hr_y1_exp))
202
261
  high_res_crops.append(crop)
203
- pdf_offsets.append((pdf_x0, pdf_y0))
204
-
262
+ pdf_offsets.append((pdf_x0, pdf_y0))
263
+
205
264
  if not high_res_crops:
206
265
  self.logger.info("No valid high-resolution table crops generated.")
207
266
  return initial_layout_detections
208
267
 
209
- structure_detections = [] # Detections relative to std_res input_image
268
+ structure_detections = [] # Detections relative to std_res input_image
210
269
 
211
- # --- Run Table Recognition (will raise error on failure) ---
212
- self.logger.debug(f"Running Surya table recognition on {len(high_res_crops)} high-res images...")
270
+ # --- Run Table Recognition (will raise error on failure) ---
271
+ self.logger.debug(
272
+ f"Running Surya table recognition on {len(high_res_crops)} high-res images..."
273
+ )
213
274
  table_predictions = table_rec_predictor(high_res_crops)
214
275
  self.logger.debug(f"Surya table recognition returned {len(table_predictions)} results.")
215
276
 
216
- # --- Process Results ---
277
+ # --- Process Results ---
217
278
  if len(table_predictions) != len(pdf_offsets):
218
- # This case is less likely if predictor didn't error, but good sanity check
219
- raise RuntimeError(f"Mismatch between table inputs ({len(pdf_offsets)}) and predictions ({len(table_predictions)}).")
279
+ # This case is less likely if predictor didn't error, but good sanity check
280
+ raise RuntimeError(
281
+ f"Mismatch between table inputs ({len(pdf_offsets)}) and predictions ({len(table_predictions)})."
282
+ )
220
283
 
221
284
  for table_pred, (offset_pdf_x0, offset_pdf_y0) in zip(table_predictions, pdf_offsets):
222
285
  # Process Rows
@@ -230,11 +293,16 @@ class SuryaLayoutDetector(LayoutDetector):
230
293
  img_row_y0 = pdf_row_y0 / img_scale_y
231
294
  img_row_x1 = pdf_row_x1 / img_scale_x
232
295
  img_row_y1 = pdf_row_y1 / img_scale_y
233
- structure_detections.append({
234
- 'bbox': (img_row_x0, img_row_y0, img_row_x1, img_row_y1),
235
- 'class': 'table-row', 'confidence': 1.0, 'normalized_class': 'table-row',
236
- 'source': 'layout', 'model': 'surya'
237
- })
296
+ structure_detections.append(
297
+ {
298
+ "bbox": (img_row_x0, img_row_y0, img_row_x1, img_row_y1),
299
+ "class": "table-row",
300
+ "confidence": 1.0,
301
+ "normalized_class": "table-row",
302
+ "source": "layout",
303
+ "model": "surya",
304
+ }
305
+ )
238
306
 
239
307
  # Process Columns
240
308
  for col_box in table_pred.cols:
@@ -247,13 +315,17 @@ class SuryaLayoutDetector(LayoutDetector):
247
315
  img_col_y0 = pdf_col_y0 / img_scale_y
248
316
  img_col_x1 = pdf_col_x1 / img_scale_x
249
317
  img_col_y1 = pdf_col_y1 / img_scale_y
250
- structure_detections.append({
251
- 'bbox': (img_col_x0, img_col_y0, img_col_x1, img_col_y1),
252
- 'class': 'table-column', 'confidence': 1.0, 'normalized_class': 'table-column',
253
- 'source': 'layout', 'model': 'surya'
254
- })
318
+ structure_detections.append(
319
+ {
320
+ "bbox": (img_col_x0, img_col_y0, img_col_x1, img_col_y1),
321
+ "class": "table-column",
322
+ "confidence": 1.0,
323
+ "normalized_class": "table-column",
324
+ "source": "layout",
325
+ "model": "surya",
326
+ }
327
+ )
255
328
 
256
329
  self.logger.info(f"Added {len(structure_detections)} table structure elements.")
257
330
 
258
331
  return initial_layout_detections + structure_detections
259
-