BabelDOC 0.1.22__tar.gz → 0.1.24__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.
Files changed (82) hide show
  1. {babeldoc-0.1.22 → babeldoc-0.1.24}/PKG-INFO +2 -1
  2. babeldoc-0.1.24/babeldoc/__init__.py +1 -0
  3. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/const.py +6 -2
  4. babeldoc-0.1.24/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +3 -0
  5. babeldoc-0.1.24/babeldoc/document_il/midend/detect_scanned_file.py +124 -0
  6. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/layout_parser.py +1 -1
  7. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/docvision/rpc_doclayout.py +18 -15
  8. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/high_level.py +24 -11
  9. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/main.py +3 -3
  10. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/progress_monitor.py +29 -8
  11. {babeldoc-0.1.22 → babeldoc-0.1.24}/pyproject.toml +3 -2
  12. {babeldoc-0.1.22 → babeldoc-0.1.24}/uv.lock +168 -1
  13. babeldoc-0.1.22/babeldoc/__init__.py +0 -1
  14. {babeldoc-0.1.22 → babeldoc-0.1.24}/.cursorignore +0 -0
  15. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  16. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  17. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/dependabot.yml +0 -0
  18. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/labels.yml +0 -0
  19. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/release-drafter.yml +0 -0
  20. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/workflows/codeql.yml +0 -0
  21. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/workflows/docs.yml +0 -0
  22. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/workflows/labeler.yml +0 -0
  23. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/workflows/publish-to-pypi.yml +0 -0
  24. {babeldoc-0.1.22 → babeldoc-0.1.24}/.github/workflows/test.yml +0 -0
  25. {babeldoc-0.1.22 → babeldoc-0.1.24}/.gitignore +0 -0
  26. {babeldoc-0.1.22 → babeldoc-0.1.24}/.pre-commit-config.yaml +0 -0
  27. {babeldoc-0.1.22 → babeldoc-0.1.24}/LICENSE +0 -0
  28. {babeldoc-0.1.22 → babeldoc-0.1.24}/README.md +0 -0
  29. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/assets/assets.py +0 -0
  30. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  31. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/asynchronize/__init__.py +0 -0
  32. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/converter.py +0 -0
  33. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/__init__.py +0 -0
  34. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/backend/__init__.py +0 -0
  35. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/backend/pdf_creater.py +0 -0
  36. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/frontend/__init__.py +0 -0
  37. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/frontend/il_creater.py +0 -0
  38. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/il_version_1.py +0 -0
  39. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/il_version_1.rnc +0 -0
  40. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/il_version_1.rng +0 -0
  41. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/il_version_1.xsd +0 -0
  42. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/__init__.py +0 -0
  43. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/add_debug_information.py +0 -0
  44. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/il_translator.py +0 -0
  45. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  46. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/remove_descent.py +0 -0
  47. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
  48. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/midend/typesetting.py +0 -0
  49. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/translator/__init__.py +0 -0
  50. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/translator/cache.py +0 -0
  51. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/translator/translator.py +0 -0
  52. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/utils/fontmap.py +0 -0
  53. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/utils/layout_helper.py +0 -0
  54. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/utils/style_helper.py +0 -0
  55. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/document_il/xml_converter.py +0 -0
  56. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/docvision/README.md +0 -0
  57. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/docvision/__init__.py +0 -0
  58. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/docvision/doclayout.py +0 -0
  59. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/pdfinterp.py +0 -0
  60. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/tools/generate_font_metadata.py +0 -0
  61. {babeldoc-0.1.22 → babeldoc-0.1.24}/babeldoc/translation_config.py +0 -0
  62. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/CODE_OF_CONDUCT.md +0 -0
  63. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/CONTRIBUTING.md +0 -0
  64. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/CONTRIBUTOR_REWARD.md +0 -0
  65. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  66. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  67. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  68. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  69. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  70. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/README.md +0 -0
  71. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  72. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  73. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/README.md +0 -0
  74. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/deploy.sh +0 -0
  75. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/images/babeldoc-banner.png +0 -0
  76. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  77. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/images/babeldoc-preview.gif +0 -0
  78. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/index.md +0 -0
  79. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/intro-to-pdf-object.md +0 -0
  80. {babeldoc-0.1.22 → babeldoc-0.1.24}/docs/requirements.txt +0 -0
  81. {babeldoc-0.1.22 → babeldoc-0.1.24}/mkdocs.yml +0 -0
  82. {babeldoc-0.1.22 → babeldoc-0.1.24}/tests/test_translation_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BabelDOC
3
- Version: 0.1.22
3
+ Version: 0.1.24
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
@@ -26,6 +26,7 @@ Requires-Dist: peewee>=3.17.8
26
26
  Requires-Dist: pydantic>=2.10.6
27
27
  Requires-Dist: pymupdf>=1.25.1
28
28
  Requires-Dist: rich>=13.9.4
29
+ Requires-Dist: scikit-image>=0.25.2
29
30
  Requires-Dist: tenacity>=9.0.0
30
31
  Requires-Dist: toml>=0.10.2
31
32
  Requires-Dist: tqdm>=4.67.1
@@ -0,0 +1 @@
1
+ __version__ = "0.1.24"
@@ -2,7 +2,7 @@ import shutil
2
2
  import subprocess
3
3
  from pathlib import Path
4
4
 
5
- __version__ = "0.1.22"
5
+ __version__ = "0.1.24"
6
6
 
7
7
  CACHE_FOLDER = Path.home() / ".cache" / "babeldoc"
8
8
 
@@ -20,7 +20,11 @@ try:
20
20
  git_path = shutil.which("git")
21
21
  if git_path is None:
22
22
  raise FileNotFoundError("git executable not found")
23
-
23
+ two_parent = Path(__file__).resolve().parent.parent
24
+ md_ = two_parent / "docs" / "README.md"
25
+ if two_parent.name == "site-packages" or not md_.exists():
26
+ print("not in git repo")
27
+ raise FileNotFoundError("not in git repo")
24
28
  WATERMARK_VERSION = (
25
29
  subprocess.check_output( # noqa: S603
26
30
  [git_path, "describe", "--always"],
@@ -0,0 +1,3 @@
1
+ class ScannedPDFError(Exception):
2
+ def __init__(self, message):
3
+ super().__init__(message)
@@ -0,0 +1,124 @@
1
+ import logging
2
+
3
+ import cv2
4
+ import numpy as np
5
+ import pymupdf
6
+ from skimage.metrics import structural_similarity
7
+
8
+ from babeldoc.document_il import il_version_1
9
+ from babeldoc.document_il.babeldoc_exception.BabelDOCException import ScannedPDFError
10
+ from babeldoc.document_il.utils.style_helper import GREEN
11
+ from babeldoc.translation_config import TranslationConfig
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class DetectScannedFile:
17
+ stage_name = "DetectScannedFile"
18
+
19
+ def __init__(self, translation_config: TranslationConfig):
20
+ self.translation_config = translation_config
21
+
22
+ def _save_debug_box_to_page(self, page: il_version_1.Page, similarity: float):
23
+ """Save debug boxes and text labels to the PDF page."""
24
+ if not self.translation_config.debug:
25
+ return
26
+
27
+ color = GREEN
28
+
29
+ # Create text label at top-left corner
30
+ # Note: PDF coordinates are from bottom-left,
31
+ # so we use y2 for top position
32
+ style = il_version_1.PdfStyle(
33
+ font_id="china-ss",
34
+ font_size=4,
35
+ graphic_state=color,
36
+ )
37
+ page_width = page.cropbox.box.x2 - page.cropbox.box.x
38
+ page_height = page.cropbox.box.y2 - page.cropbox.box.y
39
+ unicode = f"scanned score: {similarity * 100:.2f} %"
40
+ page.pdf_paragraph.append(
41
+ il_version_1.PdfParagraph(
42
+ first_line_indent=False,
43
+ box=il_version_1.Box(
44
+ x=page.cropbox.box.x + page_width * 0.03,
45
+ y=page.cropbox.box.y,
46
+ x2=page.cropbox.box.x2,
47
+ y2=page.cropbox.box.y2 - page_height * 0.03,
48
+ ),
49
+ vertical=False,
50
+ pdf_style=style,
51
+ unicode=unicode,
52
+ pdf_paragraph_composition=[
53
+ il_version_1.PdfParagraphComposition(
54
+ pdf_same_style_unicode_characters=il_version_1.PdfSameStyleUnicodeCharacters(
55
+ unicode=unicode,
56
+ pdf_style=style,
57
+ debug_info=True,
58
+ ),
59
+ ),
60
+ ],
61
+ xobj_id=-1,
62
+ ),
63
+ )
64
+
65
+ def process(self, docs: il_version_1.Document):
66
+ """Generate layouts for all pages that need to be translated."""
67
+ # Get pages that need to be translated
68
+ pages_to_translate = [
69
+ page
70
+ for page in docs.page
71
+ if self.translation_config.should_translate_page(page.page_number + 1)
72
+ ]
73
+ mupdf = pymupdf.open(self.translation_config.get_working_file_path("input.pdf"))
74
+ total = len(pages_to_translate)
75
+ threshold = 0.8 * total
76
+ threshold = max(threshold, 1)
77
+ scanned = 0
78
+ non_scanned = 0
79
+ non_scanned_threshold = total - threshold
80
+ with self.translation_config.progress_monitor.stage_start(
81
+ self.stage_name,
82
+ total,
83
+ ) as progress:
84
+ for page in pages_to_translate:
85
+ if scanned < threshold and non_scanned < non_scanned_threshold:
86
+ # Only continue detection if both counts are below thresholds
87
+ is_scanned = self.detect_page_is_scanned(page, mupdf)
88
+ if is_scanned:
89
+ scanned += 1
90
+ else:
91
+ non_scanned += 1
92
+ else:
93
+ # We have enough information to determine document type
94
+ non_scanned += 1
95
+ progress.advance(1)
96
+
97
+ if scanned > threshold:
98
+ logger.warning(
99
+ f"Detected {scanned} scanned pages, which is more than 80% of the total pages. "
100
+ "Please check the input PDF file.",
101
+ )
102
+ raise ScannedPDFError("Scanned PDF detected.")
103
+
104
+ @staticmethod
105
+ def detect_page_is_scanned(page: il_version_1.Page, pdf: pymupdf.Document) -> bool:
106
+ before_page_image = pdf[page.page_number].get_pixmap()
107
+ before_page_image = np.frombuffer(before_page_image.samples, np.uint8).reshape(
108
+ before_page_image.height,
109
+ before_page_image.width,
110
+ 3,
111
+ )[:, :, ::-1]
112
+ new_xref = pdf.get_new_xref()
113
+ pdf.update_object(new_xref, "<<>>")
114
+ pdf.update_stream(new_xref, page.base_operations.value.encode("utf-8"))
115
+ pdf[page.page_number].set_contents(new_xref)
116
+ after_page_image = pdf[page.page_number].get_pixmap()
117
+ after_page_image = np.frombuffer(after_page_image.samples, np.uint8).reshape(
118
+ after_page_image.height,
119
+ after_page_image.width,
120
+ 3,
121
+ )[:, :, ::-1]
122
+ before_page_image = cv2.cvtColor(before_page_image, cv2.COLOR_RGB2GRAY)
123
+ after_page_image = cv2.cvtColor(after_page_image, cv2.COLOR_RGB2GRAY)
124
+ return structural_similarity(before_page_image, after_page_image) > 0.9
@@ -121,7 +121,7 @@ class LayoutParser:
121
121
  total,
122
122
  ) as progress:
123
123
  # Process pages in batches
124
- batch_size = 16
124
+ batch_size = 1
125
125
  for i in range(0, total, batch_size):
126
126
  self.translation_config.raise_if_cancelled()
127
127
  batch_pages = pages_to_translate[i : i + batch_size]
@@ -130,21 +130,24 @@ class RpcDocLayoutModel(DocLayoutModel):
130
130
  image = [image]
131
131
 
132
132
  results = []
133
- preds = predict_layout(image, host=self.host, imgsz=imgsz)
134
- if len(preds) > 0:
135
- for pred in preds:
136
- boxes = [
137
- YoloBox(None, np.array(x["xyxy"]), np.array(x["conf"]), x["cls"])
138
- for x in pred["boxes"]
139
- ]
140
- results.append(
141
- YoloResult(
142
- boxes=boxes,
143
- names={int(k): v for k, v in pred["names"].items()},
144
- ),
145
- )
146
- else:
147
- results.append(YoloResult(boxes_data=np.array([]), names=[]))
133
+ for img in image:
134
+ preds = predict_layout([img], host=self.host, imgsz=imgsz)
135
+ if len(preds) > 0:
136
+ for pred in preds:
137
+ boxes = [
138
+ YoloBox(
139
+ None, np.array(x["xyxy"]), np.array(x["conf"]), x["cls"]
140
+ )
141
+ for x in pred["boxes"]
142
+ ]
143
+ results.append(
144
+ YoloResult(
145
+ boxes=boxes,
146
+ names={int(k): v for k, v in pred["names"].items()},
147
+ ),
148
+ )
149
+ else:
150
+ results.append(YoloResult(boxes_data=np.array([]), names=[]))
148
151
 
149
152
  return results
150
153
 
@@ -24,6 +24,7 @@ from babeldoc.document_il.backend.pdf_creater import SUBSET_FONT_STAGE_NAME
24
24
  from babeldoc.document_il.backend.pdf_creater import PDFCreater
25
25
  from babeldoc.document_il.frontend.il_creater import ILCreater
26
26
  from babeldoc.document_il.midend.add_debug_information import AddDebugInformation
27
+ from babeldoc.document_il.midend.detect_scanned_file import DetectScannedFile
27
28
  from babeldoc.document_il.midend.il_translator import ILTranslator
28
29
  from babeldoc.document_il.midend.layout_parser import LayoutParser
29
30
  from babeldoc.document_il.midend.paragraph_finder import ParagraphFinder
@@ -40,17 +41,18 @@ from babeldoc.translation_config import TranslationConfig
40
41
  logger = logging.getLogger(__name__)
41
42
 
42
43
  TRANSLATE_STAGES = [
43
- ILCreater.stage_name,
44
- LayoutParser.stage_name,
45
- ParagraphFinder.stage_name,
46
- StylesAndFormulas.stage_name,
47
- RemoveDescent.stage_name,
48
- ILTranslator.stage_name,
49
- Typesetting.stage_name,
50
- FontMapper.stage_name,
51
- PDFCreater.stage_name,
52
- SUBSET_FONT_STAGE_NAME,
53
- SAVE_PDF_STAGE_NAME,
44
+ (ILCreater.stage_name, 5.35), # Parse PDF and Create IR
45
+ (DetectScannedFile.stage_name, 0.5), # No historical data, estimated
46
+ (LayoutParser.stage_name, 6.42), # Parse Page Layout
47
+ (ParagraphFinder.stage_name, 2.14), # Parse Paragraphs
48
+ (StylesAndFormulas.stage_name, 1.12), # Parse Formulas and Styles
49
+ (RemoveDescent.stage_name, 0.15), # Remove Char Descent
50
+ (ILTranslator.stage_name, 75.16), # Translate Paragraphs
51
+ (Typesetting.stage_name, 3.84), # Typesetting
52
+ (FontMapper.stage_name, 0.65), # Add Fonts
53
+ (PDFCreater.stage_name, 1.41), # Generate drawing instructions
54
+ (SUBSET_FONT_STAGE_NAME, 1.29), # Subset font
55
+ (SAVE_PDF_STAGE_NAME, 2.45), # Save PDF
54
56
  ]
55
57
 
56
58
  resfont_map = {
@@ -307,6 +309,17 @@ def do_translate(pm, translation_config):
307
309
  docs,
308
310
  translation_config.get_working_file_path("create_il.debug.json"),
309
311
  )
312
+
313
+ # 检测是否为扫描文件
314
+ logger.debug("start detect scanned file")
315
+ DetectScannedFile(translation_config).process(docs)
316
+ logger.debug("finish detect scanned file")
317
+ if translation_config.debug:
318
+ xml_converter.write_json(
319
+ docs,
320
+ translation_config.get_working_file_path("detect_scanned_file.json"),
321
+ )
322
+
310
323
  # Generate layouts for all pages
311
324
  logger.debug("start generating layouts")
312
325
  docs = LayoutParser(translation_config).process(docs, doc_input)
@@ -23,7 +23,7 @@ from babeldoc.docvision.rpc_doclayout import RpcDocLayoutModel
23
23
  from babeldoc.translation_config import TranslationConfig
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
- __version__ = "0.1.22"
26
+ __version__ = "0.1.24"
27
27
 
28
28
 
29
29
  def create_parser():
@@ -392,7 +392,7 @@ def create_progress_handler(translation_config: TranslationConfig):
392
392
  stage_tasks[stage],
393
393
  completed=event["stage_current"],
394
394
  total=event["stage_total"],
395
- description=f"{event['stage']} ({event['stage_current']}/{event['stage_total']})",
395
+ description=f"{event['stage']}",
396
396
  refresh=True,
397
397
  )
398
398
  progress.update(
@@ -407,7 +407,7 @@ def create_progress_handler(translation_config: TranslationConfig):
407
407
  stage_tasks[stage],
408
408
  completed=event["stage_total"],
409
409
  total=event["stage_total"],
410
- description=f"{event['stage']} (Complete)",
410
+ description=f"{event['stage']}",
411
411
  refresh=True,
412
412
  )
413
413
  progress.update(
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
11
11
  class ProgressMonitor:
12
12
  def __init__(
13
13
  self,
14
- stages: list[str],
14
+ stages: list[tuple[str, float]],
15
15
  progress_change_callback: Callable | None = None,
16
16
  finish_callback: Callable | None = None,
17
17
  report_interval: float = 0.1,
@@ -19,7 +19,13 @@ class ProgressMonitor:
19
19
  cancel_event: threading.Event | None = None,
20
20
  loop: asyncio.AbstractEventLoop | None = None,
21
21
  ):
22
- self.stage = {k: TranslationStage(k, 0, self) for k in stages}
22
+ # Convert stages list to dict with name and weight
23
+ self.stage = {}
24
+ total_weight = sum(weight for _, weight in stages)
25
+ for name, weight in stages:
26
+ normalized_weight = weight / total_weight
27
+ self.stage[name] = TranslationStage(name, 0, self, normalized_weight)
28
+
23
29
  self.progress_change_callback = progress_change_callback
24
30
  self.finish_callback = finish_callback
25
31
  self.report_interval = report_interval
@@ -37,9 +43,9 @@ class ProgressMonitor:
37
43
  stages=[
38
44
  {
39
45
  "name": name,
40
- "percent": 1.0 / len(stages),
46
+ "percent": self.stage[name].weight,
41
47
  }
42
- for name in stages
48
+ for name, _ in stages
43
49
  ],
44
50
  )
45
51
  self.lock = threading.Lock()
@@ -102,9 +108,23 @@ class ProgressMonitor:
102
108
  )
103
109
 
104
110
  def calculate_current_progress(self, stage=None):
105
- progress = self.finish_stage_count * 100 / len(self.stage)
106
- if stage is not None:
107
- progress += stage.current * 100 / stage.total / len(self.stage)
111
+ # Count completed stages
112
+ completed_stages = sum(
113
+ 1 for s in self.stage.values() if s.run_time > 0 and s.current == s.total
114
+ )
115
+
116
+ # If all stages are complete, return exactly 100
117
+ if completed_stages == len(self.stage):
118
+ return 100
119
+
120
+ # Calculate progress based on weights
121
+ progress = sum(
122
+ s.weight * 100
123
+ for s in self.stage.values()
124
+ if s.run_time > 0 and s.current == s.total
125
+ )
126
+ if stage is not None and stage.total > 0:
127
+ progress += stage.weight * stage.current * 100 / stage.total
108
128
  return progress
109
129
 
110
130
  def stage_update(self, stage, n: int):
@@ -142,13 +162,14 @@ class ProgressMonitor:
142
162
 
143
163
 
144
164
  class TranslationStage:
145
- def __init__(self, name: str, total: int, pm: ProgressMonitor):
165
+ def __init__(self, name: str, total: int, pm: ProgressMonitor, weight: float):
146
166
  self.name = name
147
167
  self.display_name = name
148
168
  self.current = 0
149
169
  self.total = total
150
170
  self.pm = pm
151
171
  self.run_time = 0
172
+ self.weight = weight
152
173
 
153
174
  def __enter__(self):
154
175
  return self
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "BabelDOC"
3
- version = "0.1.22"
3
+ version = "0.1.24"
4
4
  description = "Yet Another Document Translator"
5
5
  license = "AGPL-3.0"
6
6
  readme = "README.md"
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "msgpack>=1.1.0",
32
32
  "pydantic>=2.10.6",
33
33
  "tenacity>=9.0.0",
34
+ "scikit-image>=0.25.2",
34
35
  ]
35
36
 
36
37
  [project.urls]
@@ -132,7 +133,7 @@ pythonpath = [".", "src"]
132
133
  testpaths = ["tests"]
133
134
 
134
135
  [bumpver]
135
- current_version = "0.1.22"
136
+ current_version = "0.1.24"
136
137
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
137
138
 
138
139
  [bumpver.file_patterns]
@@ -48,7 +48,7 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "babeldoc"
51
- version = "0.1.22"
51
+ version = "0.1.24"
52
52
  source = { editable = "." }
53
53
  dependencies = [
54
54
  { name = "bitstring" },
@@ -67,6 +67,7 @@ dependencies = [
67
67
  { name = "pydantic" },
68
68
  { name = "pymupdf" },
69
69
  { name = "rich" },
70
+ { name = "scikit-image" },
70
71
  { name = "tenacity" },
71
72
  { name = "toml" },
72
73
  { name = "tqdm" },
@@ -106,6 +107,7 @@ requires-dist = [
106
107
  { name = "pydantic", specifier = ">=2.10.6" },
107
108
  { name = "pymupdf", specifier = ">=1.25.1" },
108
109
  { name = "rich", specifier = ">=13.9.4" },
110
+ { name = "scikit-image", specifier = ">=0.25.2" },
109
111
  { name = "tenacity", specifier = ">=9.0.0" },
110
112
  { name = "toml", specifier = ">=0.10.2" },
111
113
  { name = "tqdm", specifier = ">=4.67.1" },
@@ -660,6 +662,19 @@ wheels = [
660
662
  { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
661
663
  ]
662
664
 
665
+ [[package]]
666
+ name = "imageio"
667
+ version = "2.37.0"
668
+ source = { registry = "https://pypi.org/simple" }
669
+ dependencies = [
670
+ { name = "numpy" },
671
+ { name = "pillow" },
672
+ ]
673
+ sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 }
674
+ wheels = [
675
+ { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 },
676
+ ]
677
+
663
678
  [[package]]
664
679
  name = "iniconfig"
665
680
  version = "2.0.0"
@@ -731,6 +746,18 @@ version = "3.0.1"
731
746
  source = { registry = "https://pypi.org/simple" }
732
747
  sdist = { url = "https://files.pythonhosted.org/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925 }
733
748
 
749
+ [[package]]
750
+ name = "lazy-loader"
751
+ version = "0.4"
752
+ source = { registry = "https://pypi.org/simple" }
753
+ dependencies = [
754
+ { name = "packaging" },
755
+ ]
756
+ sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 }
757
+ wheels = [
758
+ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 },
759
+ ]
760
+
734
761
  [[package]]
735
762
  name = "lexid"
736
763
  version = "2021.1006"
@@ -1116,6 +1143,15 @@ wheels = [
1116
1143
  { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 },
1117
1144
  ]
1118
1145
 
1146
+ [[package]]
1147
+ name = "networkx"
1148
+ version = "3.4.2"
1149
+ source = { registry = "https://pypi.org/simple" }
1150
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 }
1151
+ wheels = [
1152
+ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 },
1153
+ ]
1154
+
1119
1155
  [[package]]
1120
1156
  name = "nodeenv"
1121
1157
  version = "1.9.1"
@@ -1353,6 +1389,54 @@ version = "3.17.9"
1353
1389
  source = { registry = "https://pypi.org/simple" }
1354
1390
  sdist = { url = "https://files.pythonhosted.org/packages/57/09/4393bd378e70b7fc3163ee83353cc27bb520010a5c2b3c924121e7e7e068/peewee-3.17.9.tar.gz", hash = "sha256:fe15cd001758e324c8e3ca8c8ed900e7397c2907291789e1efc383e66b9bc7a8", size = 3026085 }
1355
1391
 
1392
+ [[package]]
1393
+ name = "pillow"
1394
+ version = "11.1.0"
1395
+ source = { registry = "https://pypi.org/simple" }
1396
+ sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 }
1397
+ wheels = [
1398
+ { url = "https://files.pythonhosted.org/packages/50/1c/2dcea34ac3d7bc96a1fd1bd0a6e06a57c67167fec2cff8d95d88229a8817/pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8", size = 3229983 },
1399
+ { url = "https://files.pythonhosted.org/packages/14/ca/6bec3df25e4c88432681de94a3531cc738bd85dea6c7aa6ab6f81ad8bd11/pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192", size = 3101831 },
1400
+ { url = "https://files.pythonhosted.org/packages/d4/2c/668e18e5521e46eb9667b09e501d8e07049eb5bfe39d56be0724a43117e6/pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2", size = 4314074 },
1401
+ { url = "https://files.pythonhosted.org/packages/02/80/79f99b714f0fc25f6a8499ecfd1f810df12aec170ea1e32a4f75746051ce/pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26", size = 4394933 },
1402
+ { url = "https://files.pythonhosted.org/packages/81/aa/8d4ad25dc11fd10a2001d5b8a80fdc0e564ac33b293bdfe04ed387e0fd95/pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07", size = 4353349 },
1403
+ { url = "https://files.pythonhosted.org/packages/84/7a/cd0c3eaf4a28cb2a74bdd19129f7726277a7f30c4f8424cd27a62987d864/pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482", size = 4476532 },
1404
+ { url = "https://files.pythonhosted.org/packages/8f/8b/a907fdd3ae8f01c7670dfb1499c53c28e217c338b47a813af8d815e7ce97/pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e", size = 4279789 },
1405
+ { url = "https://files.pythonhosted.org/packages/6f/9a/9f139d9e8cccd661c3efbf6898967a9a337eb2e9be2b454ba0a09533100d/pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269", size = 4413131 },
1406
+ { url = "https://files.pythonhosted.org/packages/a8/68/0d8d461f42a3f37432203c8e6df94da10ac8081b6d35af1c203bf3111088/pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49", size = 2291213 },
1407
+ { url = "https://files.pythonhosted.org/packages/14/81/d0dff759a74ba87715509af9f6cb21fa21d93b02b3316ed43bda83664db9/pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a", size = 2625725 },
1408
+ { url = "https://files.pythonhosted.org/packages/ce/1f/8d50c096a1d58ef0584ddc37e6f602828515219e9d2428e14ce50f5ecad1/pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65", size = 2375213 },
1409
+ { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 },
1410
+ { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 },
1411
+ { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 },
1412
+ { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 },
1413
+ { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 },
1414
+ { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 },
1415
+ { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 },
1416
+ { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 },
1417
+ { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 },
1418
+ { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 },
1419
+ { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 },
1420
+ { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 },
1421
+ { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 },
1422
+ { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 },
1423
+ { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 },
1424
+ { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 },
1425
+ { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 },
1426
+ { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 },
1427
+ { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 },
1428
+ { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 },
1429
+ { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 },
1430
+ { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 },
1431
+ { url = "https://files.pythonhosted.org/packages/fa/c5/389961578fb677b8b3244fcd934f720ed25a148b9a5cc81c91bdf59d8588/pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90", size = 3198345 },
1432
+ { url = "https://files.pythonhosted.org/packages/c4/fa/803c0e50ffee74d4b965229e816af55276eac1d5806712de86f9371858fd/pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb", size = 3072938 },
1433
+ { url = "https://files.pythonhosted.org/packages/dc/67/2a3a5f8012b5d8c63fe53958ba906c1b1d0482ebed5618057ef4d22f8076/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442", size = 3400049 },
1434
+ { url = "https://files.pythonhosted.org/packages/e5/a0/514f0d317446c98c478d1872497eb92e7cde67003fed74f696441e647446/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83", size = 3422431 },
1435
+ { url = "https://files.pythonhosted.org/packages/cd/00/20f40a935514037b7d3f87adfc87d2c538430ea625b63b3af8c3f5578e72/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f", size = 3446208 },
1436
+ { url = "https://files.pythonhosted.org/packages/28/3c/7de681727963043e093c72e6c3348411b0185eab3263100d4490234ba2f6/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73", size = 3509746 },
1437
+ { url = "https://files.pythonhosted.org/packages/41/67/936f9814bdd74b2dfd4822f1f7725ab5d8ff4103919a1664eb4874c58b2f/pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0", size = 2626353 },
1438
+ ]
1439
+
1356
1440
  [[package]]
1357
1441
  name = "platformdirs"
1358
1442
  version = "4.3.6"
@@ -1669,6 +1753,77 @@ wheels = [
1669
1753
  { url = "https://files.pythonhosted.org/packages/31/d8/de873d1c1b020d668d8ec9855d390764cb90cf8f6486c0983da52be8b7b7/ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf", size = 10435860 },
1670
1754
  ]
1671
1755
 
1756
+ [[package]]
1757
+ name = "scikit-image"
1758
+ version = "0.25.2"
1759
+ source = { registry = "https://pypi.org/simple" }
1760
+ dependencies = [
1761
+ { name = "imageio" },
1762
+ { name = "lazy-loader" },
1763
+ { name = "networkx" },
1764
+ { name = "numpy" },
1765
+ { name = "packaging" },
1766
+ { name = "pillow" },
1767
+ { name = "scipy" },
1768
+ { name = "tifffile" },
1769
+ ]
1770
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 }
1771
+ wheels = [
1772
+ { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922 },
1773
+ { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698 },
1774
+ { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634 },
1775
+ { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545 },
1776
+ { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908 },
1777
+ { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057 },
1778
+ { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335 },
1779
+ { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783 },
1780
+ { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376 },
1781
+ { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 },
1782
+ { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000 },
1783
+ { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893 },
1784
+ { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389 },
1785
+ { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435 },
1786
+ { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 },
1787
+ ]
1788
+
1789
+ [[package]]
1790
+ name = "scipy"
1791
+ version = "1.15.2"
1792
+ source = { registry = "https://pypi.org/simple" }
1793
+ dependencies = [
1794
+ { name = "numpy" },
1795
+ ]
1796
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 }
1797
+ wheels = [
1798
+ { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 },
1799
+ { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 },
1800
+ { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 },
1801
+ { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 },
1802
+ { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 },
1803
+ { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 },
1804
+ { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 },
1805
+ { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 },
1806
+ { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 },
1807
+ { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 },
1808
+ { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 },
1809
+ { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 },
1810
+ { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 },
1811
+ { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 },
1812
+ { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 },
1813
+ { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 },
1814
+ { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 },
1815
+ { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 },
1816
+ { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 },
1817
+ { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 },
1818
+ { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 },
1819
+ { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 },
1820
+ { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 },
1821
+ { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 },
1822
+ { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 },
1823
+ { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 },
1824
+ { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 },
1825
+ ]
1826
+
1672
1827
  [[package]]
1673
1828
  name = "six"
1674
1829
  version = "1.17.0"
@@ -1726,6 +1881,18 @@ wheels = [
1726
1881
  { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 },
1727
1882
  ]
1728
1883
 
1884
+ [[package]]
1885
+ name = "tifffile"
1886
+ version = "2025.2.18"
1887
+ source = { registry = "https://pypi.org/simple" }
1888
+ dependencies = [
1889
+ { name = "numpy" },
1890
+ ]
1891
+ sdist = { url = "https://files.pythonhosted.org/packages/de/1f/96d743b3417425f958dfed2518ad271b346a072d27b6859bb158e601bc21/tifffile-2025.2.18.tar.gz", hash = "sha256:8d731789e691b468746c1615d989bc550ac93cf753e9210865222e90a5a95d11", size = 365412 }
1892
+ wheels = [
1893
+ { url = "https://files.pythonhosted.org/packages/63/70/6f363ab13f9903557a567a4471a28ee231b962e34af8e1dd8d1b0f17e64e/tifffile-2025.2.18-py3-none-any.whl", hash = "sha256:54b36c4d5e5b8d8920134413edfe5a7cfb1c7617bb50cddf7e2772edb7149043", size = 226358 },
1894
+ ]
1895
+
1729
1896
  [[package]]
1730
1897
  name = "toml"
1731
1898
  version = "0.10.2"
@@ -1 +0,0 @@
1
- __version__ = "0.1.22"
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