BabelDOC 0.2.14__tar.gz → 0.2.15__tar.gz
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.
- {babeldoc-0.2.14 → babeldoc-0.2.15}/PKG-INFO +3 -1
- {babeldoc-0.2.14 → babeldoc-0.2.15}/README.md +1 -0
- babeldoc-0.2.15/babeldoc/__init__.py +1 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/assets/assets.py +35 -1
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/assets/embedding_assets_metadata.py +10 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/const.py +1 -1
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/paragraph_finder.py +2 -0
- babeldoc-0.2.15/babeldoc/document_il/midend/table_parser.py +164 -0
- babeldoc-0.2.15/babeldoc/docvision/table_detection/rapidocr.py +316 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/high_level.py +11 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/main.py +14 -1
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/translation_config.py +3 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/pyproject.toml +3 -2
- {babeldoc-0.2.14 → babeldoc-0.2.15}/uv.lock +94 -1
- babeldoc-0.2.14/babeldoc/__init__.py +0 -1
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.cursorignore +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/dependabot.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/labels.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/release-drafter.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/workflows/docs.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/workflows/publish-to-pypi.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.github/workflows/test.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.gitignore +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/LICENSE +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/converter.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/backend/pdf_creater.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/frontend/il_creater.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/il_version_1.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/il_version_1.rnc +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/il_translator.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/il_translator_llm_only.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/midend/typesetting.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/translator/translator.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/utils/layout_helper.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/utils/style_helper.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/CONTRIBUTING.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/deploy.sh +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/index.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/docs/requirements.txt +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/mkdocs.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.15}/tests/test_translation_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BabelDOC
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
4
4
|
Summary: Yet Another Document Translator
|
|
5
5
|
Project-URL: Homepage, https://github.com/funstory-ai/BabelDOC
|
|
6
6
|
Project-URL: Issues, https://github.com/funstory-ai/BabelDOC/issues
|
|
@@ -30,6 +30,7 @@ Requires-Dist: psutil>=7.0.0
|
|
|
30
30
|
Requires-Dist: pydantic>=2.10.6
|
|
31
31
|
Requires-Dist: pymupdf>=1.25.1
|
|
32
32
|
Requires-Dist: python-levenshtein>=0.27.1
|
|
33
|
+
Requires-Dist: rapidocr-onnxruntime>=1.4.4
|
|
33
34
|
Requires-Dist: rich>=13.9.4
|
|
34
35
|
Requires-Dist: scikit-image>=0.25.2
|
|
35
36
|
Requires-Dist: tenacity>=9.0.0
|
|
@@ -181,6 +182,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
181
182
|
- `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
|
|
182
183
|
- `--max-pages-per-part`: Maximum number of pages per part for split translation. If not set, no splitting will be performed.
|
|
183
184
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
185
|
+
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
184
186
|
|
|
185
187
|
> [!TIP]
|
|
186
188
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -136,6 +136,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
136
136
|
- `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
|
|
137
137
|
- `--max-pages-per-part`: Maximum number of pages per part for split translation. If not set, no splitting will be performed.
|
|
138
138
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
139
|
+
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
139
140
|
|
|
140
141
|
> [!TIP]
|
|
141
142
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.15"
|
|
@@ -14,6 +14,10 @@ from babeldoc.assets.embedding_assets_metadata import (
|
|
|
14
14
|
from babeldoc.assets.embedding_assets_metadata import EMBEDDING_FONT_METADATA
|
|
15
15
|
from babeldoc.assets.embedding_assets_metadata import FONT_METADATA_URL
|
|
16
16
|
from babeldoc.assets.embedding_assets_metadata import FONT_URL_BY_UPSTREAM
|
|
17
|
+
from babeldoc.assets.embedding_assets_metadata import (
|
|
18
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256,
|
|
19
|
+
)
|
|
20
|
+
from babeldoc.assets.embedding_assets_metadata import TABLE_DETECTION_RAPIDOCR_MODEL_URL
|
|
17
21
|
from babeldoc.assets.embedding_assets_metadata import TIKTOKEN_CACHES
|
|
18
22
|
from babeldoc.const import get_cache_file_path
|
|
19
23
|
from tenacity import retry
|
|
@@ -204,10 +208,36 @@ async def get_doclayout_onnx_model_path_async(client: httpx.AsyncClient | None =
|
|
|
204
208
|
return onnx_path
|
|
205
209
|
|
|
206
210
|
|
|
211
|
+
async def get_table_detection_rapidocr_model_path_async(
|
|
212
|
+
client: httpx.AsyncClient | None = None,
|
|
213
|
+
):
|
|
214
|
+
onnx_path = get_cache_file_path("ch_PP-OCRv4_det_infer.onnx", "models")
|
|
215
|
+
if verify_file(onnx_path, TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256):
|
|
216
|
+
return onnx_path
|
|
217
|
+
|
|
218
|
+
logger.info("table detection rapidocr model not found or corrupted, downloading...")
|
|
219
|
+
fastest_upstream, _ = await get_fastest_upstream_for_model(client)
|
|
220
|
+
if fastest_upstream is None:
|
|
221
|
+
logger.error("Failed to get fastest upstream")
|
|
222
|
+
exit(1)
|
|
223
|
+
|
|
224
|
+
url = TABLE_DETECTION_RAPIDOCR_MODEL_URL[fastest_upstream]
|
|
225
|
+
|
|
226
|
+
await download_file(client, url, onnx_path, TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256)
|
|
227
|
+
logger.info(
|
|
228
|
+
f"Download table detection rapidocr model from {fastest_upstream} success"
|
|
229
|
+
)
|
|
230
|
+
return onnx_path
|
|
231
|
+
|
|
232
|
+
|
|
207
233
|
def get_doclayout_onnx_model_path():
|
|
208
234
|
return run_coro(get_doclayout_onnx_model_path_async())
|
|
209
235
|
|
|
210
236
|
|
|
237
|
+
def get_table_detection_rapidocr_model_path():
|
|
238
|
+
return run_coro(get_table_detection_rapidocr_model_path_async())
|
|
239
|
+
|
|
240
|
+
|
|
211
241
|
def get_font_url_by_name_and_upstream(font_file_name: str, upstream: str):
|
|
212
242
|
if upstream not in FONT_URL_BY_UPSTREAM:
|
|
213
243
|
logger.critical(f"Invalid upstream: {upstream}")
|
|
@@ -329,7 +359,11 @@ def generate_all_assets_file_list():
|
|
|
329
359
|
{
|
|
330
360
|
"name": "doclayout_yolo_docstructbench_imgsz1024.onnx",
|
|
331
361
|
"sha3_256": DOCLAYOUT_YOLO_DOCSTRUCTBENCH_IMGSZ1024ONNX_SHA3_256,
|
|
332
|
-
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"name": "ch_PP-OCRv4_det_infer.onnx",
|
|
365
|
+
"sha3_256": TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256,
|
|
366
|
+
},
|
|
333
367
|
)
|
|
334
368
|
return result
|
|
335
369
|
|
|
@@ -2,6 +2,10 @@ DOCLAYOUT_YOLO_DOCSTRUCTBENCH_IMGSZ1024ONNX_SHA3_256 = (
|
|
|
2
2
|
"60be061226930524958b5465c8c04af3d7c03bcb0beb66454f5da9f792e3cf2a"
|
|
3
3
|
)
|
|
4
4
|
|
|
5
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256 = (
|
|
6
|
+
"062f4619afe91b33147c033acadecbb53f2a7b99ac703d157b96d5b10948da5e"
|
|
7
|
+
)
|
|
8
|
+
|
|
5
9
|
TIKTOKEN_CACHES = {
|
|
6
10
|
"fb374d419588a4632f3f557e76b4b70aebbca790": "cb04bcda5782cfbbe77f2f991d92c0ea785d9496ef1137c91dfc3c8c324528d6"
|
|
7
11
|
}
|
|
@@ -26,6 +30,12 @@ DOC_LAYOUT_ONNX_MODEL_URL = {
|
|
|
26
30
|
"modelscope": "https://www.modelscope.cn/models/AI-ModelScope/DocLayout-YOLO-DocStructBench-onnx/resolve/master/doclayout_yolo_docstructbench_imgsz1024.onnx",
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_URL = {
|
|
34
|
+
"huggingface": "https://huggingface.co/spaces/RapidAI/RapidOCR/resolve/main/models/text_det/ch_PP-OCRv4_det_infer.onnx",
|
|
35
|
+
"hf-mirror": "https://hf-mirror.com/spaces/RapidAI/RapidOCR/resolve/main/models/text_det/ch_PP-OCRv4_det_infer.onnx",
|
|
36
|
+
"modelscope": "https://www.modelscope.cn/models/RapidAI/RapidOCR/resolve/master/onnx/PP-OCRv4/det/ch_PP-OCRv4_det_infer.onnx",
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
# from https://github.com/funstory-ai/BabelDOC-Assets/blob/main/font_metadata.json
|
|
30
40
|
EMBEDDING_FONT_METADATA = {
|
|
31
41
|
"GoNotoKurrent-Bold.ttf": {
|
|
@@ -287,6 +287,7 @@ class ParagraphFinder:
|
|
|
287
287
|
"abandon",
|
|
288
288
|
"figure_caption",
|
|
289
289
|
"table_caption",
|
|
290
|
+
"table_text",
|
|
290
291
|
]
|
|
291
292
|
|
|
292
293
|
def get_layout(
|
|
@@ -313,6 +314,7 @@ class ParagraphFinder:
|
|
|
313
314
|
"table_footnote",
|
|
314
315
|
"table_caption",
|
|
315
316
|
"figure_caption",
|
|
317
|
+
"table_text",
|
|
316
318
|
"table",
|
|
317
319
|
"figure",
|
|
318
320
|
"abandon",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pymupdf import Document
|
|
7
|
+
|
|
8
|
+
from babeldoc.document_il import il_version_1
|
|
9
|
+
from babeldoc.document_il.utils.style_helper import GREEN
|
|
10
|
+
from babeldoc.translation_config import TranslationConfig
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TableParser:
|
|
16
|
+
stage_name = "Parse Table"
|
|
17
|
+
|
|
18
|
+
def __init__(self, translation_config: TranslationConfig):
|
|
19
|
+
self.translation_config = translation_config
|
|
20
|
+
self.model = translation_config.table_model
|
|
21
|
+
|
|
22
|
+
def _save_debug_image(self, image: np.ndarray, layouts, page_number: int):
|
|
23
|
+
"""Save debug image with drawn boxes if debug mode is enabled."""
|
|
24
|
+
if not self.translation_config.debug:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if not isinstance(layouts, list):
|
|
28
|
+
layouts = [layouts]
|
|
29
|
+
debug_dir = Path(
|
|
30
|
+
self.translation_config.get_working_file_path("table-ocr-box-image")
|
|
31
|
+
)
|
|
32
|
+
debug_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Draw boxes on the image
|
|
35
|
+
debug_image = image.copy()
|
|
36
|
+
for layout in layouts:
|
|
37
|
+
for box in layout.boxes:
|
|
38
|
+
x0, y0, x1, y1 = box.xyxy
|
|
39
|
+
cv2.rectangle(
|
|
40
|
+
debug_image,
|
|
41
|
+
(int(x0), int(y0)),
|
|
42
|
+
(int(x1), int(y1)),
|
|
43
|
+
(0, 255, 0),
|
|
44
|
+
2,
|
|
45
|
+
)
|
|
46
|
+
# Add text label
|
|
47
|
+
cv2.putText(
|
|
48
|
+
debug_image,
|
|
49
|
+
layout.names[box.cls],
|
|
50
|
+
(int(x0), int(y0) - 5),
|
|
51
|
+
cv2.FONT_HERSHEY_SIMPLEX,
|
|
52
|
+
0.5,
|
|
53
|
+
(0, 255, 0),
|
|
54
|
+
1,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Save the image
|
|
58
|
+
output_path = debug_dir / f"{page_number}.jpg"
|
|
59
|
+
cv2.imwrite(str(output_path), debug_image)
|
|
60
|
+
|
|
61
|
+
def _save_debug_box_to_page(self, page: il_version_1.Page):
|
|
62
|
+
"""Save debug boxes and text labels to the PDF page."""
|
|
63
|
+
if not self.translation_config.debug:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
color = GREEN
|
|
67
|
+
|
|
68
|
+
for layout in page.page_layout:
|
|
69
|
+
# Create a rectangle box
|
|
70
|
+
rect = il_version_1.PdfRectangle(
|
|
71
|
+
box=il_version_1.Box(
|
|
72
|
+
x=layout.box.x,
|
|
73
|
+
y=layout.box.y,
|
|
74
|
+
x2=layout.box.x2,
|
|
75
|
+
y2=layout.box.y2,
|
|
76
|
+
),
|
|
77
|
+
graphic_state=color,
|
|
78
|
+
debug_info=True,
|
|
79
|
+
)
|
|
80
|
+
page.pdf_rectangle.append(rect)
|
|
81
|
+
|
|
82
|
+
# Create text label at top-left corner
|
|
83
|
+
# Note: PDF coordinates are from bottom-left,
|
|
84
|
+
# so we use y2 for top position
|
|
85
|
+
style = il_version_1.PdfStyle(
|
|
86
|
+
font_id="china-ss",
|
|
87
|
+
font_size=4,
|
|
88
|
+
graphic_state=color,
|
|
89
|
+
)
|
|
90
|
+
page.pdf_paragraph.append(
|
|
91
|
+
il_version_1.PdfParagraph(
|
|
92
|
+
first_line_indent=False,
|
|
93
|
+
box=il_version_1.Box(
|
|
94
|
+
x=layout.box.x,
|
|
95
|
+
y=layout.box.y2,
|
|
96
|
+
x2=layout.box.x2,
|
|
97
|
+
y2=layout.box.y2 + 5,
|
|
98
|
+
),
|
|
99
|
+
vertical=False,
|
|
100
|
+
pdf_style=style,
|
|
101
|
+
unicode=layout.class_name,
|
|
102
|
+
pdf_paragraph_composition=[
|
|
103
|
+
il_version_1.PdfParagraphComposition(
|
|
104
|
+
pdf_same_style_unicode_characters=il_version_1.PdfSameStyleUnicodeCharacters(
|
|
105
|
+
unicode=layout.class_name,
|
|
106
|
+
pdf_style=style,
|
|
107
|
+
debug_info=True,
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
],
|
|
111
|
+
xobj_id=-1,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def process(self, docs: il_version_1.Document, mupdf_doc: Document):
|
|
116
|
+
"""Generate layouts for all pages that need to be translated."""
|
|
117
|
+
# Get pages that need to be translated
|
|
118
|
+
have_table_pages = {}
|
|
119
|
+
for page in docs.page:
|
|
120
|
+
for layout in page.page_layout:
|
|
121
|
+
if layout.class_name == "table":
|
|
122
|
+
have_table_pages[page.page_number] = page
|
|
123
|
+
with self.translation_config.progress_monitor.stage_start(
|
|
124
|
+
self.stage_name,
|
|
125
|
+
len(have_table_pages),
|
|
126
|
+
) as progress:
|
|
127
|
+
# Process predictions for each page
|
|
128
|
+
for page, layouts in self.model.handle_document(
|
|
129
|
+
have_table_pages.values(),
|
|
130
|
+
mupdf_doc,
|
|
131
|
+
self.translation_config,
|
|
132
|
+
self._save_debug_image,
|
|
133
|
+
):
|
|
134
|
+
page_layouts = []
|
|
135
|
+
for layout in layouts.boxes:
|
|
136
|
+
# Convert coordinate system from picture to il
|
|
137
|
+
# system to the il coordinate system
|
|
138
|
+
x0, y0, x1, y1 = layout.xyxy
|
|
139
|
+
pix = mupdf_doc[page.page_number].get_pixmap()
|
|
140
|
+
h, w = pix.height, pix.width
|
|
141
|
+
x0, y0, x1, y1 = (
|
|
142
|
+
np.clip(int(x0 - 1), 0, w - 1),
|
|
143
|
+
np.clip(int(h - y1 - 1), 0, h - 1),
|
|
144
|
+
np.clip(int(x1 + 1), 0, w - 1),
|
|
145
|
+
np.clip(int(h - y0 + 1), 0, h - 1),
|
|
146
|
+
)
|
|
147
|
+
page_layout = il_version_1.PageLayout(
|
|
148
|
+
id=len(page_layouts) + 1,
|
|
149
|
+
box=il_version_1.Box(
|
|
150
|
+
x0.item(),
|
|
151
|
+
y0.item(),
|
|
152
|
+
x1.item(),
|
|
153
|
+
y1.item(),
|
|
154
|
+
),
|
|
155
|
+
conf=layout.conf.item(),
|
|
156
|
+
class_name=layouts.names[layout.cls],
|
|
157
|
+
)
|
|
158
|
+
page_layouts.append(page_layout)
|
|
159
|
+
|
|
160
|
+
page.page_layout.extend(page_layouts)
|
|
161
|
+
self._save_debug_box_to_page(page)
|
|
162
|
+
progress.advance(1)
|
|
163
|
+
|
|
164
|
+
return docs
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from babeldoc.assets.assets import get_table_detection_rapidocr_model_path
|
|
8
|
+
from babeldoc.docvision.doclayout import YoloBox
|
|
9
|
+
from babeldoc.docvision.doclayout import YoloResult
|
|
10
|
+
from rapidocr_onnxruntime import RapidOCR
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import onnxruntime
|
|
14
|
+
except ImportError as e:
|
|
15
|
+
if "DLL load failed" in str(e):
|
|
16
|
+
raise OSError(
|
|
17
|
+
"Microsoft Visual C++ Redistributable is not installed. "
|
|
18
|
+
"Download it at https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
|
19
|
+
) from e
|
|
20
|
+
raise
|
|
21
|
+
import babeldoc.document_il.il_version_1
|
|
22
|
+
import pymupdf
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def convert_to_yolo_result(predictions):
|
|
28
|
+
"""
|
|
29
|
+
Convert RapidOCR predictions to YoloResult format.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
predictions (list): List of predictions, where each prediction is a list of coordinates
|
|
33
|
+
in format [[x1, y1], [x2, y2], [x3, y3], [x4, y4], (text, confidence)]
|
|
34
|
+
or a numpy array of format [x1, y1, x2, y2, ...]
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
YoloResult: Converted predictions in YoloResult format
|
|
38
|
+
"""
|
|
39
|
+
boxes = []
|
|
40
|
+
|
|
41
|
+
for pred in predictions:
|
|
42
|
+
# Check if the prediction is in the format of 4 corner points
|
|
43
|
+
if isinstance(pred, list) and len(pred) >= 5 and isinstance(pred[0], list):
|
|
44
|
+
# Convert 4 corner points to xyxy format (min x, min y, max x, max y)
|
|
45
|
+
points = np.array(pred[:4])
|
|
46
|
+
x1, y1 = points[:, 0].min(), points[:, 1].min()
|
|
47
|
+
x2, y2 = points[:, 0].max(), points[:, 1].max()
|
|
48
|
+
xyxy = [x1, y1, x2, y2]
|
|
49
|
+
box = YoloBox(xyxy=xyxy, conf=1.0, cls="text")
|
|
50
|
+
# Check if the prediction is already in xyxy format
|
|
51
|
+
elif isinstance(pred, list | np.ndarray) and len(pred) >= 4:
|
|
52
|
+
if isinstance(pred, np.ndarray):
|
|
53
|
+
pred = pred.tolist()
|
|
54
|
+
xyxy = pred[:4]
|
|
55
|
+
box = YoloBox(xyxy=xyxy, conf=1.0, cls="text")
|
|
56
|
+
else:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
boxes.append(box)
|
|
60
|
+
|
|
61
|
+
return YoloResult(names=["text"], boxes=boxes)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def create_yolo_result_from_nested_coords(nested_coords: np.ndarray, names: dict):
|
|
65
|
+
boxes = []
|
|
66
|
+
|
|
67
|
+
for quad in nested_coords.tolist():
|
|
68
|
+
if len(quad) != 4:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# Convert quad coordinates to xyxy format (min x, min y, max x, max y)
|
|
72
|
+
x1, y1, x2, y2 = quad
|
|
73
|
+
|
|
74
|
+
# Create YoloBox with confidence 1.0 and class 'text'
|
|
75
|
+
box = YoloBox(
|
|
76
|
+
xyxy=[float(x1), float(y1), float(x2), float(y2)], conf=np.array(1.0), cls=0
|
|
77
|
+
)
|
|
78
|
+
boxes.append(box)
|
|
79
|
+
|
|
80
|
+
return YoloResult(names=names, boxes=boxes)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RapidOCRModel:
|
|
84
|
+
def __init__(self):
|
|
85
|
+
self.use_cuda = False
|
|
86
|
+
self.use_dml = False
|
|
87
|
+
available_providers = onnxruntime.get_available_providers()
|
|
88
|
+
for provider in available_providers:
|
|
89
|
+
if re.match(r"dml", provider, re.IGNORECASE):
|
|
90
|
+
self.use_dml = True
|
|
91
|
+
elif re.match(r"cuda", provider, re.IGNORECASE):
|
|
92
|
+
self.use_cuda = True
|
|
93
|
+
|
|
94
|
+
self.model = RapidOCR(
|
|
95
|
+
det_model_path=get_table_detection_rapidocr_model_path(),
|
|
96
|
+
det_use_cuda=self.use_cuda,
|
|
97
|
+
det_use_dml=self.use_dml,
|
|
98
|
+
)
|
|
99
|
+
self.names = {0: "table_text"}
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def stride(self):
|
|
103
|
+
return 32
|
|
104
|
+
|
|
105
|
+
def resize_and_pad_image(self, image, new_shape):
|
|
106
|
+
"""
|
|
107
|
+
Resize and pad the image to the specified size, ensuring dimensions are multiples of stride.
|
|
108
|
+
|
|
109
|
+
Parameters:
|
|
110
|
+
- image: Input image
|
|
111
|
+
- new_shape: Target size (integer or (height, width) tuple)
|
|
112
|
+
- stride: Padding alignment stride, default 32
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
- Processed image
|
|
116
|
+
"""
|
|
117
|
+
if isinstance(new_shape, int):
|
|
118
|
+
new_shape = (new_shape, new_shape)
|
|
119
|
+
|
|
120
|
+
h, w = image.shape[:2]
|
|
121
|
+
new_h, new_w = new_shape
|
|
122
|
+
|
|
123
|
+
# Calculate scaling ratio
|
|
124
|
+
r = min(new_h / h, new_w / w)
|
|
125
|
+
resized_h, resized_w = int(round(h * r)), int(round(w * r))
|
|
126
|
+
|
|
127
|
+
# Resize image
|
|
128
|
+
image = cv2.resize(
|
|
129
|
+
image,
|
|
130
|
+
(resized_w, resized_h),
|
|
131
|
+
interpolation=cv2.INTER_LINEAR,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Calculate padding size and align to stride multiple
|
|
135
|
+
pad_w = (new_w - resized_w) % self.stride
|
|
136
|
+
pad_h = (new_h - resized_h) % self.stride
|
|
137
|
+
top, bottom = pad_h // 2, pad_h - pad_h // 2
|
|
138
|
+
left, right = pad_w // 2, pad_w - pad_w // 2
|
|
139
|
+
|
|
140
|
+
# Add padding
|
|
141
|
+
image = cv2.copyMakeBorder(
|
|
142
|
+
image,
|
|
143
|
+
top,
|
|
144
|
+
bottom,
|
|
145
|
+
left,
|
|
146
|
+
right,
|
|
147
|
+
cv2.BORDER_CONSTANT,
|
|
148
|
+
value=(114, 114, 114),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return image
|
|
152
|
+
|
|
153
|
+
def scale_boxes(self, img1_shape, boxes, img0_shape):
|
|
154
|
+
"""
|
|
155
|
+
Rescales bounding boxes (in the format of xyxy by default) from the shape of the image they were originally
|
|
156
|
+
specified in (img1_shape) to the shape of a different image (img0_shape).
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
img1_shape (tuple): The shape of the image that the bounding boxes are for,
|
|
160
|
+
in the format of (height, width).
|
|
161
|
+
boxes (torch.Tensor): the bounding boxes of the objects in the image, in the format of (x1, y1, x2, y2)
|
|
162
|
+
img0_shape (tuple): the shape of the target image, in the format of (height, width).
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
boxes (torch.Tensor): The scaled bounding boxes, in the format of (x1, y1, x2, y2)
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
# Calculate scaling ratio
|
|
169
|
+
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])
|
|
170
|
+
|
|
171
|
+
# Calculate padding size
|
|
172
|
+
pad_x = round((img1_shape[1] - img0_shape[1] * gain) / 2 - 0.1)
|
|
173
|
+
pad_y = round((img1_shape[0] - img0_shape[0] * gain) / 2 - 0.1)
|
|
174
|
+
|
|
175
|
+
# Remove padding and scale boxes
|
|
176
|
+
boxes[..., :4] = (boxes[..., :4] - [pad_x, pad_y, pad_x, pad_y]) / gain
|
|
177
|
+
return boxes
|
|
178
|
+
|
|
179
|
+
def predict(self, image, imgsz=800, batch_size=16, **kwargs):
|
|
180
|
+
"""
|
|
181
|
+
Predict the layout of document pages.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
image: A single image or a list of images of document pages.
|
|
185
|
+
imgsz: Resize the image to this size. Must be a multiple of the stride.
|
|
186
|
+
batch_size: Number of images to process in one batch.
|
|
187
|
+
**kwargs: Additional arguments.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
A YoloResult object containing the detected boxes.
|
|
191
|
+
"""
|
|
192
|
+
# Handle single image input
|
|
193
|
+
assert isinstance(image, np.ndarray) and len(image.shape) == 3
|
|
194
|
+
|
|
195
|
+
# Calculate target size based on the maximum height in the batch
|
|
196
|
+
target_imgsz = 1024
|
|
197
|
+
|
|
198
|
+
orig_shape = (image.shape[0], image.shape[1])
|
|
199
|
+
|
|
200
|
+
pix = self.resize_and_pad_image(image, new_shape=target_imgsz)
|
|
201
|
+
# pix = np.transpose(pix, (2, 0, 1)) # CHW
|
|
202
|
+
# pix = pix.astype(np.float32) / 255.0 # Normalize to [0, 1]
|
|
203
|
+
input_ = pix
|
|
204
|
+
|
|
205
|
+
new_h, new_w = input_.shape[:2]
|
|
206
|
+
|
|
207
|
+
# Run inference
|
|
208
|
+
preds = self.model(input_, use_det=True, use_cls=False, use_rec=False)
|
|
209
|
+
|
|
210
|
+
# Process each prediction in the batch
|
|
211
|
+
if len(preds) > 0:
|
|
212
|
+
preds_np = np.array(preds[0])[:, [0, 2], :].reshape([-1, 4])
|
|
213
|
+
preds_np[..., :4] = self.scale_boxes(
|
|
214
|
+
(new_h, new_w),
|
|
215
|
+
preds_np[..., :4],
|
|
216
|
+
orig_shape,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Convert predictions to YoloResult format
|
|
220
|
+
return create_yolo_result_from_nested_coords(preds_np, self.names)
|
|
221
|
+
else:
|
|
222
|
+
# Return empty YoloResult if no predictions
|
|
223
|
+
return YoloResult(names=self.names, boxes=[])
|
|
224
|
+
|
|
225
|
+
def handle_document(
|
|
226
|
+
self,
|
|
227
|
+
pages: list[babeldoc.document_il.il_version_1.Page],
|
|
228
|
+
mupdf_doc: pymupdf.Document,
|
|
229
|
+
translate_config,
|
|
230
|
+
save_debug_image,
|
|
231
|
+
) -> Generator[
|
|
232
|
+
tuple[babeldoc.document_il.il_version_1.Page, YoloResult], None, None
|
|
233
|
+
]:
|
|
234
|
+
for page in pages:
|
|
235
|
+
translate_config.raise_if_cancelled()
|
|
236
|
+
pix = mupdf_doc[page.page_number].get_pixmap(dpi=72)
|
|
237
|
+
image = np.fromstring(pix.samples, np.uint8).reshape(
|
|
238
|
+
pix.height,
|
|
239
|
+
pix.width,
|
|
240
|
+
3,
|
|
241
|
+
)[:, :, ::-1]
|
|
242
|
+
|
|
243
|
+
table_boxes = []
|
|
244
|
+
for layout in page.page_layout:
|
|
245
|
+
if layout.class_name == "table":
|
|
246
|
+
table_boxes.append(layout.box)
|
|
247
|
+
|
|
248
|
+
predict_result = self.predict(image)
|
|
249
|
+
|
|
250
|
+
ok_boxes = []
|
|
251
|
+
for box in predict_result.boxes:
|
|
252
|
+
# Convert the box coordinates to float for proper comparison
|
|
253
|
+
box_xyxy = [float(coord) for coord in box.xyxy]
|
|
254
|
+
|
|
255
|
+
# Check if this box is inside any of the table boxes
|
|
256
|
+
for table_box in table_boxes:
|
|
257
|
+
# Determine if box is inside or overlapping with table_box with image dimensions
|
|
258
|
+
if self._is_box_in_table(
|
|
259
|
+
box_xyxy, table_box, page, image.shape[1], image.shape[0]
|
|
260
|
+
):
|
|
261
|
+
ok_boxes.append(box)
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
yolo_result = YoloResult(names=self.names, boxes=ok_boxes)
|
|
265
|
+
save_debug_image(
|
|
266
|
+
image,
|
|
267
|
+
yolo_result,
|
|
268
|
+
page.page_number + 1,
|
|
269
|
+
)
|
|
270
|
+
yield page, yolo_result
|
|
271
|
+
|
|
272
|
+
def _is_box_in_table(self, box_xyxy, table_box, page, img_width, img_height):
|
|
273
|
+
"""
|
|
274
|
+
Check if a box from image coordinates is inside a table box from PDF coordinates.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
box_xyxy (list): Box coordinates in image coordinate system [x1, y1, x2, y2]
|
|
278
|
+
table_box (Box): Table box in PDF coordinate system
|
|
279
|
+
page: The page object containing information for coordinate conversion
|
|
280
|
+
img_width: Width of the image
|
|
281
|
+
img_height: Height of the image
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
bool: True if the box is inside or significantly overlapping with the table box
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
# Get table box coordinates in PDF coordinate system
|
|
288
|
+
table_pdf_x1 = table_box.x
|
|
289
|
+
table_pdf_y1 = table_box.y
|
|
290
|
+
table_pdf_x2 = table_box.x2
|
|
291
|
+
table_pdf_y2 = table_box.y2
|
|
292
|
+
|
|
293
|
+
# Convert table box to image coordinates
|
|
294
|
+
table_img_x1 = table_pdf_x1
|
|
295
|
+
table_img_y1 = img_height - table_pdf_y2
|
|
296
|
+
table_img_x2 = table_pdf_x2
|
|
297
|
+
table_img_y2 = img_height - table_pdf_y1
|
|
298
|
+
|
|
299
|
+
# Now check for overlap between the boxes
|
|
300
|
+
# Calculate the area of overlap
|
|
301
|
+
x_overlap = max(
|
|
302
|
+
0, min(box_xyxy[2], table_img_x2) - max(box_xyxy[0], table_img_x1)
|
|
303
|
+
)
|
|
304
|
+
y_overlap = max(
|
|
305
|
+
0, min(box_xyxy[3], table_img_y2) - max(box_xyxy[1], table_img_y1)
|
|
306
|
+
)
|
|
307
|
+
overlap_area = x_overlap * y_overlap
|
|
308
|
+
|
|
309
|
+
# Calculate area of the detected box
|
|
310
|
+
box_area = (box_xyxy[2] - box_xyxy[0]) * (box_xyxy[3] - box_xyxy[1])
|
|
311
|
+
|
|
312
|
+
# If overlap area is significant relative to the box area, consider it inside
|
|
313
|
+
if box_area > 0 and overlap_area / box_area > 0.5:
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
return False
|
|
@@ -34,6 +34,7 @@ from babeldoc.document_il.midend.il_translator_llm_only import ILTranslatorLLMOn
|
|
|
34
34
|
from babeldoc.document_il.midend.layout_parser import LayoutParser
|
|
35
35
|
from babeldoc.document_il.midend.paragraph_finder import ParagraphFinder
|
|
36
36
|
from babeldoc.document_il.midend.styles_and_formulas import StylesAndFormulas
|
|
37
|
+
from babeldoc.document_il.midend.table_parser import TableParser
|
|
37
38
|
from babeldoc.document_il.midend.typesetting import Typesetting
|
|
38
39
|
from babeldoc.document_il.utils.fontmap import FontMapper
|
|
39
40
|
from babeldoc.document_il.xml_converter import XMLConverter
|
|
@@ -51,6 +52,7 @@ TRANSLATE_STAGES = [
|
|
|
51
52
|
(ILCreater.stage_name, 5.35), # Parse PDF and Create IR
|
|
52
53
|
(DetectScannedFile.stage_name, 0.5), # No historical data, estimated
|
|
53
54
|
(LayoutParser.stage_name, 6.42), # Parse Page Layout
|
|
55
|
+
(TableParser.stage_name, 0.5), # Parse Table
|
|
54
56
|
(ParagraphFinder.stage_name, 2.14), # Parse Paragraphs
|
|
55
57
|
(StylesAndFormulas.stage_name, 1.12), # Parse Formulas and Styles
|
|
56
58
|
# (RemoveDescent.stage_name, 0.15), # Remove Char Descent
|
|
@@ -588,6 +590,15 @@ def _do_translate_single(
|
|
|
588
590
|
docs,
|
|
589
591
|
translation_config.get_working_file_path("layout_generator.json"),
|
|
590
592
|
)
|
|
593
|
+
|
|
594
|
+
if translation_config.table_model:
|
|
595
|
+
docs = TableParser(translation_config).process(docs, doc_pdf2zh)
|
|
596
|
+
logger.debug("finish table parser")
|
|
597
|
+
if translation_config.debug:
|
|
598
|
+
xml_converter.write_json(
|
|
599
|
+
docs,
|
|
600
|
+
translation_config.get_working_file_path("table_parser.json"),
|
|
601
|
+
)
|
|
591
602
|
ParagraphFinder(translation_config).process(docs)
|
|
592
603
|
logger.debug(f"finish paragraph finder from {temp_pdf_path}")
|
|
593
604
|
if translation_config.debug:
|
|
@@ -18,11 +18,12 @@ from babeldoc.document_il.translator.translator import OpenAITranslator
|
|
|
18
18
|
from babeldoc.document_il.translator.translator import set_translate_rate_limiter
|
|
19
19
|
from babeldoc.docvision.doclayout import DocLayoutModel
|
|
20
20
|
from babeldoc.docvision.rpc_doclayout import RpcDocLayoutModel
|
|
21
|
+
from babeldoc.docvision.table_detection.rapidocr import RapidOCRModel
|
|
21
22
|
from babeldoc.translation_config import TranslationConfig
|
|
22
23
|
from babeldoc.translation_config import WatermarkOutputMode
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
|
-
__version__ = "0.2.
|
|
26
|
+
__version__ = "0.2.15"
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def create_parser():
|
|
@@ -191,6 +192,12 @@ def create_parser():
|
|
|
191
192
|
default=0.1,
|
|
192
193
|
help="Progress report interval in seconds (default: 0.1)",
|
|
193
194
|
)
|
|
195
|
+
translation_group.add_argument(
|
|
196
|
+
"--translate-table-text",
|
|
197
|
+
action="store_true",
|
|
198
|
+
default=False,
|
|
199
|
+
help="Translate table text (experimental)",
|
|
200
|
+
)
|
|
194
201
|
# service option argument group
|
|
195
202
|
service_group = translation_group.add_mutually_exclusive_group()
|
|
196
203
|
service_group.add_argument(
|
|
@@ -276,6 +283,11 @@ async def main():
|
|
|
276
283
|
else:
|
|
277
284
|
doc_layout_model = DocLayoutModel.load_onnx()
|
|
278
285
|
|
|
286
|
+
if args.translate_table_text:
|
|
287
|
+
table_model = RapidOCRModel()
|
|
288
|
+
else:
|
|
289
|
+
table_model = None
|
|
290
|
+
|
|
279
291
|
pending_files = []
|
|
280
292
|
for file in args.files:
|
|
281
293
|
# 清理文件路径,去除两端的引号
|
|
@@ -350,6 +362,7 @@ async def main():
|
|
|
350
362
|
min_text_length=args.min_text_length,
|
|
351
363
|
watermark_output_mode=watermark_output_mode,
|
|
352
364
|
split_strategy=split_strategy,
|
|
365
|
+
table_model=table_model,
|
|
353
366
|
)
|
|
354
367
|
|
|
355
368
|
# Create progress handler
|
|
@@ -65,6 +65,7 @@ class TranslationConfig:
|
|
|
65
65
|
watermark_output_mode: WatermarkOutputMode = WatermarkOutputMode.Watermarked,
|
|
66
66
|
# Add split-related parameters
|
|
67
67
|
split_strategy: BaseSplitStrategy | None = None,
|
|
68
|
+
table_model=None,
|
|
68
69
|
):
|
|
69
70
|
self.translator = translator
|
|
70
71
|
|
|
@@ -142,6 +143,8 @@ class TranslationConfig:
|
|
|
142
143
|
self._part_working_dirs: dict[int, Path] = {}
|
|
143
144
|
self._part_output_dirs: dict[int, Path] = {}
|
|
144
145
|
|
|
146
|
+
self.table_model = table_model
|
|
147
|
+
|
|
145
148
|
def parse_pages(self, pages_str: str | None) -> list[tuple[int, int]] | None:
|
|
146
149
|
"""解析页码字符串,返回页码范围列表
|
|
147
150
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "BabelDOC"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.15"
|
|
4
4
|
description = "Yet Another Document Translator"
|
|
5
5
|
license = "AGPL-3.0"
|
|
6
6
|
readme = "README.md"
|
|
@@ -42,6 +42,7 @@ dependencies = [
|
|
|
42
42
|
"tiktoken>=0.9.0",
|
|
43
43
|
"python-levenshtein>=0.27.1",
|
|
44
44
|
"opencv-python-headless>=4.10.0.84",
|
|
45
|
+
"rapidocr-onnxruntime>=1.4.4",
|
|
45
46
|
]
|
|
46
47
|
|
|
47
48
|
[project.optional-dependencies]
|
|
@@ -147,7 +148,7 @@ pythonpath = [".", "src"]
|
|
|
147
148
|
testpaths = ["tests"]
|
|
148
149
|
|
|
149
150
|
[bumpver]
|
|
150
|
-
current_version = "0.2.
|
|
151
|
+
current_version = "0.2.15"
|
|
151
152
|
version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
|
|
152
153
|
|
|
153
154
|
[bumpver.file_patterns]
|
|
@@ -48,7 +48,7 @@ wheels = [
|
|
|
48
48
|
|
|
49
49
|
[[package]]
|
|
50
50
|
name = "babeldoc"
|
|
51
|
-
version = "0.2.
|
|
51
|
+
version = "0.2.15"
|
|
52
52
|
source = { editable = "." }
|
|
53
53
|
dependencies = [
|
|
54
54
|
{ name = "bitstring" },
|
|
@@ -69,6 +69,7 @@ dependencies = [
|
|
|
69
69
|
{ name = "pydantic" },
|
|
70
70
|
{ name = "pymupdf" },
|
|
71
71
|
{ name = "python-levenshtein" },
|
|
72
|
+
{ name = "rapidocr-onnxruntime" },
|
|
72
73
|
{ name = "rich" },
|
|
73
74
|
{ name = "scikit-image" },
|
|
74
75
|
{ name = "tenacity" },
|
|
@@ -123,6 +124,7 @@ requires-dist = [
|
|
|
123
124
|
{ name = "pydantic", specifier = ">=2.10.6" },
|
|
124
125
|
{ name = "pymupdf", specifier = ">=1.25.1" },
|
|
125
126
|
{ name = "python-levenshtein", specifier = ">=0.27.1" },
|
|
127
|
+
{ name = "rapidocr-onnxruntime", specifier = ">=1.4.4" },
|
|
126
128
|
{ name = "rich", specifier = ">=13.9.4" },
|
|
127
129
|
{ name = "scikit-image", specifier = ">=0.25.2" },
|
|
128
130
|
{ name = "tenacity", specifier = ">=9.0.0" },
|
|
@@ -1416,6 +1418,23 @@ wheels = [
|
|
|
1416
1418
|
{ url = "https://files.pythonhosted.org/packages/c7/3b/1ba418920ecd1eae7cc4d4ac8a01711ee0879b1a57dd81d10551e5b9a2ea/openai-1.66.5-py3-none-any.whl", hash = "sha256:74be528175f8389f67675830c51a15bd51e874425c86d3de6153bf70ed6c2884", size = 571144 },
|
|
1417
1419
|
]
|
|
1418
1420
|
|
|
1421
|
+
[[package]]
|
|
1422
|
+
name = "opencv-python"
|
|
1423
|
+
version = "4.11.0.86"
|
|
1424
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1425
|
+
dependencies = [
|
|
1426
|
+
{ name = "numpy" },
|
|
1427
|
+
]
|
|
1428
|
+
sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956 }
|
|
1429
|
+
wheels = [
|
|
1430
|
+
{ url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322 },
|
|
1431
|
+
{ url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197 },
|
|
1432
|
+
{ url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439 },
|
|
1433
|
+
{ url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597 },
|
|
1434
|
+
{ url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337 },
|
|
1435
|
+
{ url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044 },
|
|
1436
|
+
]
|
|
1437
|
+
|
|
1419
1438
|
[[package]]
|
|
1420
1439
|
name = "opencv-python-headless"
|
|
1421
1440
|
version = "4.11.0.86"
|
|
@@ -1637,6 +1656,32 @@ wheels = [
|
|
|
1637
1656
|
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 },
|
|
1638
1657
|
]
|
|
1639
1658
|
|
|
1659
|
+
[[package]]
|
|
1660
|
+
name = "pyclipper"
|
|
1661
|
+
version = "1.3.0.post6"
|
|
1662
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1663
|
+
sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909 }
|
|
1664
|
+
wheels = [
|
|
1665
|
+
{ url = "https://files.pythonhosted.org/packages/b5/34/0dca299fe41e9a92e78735502fed5238a4ac734755e624488df9b2eeec46/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa0f5e78cfa8262277bb3d0225537b3c2a90ef68fd90a229d5d24cf49955dcf4", size = 269504 },
|
|
1666
|
+
{ url = "https://files.pythonhosted.org/packages/8a/5b/81528b08134b3c2abdfae821e1eff975c0703802d41974b02dfb2e101c55/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f182d8938c1dc515e8508ed2442f7eebd2c25c7d5cb29281f583c1a8008a4", size = 142599 },
|
|
1667
|
+
{ url = "https://files.pythonhosted.org/packages/84/a4/3e304f6c0d000382cd54d4a1e5f0d8fc28e1ae97413a2ec1016a7b840319/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:640f20975727994d4abacd07396f564e9e5665ba5cb66ceb36b300c281f84fa4", size = 912209 },
|
|
1668
|
+
{ url = "https://files.pythonhosted.org/packages/f5/6a/28ec55cc3f972368b211fca017e081cf5a71009d1b8ec3559767cda5b289/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63002f6bb0f1efa87c0b81634cbb571066f237067e23707dabf746306c92ba5", size = 929511 },
|
|
1669
|
+
{ url = "https://files.pythonhosted.org/packages/c4/56/c326f3454c5f30a31f58a5c3154d891fce58ad73ccbf1d3f4aacfcbd344d/pyclipper-1.3.0.post6-cp310-cp310-win32.whl", hash = "sha256:106b8622cd9fb07d80cbf9b1d752334c55839203bae962376a8c59087788af26", size = 100126 },
|
|
1670
|
+
{ url = "https://files.pythonhosted.org/packages/f8/e6/f8239af6346848b20a3448c554782fe59298ab06c1d040490242dc7e3c26/pyclipper-1.3.0.post6-cp310-cp310-win_amd64.whl", hash = "sha256:9699e98862dadefd0bea2360c31fa61ca553c660cbf6fb44993acde1b959f58f", size = 110470 },
|
|
1671
|
+
{ url = "https://files.pythonhosted.org/packages/50/a9/66ca5f252dcac93ca076698591b838ba17f9729591edf4b74fef7fbe1414/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef", size = 270930 },
|
|
1672
|
+
{ url = "https://files.pythonhosted.org/packages/59/fe/2ab5818b3504e179086e54a37ecc245525d069267b8c31b18ec3d0830cbf/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa", size = 143411 },
|
|
1673
|
+
{ url = "https://files.pythonhosted.org/packages/09/f7/b58794f643e033a6d14da7c70f517315c3072f3c5fccdf4232fa8c8090c1/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0", size = 951754 },
|
|
1674
|
+
{ url = "https://files.pythonhosted.org/packages/c1/77/846a21957cd4ed266c36705ee340beaa923eb57d2bba013cfd7a5c417cfd/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac", size = 969608 },
|
|
1675
|
+
{ url = "https://files.pythonhosted.org/packages/c9/2b/580703daa6606d160caf596522d4cfdf62ae619b062a7ce6f905821a57e8/pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a", size = 100227 },
|
|
1676
|
+
{ url = "https://files.pythonhosted.org/packages/17/4b/a4cda18e8556d913ff75052585eb0d658500596b5f97fe8401d05123d47b/pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a", size = 110442 },
|
|
1677
|
+
{ url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487 },
|
|
1678
|
+
{ url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469 },
|
|
1679
|
+
{ url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206 },
|
|
1680
|
+
{ url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797 },
|
|
1681
|
+
{ url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456 },
|
|
1682
|
+
{ url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278 },
|
|
1683
|
+
]
|
|
1684
|
+
|
|
1640
1685
|
[[package]]
|
|
1641
1686
|
name = "pycparser"
|
|
1642
1687
|
version = "2.22"
|
|
@@ -1929,6 +1974,25 @@ wheels = [
|
|
|
1929
1974
|
{ url = "https://files.pythonhosted.org/packages/09/f6/fa777f336629aee8938f3d5c95c09df38459d4eadbdbe34642889857fb6a/rapidfuzz-3.12.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:54bb69ebe5ca0bd7527357e348f16a4c0c52fe0c2fcc8a041010467dcb8385f7", size = 1555000 },
|
|
1930
1975
|
]
|
|
1931
1976
|
|
|
1977
|
+
[[package]]
|
|
1978
|
+
name = "rapidocr-onnxruntime"
|
|
1979
|
+
version = "1.4.4"
|
|
1980
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1981
|
+
dependencies = [
|
|
1982
|
+
{ name = "numpy" },
|
|
1983
|
+
{ name = "onnxruntime" },
|
|
1984
|
+
{ name = "opencv-python" },
|
|
1985
|
+
{ name = "pillow" },
|
|
1986
|
+
{ name = "pyclipper" },
|
|
1987
|
+
{ name = "pyyaml" },
|
|
1988
|
+
{ name = "shapely" },
|
|
1989
|
+
{ name = "six" },
|
|
1990
|
+
{ name = "tqdm" },
|
|
1991
|
+
]
|
|
1992
|
+
wheels = [
|
|
1993
|
+
{ url = "https://files.pythonhosted.org/packages/ba/12/1e5497183bdbe782dbb91bad1d0d2297dba4d2831b2652657f7517bfc6df/rapidocr_onnxruntime-1.4.4-py3-none-any.whl", hash = "sha256:971d7d5f223a7a808662229df1ef69893809d8457d834e6373d3854bc1782cbf", size = 14915192 },
|
|
1994
|
+
]
|
|
1995
|
+
|
|
1932
1996
|
[[package]]
|
|
1933
1997
|
name = "regex"
|
|
1934
1998
|
version = "2024.11.6"
|
|
@@ -2108,6 +2172,35 @@ wheels = [
|
|
|
2108
2172
|
{ url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 },
|
|
2109
2173
|
]
|
|
2110
2174
|
|
|
2175
|
+
[[package]]
|
|
2176
|
+
name = "shapely"
|
|
2177
|
+
version = "2.0.7"
|
|
2178
|
+
source = { registry = "https://pypi.org/simple" }
|
|
2179
|
+
dependencies = [
|
|
2180
|
+
{ name = "numpy" },
|
|
2181
|
+
]
|
|
2182
|
+
sdist = { url = "https://files.pythonhosted.org/packages/21/c0/a911d1fd765d07a2b6769ce155219a281bfbe311584ebe97340d75c5bdb1/shapely-2.0.7.tar.gz", hash = "sha256:28fe2997aab9a9dc026dc6a355d04e85841546b2a5d232ed953e3321ab958ee5", size = 283413 }
|
|
2183
|
+
wheels = [
|
|
2184
|
+
{ url = "https://files.pythonhosted.org/packages/15/2e/02c694d6ddacd4f13b625722d313d2838f23c5b988cbc680132983f73ce3/shapely-2.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:33fb10e50b16113714ae40adccf7670379e9ccf5b7a41d0002046ba2b8f0f691", size = 1478310 },
|
|
2185
|
+
{ url = "https://files.pythonhosted.org/packages/87/69/b54a08bcd25e561bdd5183c008ace4424c25e80506e80674032504800efd/shapely-2.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f44eda8bd7a4bccb0f281264b34bf3518d8c4c9a8ffe69a1a05dabf6e8461147", size = 1336082 },
|
|
2186
|
+
{ url = "https://files.pythonhosted.org/packages/b3/f9/40473fcb5b66ff849e563ca523d2a26dafd6957d52dd876ffd0eded39f1c/shapely-2.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf6c50cd879831955ac47af9c907ce0310245f9d162e298703f82e1785e38c98", size = 2371047 },
|
|
2187
|
+
{ url = "https://files.pythonhosted.org/packages/d6/f3/c9cc07a7a03b5f5e83bd059f9adf3e21cf086b0e41d7f95e6464b151e798/shapely-2.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04a65d882456e13c8b417562c36324c0cd1e5915f3c18ad516bb32ee3f5fc895", size = 2469112 },
|
|
2188
|
+
{ url = "https://files.pythonhosted.org/packages/5d/b9/fc63d6b0b25063a3ff806857a5dc88851d54d1c278288f18cef1b322b449/shapely-2.0.7-cp310-cp310-win32.whl", hash = "sha256:7e97104d28e60b69f9b6a957c4d3a2a893b27525bc1fc96b47b3ccef46726bf2", size = 1296057 },
|
|
2189
|
+
{ url = "https://files.pythonhosted.org/packages/fe/d1/8df43f94cf4cda0edbab4545f7cdd67d3f1d02910eaff152f9f45c6d00d8/shapely-2.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:35524cc8d40ee4752520819f9894b9f28ba339a42d4922e92c99b148bed3be39", size = 1441787 },
|
|
2190
|
+
{ url = "https://files.pythonhosted.org/packages/1d/ad/21798c2fec013e289f8ab91d42d4d3299c315b8c4460c08c75fef0901713/shapely-2.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5cf23400cb25deccf48c56a7cdda8197ae66c0e9097fcdd122ac2007e320bc34", size = 1473091 },
|
|
2191
|
+
{ url = "https://files.pythonhosted.org/packages/15/63/eef4f180f1b5859c70e7f91d2f2570643e5c61e7d7c40743d15f8c6cbc42/shapely-2.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f1da01c04527f7da59ee3755d8ee112cd8967c15fab9e43bba936b81e2a013", size = 1332921 },
|
|
2192
|
+
{ url = "https://files.pythonhosted.org/packages/fe/67/77851dd17738bbe7762a0ef1acf7bc499d756f68600dd68a987d78229412/shapely-2.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f623b64bb219d62014781120f47499a7adc30cf7787e24b659e56651ceebcb0", size = 2427949 },
|
|
2193
|
+
{ url = "https://files.pythonhosted.org/packages/0b/a5/2c8dbb0f383519771df19164e3bf3a8895d195d2edeab4b6040f176ee28e/shapely-2.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6d95703efaa64aaabf278ced641b888fc23d9c6dd71f8215091afd8a26a66e3", size = 2529282 },
|
|
2194
|
+
{ url = "https://files.pythonhosted.org/packages/dc/4e/e1d608773c7fe4cde36d48903c0d6298e3233dc69412403783ac03fa5205/shapely-2.0.7-cp311-cp311-win32.whl", hash = "sha256:2f6e4759cf680a0f00a54234902415f2fa5fe02f6b05546c662654001f0793a2", size = 1295751 },
|
|
2195
|
+
{ url = "https://files.pythonhosted.org/packages/27/57/8ec7c62012bed06731f7ee979da7f207bbc4b27feed5f36680b6a70df54f/shapely-2.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:b52f3ab845d32dfd20afba86675c91919a622f4627182daec64974db9b0b4608", size = 1442684 },
|
|
2196
|
+
{ url = "https://files.pythonhosted.org/packages/4f/3e/ea100eec5811bafd0175eb21828a3be5b0960f65250f4474391868be7c0f/shapely-2.0.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4c2b9859424facbafa54f4a19b625a752ff958ab49e01bc695f254f7db1835fa", size = 1482451 },
|
|
2197
|
+
{ url = "https://files.pythonhosted.org/packages/ce/53/c6a3487716fd32e1f813d2a9608ba7b72a8a52a6966e31c6443480a1d016/shapely-2.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5aed1c6764f51011d69a679fdf6b57e691371ae49ebe28c3edb5486537ffbd51", size = 1345765 },
|
|
2198
|
+
{ url = "https://files.pythonhosted.org/packages/fd/dd/b35d7891d25cc11066a70fb8d8169a6a7fca0735dd9b4d563a84684969a3/shapely-2.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73c9ae8cf443187d784d57202199bf9fd2d4bb7d5521fe8926ba40db1bc33e8e", size = 2421540 },
|
|
2199
|
+
{ url = "https://files.pythonhosted.org/packages/62/de/8dbd7df60eb23cb983bb698aac982944b3d602ef0ce877a940c269eae34e/shapely-2.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9469f49ff873ef566864cb3516091881f217b5d231c8164f7883990eec88b73", size = 2525741 },
|
|
2200
|
+
{ url = "https://files.pythonhosted.org/packages/96/64/faf0413ebc7a84fe7a0790bf39ec0b02b40132b68e57aba985c0b6e4e7b6/shapely-2.0.7-cp312-cp312-win32.whl", hash = "sha256:6bca5095e86be9d4ef3cb52d56bdd66df63ff111d580855cb8546f06c3c907cd", size = 1296552 },
|
|
2201
|
+
{ url = "https://files.pythonhosted.org/packages/63/05/8a1c279c226d6ad7604d9e237713dd21788eab96db97bf4ce0ea565e5596/shapely-2.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:f86e2c0259fe598c4532acfcf638c1f520fa77c1275912bbc958faecbf00b108", size = 1443464 },
|
|
2202
|
+
]
|
|
2203
|
+
|
|
2111
2204
|
[[package]]
|
|
2112
2205
|
name = "six"
|
|
2113
2206
|
version = "1.17.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.14"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-big-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-small-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.2.14 → babeldoc-0.2.15}/docs/images/babeldoc-small-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|