feat: 更新MinerU(v2.5) 配置,修改镜像名称和后端,优化OCR处理逻辑

This commit is contained in:
Wenjie Zhang 2025-10-24 01:09:24 +08:00
parent a1fdcbc36c
commit c8fa8a0131
4 changed files with 177 additions and 184 deletions

View File

@ -207,23 +207,27 @@ services:
networks: networks:
- app-network - app-network
restart: unless-stopped restart: unless-stopped
# lastest version: wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/compose.yaml # lastest version: wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/compose.yaml
mineru: mineru:
build: build:
context: . context: .
dockerfile: docker/mineru.Dockerfile dockerfile: docker/mineru.Dockerfile
image: mineru-sglang:latest image: mineru-vllm:latest
container_name: mineru container_name: mineru
profiles: profiles:
- all - all
ports: ports:
- 30000:30000 - 30000:30000
environment: environment:
MINERU_MODEL_SOURCE: modelscope MINERU_MODEL_SOURCE: local
entrypoint: mineru-sglang-server entrypoint: mineru-vllm-server
command: command:
--host 0.0.0.0 --host 0.0.0.0
--port 30000 --port 30000
# parameters for vllm-engine
# --data-parallel-size 2 # If using multiple GPUs, increase throughput using vllm's multi-GPU parallel mode
# --gpu-memory-utilization 0.5 # If running on a single GPU and encountering VRAM shortage, reduce the KV cache size by this parameter, if VRAM issues persist, try lowering it further to `0.4` or below.
ulimits: ulimits:
memlock: -1 memlock: -1
stack: 67108864 stack: 67108864

View File

@ -1,9 +1,15 @@
# Lastest version: wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/Dockerfile # Use DaoCloud mirrored vllm image for China region for gpu with Ampere architecture and above (Compute Capability>=8.0)
# Compute Capability version query (https://developer.nvidia.com/cuda-gpus)
FROM docker.m.daocloud.io/vllm/vllm-openai:v0.10.1.1
# Use the official sglang image # Use the official vllm image
FROM lmsysorg/sglang:v0.4.9.post3-cu126 # FROM vllm/vllm-openai:v0.10.1.1
# For blackwell GPU, use the following line instead:
# FROM lmsysorg/sglang:v0.4.9.post3-cu128-b200 # Use DaoCloud mirrored vllm image for China region for gpu with Turing architecture and below (Compute Capability<8.0)
# FROM docker.m.daocloud.io/vllm/vllm-openai:v0.10.2
# Use the official vllm image
# FROM vllm/vllm-openai:v0.10.2
# Install libgl for opencv support & Noto fonts for Chinese characters # Install libgl for opencv support & Noto fonts for Chinese characters
RUN apt-get update && \ RUN apt-get update && \

View File

@ -270,7 +270,7 @@ class OCRPlugin:
file_path_list = [file_path] file_path_list = [file_path]
output_dir = os.path.join(os.getcwd(), "tmp", "mineru_ocr") output_dir = os.path.join(os.getcwd(), "tmp", "mineru_ocr")
text = parse_doc(file_path_list, output_dir, backend="vlm-sglang-client", server_url=mineru_ocr_uri)[0] text = parse_doc(file_path_list, output_dir, backend="vlm-vllm-client", server_url=mineru_ocr_uri)[0]
processing_time = time.time() - start_time processing_time = time.time() - start_time
log_ocr_request("mineru_ocr", file_path, True, processing_time) log_ocr_request("mineru_ocr", file_path, True, processing_time)

View File

@ -4,18 +4,18 @@ import json
import os import os
from pathlib import Path from pathlib import Path
from mineru.backend.pipeline.model_json_to_middle_json import result_to_middle_json as pipeline_result_to_middle_json from loguru import logger
from mineru.backend.pipeline.pipeline_analyze import doc_analyze as pipeline_doc_analyze
from mineru.backend.pipeline.pipeline_middle_json_mkcontent import union_make as pipeline_union_make
from mineru.backend.vlm.vlm_analyze import doc_analyze as vlm_doc_analyze
from mineru.backend.vlm.vlm_middle_json_mkcontent import union_make as vlm_union_make
from mineru.cli.common import convert_pdf_bytes_to_bytes_by_pypdfium2, prepare_env, read_fn from mineru.cli.common import convert_pdf_bytes_to_bytes_by_pypdfium2, prepare_env, read_fn
from mineru.data.data_reader_writer import FileBasedDataWriter from mineru.data.data_reader_writer import FileBasedDataWriter
from mineru.utils.draw_bbox import draw_layout_bbox, draw_span_bbox from mineru.utils.draw_bbox import draw_layout_bbox, draw_span_bbox
from mineru.utils.enum_class import MakeMode from mineru.utils.enum_class import MakeMode
from tqdm import tqdm from mineru.backend.vlm.vlm_analyze import doc_analyze as vlm_doc_analyze
from mineru.backend.pipeline.pipeline_analyze import doc_analyze as pipeline_doc_analyze
from src.utils.logging_config import logger from mineru.backend.pipeline.pipeline_middle_json_mkcontent import union_make as pipeline_union_make
from mineru.backend.pipeline.model_json_to_middle_json import result_to_middle_json as pipeline_result_to_middle_json
from mineru.backend.vlm.vlm_middle_json_mkcontent import union_make as vlm_union_make
from mineru.utils.guess_suffix_or_lang import guess_suffix_by_path
def do_parse( def do_parse(
@ -25,9 +25,9 @@ def do_parse(
p_lang_list: list[str], # List of languages for each PDF, default is 'ch' (Chinese) p_lang_list: list[str], # List of languages for each PDF, default is 'ch' (Chinese)
backend="pipeline", # The backend for parsing PDF, default is 'pipeline' backend="pipeline", # The backend for parsing PDF, default is 'pipeline'
parse_method="auto", # The method for parsing PDF, default is 'auto' parse_method="auto", # The method for parsing PDF, default is 'auto'
p_formula_enable=True, # Enable formula parsing formula_enable=True, # Enable formula parsing
p_table_enable=True, # Enable table parsing table_enable=True, # Enable table parsing
server_url=None, # Server URL for vlm-sglang-client backend server_url=None, # Server URL for vlm-http-client backend
f_draw_layout_bbox=True, # Whether to draw layout bounding boxes f_draw_layout_bbox=True, # Whether to draw layout bounding boxes
f_draw_span_bbox=True, # Whether to draw span bounding boxes f_draw_span_bbox=True, # Whether to draw span bounding boxes
f_dump_md=True, # Whether to dump markdown files f_dump_md=True, # Whether to dump markdown files
@ -38,22 +38,15 @@ def do_parse(
f_make_md_mode=MakeMode.MM_MD, # The mode for making markdown content, default is MM_MD f_make_md_mode=MakeMode.MM_MD, # The mode for making markdown content, default is MM_MD
start_page_id=0, # Start page ID for parsing, default is 0 start_page_id=0, # Start page ID for parsing, default is 0
end_page_id=None, # End page ID for parsing, default is None (parse all pages until the end of the document) end_page_id=None, # End page ID for parsing, default is None (parse all pages until the end of the document)
) -> list[str]: ):
if backend == "pipeline": if backend == "pipeline":
for idx, pdf_bytes in enumerate(pdf_bytes_list): for idx, pdf_bytes in enumerate(pdf_bytes_list):
new_pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id) new_pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
pdf_bytes_list[idx] = new_pdf_bytes pdf_bytes_list[idx] = new_pdf_bytes
result = pipeline_doc_analyze( infer_results, all_image_lists, all_pdf_docs, lang_list, ocr_enabled_list = pipeline_doc_analyze(pdf_bytes_list, p_lang_list, parse_method=parse_method, formula_enable=formula_enable,table_enable=table_enable)
pdf_bytes_list,
p_lang_list,
parse_method=parse_method,
formula_enable=p_formula_enable,
table_enable=p_table_enable,
)
infer_results, all_image_lists, all_pdf_docs, lang_list, ocr_enabled_list = result
md_results = []
for idx, model_list in enumerate(infer_results): for idx, model_list in enumerate(infer_results):
model_json = copy.deepcopy(model_list) model_json = copy.deepcopy(model_list)
pdf_file_name = pdf_file_names[idx] pdf_file_name = pdf_file_names[idx]
@ -64,13 +57,60 @@ def do_parse(
pdf_doc = all_pdf_docs[idx] pdf_doc = all_pdf_docs[idx]
_lang = lang_list[idx] _lang = lang_list[idx]
_ocr_enable = ocr_enabled_list[idx] _ocr_enable = ocr_enabled_list[idx]
middle_json = pipeline_result_to_middle_json( middle_json = pipeline_result_to_middle_json(model_list, images_list, pdf_doc, image_writer, _lang, _ocr_enable, formula_enable)
model_list, images_list, pdf_doc, image_writer, _lang, _ocr_enable, p_formula_enable
)
pdf_info = middle_json["pdf_info"] pdf_info = middle_json["pdf_info"]
pdf_bytes = pdf_bytes_list[idx] pdf_bytes = pdf_bytes_list[idx]
_process_output(
pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
f_make_md_mode, middle_json, model_json, is_pipeline=True
)
else:
if backend.startswith("vlm-"):
backend = backend[4:]
f_draw_span_bbox = False
parse_method = "vlm"
for idx, pdf_bytes in enumerate(pdf_bytes_list):
pdf_file_name = pdf_file_names[idx]
pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)
middle_json, infer_result = vlm_doc_analyze(pdf_bytes, image_writer=image_writer, backend=backend, server_url=server_url)
pdf_info = middle_json["pdf_info"]
_process_output(
pdf_info, pdf_bytes, pdf_file_name, local_md_dir, local_image_dir,
md_writer, f_draw_layout_bbox, f_draw_span_bbox, f_dump_orig_pdf,
f_dump_md, f_dump_content_list, f_dump_middle_json, f_dump_model_output,
f_make_md_mode, middle_json, infer_result, is_pipeline=False
)
def _process_output(
pdf_info,
pdf_bytes,
pdf_file_name,
local_md_dir,
local_image_dir,
md_writer,
f_draw_layout_bbox,
f_draw_span_bbox,
f_dump_orig_pdf,
f_dump_md,
f_dump_content_list,
f_dump_middle_json,
f_dump_model_output,
f_make_md_mode,
middle_json,
model_output=None,
is_pipeline=True
):
"""处理输出文件"""
if f_draw_layout_bbox: if f_draw_layout_bbox:
draw_layout_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_layout.pdf") draw_layout_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_layout.pdf")
@ -83,18 +123,19 @@ def do_parse(
pdf_bytes, pdf_bytes,
) )
if f_dump_md:
image_dir = str(os.path.basename(local_image_dir)) image_dir = str(os.path.basename(local_image_dir))
md_content_str = pipeline_union_make(pdf_info, f_make_md_mode, image_dir)
if f_dump_md:
make_func = pipeline_union_make if is_pipeline else vlm_union_make
md_content_str = make_func(pdf_info, f_make_md_mode, image_dir)
md_writer.write_string( md_writer.write_string(
f"{pdf_file_name}.md", f"{pdf_file_name}.md",
md_content_str, md_content_str,
) )
md_results.append(md_content_str)
if f_dump_content_list: if f_dump_content_list:
image_dir = str(os.path.basename(local_image_dir)) make_func = pipeline_union_make if is_pipeline else vlm_union_make
content_list = pipeline_union_make(pdf_info, MakeMode.CONTENT_LIST, image_dir) content_list = make_func(pdf_info, MakeMode.CONTENT_LIST, image_dir)
md_writer.write_string( md_writer.write_string(
f"{pdf_file_name}_content_list.json", f"{pdf_file_name}_content_list.json",
json.dumps(content_list, ensure_ascii=False, indent=4), json.dumps(content_list, ensure_ascii=False, indent=4),
@ -109,78 +150,11 @@ def do_parse(
if f_dump_model_output: if f_dump_model_output:
md_writer.write_string( md_writer.write_string(
f"{pdf_file_name}_model.json", f"{pdf_file_name}_model.json",
json.dumps(model_json, ensure_ascii=False, indent=4), json.dumps(model_output, ensure_ascii=False, indent=4),
) )
logger.info(f"local output dir is {local_md_dir}") logger.info(f"local output dir is {local_md_dir}")
return md_results
else:
if backend.startswith("vlm-"):
backend = backend[4:]
f_draw_span_bbox = False
parse_method = "vlm"
md_results = []
for idx, pdf_bytes in enumerate(tqdm(pdf_bytes_list, desc="Parsing documents bytes")):
pdf_file_name = pdf_file_names[idx]
pdf_bytes = convert_pdf_bytes_to_bytes_by_pypdfium2(pdf_bytes, start_page_id, end_page_id)
local_image_dir, local_md_dir = prepare_env(output_dir, pdf_file_name, parse_method)
image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)
middle_json, infer_result = vlm_doc_analyze(
pdf_bytes, image_writer=image_writer, backend=backend, server_url=server_url
)
pdf_info = middle_json["pdf_info"]
if f_draw_layout_bbox:
draw_layout_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_layout.pdf")
if f_draw_span_bbox:
draw_span_bbox(pdf_info, pdf_bytes, local_md_dir, f"{pdf_file_name}_span.pdf")
if f_dump_orig_pdf:
md_writer.write(
f"{pdf_file_name}_origin.pdf",
pdf_bytes,
)
if f_dump_md:
image_dir = str(os.path.basename(local_image_dir))
md_content_str = vlm_union_make(pdf_info, f_make_md_mode, image_dir)
md_writer.write_string(
f"{pdf_file_name}.md",
md_content_str,
)
md_results.append(md_content_str)
if f_dump_content_list:
image_dir = str(os.path.basename(local_image_dir))
content_list = vlm_union_make(pdf_info, MakeMode.CONTENT_LIST, image_dir)
md_writer.write_string(
f"{pdf_file_name}_content_list.json",
json.dumps(content_list, ensure_ascii=False, indent=4),
)
if f_dump_middle_json:
md_writer.write_string(
f"{pdf_file_name}_middle.json",
json.dumps(middle_json, ensure_ascii=False, indent=4),
)
if f_dump_model_output:
model_output = ("\n" + "-" * 50 + "\n").join(infer_result)
md_writer.write_string(
f"{pdf_file_name}_model_output.txt",
model_output,
)
logger.info(f"local output dir is {local_md_dir}")
return md_results
def parse_doc( def parse_doc(
path_list: list[Path], path_list: list[Path],
@ -189,24 +163,21 @@ def parse_doc(
backend="pipeline", backend="pipeline",
method="auto", method="auto",
server_url=None, server_url=None,
start_page_id=0, # Start page ID for parsing, default is 0 start_page_id=0,
end_page_id=None, # End page ID for parsing, default is None (parse all pages until the end of the document) end_page_id=None
) -> list[str]: ):
""" """
Parameter description: Parameter description:
path_list: List of document paths to be parsed, can be PDF or image files. path_list: List of document paths to be parsed, can be PDF or image files.
output_dir: Output directory for storing parsing results. output_dir: Output directory for storing parsing results.
lang: Language option, default is 'ch', lang: Language option, default is 'ch', optional values include['ch', 'ch_server', 'ch_lite', 'en', 'korean', 'japan', 'chinese_cht', 'ta', 'te', 'ka']
optional values include[
'ch', 'ch_server', 'ch_lite', 'en', 'korean', 'japan', 'chinese_cht', 'ta', 'te', 'ka'
]
Input the languages in the pdf (if known) to improve OCR accuracy. Optional. Input the languages in the pdf (if known) to improve OCR accuracy. Optional.
Adapted only for the case where the backend is set to "pipeline" Adapted only for the case where the backend is set to "pipeline"
backend: the backend for parsing pdf: backend: the backend for parsing pdf:
pipeline: More general. pipeline: More general.
vlm-transformers: More general. vlm-transformers: More general.
vlm-sglang-engine: Faster(engine). vlm-vllm-engine: Faster(engine).
vlm-sglang-client: Faster(client). vlm-http-client: Faster(client).
without method specified, pipeline will be used by default. without method specified, pipeline will be used by default.
method: the method for parsing pdf: method: the method for parsing pdf:
auto: Automatically determine the method based on the file type. auto: Automatically determine the method based on the file type.
@ -214,19 +185,21 @@ def parse_doc(
ocr: Use OCR method for image-based PDFs. ocr: Use OCR method for image-based PDFs.
Without method specified, 'auto' will be used by default. Without method specified, 'auto' will be used by default.
Adapted only for the case where the backend is set to "pipeline". Adapted only for the case where the backend is set to "pipeline".
server_url: When the backend is `sglang-client`, you need to specify the server_url, for example:`http://127.0.0.1:30000` server_url: When the backend is `http-client`, you need to specify the server_url, for example:`http://127.0.0.1:30000`
start_page_id: Start page ID for parsing, default is 0
end_page_id: End page ID for parsing, default is None (parse all pages until the end of the document)
""" """
try:
file_name_list = [] file_name_list = []
pdf_bytes_list = [] pdf_bytes_list = []
lang_list = [] lang_list = []
for path in tqdm(path_list, desc="Parsing documents"): for path in path_list:
file_name = str(Path(path).stem) file_name = str(Path(path).stem)
pdf_bytes = read_fn(path) pdf_bytes = read_fn(path)
file_name_list.append(file_name) file_name_list.append(file_name)
pdf_bytes_list.append(pdf_bytes) pdf_bytes_list.append(pdf_bytes)
lang_list.append(lang) lang_list.append(lang)
do_parse(
result = do_parse(
output_dir=output_dir, output_dir=output_dir,
pdf_file_names=file_name_list, pdf_file_names=file_name_list,
pdf_bytes_list=pdf_bytes_list, pdf_bytes_list=pdf_bytes_list,
@ -235,22 +208,32 @@ def parse_doc(
parse_method=method, parse_method=method,
server_url=server_url, server_url=server_url,
start_page_id=start_page_id, start_page_id=start_page_id,
end_page_id=end_page_id, end_page_id=end_page_id
) )
return result if result else [""] except Exception as e:
logger.exception(e)
if __name__ == "__main__": if __name__ == '__main__':
pdf_files_dir = "/home/zwj/workspace/projects/Yuxi-Know/test/struct_pdf" # args
output_dir = "/home/zwj/workspace/projects/Yuxi-Know/test/struct_pdf_output" __dir__ = os.path.dirname(os.path.abspath(__file__))
pdf_suffixes = [".pdf"] pdf_files_dir = os.path.join(__dir__, "pdfs")
image_suffixes = [".png", ".jpeg", ".jpg"] output_dir = os.path.join(__dir__, "output")
pdf_suffixes = ["pdf"]
image_suffixes = ["png", "jpeg", "jp2", "webp", "gif", "bmp", "jpg"]
doc_path_list = [] doc_path_list = []
for doc_path in Path(pdf_files_dir).glob("*"): for doc_path in Path(pdf_files_dir).glob('*'):
if doc_path.suffix in pdf_suffixes + image_suffixes: if guess_suffix_by_path(doc_path) in pdf_suffixes + image_suffixes:
doc_path_list.append(doc_path) doc_path_list.append(doc_path)
parse_doc( """如果您由于网络问题无法下载模型可以设置环境变量MINERU_MODEL_SOURCE为modelscope使用免代理仓库下载模型"""
doc_path_list, output_dir, backend="vlm-sglang-client", server_url="http://172.19.13.5:30000" # os.environ['MINERU_MODEL_SOURCE'] = "modelscope"
) # faster(client).
"""Use pipeline mode if your environment does not support VLM"""
parse_doc(doc_path_list, output_dir, backend="pipeline")
"""To enable VLM mode, change the backend to 'vlm-xxx'"""
# parse_doc(doc_path_list, output_dir, backend="vlm-transformers") # more general.
# parse_doc(doc_path_list, output_dir, backend="vlm-vllm-engine") # faster(engine).
# parse_doc(doc_path_list, output_dir, backend="vlm-http-client", server_url="http://127.0.0.1:30000") # faster(client).