-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Dev #4473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dev #4473
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||||||||||||||||||
| # Copyright (c) Opendatalab. All rights reserved. | ||||||||||||||||||||||
| import os | ||||||||||||||||||||||
| import signal | ||||||||||||||||||||||
myhloli marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
| import time | ||||||||||||||||||||||
| from io import BytesIO | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||
|
|
@@ -9,13 +11,13 @@ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| from mineru.data.data_reader_writer import FileBasedDataWriter | ||||||||||||||||||||||
| from mineru.utils.check_sys_env import is_windows_environment | ||||||||||||||||||||||
| from mineru.utils.os_env_config import get_load_images_timeout | ||||||||||||||||||||||
| from mineru.utils.os_env_config import get_load_images_timeout, get_load_images_threads | ||||||||||||||||||||||
| from mineru.utils.pdf_reader import image_to_b64str, image_to_bytes, page_to_image | ||||||||||||||||||||||
| from mineru.utils.enum_class import ImageType | ||||||||||||||||||||||
| from mineru.utils.hash_utils import str_sha256 | ||||||||||||||||||||||
| from mineru.utils.pdf_page_id import get_end_page_id | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from concurrent.futures import ProcessPoolExecutor, TimeoutError as FuturesTimeoutError | ||||||||||||||||||||||
| from concurrent.futures import ProcessPoolExecutor, wait, ALL_COMPLETED | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def pdf_page_to_image(page: pdfium.PdfPage, dpi=200, image_type=ImageType.PIL) -> dict: | ||||||||||||||||||||||
|
|
@@ -57,7 +59,7 @@ def load_images_from_pdf( | |||||||||||||||||||||
| end_page_id=None, | ||||||||||||||||||||||
| image_type=ImageType.PIL, | ||||||||||||||||||||||
| timeout=None, | ||||||||||||||||||||||
| threads=4, | ||||||||||||||||||||||
| threads=None, | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| """带超时控制的 PDF 转图片函数,支持多进程加速 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -67,8 +69,8 @@ def load_images_from_pdf( | |||||||||||||||||||||
| start_page_id (int, optional): 起始页码. Defaults to 0. | ||||||||||||||||||||||
| end_page_id (int | None, optional): 结束页码. Defaults to None. | ||||||||||||||||||||||
| image_type (ImageType, optional): 图片类型. Defaults to ImageType.PIL. | ||||||||||||||||||||||
| timeout (int | None, optional): 超时时间(秒)。如果为 None,则从环境变量 MINERU_PDF_LOAD_IMAGES_TIMEOUT 读取,若未设置则默认为 300 秒。 | ||||||||||||||||||||||
| threads (int): 进程数,默认 4 | ||||||||||||||||||||||
| timeout (int | None, optional): 超时时间(秒)。如果为 None,则从环境变量 MINERU_PDF_RENDER_TIMEOUT 读取,若未设置则默认为 300 秒。 | ||||||||||||||||||||||
| threads (int): 进程数, 如果为 None,则从环境变量 MINERU_PDF_RENDER_THREADS 读取,若未设置则默认为 4. | ||||||||||||||||||||||
myhloli marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||
| TimeoutError: 当转换超时时抛出 | ||||||||||||||||||||||
|
|
@@ -86,6 +88,9 @@ def load_images_from_pdf( | |||||||||||||||||||||
| else: | ||||||||||||||||||||||
| if timeout is None: | ||||||||||||||||||||||
| timeout = get_load_images_timeout() | ||||||||||||||||||||||
| if threads is None: | ||||||||||||||||||||||
| threads = get_load_images_threads() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| end_page_id = get_end_page_id(end_page_id, len(pdf_doc)) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 计算总页数 | ||||||||||||||||||||||
|
|
@@ -108,11 +113,13 @@ def load_images_from_pdf( | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| page_ranges.append((range_start, range_end)) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # logger.debug(f"PDF to images using {actual_threads} processes, page ranges: {page_ranges}") | ||||||||||||||||||||||
| logger.debug(f"PDF to images using {actual_threads} processes, page ranges: {page_ranges}") | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| with ProcessPoolExecutor(max_workers=actual_threads) as executor: | ||||||||||||||||||||||
| executor = ProcessPoolExecutor(max_workers=actual_threads) | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| # 提交所有任务 | ||||||||||||||||||||||
| futures = [] | ||||||||||||||||||||||
| future_to_range = {} | ||||||||||||||||||||||
| for range_start, range_end in page_ranges: | ||||||||||||||||||||||
| future = executor.submit( | ||||||||||||||||||||||
| _load_images_from_pdf_worker, | ||||||||||||||||||||||
|
|
@@ -122,27 +129,68 @@ def load_images_from_pdf( | |||||||||||||||||||||
| range_end, | ||||||||||||||||||||||
| image_type, | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| futures.append((range_start, future)) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| # 收集结果并按页码排序 | ||||||||||||||||||||||
| all_results = [] | ||||||||||||||||||||||
| for range_start, future in futures: | ||||||||||||||||||||||
| images_list = future.result(timeout=timeout) | ||||||||||||||||||||||
| all_results.append((range_start, images_list)) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 按起始页码排序并合并结果 | ||||||||||||||||||||||
| all_results.sort(key=lambda x: x[0]) | ||||||||||||||||||||||
| images_list = [] | ||||||||||||||||||||||
| for _, imgs in all_results: | ||||||||||||||||||||||
| images_list.extend(imgs) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return images_list, pdf_doc | ||||||||||||||||||||||
| except FuturesTimeoutError: | ||||||||||||||||||||||
| futures.append(future) | ||||||||||||||||||||||
| future_to_range[future] = range_start | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 使用 wait() 设置单一全局超时 | ||||||||||||||||||||||
| done, not_done = wait(futures, timeout=timeout, return_when=ALL_COMPLETED) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 检查是否有未完成的任务(超时情况) | ||||||||||||||||||||||
| if not_done: | ||||||||||||||||||||||
| # 超时:强制终止所有子进程 | ||||||||||||||||||||||
| _terminate_executor_processes(executor) | ||||||||||||||||||||||
| pdf_doc.close() | ||||||||||||||||||||||
| executor.shutdown(wait=False, cancel_futures=True) | ||||||||||||||||||||||
| raise TimeoutError(f"PDF to images conversion timeout after {timeout}s") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 所有任务完成,收集结果 | ||||||||||||||||||||||
| all_results = [] | ||||||||||||||||||||||
| for future in futures: | ||||||||||||||||||||||
| range_start = future_to_range[future] | ||||||||||||||||||||||
| # 这里不需要 timeout,因为任务已完成 | ||||||||||||||||||||||
| images_list = future.result() | ||||||||||||||||||||||
|
Comment on lines
+149
to
+150
|
||||||||||||||||||||||
| # 这里不需要 timeout,因为任务已完成 | |
| images_list = future.result() | |
| # 这里不需要 timeout,因为任务已完成,但需要捕获并标注异常范围 | |
| try: | |
| images_list = future.result() | |
| except Exception as e: | |
| # 为调用方提供更清晰的错误上下文,指明是哪个页码范围失败 | |
| raise RuntimeError( | |
| f"Error processing PDF pages starting at {range_start}" | |
| ) from e |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _terminate_executor_processes function is called both at line 141 (for timeout) and at line 163 (for any exception). If a timeout occurs (raising TimeoutError at line 143), the exception handler at line 161 will catch it and call _terminate_executor_processes again at line 163. This means processes could be terminated twice in the timeout scenario. While this shouldn't cause errors due to exception handling in the function, it's inefficient. Consider restructuring to avoid the duplicate call.
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception handling logic has a subtle issue. At line 165-167, if the exception is a TimeoutError, it's re-raised, but this will only catch the TimeoutError raised at line 143. Any other exception will also be re-raised at line 167. The redundant check at line 165-166 doesn't add value since line 167 will re-raise all exceptions anyway. Consider simplifying by removing lines 165-166.
| if isinstance(e, TimeoutError): | |
| raise |
Uh oh!
There was an error while loading. Please reload this page.