diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 17c06480a856f1cea6b25968a41d6175a01c2d97..83a5d57d8aba97ec117b8cc843471084830b5469 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -31,9 +31,9 @@ jobs:
conda env list
pip show coverage
cd $GITHUB_WORKSPACE && sh tests/retry_env.sh
- cd $GITHUB_WORKSPACE && python tests/clean_coverage.py
- cd $GITHUB_WORKSPACE && coverage run -m pytest tests/unittest/ --cov=magic_pdf/ --cov-report html --cov-report term-missing
- cd $GITHUB_WORKSPACE && python tests/get_coverage.py
+ # cd $GITHUB_WORKSPACE && python tests/clean_coverage.py
+ # cd $GITHUB_WORKSPACE && coverage run -m pytest tests/unittest/ --cov=magic_pdf/ --cov-report html --cov-report term-missing
+ # cd $GITHUB_WORKSPACE && python tests/get_coverage.py
cd $GITHUB_WORKSPACE && pytest -m P0 -s -v tests/test_cli/test_cli_sdk.py
notify_to_feishu:
diff --git a/.github/workflows/huigui.yml b/.github/workflows/huigui.yml
index 8d0d55a1022f190cabcd837d39d0e610451cba01..95ceffc83375b85668612d8515326848a2ae587a 100644
--- a/.github/workflows/huigui.yml
+++ b/.github/workflows/huigui.yml
@@ -30,9 +30,9 @@ jobs:
conda env list
pip show coverage
cd $GITHUB_WORKSPACE && sh tests/retry_env.sh
- cd $GITHUB_WORKSPACE && python tests/clean_coverage.py
- cd $GITHUB_WORKSPACE && coverage run -m pytest tests/unittest/ --cov=magic_pdf/ --cov-report html --cov-report term-missing
- cd $GITHUB_WORKSPACE && python tests/get_coverage.py
+ # cd $GITHUB_WORKSPACE && python tests/clean_coverage.py
+ # cd $GITHUB_WORKSPACE && coverage run -m pytest tests/unittest/ --cov=magic_pdf/ --cov-report html --cov-report term-missing
+ # cd $GITHUB_WORKSPACE && python tests/get_coverage.py
cd $GITHUB_WORKSPACE && pytest -s -v tests/test_cli/test_cli_sdk.py
notify_to_feishu:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 71a9e367fe6c3e26930e72da7c73f6940a7bb1c8..335d46290bda4a04c5a7336e1164728717efcd91 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -32,14 +32,14 @@ jobs:
- name: Verify version.py
run: |
- ls -l magic_pdf/libs/version.py
- cat magic_pdf/libs/version.py
+ ls -l mineru/version.py
+ cat mineru/version.py
- name: Commit changes
run: |
git config --local user.email "moe@myhloli.com"
git config --local user.name "myhloli"
- git add magic_pdf/libs/version.py
+ git add mineru/version.py
if git diff-index --quiet HEAD; then
echo "No changes to commit"
else
@@ -71,18 +71,18 @@ jobs:
- name: Verify version.py
run: |
- ls -l magic_pdf/libs/version.py
- cat magic_pdf/libs/version.py
+ ls -l mineru/version.py
+ cat mineru/version.py
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- - name: Install magic-pdf
+ - name: Install mineru
run: |
python -m pip install --upgrade pip
- pip install -e .[full]
+ pip install -e .[all]
build:
needs: [ check-install ]
@@ -103,10 +103,11 @@ jobs:
- name: Install wheel
run: |
python -m pip install wheel
+ pip install build
- name: Build wheel
run: |
- python setup.py bdist_wheel
+ python -m build --wheel
- name: Upload artifact
uses: actions/upload-artifact@v4
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100644
index fc7446dfd8c83f8410d2472790c0f917bdfed2f1..0000000000000000000000000000000000000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,47 +0,0 @@
-repos:
- - repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
- hooks:
- - id: flake8
- args: ["--max-line-length=150", "--ignore=E131,E125,W503,W504,E203"]
- - repo: https://github.com/PyCQA/isort
- rev: 5.11.5
- hooks:
- - id: isort
- - repo: https://github.com/pre-commit/mirrors-yapf
- rev: v0.32.0
- hooks:
- - id: yapf
- args: ["--style={based_on_style: google, column_limit: 150, indent_width: 4}"]
- - repo: https://github.com/codespell-project/codespell
- rev: v2.2.1
- hooks:
- - id: codespell
- args: ['--skip', '*.json']
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
- hooks:
- - id: trailing-whitespace
- - id: check-yaml
- - id: end-of-file-fixer
- - id: requirements-txt-fixer
- - id: double-quote-string-fixer
- - id: check-merge-conflict
- - id: fix-encoding-pragma
- args: ["--remove"]
- - id: mixed-line-ending
- args: ["--fix=lf"]
- - repo: https://github.com/executablebooks/mdformat
- rev: 0.7.9
- hooks:
- - id: mdformat
- args: ["--number", "--table-width", "200"]
- additional_dependencies:
- - mdformat-openmmlab
- - mdformat_frontmatter
- - linkify-it-py
- - repo: https://github.com/myint/docformatter
- rev: v1.3.1
- hooks:
- - id: docformatter
- args: ["--in-place", "--wrap-descriptions", "119"]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
deleted file mode 100644
index 09a7b6b14dcec50e52b2daa1a848110f14e379c3..0000000000000000000000000000000000000000
--- a/.readthedocs.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-version: 2
-
-build:
- os: ubuntu-22.04
- tools:
- python: "3.10"
-
-formats:
- - epub
-
-python:
- install:
- - requirements: next_docs/zh_cn/requirements.txt
-
-sphinx:
- configuration: next_docs/zh_cn/conf.py
diff --git a/README.md b/README.md
index 09bdd782183e44b2bd8f7a4c6972516af632a7b1..cb4363b63439a1811948a59832386693e8b35393 100644
--- a/README.md
+++ b/README.md
@@ -10,14 +10,17 @@
[](https://github.com/opendatalab/MinerU)
[](https://github.com/opendatalab/MinerU/issues)
[](https://github.com/opendatalab/MinerU/issues)
-[](https://pypi.org/project/magic-pdf/)
-[](https://pypi.org/project/magic-pdf/)
-[](https://pepy.tech/project/magic-pdf)
-[](https://pepy.tech/project/magic-pdf)
+
+[](https://pypi.org/project/mineru/)
+[](https://pypi.org/project/mineru/)
+[](https://pepy.tech/project/mineru)
+[](https://pepy.tech/project/mineru)
[](https://mineru.net/OpenSourceTools/Extractor?source=github)
[](https://huggingface.co/spaces/opendatalab/MinerU)
[](https://www.modelscope.cn/studios/OpenDataLab/MinerU)
+
+[](https://huggingface.co/spaces/opendatalab/mineru2)
[](https://colab.research.google.com/gist/myhloli/3b3a00a4a0a61577b6c30f989092d20d/mineru_demo.ipynb)
[](https://arxiv.org/abs/2409.18839)
@@ -48,251 +51,344 @@ Easier to use: Just grab MinerU Desktop. No coding, no login, just a simple inte
# Changelog
-- 2025/05/24 1.3.12 Released
- - Added support for ppocrv5 model, updated `ch_server` model to `PP-OCRv5_rec_server` and `ch_lite` model to `PP-OCRv5_rec_mobile` (model update required)
- - In testing, we found that ppocrv5(server) shows some improvement for handwritten documents, but slightly lower accuracy than v4_server_doc for other document types. Therefore, the default ch model remains unchanged as `PP-OCRv4_server_rec_doc`.
- - Since ppocrv5 enhances recognition capabilities for handwritten text and special characters, you can manually select ppocrv5 models for Japanese, traditional Chinese mixed scenarios and handwritten document scenarios
- - You can select the appropriate model through the lang parameter `lang='ch_server'` (python api) or `--lang ch_server` (command line):
- - `ch`: `PP-OCRv4_rec_server_doc` (default) (Chinese, English, Japanese, Traditional Chinese mixed/15k dictionary)
- - `ch_server`: `PP-OCRv5_rec_server` (Chinese, English, Japanese, Traditional Chinese mixed + handwriting/18k dictionary)
- - `ch_lite`: `PP-OCRv5_rec_mobile` (Chinese, English, Japanese, Traditional Chinese mixed + handwriting/18k dictionary)
- - `ch_server_v4`: `PP-OCRv4_rec_server` (Chinese, English mixed/6k dictionary)
- - `ch_lite_v4`: `PP-OCRv4_rec_mobile` (Chinese, English mixed/6k dictionary)
- - Added support for handwritten documents by optimizing layout recognition of handwritten text areas
- - This feature is supported by default, no additional configuration needed
- - You can refer to the instructions above to manually select ppocrv5 model for better handwritten document parsing
- - The demos on `huggingface` and `modelscope` have been updated to support handwriting recognition and ppocrv5 models, which you can experience online
-- 2025/04/29 1.3.10 Released
- - Support for custom formula delimiters can be achieved by modifying the `latex-delimiter-config` item in the `magic-pdf.json` file under the user directory.
-- 2025/04/27 1.3.9 Released
- - Optimized the formula parsing function to improve the success rate of formula rendering
-- 2025/04/23 1.3.8 Released
- - The default `ocr` model (`ch`) has been updated to `PP-OCRv4_server_rec_doc` (model update required)
- - `PP-OCRv4_server_rec_doc` is trained on a mix of more Chinese document data and PP-OCR training data, enhancing recognition capabilities for some traditional Chinese characters, Japanese, and special characters. It supports over 15,000 recognizable characters, improving text recognition in documents while also boosting general text recognition.
- - [Performance comparison between PP-OCRv4_server_rec_doc, PP-OCRv4_server_rec, and PP-OCRv4_mobile_rec](https://paddlepaddle.github.io/PaddleX/latest/en/module_usage/tutorials/ocr_modules/text_recognition.html#ii-supported-model-list)
- - Verified results show that the `PP-OCRv4_server_rec_doc` model significantly improves accuracy in both single-language (`Chinese`, `English`, `Japanese`, `Traditional Chinese`) and mixed-language scenarios, with speed comparable to `PP-OCRv4_server_rec`, making it suitable for most use cases.
- - In a small number of pure English scenarios, the `PP-OCRv4_server_rec_doc` model may encounter word concatenation issues, whereas `PP-OCRv4_server_rec` performs better in such cases. Therefore, we have retained the `PP-OCRv4_server_rec` model, which users can invoke by passing the parameter `lang='ch_server'`(python api) or `--lang ch_server`(cli).
-- 2025/04/22 1.3.7 Released
- - Fixed the issue where the `lang` parameter was ineffective during table parsing model initialization.
- - Fixed the significant slowdown in OCR and table parsing speed in `cpu` mode.
-- 2025/04/16 1.3.4 Released
- - Slightly improved the speed of OCR detection by removing some unused blocks.
- - Fixed page-level sorting errors caused by footnotes in certain cases.
-- 2025/04/12 1.3.2 released
- - Fixed the issue of incompatible dependency package versions when installing in Python 3.13 environment on Windows systems.
- - Optimized memory usage during batch inference.
- - Improved the parsing effect of tables rotated by 90 degrees.
- - Enhanced the parsing accuracy for large tables in financial report samples.
- - Fixed the occasional word concatenation issue in English text areas when OCR language is not specified.(The model needs to be updated)
-- 2025/04/08 1.3.1 released, fixed some compatibility issues
- - Supported Python 3.13
- - Made the final adaptation for some outdated Linux systems (e.g., CentOS 7), and no further support will be guaranteed for subsequent versions. [Installation Instructions](https://github.com/opendatalab/MinerU/issues/1004)
-- 2025/04/03 1.3.0 released, in this version we made many optimizations and improvements:
- - Installation and compatibility optimization
- - By removing the use of `layoutlmv3` in layout, resolved compatibility issues caused by `detectron2`.
- - Torch version compatibility extended to 2.2~2.6 (excluding 2.5).
- - CUDA compatibility supports 11.8/12.4/12.6/12.8 (CUDA version determined by torch), resolving compatibility issues for some users with 50-series and H-series GPUs.
- - Python compatible versions expanded to 3.10~3.12, solving the problem of automatic downgrade to 0.6.1 during installation in non-3.10 environments.
- - Offline deployment process optimized; no internet connection required after successful deployment to download any model files.
- - Performance optimization
- - By supporting batch processing of multiple PDF files ([script example](demo/batch_demo.py)), improved parsing speed for small files in batches (compared to version 1.0.1, formula parsing speed increased by over 1400%, overall parsing speed increased by over 500%).
- - Optimized loading and usage of the mfr model, reducing GPU memory usage and improving parsing speed (requires re-execution of the [model download process](docs/how_to_download_models_en.md) to obtain incremental updates of model files).
- - Optimized GPU memory usage, requiring only a minimum of 6GB to run this project.
- - Improved running speed on MPS devices.
- - Parsing effect optimization
- - Updated the mfr model to `unimernet(2503)`, solving the issue of lost line breaks in multi-line formulas.
- - Usability Optimization
- - By using `paddleocr2torch`, completely replaced the use of the `paddle` framework and `paddleocr` in the project, resolving conflicts between `paddle` and `torch`, as well as thread safety issues caused by the `paddle` framework.
- - Added a real-time progress bar during the parsing process to accurately track progress, making the wait less painful.
-
-2025/03/03 1.2.1 released
-
- - Fixed the impact on punctuation marks during full-width to half-width conversion of letters and numbers
- - Fixed caption matching inaccuracies in certain scenarios
- - Fixed formula span loss issues in certain scenarios
-
-
-
-
-2025/02/24 1.2.0 released
-This version includes several fixes and improvements to enhance parsing efficiency and accuracy:
-
- - Performance Optimization
-
- - Increased classification speed for PDF documents in auto mode.
-
-
- - Parsing Optimization
-
- - Improved parsing logic for documents containing watermarks, significantly enhancing the parsing results for such documents.
- - Enhanced the matching logic for multiple images/tables and captions within a single page, improving the accuracy of image-text matching in complex layouts.
-
-
- - Bug Fixes
-
- - Fixed an issue where image/table spans were incorrectly filled into text blocks under certain conditions.
- - Resolved an issue where title blocks were empty in some cases.
-
-
-
-
-
-
-2025/01/22 1.1.0 released
-In this version we have focused on improving parsing accuracy and efficiency:
-
- - Model capability upgrade (requires re-executing the model download process to obtain incremental updates of model files)
-
- - The layout recognition model has been upgraded to the latest
doclayout_yolo(2501) model, improving layout recognition accuracy.
- - The formula parsing model has been upgraded to the latest
unimernet(2501) model, improving formula recognition accuracy.
-
-
- - Performance optimization
-
- - On devices that meet certain configuration requirements (16GB+ VRAM), by optimizing resource usage and restructuring the processing pipeline, overall parsing speed has been increased by more than 50%.
-
-
- - Parsing effect optimization
-
- - Added a new heading classification feature (testing version, enabled by default) to the online demo (mineru.net/huggingface/modelscope), which supports hierarchical classification of headings, thereby enhancing document structuring.
-
-
-
-
+- 2025/06/13 2.0.0 Released
+ - MinerU 2.0 represents a comprehensive reconstruction and upgrade from architecture to functionality, delivering a more streamlined design, enhanced performance, and more flexible user experience.
+ - **New Architecture**: MinerU 2.0 has been deeply restructured in code organization and interaction methods, significantly improving system usability, maintainability, and extensibility.
+ - **Removal of Third-party Dependency Limitations**: Completely eliminated the dependency on `pymupdf`, moving the project toward a more open and compliant open-source direction.
+ - **Ready-to-use, Easy Configuration**: No need to manually edit JSON configuration files; most parameters can now be set directly via command line or API.
+ - **Automatic Model Management**: Added automatic model download and update mechanisms, allowing users to complete model deployment without manual intervention.
+ - **Offline Deployment Friendly**: Provides built-in model download commands, supporting deployment requirements in completely offline environments.
+ - **Streamlined Code Structure**: Removed thousands of lines of redundant code, simplified class inheritance logic, significantly improving code readability and development efficiency.
+ - **Unified Intermediate Format Output**: Adopted standardized `middle_json` format, compatible with most secondary development scenarios based on this format, ensuring seamless ecosystem business migration.
+ - **New Model**: MinerU 2.0 integrates our latest small-parameter, high-performance multimodal document parsing model, achieving end-to-end high-speed, high-precision document understanding.
+ - **Small Model, Big Capabilities**: With parameters under 1B, yet surpassing traditional 72B-level vision-language models (VLMs) in parsing accuracy.
+ - **Multiple Functions in One**: A single model covers multilingual recognition, handwriting recognition, layout analysis, table parsing, formula recognition, reading order sorting, and other core tasks.
+ - **Ultimate Inference Speed**: Achieves peak throughput exceeding 10,000 tokens/s through `sglang` acceleration on a single NVIDIA 4090 card, easily handling large-scale document processing requirements.
+ - **Online Experience**: You can experience this model online on our Hugging Face demo: [](https://huggingface.co/spaces/opendatalab/mineru2)
+ - **Incompatible Changes Notice**: To improve overall architectural rationality and long-term maintainability, this version contains some incompatible changes:
+ - Python package name changed from `magic-pdf` to `mineru`, and the command-line tool changed from `magic-pdf` to `mineru`. Please update your scripts and command calls accordingly.
+ - For modular system design and ecosystem consistency considerations, MinerU 2.0 no longer includes the LibreOffice document conversion module. If you need to process Office documents, we recommend converting them to PDF format through an independently deployed LibreOffice service before proceeding with subsequent parsing operations.
-2025/01/10 1.0.1 released
-This is our first official release, where we have introduced a completely new API interface and enhanced compatibility through extensive refactoring, as well as a brand new automatic language identification feature:
-
- - New API Interface
-
- - For the data-side API, we have introduced the Dataset class, designed to provide a robust and flexible data processing framework. This framework currently supports a variety of document formats, including images (.jpg and .png), PDFs, Word documents (.doc and .docx), and PowerPoint presentations (.ppt and .pptx). It ensures effective support for data processing tasks ranging from simple to complex.
- - For the user-side API, we have meticulously designed the MinerU processing workflow as a series of composable Stages. Each Stage represents a specific processing step, allowing users to define new Stages according to their needs and creatively combine these stages to customize their data processing workflows.
-
-
- - Enhanced Compatibility
-
- - By optimizing the dependency environment and configuration items, we ensure stable and efficient operation on ARM architecture Linux systems.
- - We have deeply integrated with Huawei Ascend NPU acceleration, providing autonomous and controllable high-performance computing capabilities. This supports the localization and development of AI application platforms in China. Ascend NPU Acceleration
-
-
- - Automatic Language Identification
-
- - By introducing a new language recognition model, setting the
lang configuration to auto during document parsing will automatically select the appropriate OCR language model, improving the accuracy of scanned document parsing.
-
-
-
-
-
-
-2024/11/22 0.10.0 released
-Introducing hybrid OCR text extraction capabilities:
-
- - Significantly improved parsing performance in complex text distribution scenarios such as dense formulas, irregular span regions, and text represented by images.
- - Combines the dual advantages of accurate content extraction and faster speed in text mode, and more precise span/line region recognition in OCR mode.
-
-
-
-
-2024/11/15 0.9.3 released
-Integrated RapidTable for table recognition, improving single-table parsing speed by more than 10 times, with higher accuracy and lower GPU memory usage.
-
-
-
-2024/11/06 0.9.2 released
-Integrated the StructTable-InternVL2-1B model for table recognition functionality.
-
-
-
-2024/10/31 0.9.0 released
-This is a major new version with extensive code refactoring, addressing numerous issues, improving performance, reducing hardware requirements, and enhancing usability:
-
- - Refactored the sorting module code to use layoutreader for reading order sorting, ensuring high accuracy in various layouts.
- - Refactored the paragraph concatenation module to achieve good results in cross-column, cross-page, cross-figure, and cross-table scenarios.
- - Refactored the list and table of contents recognition functions, significantly improving the accuracy of list blocks and table of contents blocks, as well as the parsing of corresponding text paragraphs.
- - Refactored the matching logic for figures, tables, and descriptive text, greatly enhancing the accuracy of matching captions and footnotes to figures and tables, and reducing the loss rate of descriptive text to near zero.
- - Added multi-language support for OCR, supporting detection and recognition of 84 languages. For the list of supported languages, see OCR Language Support List.
- - Added memory recycling logic and other memory optimization measures, significantly reducing memory usage. The memory requirement for enabling all acceleration features except table acceleration (layout/formula/OCR) has been reduced from 16GB to 8GB, and the memory requirement for enabling all acceleration features has been reduced from 24GB to 10GB.
- - Optimized configuration file feature switches, adding an independent formula detection switch to significantly improve speed and parsing results when formula detection is not needed.
- - Integrated PDF-Extract-Kit 1.0:
-
- - Added the self-developed
doclayout_yolo model, which speeds up processing by more than 10 times compared to the original solution while maintaining similar parsing effects, and can be freely switched with layoutlmv3 via the configuration file.
- - Upgraded formula parsing to
unimernet 0.2.1, improving formula parsing accuracy while significantly reducing memory usage.
- - Due to the repository change for
PDF-Extract-Kit 1.0, you need to re-download the model. Please refer to How to Download Models for detailed steps.
-
-
-
-
-
-
-2024/09/27 Version 0.8.1 released
-Fixed some bugs, and providing a localized deployment version of the online demo and the front-end interface.
-
-
-
-2024/09/09 Version 0.8.0 released
-Supporting fast deployment with Dockerfile, and launching demos on Huggingface and Modelscope.
-
-
-
-2024/08/30 Version 0.7.1 released
-Add paddle tablemaster table recognition option
-
-
-
-2024/08/09 Version 0.7.0b1 released
-Simplified installation process, added table recognition functionality
-
-
-
-2024/08/01 Version 0.6.2b1 released
-Optimized dependency conflict issues and installation documentation
-
-
-
-2024/07/05 Initial open-source release
-
-
-
-
-
- Table of Contents
-
- -
- MinerU
-
- - Project Introduction
- - Key Features
- - Quick Start
+
History Log
+
+ 2025/05/24 Release 1.3.12
+
+ - Added support for PPOCRv5 models, updated
ch_server model to PP-OCRv5_rec_server, and ch_lite model to PP-OCRv5_rec_mobile (model update required)
+
+ - In testing, we found that PPOCRv5(server) has some improvement for handwritten documents, but has slightly lower accuracy than v4_server_doc for other document types, so the default ch model remains unchanged as
PP-OCRv4_server_rec_doc.
+ - Since PPOCRv5 has enhanced recognition capabilities for handwriting and special characters, you can manually choose the PPOCRv5 model for Japanese-Traditional Chinese mixed scenarios and handwritten documents
+ - You can select the appropriate model through the lang parameter
lang='ch_server' (Python API) or --lang ch_server (command line):
- - Online Demo
- - Quick CPU Demo
- - Using GPU
- - Using NPU
+ ch: PP-OCRv4_server_rec_doc (default) (Chinese/English/Japanese/Traditional Chinese mixed/15K dictionary)
+ ch_server: PP-OCRv5_rec_server (Chinese/English/Japanese/Traditional Chinese mixed + handwriting/18K dictionary)
+ ch_lite: PP-OCRv5_rec_mobile (Chinese/English/Japanese/Traditional Chinese mixed + handwriting/18K dictionary)
+ ch_server_v4: PP-OCRv4_rec_server (Chinese/English mixed/6K dictionary)
+ ch_lite_v4: PP-OCRv4_rec_mobile (Chinese/English mixed/6K dictionary)
-
- - Usage
-
-
+
+
+
+ - Added support for handwritten documents through optimized layout recognition of handwritten text areas
+
+ - This feature is supported by default, no additional configuration required
+ - You can refer to the instructions above to manually select the PPOCRv5 model for better handwritten document parsing results
+
+
+ - The
huggingface and modelscope demos have been updated to versions that support handwriting recognition and PPOCRv5 models, which you can experience online
+
+
+
+
+ 2025/04/29 Release 1.3.10
+
+ - Added support for custom formula delimiters, which can be configured by modifying the
latex-delimiter-config section in the magic-pdf.json file in your user directory.
+
+
+
+
+ 2025/04/27 Release 1.3.9
+
+ - Optimized formula parsing functionality, improved formula rendering success rate
+
+
+
+
+ 2025/04/23 Release 1.3.8
+
+ - The default
ocr model (ch) has been updated to PP-OCRv4_server_rec_doc (model update required)
+
+ PP-OCRv4_server_rec_doc is trained on a mixture of more Chinese document data and PP-OCR training data based on PP-OCRv4_server_rec, adding recognition capabilities for some traditional Chinese characters, Japanese, and special characters. It can recognize over 15,000 characters and improves both document-specific and general text recognition abilities.
+ - Performance comparison of PP-OCRv4_server_rec_doc/PP-OCRv4_server_rec/PP-OCRv4_mobile_rec
+ - After verification, the
PP-OCRv4_server_rec_doc model shows significant accuracy improvements in Chinese/English/Japanese/Traditional Chinese in both single language and mixed language scenarios, with comparable speed to PP-OCRv4_server_rec, making it suitable for most use cases.
+ - In some pure English scenarios,
PP-OCRv4_server_rec_doc may have word adhesion issues, while PP-OCRv4_server_rec performs better in these cases. Therefore, we've kept the PP-OCRv4_server_rec model, which users can access by adding the parameter lang='ch_server' (Python API) or --lang ch_server (command line).
+
+
+
+
+
+
+ 2025/04/22 Release 1.3.7
+
+ - Fixed the issue where the lang parameter was ineffective during table parsing model initialization
+ - Fixed the significant speed reduction of OCR and table parsing in
cpu mode
+
+
+
+
+ 2025/04/16 Release 1.3.4
+
+ - Slightly improved OCR-det speed by removing some unnecessary blocks
+ - Fixed page-internal sorting errors caused by footnotes in certain cases
+
+
+
+
+ 2025/04/12 Release 1.3.2
+
+ - Fixed dependency version incompatibility issues when installing on Windows with Python 3.13
+ - Optimized memory usage during batch inference
+ - Improved parsing of tables rotated 90 degrees
+ - Enhanced parsing of oversized tables in financial report samples
+ - Fixed the occasional word adhesion issue in English text areas when OCR language is not specified (model update required)
+
+
+
+
+ 2025/04/08 Release 1.3.1
+
+ - Fixed several compatibility issues
+
+ - Added support for Python 3.13
+ - Made final adaptations for outdated Linux systems (such as CentOS 7) with no guarantee of continued support in future versions, installation instructions
+
+
+
+
+
+
+ 2025/04/03 Release 1.3.0
+
+ - Installation and compatibility optimizations
+
+ - Resolved compatibility issues caused by
detectron2 by removing layoutlmv3 usage in layout
+ - Extended torch version compatibility to 2.2~2.6 (excluding 2.5)
+ - Added CUDA compatibility for versions 11.8/12.4/12.6/12.8 (CUDA version determined by torch), solving compatibility issues for users with 50-series and H-series GPUs
+ - Extended Python compatibility to versions 3.10~3.12, fixing the issue of automatic downgrade to version 0.6.1 when installing in non-3.10 environments
+ - Optimized offline deployment process, eliminating the need to download any model files after successful deployment
+
+
+ - Performance optimizations
+
+ - Enhanced parsing speed for batches of small files by supporting batch processing of multiple PDF files (script example), with formula parsing speed improved by up to 1400% and overall parsing speed improved by up to 500% compared to version 1.0.1
+ - Reduced memory usage and improved parsing speed by optimizing MFR model loading and usage (requires re-running the model download process to get incremental updates to model files)
+ - Optimized GPU memory usage, requiring only 6GB minimum to run this project
+ - Improved running speed on MPS devices
+
+
+ - Parsing effect optimizations
+
+ - Updated MFR model to
unimernet(2503), fixing line break loss issues in multi-line formulas
+
+
+ - Usability optimizations
+
+ - Completely replaced the
paddle framework and paddleocr in the project by using paddleocr2torch, resolving conflicts between paddle and torch, as well as thread safety issues caused by the paddle framework
+ - Added real-time progress bar display during parsing, allowing precise tracking of parsing progress and making the waiting process more bearable
+
+
+
+
+
+ 2025/03/03 1.2.1 released
+
+ - Fixed the impact on punctuation marks during full-width to half-width conversion of letters and numbers
+ - Fixed caption matching inaccuracies in certain scenarios
+ - Fixed formula span loss issues in certain scenarios
+
+
+
+
+ 2025/02/24 1.2.0 released
+ This version includes several fixes and improvements to enhance parsing efficiency and accuracy:
+
+ - Performance Optimization
+
+ - Increased classification speed for PDF documents in auto mode.
+
+
+ - Parsing Optimization
+
+ - Improved parsing logic for documents containing watermarks, significantly enhancing the parsing results for such documents.
+ - Enhanced the matching logic for multiple images/tables and captions within a single page, improving the accuracy of image-text matching in complex layouts.
+
+
+ - Bug Fixes
+
+ - Fixed an issue where image/table spans were incorrectly filled into text blocks under certain conditions.
+ - Resolved an issue where title blocks were empty in some cases.
- - TODO
- - Known Issues
- - FAQ
- - All Thanks To Our Contributors
- - License Information
- - Acknowledgments
- - Citation
- - Star History
- - Magic-doc
- - Magic-html
- - Links
-
+
+
+
+
+ 2025/01/22 1.1.0 released
+ In this version we have focused on improving parsing accuracy and efficiency:
+
+ - Model capability upgrade (requires re-executing the model download process to obtain incremental updates of model files)
+
+ - The layout recognition model has been upgraded to the latest
doclayout_yolo(2501) model, improving layout recognition accuracy.
+ - The formula parsing model has been upgraded to the latest
unimernet(2501) model, improving formula recognition accuracy.
+
+
+ - Performance optimization
+
+ - On devices that meet certain configuration requirements (16GB+ VRAM), by optimizing resource usage and restructuring the processing pipeline, overall parsing speed has been increased by more than 50%.
+
+
+ - Parsing effect optimization
+
+ - Added a new heading classification feature (testing version, enabled by default) to the online demo (mineru.net/huggingface/modelscope), which supports hierarchical classification of headings, thereby enhancing document structuring.
+
+
+
+
+
+
+ 2025/01/10 1.0.1 released
+ This is our first official release, where we have introduced a completely new API interface and enhanced compatibility through extensive refactoring, as well as a brand new automatic language identification feature:
+
+ - New API Interface
+
+ - For the data-side API, we have introduced the Dataset class, designed to provide a robust and flexible data processing framework. This framework currently supports a variety of document formats, including images (.jpg and .png), PDFs, Word documents (.doc and .docx), and PowerPoint presentations (.ppt and .pptx). It ensures effective support for data processing tasks ranging from simple to complex.
+ - For the user-side API, we have meticulously designed the MinerU processing workflow as a series of composable Stages. Each Stage represents a specific processing step, allowing users to define new Stages according to their needs and creatively combine these stages to customize their data processing workflows.
+
+
+ - Enhanced Compatibility
+
+ - By optimizing the dependency environment and configuration items, we ensure stable and efficient operation on ARM architecture Linux systems.
+ - We have deeply integrated with Huawei Ascend NPU acceleration, providing autonomous and controllable high-performance computing capabilities. This supports the localization and development of AI application platforms in China. Ascend NPU Acceleration
+
+
+ - Automatic Language Identification
+
+ - By introducing a new language recognition model, setting the
lang configuration to auto during document parsing will automatically select the appropriate OCR language model, improving the accuracy of scanned document parsing.
+
+
+
+
+
+
+ 2024/11/22 0.10.0 released
+ Introducing hybrid OCR text extraction capabilities:
+
+ - Significantly improved parsing performance in complex text distribution scenarios such as dense formulas, irregular span regions, and text represented by images.
+ - Combines the dual advantages of accurate content extraction and faster speed in text mode, and more precise span/line region recognition in OCR mode.
+
+
+
+
+ 2024/11/15 0.9.3 released
+ Integrated RapidTable for table recognition, improving single-table parsing speed by more than 10 times, with higher accuracy and lower GPU memory usage.
+
+
+
+ 2024/11/06 0.9.2 released
+ Integrated the StructTable-InternVL2-1B model for table recognition functionality.
+
+
+
+ 2024/10/31 0.9.0 released
+ This is a major new version with extensive code refactoring, addressing numerous issues, improving performance, reducing hardware requirements, and enhancing usability:
+
+ - Refactored the sorting module code to use layoutreader for reading order sorting, ensuring high accuracy in various layouts.
+ - Refactored the paragraph concatenation module to achieve good results in cross-column, cross-page, cross-figure, and cross-table scenarios.
+ - Refactored the list and table of contents recognition functions, significantly improving the accuracy of list blocks and table of contents blocks, as well as the parsing of corresponding text paragraphs.
+ - Refactored the matching logic for figures, tables, and descriptive text, greatly enhancing the accuracy of matching captions and footnotes to figures and tables, and reducing the loss rate of descriptive text to near zero.
+ - Added multi-language support for OCR, supporting detection and recognition of 84 languages. For the list of supported languages, see OCR Language Support List.
+ - Added memory recycling logic and other memory optimization measures, significantly reducing memory usage. The memory requirement for enabling all acceleration features except table acceleration (layout/formula/OCR) has been reduced from 16GB to 8GB, and the memory requirement for enabling all acceleration features has been reduced from 24GB to 10GB.
+ - Optimized configuration file feature switches, adding an independent formula detection switch to significantly improve speed and parsing results when formula detection is not needed.
+ - Integrated PDF-Extract-Kit 1.0:
+
+ - Added the self-developed
doclayout_yolo model, which speeds up processing by more than 10 times compared to the original solution while maintaining similar parsing effects, and can be freely switched with layoutlmv3 via the configuration file.
+ - Upgraded formula parsing to
unimernet 0.2.1, improving formula parsing accuracy while significantly reducing memory usage.
+ - Due to the repository change for
PDF-Extract-Kit 1.0, you need to re-download the model. Please refer to How to Download Models for detailed steps.
+
+
+
+
+
+
+ 2024/09/27 Version 0.8.1 released
+ Fixed some bugs, and providing a localized deployment version of the online demo and the front-end interface.
+
+
+
+ 2024/09/09 Version 0.8.0 released
+ Supporting fast deployment with Dockerfile, and launching demos on Huggingface and Modelscope.
+
+
+
+ 2024/08/30 Version 0.7.1 released
+ Add paddle tablemaster table recognition option
+
+
+
+ 2024/08/09 Version 0.7.0b1 released
+ Simplified installation process, added table recognition functionality
+
+
+
+ 2024/08/01 Version 0.6.2b1 released
+ Optimized dependency conflict issues and installation documentation
+
+
+
+ 2024/07/05 Initial open-source release
+
+
+
+
+
+ Table of Contents
+
+ -
+ MinerU
+
+
+ - TODO
+ - Known Issues
+ - FAQ
+ - All Thanks To Our Contributors
+ - License Information
+ - Acknowledgments
+ - Citation
+ - Star History
+ - Magic-doc
+ - Magic-html
+ - Links
+
+
# MinerU
@@ -326,12 +422,9 @@ If you encounter any installation issues, please first consult the Known Issues.
There are three different ways to experience MinerU:
-- [Online Demo (No Installation Required)](#online-demo)
-- [Quick CPU Demo (Windows, Linux, Mac)](#quick-cpu-demo)
-- Accelerate inference by using CUDA/CANN/MPS
- - [Linux/Windows + CUDA](#Using-GPU)
- - [Linux + CANN](#using-npu)
- - [MacOS + MPS](#using-mps)
+- [Online Demo](#online-demo)
+- [Local Deployment](#local-deployment)
+
> [!WARNING]
> **Pre-installation Notice—Hardware and Software Environment Support**
@@ -342,182 +435,235 @@ There are three different ways to experience MinerU:
>
> In non-mainline environments, due to the diversity of hardware and software configurations, as well as third-party dependency compatibility issues, we cannot guarantee 100% project availability. Therefore, for users who wish to use this project in non-recommended environments, we suggest carefully reading the documentation and FAQ first. Most issues already have corresponding solutions in the FAQ. We also encourage community feedback to help us gradually expand support.
-
-
- | Operating System |
-
+
- | Linux after 2019 |
- Windows 10 / 11 |
- macOS 11+ |
+ Parsing Backend |
+ pipeline |
+ vlm-transformers |
+ vlm-sgslang |
- | CPU |
- x86_64 / arm64 |
- x86_64(unsupported ARM Windows) |
- x86_64 / arm64 |
+ Operating System |
+ windows/linux/mac |
+ windows/linux |
+ windows(wsl2)/linux |
- | Memory Requirements |
- 16GB or more, recommended 32GB+ |
+ Memory Requirements |
+ Minimum 16GB+, 32GB+ recommended |
- | Storage Requirements |
- 20GB or more, with a preference for SSD |
+ Disk Space Requirements |
+ 20GB+, SSD recommended |
- | Python Version |
- 3.10~3.13 |
+ Python Version |
+ 3.10-3.13 |
- | Nvidia Driver Version |
- latest (Proprietary Driver) |
- latest |
- None |
+ CPU Inference Support |
+ ✅ |
+ ❌ |
+ ❌ |
- | CUDA Environment |
- Refer to the PyTorch official website |
- None |
-
-
- | CANN Environment(NPU support) |
- 8.0+(Ascend 910b) |
- None |
- None |
-
-
- | GPU/MPS Hardware Support List |
- GPU VRAM 6GB or more |
- All GPUs with Tensor Cores produced from Volta(2017) onwards.
- More than 6GB VRAM |
- Apple silicon |
+ GPU Requirements |
+ Turing architecture or later, 6GB+ VRAM or Apple Silicon |
+ Ampere architecture or later, 8GB+ VRAM |
+ Ampere architecture or later, 24GB+ VRAM |
-### Online Demo
-
-Synced with dev branch updates:
+## Online Demo
[](https://mineru.net/OpenSourceTools/Extractor?source=github)
[](https://huggingface.co/spaces/opendatalab/MinerU)
[](https://www.modelscope.cn/studios/OpenDataLab/MinerU)
-### Quick CPU Demo
+### 🚀🚀🚀VLM demo
+[](https://huggingface.co/spaces/opendatalab/mineru2)
+
+## Local Deployment
+
+### 1. Install MinerU
+
+#### 1.1 Install via pip or uv
+
+```bash
+pip install --upgrade pip
+pip install uv
+uv pip install "mineru[core]>=2.0.0"
+```
-#### 1. Install magic-pdf
+#### 1.2 Install from source
```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-pip install -U "magic-pdf[full]"
+git clone https://github.com/opendatalab/MinerU.git
+cd MinerU
+uv pip install -e .[core]
```
-#### 2. Download model weight files
+#### 1.3 Install full version (with sglang acceleration)
-Refer to [How to Download Model Files](docs/how_to_download_models_en.md) for detailed instructions.
+To use **sglang acceleration for VLM model inference**, install the full version:
-#### 3. Modify the Configuration File for Additional Configuration
+```bash
+uv pip install "mineru[all]>=2.0.0"
+```
-After completing the [2. Download model weight files](#2-download-model-weight-files) step, the script will automatically generate a `magic-pdf.json` file in the user directory and configure the default model path.
-You can find the `magic-pdf.json` file in your 【user directory】.
+Or install from source:
-> [!TIP]
-> The user directory for Windows is "C:\\Users\\username", for Linux it is "/home/username", and for macOS it is "/Users/username".
+```bash
+uv pip install -e .[all]
+```
-You can modify certain configurations in this file to enable or disable features, such as table recognition:
+---
+### 2. Using MinerU
-> [!NOTE]
-> If the following items are not present in the JSON, please manually add the required items and remove the comment content (standard JSON does not support comments).
+#### 2.1 Command Line Usage
-```json
-{
- // other config
- "layout-config": {
- "model": "doclayout_yolo"
- },
- "formula-config": {
- "mfd_model": "yolo_v8_mfd",
- "mfr_model": "unimernet_small",
- "enable": true // The formula recognition feature is enabled by default. If you need to disable it, please change the value here to "false".
- },
- "table-config": {
- "model": "rapid_table",
- "sub_model": "slanet_plus",
- "enable": true, // The table recognition feature is enabled by default. If you need to disable it, please change the value here to "false".
- "max_time": 400
- }
-}
+##### Basic Usage
+
+The simplest command line invocation is:
+
+```bash
+mineru -p -o
```
-### Using GPU
+- ``: Local PDF file or directory (supports pdf/png/jpg/jpeg)
+- ``: Output directory
-If your device supports CUDA and meets the GPU requirements of the mainline environment, you can use GPU acceleration. Please select the appropriate guide based on your system:
+##### View Help Information
-- [Ubuntu 22.04 LTS + GPU](docs/README_Ubuntu_CUDA_Acceleration_en_US.md)
-- [Windows 10/11 + GPU](docs/README_Windows_CUDA_Acceleration_en_US.md)
-- Quick Deployment with Docker
-> [!IMPORTANT]
-> Docker requires a GPU with at least 6GB of VRAM, and all acceleration features are enabled by default.
->
-> Before running this Docker, you can use the following command to check if your device supports CUDA acceleration on Docker.
->
-> ```bash
-> docker run --rm --gpus=all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi
-> ```
- ```bash
- wget https://github.com/opendatalab/MinerU/raw/master/docker/global/Dockerfile -O Dockerfile
- docker build -t mineru:latest .
- docker run -it --name mineru --gpus=all mineru:latest /bin/bash -c "echo 'source /opt/mineru_venv/bin/activate' >> ~/.bashrc && exec bash"
- magic-pdf --help
- ```
+Get all available parameter descriptions:
-### Using NPU
+```bash
+mineru --help
+```
-If your device has NPU acceleration hardware, you can follow the tutorial below to use NPU acceleration:
+##### Parameter Details
+
+```text
+Usage: mineru [OPTIONS]
+
+Options:
+ -v, --version Show version and exit
+ -p, --path PATH Input file path or directory (required)
+ -o, --output PATH Output directory (required)
+ -m, --method [auto|txt|ocr] Parsing method: auto (default), txt, ocr (pipeline backend only)
+ -b, --backend [pipeline|vlm-transformers|vlm-sglang-engine|vlm-sglang-client]
+ Parsing backend (default: pipeline)
+ -l, --lang [ch|ch_server|... ] Specify document language (improves OCR accuracy, pipeline backend only)
+ -u, --url TEXT Service address when using sglang-client
+ -s, --start INTEGER Starting page number (0-based)
+ -e, --end INTEGER Ending page number (0-based)
+ -f, --formula BOOLEAN Enable formula parsing (default: on, pipeline backend only)
+ -t, --table BOOLEAN Enable table parsing (default: on, pipeline backend only)
+ -d, --device TEXT Inference device (e.g., cpu/cuda/cuda:0/npu/mps, pipeline backend only)
+ --vram INTEGER Maximum GPU VRAM usage per process (pipeline backend only)
+ --source [huggingface|modelscope|local]
+ Model source, default: huggingface
+ --help Show help information
+```
-[Ascend NPU Acceleration](docs/README_Ascend_NPU_Acceleration_zh_CN.md)
+---
-### Using MPS
+#### 2.2 Model Source Configuration
-If your device uses Apple silicon chips, you can enable MPS acceleration for your tasks.
+MinerU automatically downloads required models from HuggingFace on first run. If HuggingFace is inaccessible, you can switch model sources:
-You can enable MPS acceleration by setting the `device-mode` parameter to `mps` in the `magic-pdf.json` configuration file.
+##### Switch to ModelScope Source
-```json
-{
- // other config
- "device-mode": "mps"
-}
+```bash
+mineru -p -o --source modelscope
+```
+
+Or set environment variable:
+
+```bash
+export MINERU_MODEL_SOURCE=modelscope
+mineru -p -o
+```
+
+##### Using Local Models
+
+###### 1. Download Models Locally
+
+```bash
+mineru-models-download --help
+```
+
+Or use interactive command-line tool to select models:
+
+```bash
+mineru-models-download
+```
+
+After download, model paths will be displayed in current terminal and automatically written to `mineru.json` in user directory.
+
+###### 2. Parse Using Local Models
+
+```bash
+mineru -p -o --source local
```
+Or enable via environment variable:
-## Usage
+```bash
+export MINERU_MODEL_SOURCE=local
+mineru -p -o
+```
+
+---
+
+#### 2.3 Using sglang to Accelerate VLM Model Inference
+
+##### Start sglang-engine Mode
+
+```bash
+mineru -p -o -b vlm-sglang-engine
+```
+
+##### Start sglang-server/client Mode
+
+1. Start Server:
+
+```bash
+mineru-sglang-server --port 30000
+```
+
+2. Use Client in another terminal:
+
+```bash
+mineru -p -o -b vlm-sglang-client -u http://127.0.0.1:30000
+```
-### Command Line
+> 💡 For more information about output files, please refer to [Output File Documentation](docs/output_file_en_us.md)
-[Using MinerU via Command Line](https://mineru.readthedocs.io/en/latest/user_guide/usage/command_line.html)
+---
-> [!TIP]
-> For more information about the output files, please refer to the [Output File Description](docs/output_file_en_us.md).
+### 3. API Usage
-### API
+You can also call MinerU through Python code, see example code at:
+👉 [Python Usage Example](demo/demo.py)
-[Using MinerU via Python API](https://mineru.readthedocs.io/en/latest/user_guide/usage/api.html)
+---
+### 4. Deploy Derivative Projects
-### Deploy Derived Projects
+Community developers have created various extensions based on MinerU, including:
-Derived projects include secondary development projects based on MinerU by project developers and community developers,
-such as application interfaces based on Gradio, RAG based on llama, web demos similar to the official website, lightweight multi-GPU load balancing client/server ends, etc.
-These projects may offer more features and a better user experience.
-For specific deployment methods, please refer to the [Derived Project README](projects/README.md)
+- Graphical interface based on Gradio
+- Web API based on FastAPI
+- Client/server architecture with multi-GPU load balancing, etc.
+These projects typically offer better user experience and additional features.
-### Development Guide
+For detailed deployment instructions, please refer to:
+👉 [Derivative Projects Documentation](projects/README.md)
-TODO
+---
# TODO
@@ -556,21 +702,22 @@ TODO
[LICENSE.md](LICENSE.md)
-This project currently uses PyMuPDF to achieve advanced functionality. However, since it adheres to the AGPL license, it may impose restrictions on certain usage scenarios. In future iterations, we plan to explore and replace it with a more permissive PDF processing library to enhance user-friendliness and flexibility.
+Currently, some models in this project are trained based on YOLO. However, since YOLO follows the AGPL license, it may impose restrictions on certain use cases. In future iterations, we plan to explore and replace these with models under more permissive licenses to enhance user-friendliness and flexibility.
# Acknowledgments
- [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit)
- [DocLayout-YOLO](https://github.com/opendatalab/DocLayout-YOLO)
-- [StructEqTable](https://github.com/UniModal4Reasoning/StructEqTable-Deploy)
+- [UniMERNet](https://github.com/opendatalab/UniMERNet)
- [RapidTable](https://github.com/RapidAI/RapidTable)
- [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)
-- [RapidOCR](https://github.com/RapidAI/RapidOCR)
- [PaddleOCR2Pytorch](https://github.com/frotms/PaddleOCR2Pytorch)
-- [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
- [layoutreader](https://github.com/ppaanngggg/layoutreader)
+- [xy-cut](https://github.com/Sanster/xy-cut)
- [fast-langdetect](https://github.com/LlmKira/fast-langdetect)
+- [pypdfium2](https://github.com/pypdfium2-team/pypdfium2)
- [pdfminer.six](https://github.com/pdfminer/pdfminer.six)
+- [pypdf](https://github.com/py-pdf/pypdf)
# Citation
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 69512ae20ff33d67233beeba345c60551c9103cf..9826aa6343ee090d6f409fa7d2cdbb61c33c58ba 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -10,14 +10,17 @@
[](https://github.com/opendatalab/MinerU)
[](https://github.com/opendatalab/MinerU/issues)
[](https://github.com/opendatalab/MinerU/issues)
-[](https://pypi.org/project/magic-pdf/)
-[](https://pypi.org/project/magic-pdf/)
-[](https://pepy.tech/project/magic-pdf)
-[](https://pepy.tech/project/magic-pdf)
+
+[](https://pypi.org/project/mineru/)
+[](https://pypi.org/project/mineru/)
+[](https://pepy.tech/project/mineru)
+[](https://pepy.tech/project/mineru)
[](https://mineru.net/OpenSourceTools/Extractor?source=github)
[](https://www.modelscope.cn/studios/OpenDataLab/MinerU)
[](https://huggingface.co/spaces/opendatalab/MinerU)
+
+[](https://huggingface.co/spaces/opendatalab/mineru2)
[](https://colab.research.google.com/gist/myhloli/3b3a00a4a0a61577b6c30f989092d20d/mineru_demo.ipynb)
[](https://arxiv.org/abs/2409.18839)
@@ -47,241 +50,337 @@
# 更新记录
-- 2025/05/24 1.3.12 发布
- - 增加ppocrv5模型的支持,将`ch_server`模型更新为`PP-OCRv5_rec_server`,`ch_lite`模型更新为`PP-OCRv5_rec_mobile`(需更新模型)
- - 在测试中,发现ppocrv5(server)对手写文档效果有一定提升,但在其余类别文档的精度略差于v4_server_doc,因此默认的ch模型保持不变,仍为`PP-OCRv4_server_rec_doc`。
- - 由于ppocrv5强化了手写场景和特殊字符的识别能力,因此您可以在日繁混合场景以及手写文档场景下手动选择使用ppocrv5模型
- - 您可通过lang参数`lang='ch_server'`(python api)或`--lang ch_server`(命令行)自行选择相应的模型:
- - `ch` :`PP-OCRv4_rec_server_doc`(默认)(中英日繁混合/1.5w字典)
- - `ch_server` :`PP-OCRv5_rec_server`(中英日繁混合+手写场景/1.8w字典)
- - `ch_lite` :`PP-OCRv5_rec_mobile`(中英日繁混合+手写场景/1.8w字典)
- - `ch_server_v4` :`PP-OCRv4_rec_server`(中英混合/6k字典)
- - `ch_lite_v4` :`PP-OCRv4_rec_mobile`(中英混合/6k字典)
- - 增加手写文档的支持,通过优化layout对手写文本区域的识别,现已支持手写文档的解析
- - 默认支持此功能,无需额外配置
- - 可以参考上述说明,手动选择ppocrv5模型以获得更好的手写文档解析效果
- - `huggingface`和`modelscope`的demo已更新为支持手写识别和ppocrv5模型的版本,可自行在线体验
-- 2025/04/29 1.3.10 发布
- - 支持使用自定义公式标识符,可通过修改用户目录下的`magic-pdf.json`文件中的`latex-delimiter-config`项实现。
-- 2025/04/27 1.3.9 发布
- - 优化公式解析功能,提升公式渲染的成功率
-- 2025/04/23 1.3.8 发布
- - `ocr`默认模型(`ch`)更新为`PP-OCRv4_server_rec_doc`(需更新模型)
- - `PP-OCRv4_server_rec_doc`是在`PP-OCRv4_server_rec`的基础上,在更多中文文档数据和PP-OCR训练数据的混合数据训练而成,增加了部分繁体字、日文、特殊字符的识别能力,可支持识别的字符为1.5万+,除文档相关的文字识别能力提升外,也同时提升了通用文字的识别能力。
- - [PP-OCRv4_server_rec_doc/PP-OCRv4_server_rec/PP-OCRv4_mobile_rec 性能对比](https://paddlepaddle.github.io/PaddleX/latest/module_usage/tutorials/ocr_modules/text_recognition.html#_3)
- - 经验证,`PP-OCRv4_server_rec_doc`模型在`中英日繁`单种语言或多种语言混合场景均有明显精度提升,且速度与`PP-OCRv4_server_rec`相当,适合绝大部分场景使用。
- - `PP-OCRv4_server_rec_doc`在小部分纯英文场景可能会发生单词粘连问题,`PP-OCRv4_server_rec`则在此场景下表现更好,因此我们保留了`PP-OCRv4_server_rec`模型,用户可通过增加参数`lang='ch_server'`(python api)或`--lang ch_server`(命令行)调用。
-- 2025/04/22 1.3.7 发布
- - 修复表格解析模型初始化时lang参数失效的问题
- - 修复在`cpu`模式下ocr和表格解析速度大幅下降的问题
-- 2025/04/16 1.3.4 发布
- - 通过移除一些无用的块,小幅提升了ocr-det的速度
- - 修复部分情况下由footnote导致的页面内排序错误
-- 2025/04/12 1.3.2 发布
- - 修复了windows系统下,在python3.13环境安装时一些依赖包版本不兼容的问题
- - 优化批量推理时的内存占用
- - 优化旋转90度表格的解析效果
- - 优化财报样本中超大表格的解析效果
- - 修复了在未指定OCR语言时,英文文本区域偶尔出现的单词黏连问题(需要更新模型)
-- 2025/04/08 1.3.1 发布,修复了一些兼容问题
- - 支持python 3.13
- - 为部分过时的linux系统(如centos7)做出最后适配,并不再保证后续版本的继续支持,[安装说明](https://github.com/opendatalab/MinerU/issues/1004)
-- 2025/04/03 1.3.0 发布,在这个版本我们做出了许多优化和改进:
- - 安装与兼容性优化
- - 通过移除layout中`layoutlmv3`的使用,解决了由`detectron2`导致的兼容问题
- - torch版本兼容扩展到2.2~2.6(2.5除外)
- - cuda兼容支持11.8/12.4/12.6/12.8(cuda版本由torch决定),解决部分用户50系显卡与H系显卡的兼容问题
- - python兼容版本扩展到3.10~3.12,解决了在非3.10环境下安装时自动降级到0.6.1的问题
- - 优化离线部署流程,部署成功后不需要联网下载任何模型文件
- - 性能优化
- - 通过支持多个pdf文件的batch处理([脚本样例](demo/batch_demo.py)),提升了批量小文件的解析速度 (与1.0.1版本相比,公式解析速度最高提升超过1400%,整体解析速度最高提升超过500%)
- - 通过优化mfr模型的加载和使用,降低了显存占用并提升了解析速度(需重新执行[模型下载流程](docs/how_to_download_models_zh_cn.md)以获得模型文件的增量更新)
- - 优化显存占用,最低仅需6GB即可运行本项目
- - 优化了在mps设备上的运行速度
- - 解析效果优化
- - mfr模型更新到`unimernet(2503)`,解决多行公式中换行丢失的问题
- - 易用性优化
- - 通过使用`paddleocr2torch`,完全替代`paddle`框架以及`paddleocr`在项目中的使用,解决了`paddle`和`torch`的冲突问题,和由于`paddle`框架导致的线程不安全问题
- - 解析过程增加实时进度条显示,精准把握解析进度,让等待不再痛苦
-
-2025/03/03 1.2.1 发布,修复了一些问题
-
- - 修复在字母与数字的全角转半角操作时对标点符号的影响
- - 修复在某些情况下caption的匹配不准确问题
- - 修复在某些情况下的公式span丢失问题
-
-
+- 2025/06/13 2.0.0发布
+ - MinerU 2.0 是一次从架构到功能的全面重构与升级,带来了更简洁的设计、更强的性能以及更灵活的使用体验。
+ - **全新架构**:MinerU 2.0 在代码结构和交互方式上进行了深度重构,显著提升了系统的易用性、可维护性与扩展能力。
+ - **去除第三方依赖限制**:彻底移除对 `pymupdf` 的依赖,推动项目向更开放、合规的开源方向迈进。
+ - **开箱即用,配置便捷**:无需手动编辑 JSON 配置文件,绝大多数参数已支持命令行或 API 直接设置。
+ - **模型自动管理**:新增模型自动下载与更新机制,用户无需手动干预即可完成模型部署。
+ - **离线部署友好**:提供内置模型下载命令,支持完全断网环境下的部署需求。
+ - **代码结构精简**:移除数千行冗余代码,简化类继承逻辑,显著提升代码可读性与开发效率。
+ - **统一中间格式输出**:采用标准化的 `middle_json` 格式,兼容多数基于该格式的二次开发场景,确保生态业务无缝迁移。
+ - **全新模型**:MinerU 2.0 集成了我们最新研发的小参数量、高性能多模态文档解析模型,实现端到端的高速、高精度文档理解。
+ - **小模型,大能力**:模型参数不足 1B,却在解析精度上超越传统 72B 级别的视觉语言模型(VLM)。
+ - **多功能合一**:单模型覆盖多语言识别、手写识别、版面分析、表格解析、公式识别、阅读顺序排序等核心任务。
+ - **极致推理速度**:在单卡 NVIDIA 4090 上通过 `sglang` 加速,达到峰值吞吐量超过 10,000 token/s,轻松应对大规模文档处理需求。
+ - **在线体验**:您可在我们的huggingface demo上在线体验该模型:[](https://huggingface.co/spaces/opendatalab/mineru2)
+ - **不兼容变更说明**:为提升整体架构合理性与长期可维护性,本版本包含部分不兼容的变更:
+ - Python 包名从 `magic-pdf` 更改为 `mineru`,命令行工具也由 `magic-pdf` 改为 `mineru`,请同步更新脚本与调用命令。
+ - 出于对系统模块化设计与生态一致性的考虑,MinerU 2.0 已不再内置 LibreOffice 文档转换模块。如需处理 Office 文档,建议通过独立部署的 LibreOffice 服务先行转换为 PDF 格式,再进行后续解析操作。
+
-2025/02/24 1.2.0 发布,这个版本我们修复了一些问题,提升了解析的效率与精度:
-
- - 性能优化
+
历史日志
+
+ 2025/05/24 1.3.12 发布
+
+ - 增加ppocrv5模型的支持,将
ch_server模型更新为PP-OCRv5_rec_server,ch_lite模型更新为PP-OCRv5_rec_mobile(需更新模型)
- - auto模式下pdf文档的分类速度提升
+ - 在测试中,发现ppocrv5(server)对手写文档效果有一定提升,但在其余类别文档的精度略差于v4_server_doc,因此默认的ch模型保持不变,仍为
PP-OCRv4_server_rec_doc。
+ - 由于ppocrv5强化了手写场景和特殊字符的识别能力,因此您可以在日繁混合场景以及手写文档场景下手动选择使用ppocrv5模型
+ - 您可通过lang参数
lang='ch_server'(python api)或--lang ch_server(命令行)自行选择相应的模型:
+
+ ch :PP-OCRv4_rec_server_doc(默认)(中英日繁混合/1.5w字典)
+ ch_server :PP-OCRv5_rec_server(中英日繁混合+手写场景/1.8w字典)
+ ch_lite :PP-OCRv5_rec_mobile(中英日繁混合+手写场景/1.8w字典)
+ ch_server_v4 :PP-OCRv4_rec_server(中英混合/6k字典)
+ ch_lite_v4 :PP-OCRv4_rec_mobile(中英混合/6k字典)
+
+
-
- - 解析优化
+
+ - 增加手写文档的支持,通过优化layout对手写文本区域的识别,现已支持手写文档的解析
- - 优化对包含水印文档的解析逻辑,显著提升包含水印文档的解析效果
- - 改进了单页内多个图像/表格与caption的匹配逻辑,提升了复杂布局下图文匹配的准确性
+ - 默认支持此功能,无需额外配置
+ - 可以参考上述说明,手动选择ppocrv5模型以获得更好的手写文档解析效果
-
- - 问题修复
+
+ huggingface和modelscope的demo已更新为支持手写识别和ppocrv5模型的版本,可自行在线体验
+
+
+
+
+ 2025/04/29 1.3.10 发布
+
+ - 支持使用自定义公式标识符,可通过修改用户目录下的
magic-pdf.json文件中的latex-delimiter-config项实现。
+
+
+
+
+ 2025/04/27 1.3.9 发布
+
+
+
+
+ 2025/04/23 1.3.8 发布
+
+ ocr默认模型(ch)更新为PP-OCRv4_server_rec_doc(需更新模型)
- - 修复在某些情况下图片/表格span被填充进textblock导致的异常
- - 修复在某些情况下标题block为空的问题
+ PP-OCRv4_server_rec_doc是在PP-OCRv4_server_rec的基础上,在更多中文文档数据和PP-OCR训练数据的混合数据训练而成,增加了部分繁体字、日文、特殊字符的识别能力,可支持识别的字符为1.5万+,除文档相关的文字识别能力提升外,也同时提升了通用文字的识别能力。
+ - PP-OCRv4_server_rec_doc/PP-OCRv4_server_rec/PP-OCRv4_mobile_rec 性能对比
+ - 经验证,
PP-OCRv4_server_rec_doc模型在中英日繁单种语言或多种语言混合场景均有明显精度提升,且速度与PP-OCRv4_server_rec相当,适合绝大部分场景使用。
+ PP-OCRv4_server_rec_doc在小部分纯英文场景可能会发生单词粘连问题,PP-OCRv4_server_rec则在此场景下表现更好,因此我们保留了PP-OCRv4_server_rec模型,用户可通过增加参数lang='ch_server'(python api)或--lang ch_server(命令行)调用。
-
-
-
-
-
-2025/01/22 1.1.0 发布,在这个版本我们重点提升了解析的精度与效率:
-
- - 模型能力升级(需重新执行 模型下载流程 以获得模型文件的增量更新)
+
+
+
+
+
+ 2025/04/22 1.3.7 发布
+
+ - 修复表格解析模型初始化时lang参数失效的问题
+ - 修复在
cpu模式下ocr和表格解析速度大幅下降的问题
+
+
+
+
+ 2025/04/16 1.3.4 发布
+
+ - 通过移除一些无用的块,小幅提升了ocr-det的速度
+ - 修复部分情况下由footnote导致的页面内排序错误
+
+
+
+
+ 2025/04/12 1.3.2 发布
+
+ - 修复了windows系统下,在python3.13环境安装时一些依赖包版本不兼容的问题
+ - 优化批量推理时的内存占用
+ - 优化旋转90度表格的解析效果
+ - 优化财报样本中超大表格的解析效果
+ - 修复了在未指定OCR语言时,英文文本区域偶尔出现的单词黏连问题(需要更新模型)
+
+
+
+
+ 2025/04/08 1.3.1 发布
+
+ - 修复了一些兼容问题
- - 布局识别模型升级到最新的 `doclayout_yolo(2501)` 模型,提升了layout识别精度
- - 公式解析模型升级到最新的 `unimernet(2501)` 模型,提升了公式识别精度
+ - 支持python 3.13
+ - 为部分过时的linux系统(如centos7)做出最后适配,并不再保证后续版本的继续支持,安装说明
-
- - 性能优化
+
+
+
+
+
+ 2025/04/03 1.3.0 发布
+
+ - 安装与兼容性优化
- - 在配置满足一定条件(显存16GB+)的设备上,通过优化资源占用和重构处理流水线,整体解析速度提升50%以上
+ - 通过移除layout中
layoutlmv3的使用,解决了由detectron2导致的兼容问题
+ - torch版本兼容扩展到2.2~2.6(2.5除外)
+ - cuda兼容支持11.8/12.4/12.6/12.8(cuda版本由torch决定),解决部分用户50系显卡与H系显卡的兼容问题
+ - python兼容版本扩展到3.10~3.12,解决了在非3.10环境下安装时自动降级到0.6.1的问题
+ - 优化离线部署流程,部署成功后不需要联网下载任何模型文件
-
- - 解析效果优化
+
+ - 性能优化
- - 在线demo(mineru.net / huggingface / modelscope)上新增标题分级功能(测试版本,默认开启),支持对标题进行分级,提升文档结构化程度
+ - 通过支持多个pdf文件的batch处理(脚本样例),提升了批量小文件的解析速度 (与1.0.1版本相比,公式解析速度最高提升超过1400%,整体解析速度最高提升超过500%)
+ - 通过优化mfr模型的加载和使用,降低了显存占用并提升了解析速度(需重新执行模型下载流程以获得模型文件的增量更新)
+ - 优化显存占用,最低仅需6GB即可运行本项目
+ - 优化了在mps设备上的运行速度
-
-
-
-
-
-2025/01/10 1.0.1 发布,这是我们的第一个正式版本,在这个版本中,我们通过大量重构带来了全新的API接口和更广泛的兼容性,以及全新的自动语言识别功能:
-
- - 全新API接口
+
+ - 解析效果优化
- - 对于数据侧API,我们引入了Dataset类,旨在提供一个强大而灵活的数据处理框架。该框架当前支持包括图像(.jpg及.png)、PDF、Word(.doc及.docx)、以及PowerPoint(.ppt及.pptx)在内的多种文档格式,确保了从简单到复杂的数据处理任务都能得到有效的支持。
- - 针对用户侧API,我们将MinerU的处理流程精心设计为一系列可组合的Stage阶段。每个Stage代表了一个特定的处理步骤,用户可以根据自身需求自由地定义新的Stage,并通过创造性地组合这些阶段来定制专属的数据处理流程。
+ - mfr模型更新到
unimernet(2503),解决多行公式中换行丢失的问题
-
- - 更广泛的兼容性适配
+
+ - 易用性优化
- - 通过优化依赖环境和配置项,确保在ARM架构的Linux系统上能够稳定高效运行。
- - 深度适配华为昇腾NPU加速,积极响应信创要求,提供自主可控的高性能计算能力,助力人工智能应用平台的国产化应用与发展。 NPU加速教程
+ - 通过使用
paddleocr2torch,完全替代paddle框架以及paddleocr在项目中的使用,解决了paddle和torch的冲突问题,和由于paddle框架导致的线程不安全问题
+ - 解析过程增加实时进度条显示,精准把握解析进度,让等待不再痛苦
-
- - 自动语言识别
+
+
+
+
+
+ 2025/03/03 1.2.1 发布,修复了一些问题
+
+ - 修复在字母与数字的全角转半角操作时对标点符号的影响
+ - 修复在某些情况下caption的匹配不准确问题
+ - 修复在某些情况下的公式span丢失问题
+
+
+
+
+ 2025/02/24 1.2.0 发布,这个版本我们修复了一些问题,提升了解析的效率与精度:
+
+ - 性能优化
+
+
+ - 解析优化
+
+ - 优化对包含水印文档的解析逻辑,显著提升包含水印文档的解析效果
+ - 改进了单页内多个图像/表格与caption的匹配逻辑,提升了复杂布局下图文匹配的准确性
+
+
+ - 问题修复
+
+ - 修复在某些情况下图片/表格span被填充进textblock导致的异常
+ - 修复在某些情况下标题block为空的问题
+
+
+
+
+
+
+ 2025/01/22 1.1.0 发布,在这个版本我们重点提升了解析的精度与效率:
+
+ - 模型能力升级(需重新执行 模型下载流程 以获得模型文件的增量更新)
+
+ - 布局识别模型升级到最新的 `doclayout_yolo(2501)` 模型,提升了layout识别精度
+ - 公式解析模型升级到最新的 `unimernet(2501)` 模型,提升了公式识别精度
+
+
+ - 性能优化
+
+ - 在配置满足一定条件(显存16GB+)的设备上,通过优化资源占用和重构处理流水线,整体解析速度提升50%以上
+
+
+ - 解析效果优化
+
+
+
+
+
+
+ 2025/01/10 1.0.1 发布,这是我们的第一个正式版本,在这个版本中,我们通过大量重构带来了全新的API接口和更广泛的兼容性,以及全新的自动语言识别功能:
+
+ - 全新API接口
+
+ - 对于数据侧API,我们引入了Dataset类,旨在提供一个强大而灵活的数据处理框架。该框架当前支持包括图像(.jpg及.png)、PDF、Word(.doc及.docx)、以及PowerPoint(.ppt及.pptx)在内的多种文档格式,确保了从简单到复杂的数据处理任务都能得到有效的支持。
+ - 针对用户侧API,我们将MinerU的处理流程精心设计为一系列可组合的Stage阶段。每个Stage代表了一个特定的处理步骤,用户可以根据自身需求自由地定义新的Stage,并通过创造性地组合这些阶段来定制专属的数据处理流程。
+
+
+ - 更广泛的兼容性适配
+
+ - 通过优化依赖环境和配置项,确保在ARM架构的Linux系统上能够稳定高效运行。
+ - 深度适配华为昇腾NPU加速,积极响应信创要求,提供自主可控的高性能计算能力,助力人工智能应用平台的国产化应用与发展。 NPU加速教程
+
+
+ - 自动语言识别
+
+ - 通过引入全新的语言识别模型, 在文档解析中将 `lang` 配置为 `auto`,即可自动选择合适的OCR语言模型,提升扫描类文档解析的准确性。
+
+
+
+
+
+
+ 2024/11/22 0.10.0发布,通过引入混合OCR文本提取能力,
+
+ - 在公式密集、span区域不规范、部分文本使用图像表现等复杂文本分布场景下获得解析效果的显著提升
+ - 同时具备文本模式内容提取准确、速度更快与OCR模式span/line区域识别更准的双重优势
+
+
+
+
+ 2024/11/15 0.9.3发布,为表格识别功能接入了RapidTable,单表解析速度提升10倍以上,准确率更高,显存占用更低
+
+
+
+ 2024/11/06 0.9.2发布,为表格识别功能接入了StructTable-InternVL2-1B模型
+
+
+
+ 2024/10/31 0.9.0发布,这是我们进行了大量代码重构的全新版本,解决了众多问题,提升了性能,降低了硬件需求,并提供了更丰富的易用性:
+
+ - 重构排序模块代码,使用 layoutreader 进行阅读顺序排序,确保在各种排版下都能实现极高准确率
+ - 重构段落拼接模块,在跨栏、跨页、跨图、跨表情况下均能实现良好的段落拼接效果
+ - 重构列表和目录识别功能,极大提升列表块和目录块识别的准确率及对应文本段落的解析效果
+ - 重构图、表与描述性文本的匹配逻辑,大幅提升 caption 和 footnote 与图表的匹配准确率,并将描述性文本的丢失率降至接近0
+ - 增加 OCR 的多语言支持,支持 84 种语言的检测与识别,语言支持列表详见 OCR 语言支持列表
+ - 增加显存回收逻辑及其他显存优化措施,大幅降低显存使用需求。开启除表格加速外的全部加速功能(layout/公式/OCR)的显存需求从16GB降至8GB,开启全部加速功能的显存需求从24GB降至10GB
+ - 优化配置文件的功能开关,增加独立的公式检测开关,无需公式检测时可大幅提升速度和解析效果
+ - 集成 PDF-Extract-Kit 1.0
+
+ - 加入自研的 `doclayout_yolo` 模型,在相近解析效果情况下比原方案提速10倍以上,可通过配置文件与 `layoutlmv3` 自由切换
+ - 公式解析升级至 `unimernet 0.2.1`,在提升公式解析准确率的同时,大幅降低显存需求
+ - 因 `PDF-Extract-Kit 1.0` 更换仓库,需要重新下载模型,步骤详见 如何下载模型
+
+
+
+
+
+
+ 2024/09/27 0.8.1发布,修复了一些bug,同时提供了在线demo的本地化部署版本和前端界面
+
+
+
+ 2024/09/09 0.8.0发布,支持Dockerfile快速部署,同时上线了huggingface、modelscope demo
+
+
+
+ 2024/08/30 0.7.1发布,集成了paddle tablemaster表格识别功能
+
+
+
+ 2024/08/09 0.7.0b1发布,简化安装步骤提升易用性,加入表格识别功能
+
+
+
+ 2024/08/01 0.6.2b1发布,优化了依赖冲突问题和安装文档
+
+
+
+ 2024/07/05 首次开源
+
+
+
+
+
+
+ 文档目录
+
+ -
+ MinerU
- - 通过引入全新的语言识别模型, 在文档解析中将 `lang` 配置为 `auto`,即可自动选择合适的OCR语言模型,提升扫描类文档解析的准确性。
+ - 项目简介
+ - 主要功能
+ - 快速开始
+
+
+ - 使用方式
+
+
-
-
+
+ TODO
+ Known Issues
+ FAQ
+ Contributors
+ License Information
+ Acknowledgements
+ Citation
+ Star History
+ magic-doc快速提取PPT/DOC/PDF
+ magic-html提取混合网页内容
+ Links
+
+
-
-2024/11/22 0.10.0发布,通过引入混合OCR文本提取能力,
-
- - 在公式密集、span区域不规范、部分文本使用图像表现等复杂文本分布场景下获得解析效果的显著提升
- - 同时具备文本模式内容提取准确、速度更快与OCR模式span/line区域识别更准的双重优势
-
-
-
-
-2024/11/15 0.9.3发布,为表格识别功能接入了RapidTable,单表解析速度提升10倍以上,准确率更高,显存占用更低
-
-
-
-2024/11/06 0.9.2发布,为表格识别功能接入了StructTable-InternVL2-1B模型
-
-
-
-2024/10/31 0.9.0发布,这是我们进行了大量代码重构的全新版本,解决了众多问题,提升了性能,降低了硬件需求,并提供了更丰富的易用性:
-
- - 重构排序模块代码,使用 layoutreader 进行阅读顺序排序,确保在各种排版下都能实现极高准确率
- - 重构段落拼接模块,在跨栏、跨页、跨图、跨表情况下均能实现良好的段落拼接效果
- - 重构列表和目录识别功能,极大提升列表块和目录块识别的准确率及对应文本段落的解析效果
- - 重构图、表与描述性文本的匹配逻辑,大幅提升 caption 和 footnote 与图表的匹配准确率,并将描述性文本的丢失率降至接近0
- - 增加 OCR 的多语言支持,支持 84 种语言的检测与识别,语言支持列表详见 OCR 语言支持列表
- - 增加显存回收逻辑及其他显存优化措施,大幅降低显存使用需求。开启除表格加速外的全部加速功能(layout/公式/OCR)的显存需求从16GB降至8GB,开启全部加速功能的显存需求从24GB降至10GB
- - 优化配置文件的功能开关,增加独立的公式检测开关,无需公式检测时可大幅提升速度和解析效果
- - 集成 PDF-Extract-Kit 1.0
-
- - 加入自研的 `doclayout_yolo` 模型,在相近解析效果情况下比原方案提速10倍以上,可通过配置文件与 `layoutlmv3` 自由切换
- - 公式解析升级至 `unimernet 0.2.1`,在提升公式解析准确率的同时,大幅降低显存需求
- - 因 `PDF-Extract-Kit 1.0` 更换仓库,需要重新下载模型,步骤详见 如何下载模型
-
-
-
-
-
-
-2024/09/27 0.8.1发布,修复了一些bug,同时提供了在线demo的本地化部署版本和前端界面
-
-
-
-2024/09/09 0.8.0发布,支持Dockerfile快速部署,同时上线了huggingface、modelscope demo
-
-
-
-2024/08/30 0.7.1发布,集成了paddle tablemaster表格识别功能
-
-
-
-2024/08/09 0.7.0b1发布,简化安装步骤提升易用性,加入表格识别功能
-
-
-
-2024/08/01 0.6.2b1发布,优化了依赖冲突问题和安装文档
-
-
-
-2024/07/05 首次开源
-
-
-
-
-
-
- 文档目录
-
- -
- MinerU
-
-
- - TODO
- - Known Issues
- - FAQ
- - Contributors
- - License Information
- - Acknowledgements
- - Citation
- - Star History
- - magic-doc快速提取PPT/DOC/PDF
- - magic-html提取混合网页内容
- - Links
-
-
# MinerU
@@ -312,14 +411,10 @@ https://github.com/user-attachments/assets/4bea02c9-6d54-4cd6-97ed-dff14340982c
如果遇到任何安装问题,请先查询 FAQ
如果遇到解析效果不及预期,参考 Known Issues
-有3种不同方式可以体验MinerU的效果:
+有2种不同方式可以体验MinerU的效果:
-- [在线体验(无需任何安装)](#在线体验)
-- [使用CPU快速体验(Windows,Linux,Mac)](#使用cpu快速体验)
-- 使用 CUDA/CANN/MPS 加速推理
- - [Linux/Windows + CUDA](#使用gpu)
- - [Linux + CANN](#使用npu)
- - [MacOS + MPS](#使用mps)
+- [在线体验](#在线体验)
+- [本地部署](#本地部署)
> [!WARNING]
@@ -331,184 +426,236 @@ https://github.com/user-attachments/assets/4bea02c9-6d54-4cd6-97ed-dff14340982c
>
> 在非主线环境中,由于硬件、软件配置的多样性,以及第三方依赖项的兼容性问题,我们无法100%保证项目的完全可用性。因此,对于希望在非推荐环境中使用本项目的用户,我们建议先仔细阅读文档以及FAQ,大多数问题已经在FAQ中有对应的解决方案,除此之外我们鼓励社区反馈问题,以便我们能够逐步扩大支持范围。
-
-
- | 操作系统 |
-
+
- | Linux after 2019 |
- Windows 10 / 11 |
- macOS 11+ |
+ 解析后端 |
+ pipeline |
+ vlm-transformers |
+ vlm-sgslang |
- | CPU |
- x86_64 / arm64 |
- x86_64(暂不支持ARM Windows) |
- x86_64 / arm64 |
+ 操作系统 |
+ windows/linux/mac |
+ windows/linux |
+ windows(wsl2)/linux |
- | 内存 |
- 大于等于16GB,推荐32G以上 |
+ 内存要求 |
+ 最低16G以上,推荐32G以上 |
- | 存储空间 |
- 大于等于20GB,推荐使用SSD以获得最佳性能 |
+ 磁盘空间要求 |
+ 20G以上,推荐使用SSD |
- | python版本 |
- 3.10~3.13 |
+ python版本 |
+ 3.10-3.13 |
- | Nvidia Driver 版本 |
- latest(专有驱动) |
- latest |
- None |
+ CPU推理支持 |
+ ✅ |
+ ❌ |
+ ❌ |
- | CUDA环境 |
- Refer to the PyTorch official website |
- None |
-
-
- | CANN环境(NPU支持) |
- 8.0+(Ascend 910b) |
- None |
- None |
-
-
- | GPU/MPS 硬件支持列表 |
- 显存6G以上 |
-
- Volta(2017)及之后生产的全部带Tensor Core的GPU
- 6G显存及以上 |
- Apple silicon |
+ GPU要求 |
+ Turing及以后架构,6G显存以上或Apple Silicon |
+ Ampere及以后架构,8G显存以上 |
+ Ampere及以后架构,24G显存及以上 |
-### 在线体验
-
-同步dev分支更新:
+## 在线体验
[](https://mineru.net/OpenSourceTools/Extractor?source=github)
[](https://www.modelscope.cn/studios/OpenDataLab/MinerU)
[](https://huggingface.co/spaces/opendatalab/MinerU)
-### 使用CPU快速体验
+### 🚀🚀🚀VLM demo
+[](https://huggingface.co/spaces/opendatalab/mineru2)
+
+## 本地部署
+
+### 1. 安装 MinerU
-#### 1. 安装magic-pdf
+#### 1.1 使用 pip 或 uv 安装
-> [!NOTE]
-> 最新版本国内镜像源同步可能会有延迟,请耐心等待
+```bash
+pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple
+pip install uv -i https://mirrors.aliyun.com/pypi/simple
+uv pip install "mineru[core]>=2.0.0" -i https://mirrors.aliyun.com/pypi/simple
+```
+
+#### 1.2 源码安装
```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-pip install -U "magic-pdf[full]" -i https://mirrors.aliyun.com/pypi/simple
+git clone https://github.com/opendatalab/MinerU.git
+cd MinerU
+uv pip install -e .[core] -i https://mirrors.aliyun.com/pypi/simple
```
-#### 2. 下载模型权重文件
+#### 1.3 安装完整版(支持 sglang 加速)
-详细参考 [如何下载模型文件](docs/how_to_download_models_zh_cn.md)
+如需使用 **sglang 加速 VLM 模型推理**,请安装完整版本:
-#### 3. 修改配置文件以进行额外配置
+```bash
+uv pip install "mineru[all]>=2.0.0" -i https://mirrors.aliyun.com/pypi/simple
+```
-完成[2. 下载模型权重文件](#2-下载模型权重文件)步骤后,脚本会自动生成用户目录下的magic-pdf.json文件,并自动配置默认模型路径。
-您可在【用户目录】下找到magic-pdf.json文件。
+或从源码安装:
-> [!TIP]
-> windows的用户目录为 "C:\\Users\\用户名", linux用户目录为 "/home/用户名", macOS用户目录为 "/Users/用户名"
+```bash
+uv pip install -e .[all] -i https://mirrors.aliyun.com/pypi/simple
+```
-您可修改该文件中的部分配置实现功能的开关,如表格识别功能:
+---
-> [!NOTE]
->如json内没有如下项目,请手动添加需要的项目,并删除注释内容(标准json不支持注释)
+### 2. 使用 MinerU
-```json
-{
- // other config
- "layout-config": {
- "model": "doclayout_yolo"
- },
- "formula-config": {
- "mfd_model": "yolo_v8_mfd",
- "mfr_model": "unimernet_small",
- "enable": true // 公式识别功能默认是开启的,如果需要关闭请修改此处的值为"false"
- },
- "table-config": {
- "model": "rapid_table",
- "sub_model": "slanet_plus",
- "enable": true, // 表格识别功能默认是开启的,如果需要关闭请修改此处的值为"false"
- "max_time": 400
- }
-}
+#### 2.1 命令行使用方式
+
+##### 基础用法
+
+最简单的命令行调用方式如下:
+
+```bash
+mineru -p -o
```
-### 使用GPU
+- ``:本地 PDF 文件或目录(支持 pdf/png/jpg/jpeg)
+- ``:输出目录
-如果您的设备支持CUDA,且满足主线环境中的显卡要求,则可以使用GPU加速,请根据自己的系统选择适合的教程:
+##### 查看帮助信息
-- [Ubuntu22.04LTS + GPU](docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md)
-- [Windows10/11 + GPU](docs/README_Windows_CUDA_Acceleration_zh_CN.md)
-- 使用Docker快速部署
-> [!IMPORTANT]
-> Docker 需设备gpu显存大于等于6GB,默认开启所有加速功能
->
-> 运行本docker前可以通过以下命令检测自己的设备是否支持在docker上使用CUDA加速
->
-> ```bash
-> docker run --rm --gpus=all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi
-> ```
- ```bash
- wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/Dockerfile -O Dockerfile
- docker build -t mineru:latest .
- docker run -it --name mineru --gpus=all mineru:latest /bin/bash -c "echo 'source /opt/mineru_venv/bin/activate' >> ~/.bashrc && exec bash"
- magic-pdf --help
- ```
-### 使用NPU
-
-如果您的设备存在NPU加速硬件,则可以通过以下教程使用NPU加速:
-
-[NPU加速教程](docs/README_Ascend_NPU_Acceleration_zh_CN.md)
-
-### 使用MPS
-如果您的设备使用Apple silicon 芯片,您可以开启mps加速:
-
-您可以通过在 `magic-pdf.json` 配置文件中将 `device-mode` 参数设置为 `mps` 来启用 MPS 加速。
-
-```json
-{
- // other config
- "device-mode": "mps"
-}
+获取所有可用参数说明:
+
+```bash
+mineru --help
+```
+
+##### 参数详解
+
+```text
+Usage: mineru [OPTIONS]
+
+Options:
+ -v, --version 显示版本并退出
+ -p, --path PATH 输入文件路径或目录(必填)
+ -o, --output PATH 输出目录(必填)
+ -m, --method [auto|txt|ocr] 解析方法:auto(默认)、txt、ocr(仅用于 pipeline 后端)
+ -b, --backend [pipeline|vlm-transformers|vlm-sglang-engine|vlm-sglang-client]
+ 解析后端(默认为 pipeline)
+ -l, --lang [ch|ch_server|... ] 指定文档语言(可提升 OCR 准确率,仅用于 pipeline 后端)
+ -u, --url TEXT 当使用 sglang-client 时,需指定服务地址
+ -s, --start INTEGER 开始解析的页码(从 0 开始)
+ -e, --end INTEGER 结束解析的页码(从 0 开始)
+ -f, --formula BOOLEAN 是否启用公式解析(默认开启,仅 pipeline 后端)
+ -t, --table BOOLEAN 是否启用表格解析(默认开启,仅 pipeline 后端)
+ -d, --device TEXT 推理设备(如 cpu/cuda/cuda:0/npu/mps,仅 pipeline 后端)
+ --vram INTEGER 单进程最大 GPU 显存占用(仅 pipeline 后端)
+ --source [huggingface|modelscope|local]
+ 模型来源,默认 huggingface
+ --help 显示帮助信息
```
+---
+
+#### 2.2 模型源配置
+
+MinerU 默认在首次运行时自动从 HuggingFace 下载所需模型。若无法访问 HuggingFace,可通过以下方式切换模型源:
+
+##### 切换至 ModelScope 源
+
+```bash
+mineru -p -o --source modelscope
+```
+
+或设置环境变量:
+
+```bash
+export MINERU_MODEL_SOURCE=modelscope
+mineru -p -o
+```
+
+##### 使用本地模型
+
+###### 1. 下载模型到本地
+```bash
+mineru-models-download --help
+```
+
+或使用交互式命令行工具选择模型下载:
+
+```bash
+mineru-models-download
+```
+
+下载完成后,模型路径会在当前终端窗口输出,并自动写入用户目录下的 `mineru.json`。
+
+###### 2. 使用本地模型进行解析
+
+```bash
+mineru -p -o --source local
+```
+
+或通过环境变量启用:
+
+```bash
+export MINERU_MODEL_SOURCE=local
+mineru -p -o
+```
+
+---
+
+#### 2.3 使用 sglang 加速 VLM 模型推理
+
+##### 启动 sglang-engine 模式
+
+```bash
+mineru -p -o -b vlm-sglang-engine
+```
+
+##### 启动 sglang-server/client 模式
+
+1. 启动 Server:
+
+```bash
+mineru-sglang-server --port 30000
+```
+
+2. 在另一个终端中使用 Client 调用:
+
+```bash
+mineru -p -o -b vlm-sglang-client -u http://127.0.0.1:30000
+```
-## 使用
+> 💡 更多关于输出文件的信息,请参考 [输出文件说明](docs/output_file_zh_cn.md)
-### 命令行
+---
-[通过命令行使用MinerU](https://mineru.readthedocs.io/en/latest/user_guide/usage/command_line.html)
+### 3. API 调用方式
-> [!TIP]
-> 更多有关输出文件的信息,请参考[输出文件说明](docs/output_file_zh_cn.md)
+您也可以通过 Python 代码调用 MinerU,示例代码请参考:
+👉 [Python 调用示例](demo/demo.py)
-### API
+---
-[通过Python代码调用MinerU](https://mineru.readthedocs.io/en/latest/user_guide/usage/api.html)
+### 4. 部署衍生项目
+社区开发者基于 MinerU 进行了多种二次开发,包括:
-### 部署衍生项目
+- 基于 Gradio 的图形界面
+- 基于 FastAPI 的 Web API
+- 多卡负载均衡的客户端/服务端架构等
-衍生项目包含项目开发者和社群开发者们基于MinerU的二次开发项目,
-例如基于Gradio的应用界面、基于llama的RAG、官网同款web demo、轻量级的多卡负载均衡c/s端等,
-这些项目可能会提供更多的功能和更好的用户体验。
-具体部署方式请参考 [衍生项目readme](projects/README_zh-CN.md)
+这些项目通常提供更好的用户体验和更多功能。
+详细部署方式请参阅:
+👉 [衍生项目说明](projects/README_zh-CN.md)
-### 二次开发
+---
-TODO
# TODO
@@ -548,21 +695,22 @@ TODO
[LICENSE.md](LICENSE.md)
-本项目目前采用PyMuPDF以实现高级功能,但因其遵循AGPL协议,可能对某些使用场景构成限制。未来版本迭代中,我们计划探索并替换为许可条款更为宽松的PDF处理库,以提升用户友好度及灵活性。
+本项目目前部分模型基于YOLO训练,但因其遵循AGPL协议,可能对某些使用场景构成限制。未来版本迭代中,我们计划探索并替换为许可条款更为宽松的模型,以提升用户友好度及灵活性。
# Acknowledgments
- [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit)
- [DocLayout-YOLO](https://github.com/opendatalab/DocLayout-YOLO)
-- [StructEqTable](https://github.com/UniModal4Reasoning/StructEqTable-Deploy)
+- [UniMERNet](https://github.com/opendatalab/UniMERNet)
- [RapidTable](https://github.com/RapidAI/RapidTable)
- [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)
-- [RapidOCR](https://github.com/RapidAI/RapidOCR)
- [PaddleOCR2Pytorch](https://github.com/frotms/PaddleOCR2Pytorch)
-- [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
- [layoutreader](https://github.com/ppaanngggg/layoutreader)
+- [xy-cut](https://github.com/Sanster/xy-cut)
- [fast-langdetect](https://github.com/LlmKira/fast-langdetect)
+- [pypdfium2](https://github.com/pypdfium2-team/pypdfium2)
- [pdfminer.six](https://github.com/pdfminer/pdfminer.six)
+- [pypdf](https://github.com/py-pdf/pypdf)
# Citation
diff --git a/demo/batch_demo.py b/demo/batch_demo.py
deleted file mode 100644
index 7f3cf468d6496725a2121e5b129e2e18f3572e43..0000000000000000000000000000000000000000
--- a/demo/batch_demo.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-from pathlib import Path
-from magic_pdf.data.batch_build_dataset import batch_build_dataset
-from magic_pdf.tools.common import batch_do_parse
-
-
-def batch(pdf_dir, output_dir, method, lang):
- os.makedirs(output_dir, exist_ok=True)
- doc_paths = []
- for doc_path in Path(pdf_dir).glob('*'):
- if doc_path.suffix == '.pdf':
- doc_paths.append(doc_path)
-
- # build dataset with 2 workers
- datasets = batch_build_dataset(doc_paths, 4, lang)
-
- # os.environ["MINERU_MIN_BATCH_INFERENCE_SIZE"] = "200" # every 200 pages will be parsed in one batch
- batch_do_parse(output_dir, [str(doc_path.stem) for doc_path in doc_paths], datasets, method)
-
-
-if __name__ == '__main__':
- batch("pdfs", "output", "auto", "")
-
diff --git a/demo/demo.py b/demo/demo.py
index 2a1377b64b70a7d5adfdee745c9002a03cf7699a..c4094cd405603e4a189dad4729f6a9946e0501c9 100644
--- a/demo/demo.py
+++ b/demo/demo.py
@@ -1,68 +1,243 @@
# Copyright (c) Opendatalab. All rights reserved.
+import copy
+import json
import os
-
-from magic_pdf.data.data_reader_writer import FileBasedDataWriter, FileBasedDataReader
-from magic_pdf.data.dataset import PymuDocDataset
-from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze
-from magic_pdf.config.enums import SupportedPdfParseMethod
-
-# args
-__dir__ = os.path.dirname(os.path.abspath(__file__))
-pdf_file_name = os.path.join(__dir__, "pdfs", "demo1.pdf") # replace with the real pdf path
-name_without_extension = os.path.basename(pdf_file_name).split('.')[0]
-
-# prepare env
-local_image_dir = os.path.join(__dir__, "output", name_without_extension, "images")
-local_md_dir = os.path.join(__dir__, "output", name_without_extension)
-image_dir = str(os.path.basename(local_image_dir))
-os.makedirs(local_image_dir, exist_ok=True)
-
-image_writer, md_writer = FileBasedDataWriter(local_image_dir), FileBasedDataWriter(local_md_dir)
-
-# read bytes
-reader1 = FileBasedDataReader("")
-pdf_bytes = reader1.read(pdf_file_name) # read the pdf content
-
-# proc
-## Create Dataset Instance
-ds = PymuDocDataset(pdf_bytes)
-
-## inference
-if ds.classify() == SupportedPdfParseMethod.OCR:
- infer_result = ds.apply(doc_analyze, ocr=True)
-
- ## pipeline
- pipe_result = infer_result.pipe_ocr_mode(image_writer)
-
-else:
- infer_result = ds.apply(doc_analyze, ocr=False)
-
- ## pipeline
- pipe_result = infer_result.pipe_txt_mode(image_writer)
-
-### get model inference result
-model_inference_result = infer_result.get_infer_res()
-
-### draw layout result on each page
-pipe_result.draw_layout(os.path.join(local_md_dir, f"{name_without_extension}_layout.pdf"))
-
-### draw spans result on each page
-pipe_result.draw_span(os.path.join(local_md_dir, f"{name_without_extension}_spans.pdf"))
-
-### get markdown content
-md_content = pipe_result.get_markdown(image_dir)
-
-### dump markdown
-pipe_result.dump_md(md_writer, f"{name_without_extension}.md", image_dir)
-
-### get content list content
-content_list_content = pipe_result.get_content_list(image_dir)
-
-### dump content list
-pipe_result.dump_content_list(md_writer, f"{name_without_extension}_content_list.json", image_dir)
-
-### get middle json
-middle_json_content = pipe_result.get_middle_json()
-
-### dump middle json
-pipe_result.dump_middle_json(md_writer, f'{name_without_extension}_middle.json')
+from pathlib import Path
+
+from loguru import logger
+
+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.utils.draw_bbox import draw_layout_bbox, draw_span_bbox
+from mineru.utils.enum_class import MakeMode
+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 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.models_download_utils import auto_download_and_get_model_root_path
+
+
+def do_parse(
+ output_dir, # Output directory for storing parsing results
+ pdf_file_names: list[str], # List of PDF file names to be parsed
+ pdf_bytes_list: list[bytes], # List of PDF bytes to be parsed
+ 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'
+ parse_method="auto", # The method for parsing PDF, default is 'auto'
+ p_formula_enable=True, # Enable formula parsing
+ p_table_enable=True, # Enable table parsing
+ server_url=None, # Server URL for vlm-sglang-client backend
+ f_draw_layout_bbox=True, # Whether to draw layout bounding boxes
+ f_draw_span_bbox=True, # Whether to draw span bounding boxes
+ f_dump_md=True, # Whether to dump markdown files
+ f_dump_middle_json=True, # Whether to dump middle JSON files
+ f_dump_model_output=True, # Whether to dump model output files
+ f_dump_orig_pdf=True, # Whether to dump original PDF files
+ f_dump_content_list=True, # Whether to dump content list files
+ 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
+ end_page_id=None, # End page ID for parsing, default is None (parse all pages until the end of the document)
+):
+
+ if backend == "pipeline":
+ 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)
+ pdf_bytes_list[idx] = new_pdf_bytes
+
+ 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=p_formula_enable,table_enable=p_table_enable)
+
+ for idx, model_list in enumerate(infer_results):
+ model_json = copy.deepcopy(model_list)
+ pdf_file_name = pdf_file_names[idx]
+ 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)
+
+ images_list = all_image_lists[idx]
+ pdf_doc = all_pdf_docs[idx]
+ _lang = lang_list[idx]
+ _ocr_enable = ocr_enabled_list[idx]
+ middle_json = pipeline_result_to_middle_json(model_list, images_list, pdf_doc, image_writer, _lang, _ocr_enable, p_formula_enable)
+
+ pdf_info = middle_json["pdf_info"]
+
+ pdf_bytes = pdf_bytes_list[idx]
+ 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 = pipeline_union_make(pdf_info, f_make_md_mode, image_dir)
+ md_writer.write_string(
+ f"{pdf_file_name}.md",
+ md_content_str,
+ )
+
+ if f_dump_content_list:
+ image_dir = str(os.path.basename(local_image_dir))
+ content_list = pipeline_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:
+ md_writer.write_string(
+ f"{pdf_file_name}_model.json",
+ json.dumps(model_json, ensure_ascii=False, indent=4),
+ )
+
+ logger.info(f"local output dir is {local_md_dir}")
+ 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"]
+
+ 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,
+ )
+
+ 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}")
+
+
+def parse_doc(
+ path_list: list[Path],
+ output_dir,
+ lang="ch",
+ backend="pipeline",
+ method="auto",
+ server_url=None,
+ 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)
+):
+ """
+ Parameter description:
+ path_list: List of document paths to be parsed, can be PDF or image files.
+ output_dir: Output directory for storing parsing results.
+ lang: Language option, default is 'ch', 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.
+ Adapted only for the case where the backend is set to "pipeline"
+ backend: the backend for parsing pdf:
+ pipeline: More general.
+ vlm-transformers: More general.
+ vlm-sglang-engine: Faster(engine).
+ vlm-sglang-client: Faster(client).
+ without method specified, pipeline will be used by default.
+ method: the method for parsing pdf:
+ auto: Automatically determine the method based on the file type.
+ txt: Use text extraction method.
+ ocr: Use OCR method for image-based PDFs.
+ Without method specified, 'auto' will be used by default.
+ 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`
+ """
+ try:
+ file_name_list = []
+ pdf_bytes_list = []
+ lang_list = []
+ for path in path_list:
+ file_name = str(Path(path).stem)
+ pdf_bytes = read_fn(path)
+ file_name_list.append(file_name)
+ pdf_bytes_list.append(pdf_bytes)
+ lang_list.append(lang)
+ do_parse(
+ output_dir=output_dir,
+ pdf_file_names=file_name_list,
+ pdf_bytes_list=pdf_bytes_list,
+ p_lang_list=lang_list,
+ backend=backend,
+ parse_method=method,
+ server_url=server_url,
+ start_page_id=start_page_id,
+ end_page_id=end_page_id
+ )
+ except Exception as e:
+ logger.exception(e)
+
+
+if __name__ == '__main__':
+ # args
+ __dir__ = os.path.dirname(os.path.abspath(__file__))
+ pdf_files_dir = os.path.join(__dir__, "pdfs")
+ output_dir = os.path.join(__dir__, "output")
+ pdf_suffixes = [".pdf"]
+ image_suffixes = [".png", ".jpeg", ".jpg"]
+
+ doc_path_list = []
+ for doc_path in Path(pdf_files_dir).glob('*'):
+ if doc_path.suffix in pdf_suffixes + image_suffixes:
+ doc_path_list.append(doc_path)
+
+ """如果您由于网络问题无法下载模型,可以设置环境变量MINERU_MODEL_SOURCE为modelscope使用免代理仓库下载模型"""
+ # os.environ['MINERU_MODEL_SOURCE'] = "modelscope"
+
+ """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-sglang-engine") # faster(engine).
+ # parse_doc(doc_path_list, output_dir, backend="vlm-sglang-client", server_url="http://127.0.0.1:30000") # faster(client).
\ No newline at end of file
diff --git a/docker/ascend_npu/Dockerfile b/docker/ascend_npu/Dockerfile
deleted file mode 100644
index d19d2b69266d38347d5d60e5ae48a73f3693526a..0000000000000000000000000000000000000000
--- a/docker/ascend_npu/Dockerfile
+++ /dev/null
@@ -1,51 +0,0 @@
-# Use the official Ubuntu base image
-FROM swr.cn-central-221.ovaijisuan.com/mindformers/mindformers1.2_mindspore2.3:20240722
-
-USER root
-
-# Set environment variables to non-interactive to avoid prompts during installation
-ENV DEBIAN_FRONTEND=noninteractive
-
-# Update the package list and install necessary packages
-RUN apt-get update && \
- apt-get install -y \
- software-properties-common && \
- add-apt-repository -y ppa:deadsnakes/ppa && \
- apt-get update && \
- apt-get install -y \
- python3.10 \
- python3.10-venv \
- python3.10-distutils \
- python3.10-dev \
- python3-pip \
- wget \
- git \
- libgl1 \
- libglib2.0-0 \
- && rm -rf /var/lib/apt/lists/*
-
-# Set Python 3.10 as the default python3
-RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
-
-# Create a virtual environment for MinerU
-RUN python3 -m venv /opt/mineru_venv
-
-# Copy the configuration file template and install magic-pdf latest
-RUN /bin/bash -c "wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/magic-pdf.template.json && \
- cp magic-pdf.template.json /root/magic-pdf.json && \
- source /opt/mineru_venv/bin/activate && \
- pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
- pip3 install torch==2.3.1 torchvision==0.18.1 -i https://mirrors.aliyun.com/pypi/simple && \
- pip3 install -U magic-pdf[full] 'numpy<2' decorator attrs absl-py cloudpickle ml-dtypes tornado einops -i https://mirrors.aliyun.com/pypi/simple && \
- wget https://gitee.com/ascend/pytorch/releases/download/v6.0.rc2-pytorch2.3.1/torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl && \
- pip3 install torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
-
-# Download models and update the configuration file
-RUN /bin/bash -c "source /opt/mineru_venv/bin/activate && \
- pip3 install modelscope -i https://mirrors.aliyun.com/pypi/simple && \
- wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/scripts/download_models.py -O download_models.py && \
- python3 download_models.py && \
- sed -i 's|cpu|npu|g' /root/magic-pdf.json"
-
-# Set the entry point to activate the virtual environment and run the command line tool
-ENTRYPOINT ["/bin/bash", "-c", "source /opt/mineru_venv/bin/activate && exec \"$@\"", "--"]
diff --git a/docker/china/Dockerfile b/docker/china/Dockerfile
index 6278cec7de6918df9ba11e594dc3c9705906edea..029917b21c6056660d579f12395f9d8f6bb718ce 100644
--- a/docker/china/Dockerfile
+++ b/docker/china/Dockerfile
@@ -18,37 +18,19 @@ RUN apt-get update && \
wget \
git \
libgl1 \
- libreoffice \
- fonts-noto-cjk \
- fonts-wqy-zenhei \
- fonts-wqy-microhei \
- ttf-mscorefonts-installer \
- fontconfig \
libglib2.0-0 \
- libxrender1 \
- libsm6 \
- libxext6 \
- poppler-utils \
&& rm -rf /var/lib/apt/lists/*
# Set Python 3.10 as the default python3
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
-# Create a virtual environment for MinerU
-RUN python3 -m venv /opt/mineru_venv
-
-# Copy the configuration file template and install magic-pdf latest
-RUN /bin/bash -c "wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/magic-pdf.template.json && \
- cp magic-pdf.template.json /root/magic-pdf.json && \
- source /opt/mineru_venv/bin/activate && \
- pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
- pip3 install -U magic-pdf[full] -i https://mirrors.aliyun.com/pypi/simple"
+# install mineru latest
+RUN /bin/bash -c "pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
+ pip3 install uv -i https://mirrors.aliyun.com/pypi/simple && \
+ uv pip install 'mineru[all]>=2.0.0' -i https://mirrors.aliyun.com/pypi/simple"
# Download models and update the configuration file
-RUN /bin/bash -c "pip3 install modelscope -i https://mirrors.aliyun.com/pypi/simple && \
- wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/scripts/download_models.py -O download_models.py && \
- python3 download_models.py && \
- sed -i 's|cpu|cuda|g' /root/magic-pdf.json"
+RUN /bin/bash -c "mineru-models-download -s modelscope -m all"
# Set the entry point to activate the virtual environment and run the command line tool
-ENTRYPOINT ["/bin/bash", "-c", "source /opt/mineru_venv/bin/activate && exec \"$@\"", "--"]
+ENTRYPOINT ["/bin/bash", "-c", "export MINERU_MODEL_SOURCE=local && exec \"$@\"", "--"]
\ No newline at end of file
diff --git a/docker/global/Dockerfile b/docker/global/Dockerfile
index c698c0bc62578599a59fe6f6cc6571cb37553100..7b0c8610078971d02075fcac12bd231da3240d14 100644
--- a/docker/global/Dockerfile
+++ b/docker/global/Dockerfile
@@ -18,37 +18,19 @@ RUN apt-get update && \
wget \
git \
libgl1 \
- libreoffice \
- fonts-noto-cjk \
- fonts-wqy-zenhei \
- fonts-wqy-microhei \
- ttf-mscorefonts-installer \
- fontconfig \
libglib2.0-0 \
- libxrender1 \
- libsm6 \
- libxext6 \
- poppler-utils \
&& rm -rf /var/lib/apt/lists/*
# Set Python 3.10 as the default python3
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
-# Create a virtual environment for MinerU
-RUN python3 -m venv /opt/mineru_venv
-
-# Copy the configuration file template and install magic-pdf latest
-RUN /bin/bash -c "wget https://github.com/opendatalab/MinerU/raw/master/magic-pdf.template.json && \
- cp magic-pdf.template.json /root/magic-pdf.json && \
- source /opt/mineru_venv/bin/activate && \
- pip3 install --upgrade pip && \
- pip3 install -U magic-pdf[full]"
+# install mineru latest
+RUN /bin/bash -c "pip3 install --upgrade pip && \
+ pip3 install uv && \
+ uv pip install 'mineru[all]>=2.0.0'"
# Download models and update the configuration file
-RUN /bin/bash -c "pip3 install huggingface_hub && \
- wget https://github.com/opendatalab/MinerU/raw/master/scripts/download_models_hf.py -O download_models.py && \
- python3 download_models.py && \
- sed -i 's|cpu|cuda|g' /root/magic-pdf.json"
+RUN /bin/bash -c "mineru-models-download -s huggingface -m all"
# Set the entry point to activate the virtual environment and run the command line tool
-ENTRYPOINT ["/bin/bash", "-c", "source /opt/mineru_venv/bin/activate && exec \"$@\"", "--"]
+ENTRYPOINT ["/bin/bash", "-c", "export MINERU_MODEL_SOURCE=local && exec \"$@\"", "--"]
\ No newline at end of file
diff --git a/docs/README_Ascend_NPU_Acceleration_zh_CN.md b/docs/README_Ascend_NPU_Acceleration_zh_CN.md
deleted file mode 100644
index a5b4913b34d4602dd61c30c32fa650d26357ffbb..0000000000000000000000000000000000000000
--- a/docs/README_Ascend_NPU_Acceleration_zh_CN.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Ascend NPU 加速
-
-## 简介
-
-本文档介绍如何在 Ascend NPU 上使用 MinerU。本文档内容已在`华为 Atlas 800T A2`服务器上测试通过。
-```
-CPU:鲲鹏 920 aarch64 2.6GHz
-NPU:Ascend 910B 64GB
-OS:openEuler 22.03 (LTS-SP3)/ Ubuntu 22.04.5 LTS
-CANN:8.0.RC2
-驱动版本:24.1.rc2.1
-```
-由于适配 Ascend NPU 的环境较为复杂,建议使用 Docker 容器运行 MinerU。
-
-通过docker运行MinerU前需确保物理机已安装支持CANN 8.0.RC2的驱动和固件。
-
-
-## 构建镜像
-请保持网络状况良好,并执行以下代码构建镜像。
-```bash
-wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/ascend_npu/Dockerfile -O Dockerfile
-docker build -t mineru_npu:latest .
-```
-如果构建过程中未发生报错则说明镜像构建成功。
-
-
-## 运行容器
-
-```bash
-docker run -it -u root --name mineru-npu --privileged=true \
- --ipc=host \
- --network=host \
- --device=/dev/davinci0 \
- --device=/dev/davinci1 \
- --device=/dev/davinci2 \
- --device=/dev/davinci3 \
- --device=/dev/davinci4 \
- --device=/dev/davinci5 \
- --device=/dev/davinci6 \
- --device=/dev/davinci7 \
- --device=/dev/davinci_manager \
- --device=/dev/devmm_svm \
- --device=/dev/hisi_hdc \
- -v /var/log/npu/:/usr/slog \
- -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
- -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
- mineru_npu:latest \
- /bin/bash -c "echo 'source /opt/mineru_venv/bin/activate' >> ~/.bashrc && exec bash"
-
-magic-pdf --help
-```
diff --git a/docs/README_Ubuntu_CUDA_Acceleration_en_US.md b/docs/README_Ubuntu_CUDA_Acceleration_en_US.md
deleted file mode 100644
index 3d2a32642196bd628318b439694408adbf4a3820..0000000000000000000000000000000000000000
--- a/docs/README_Ubuntu_CUDA_Acceleration_en_US.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# Ubuntu 22.04 LTS
-
-### 1. Check if NVIDIA Drivers Are Installed
-
-```sh
-nvidia-smi
-```
-
-If you see information similar to the following, it means that the NVIDIA drivers are already installed, and you can skip Step 2.
-
-> [!NOTE]
-> Notice:`CUDA Version` should be >= 12.4, If the displayed version number is less than 12.4, please upgrade the driver.
-
-```plaintext
-+---------------------------------------------------------------------------------------+
-| NVIDIA-SMI 570.133.07 Driver Version: 572.83 CUDA Version: 12.8 |
-|-----------------------------------------+----------------------+----------------------+
-| GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC |
-| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
-| | | MIG M. |
-|=========================================+======================+======================|
-| 0 NVIDIA GeForce RTX 3060 Ti WDDM | 00000000:01:00.0 On | N/A |
-| 0% 51C P8 12W / 200W | 1489MiB / 8192MiB | 5% Default |
-| | | N/A |
-+-----------------------------------------+----------------------+----------------------+
-```
-
-### 2. Install the Driver
-
-If no driver is installed, use the following command:
-
-```sh
-sudo apt-get update
-sudo apt-get install nvidia-driver-570-server
-```
-
-Install the proprietary driver and restart your computer after installation.
-
-```sh
-reboot
-```
-
-### 3. Install Anaconda
-
-If Anaconda is already installed, skip this step.
-
-```sh
-wget https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Linux-x86_64.sh
-bash Anaconda3-2024.06-1-Linux-x86_64.sh
-```
-
-In the final step, enter `yes`, close the terminal, and reopen it.
-
-### 4. Create an Environment Using Conda
-
-```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-```
-
-### 5. Install Applications
-
-```sh
-pip install -U magic-pdf[full]
-```
-> [!TIP]
-> After installation, you can check the version of `magic-pdf` using the following command:
->
-> ```sh
-> magic-pdf --version
-> ```
-
-
-### 6. Download Models
-
-
-Refer to detailed instructions on [how to download model files](how_to_download_models_en.md).
-
-
-## 7. Understand the Location of the Configuration File
-
-After completing the [6. Download Models](#6-download-models) step, the script will automatically generate a `magic-pdf.json` file in the user directory and configure the default model path.
-You can find the `magic-pdf.json` file in your user directory.
-
-> [!TIP]
-> The user directory for Linux is "/home/username".
-
-
-### 8. First Run
-
-Download a sample file from the repository and test it.
-
-```sh
-wget https://github.com/opendatalab/MinerU/raw/master/demo/pdfs/small_ocr.pdf
-magic-pdf -p small_ocr.pdf -o ./output
-```
-
-### 9. Test CUDA Acceleration
-
-If your graphics card has at least **6GB** of VRAM, follow these steps to test CUDA acceleration:
-
-1. Modify the value of `"device-mode"` in the `magic-pdf.json` configuration file located in your home directory.
- ```json
- {
- "device-mode": "cuda"
- }
- ```
-2. Test CUDA acceleration with the following command:
- ```sh
- magic-pdf -p small_ocr.pdf -o ./output
- ```
\ No newline at end of file
diff --git a/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md b/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md
deleted file mode 100644
index ced8aa5c5f6579678f784f8d881f19f36b2da258..0000000000000000000000000000000000000000
--- a/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md
+++ /dev/null
@@ -1,115 +0,0 @@
-# Ubuntu 22.04 LTS
-
-## 1. 检测是否已安装nvidia驱动
-
-```bash
-nvidia-smi
-```
-
-如果看到类似如下的信息,说明已经安装了nvidia驱动,可以跳过步骤2
-
-> [!NOTE]
-> `CUDA Version` 显示的版本号应 >= 12.4,如显示的版本号小于12.4,请升级驱动
-
-```plaintext
-+---------------------------------------------------------------------------------------+
-| NVIDIA-SMI 570.133.07 Driver Version: 572.83 CUDA Version: 12.8 |
-|-----------------------------------------+----------------------+----------------------+
-| GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC |
-| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
-| | | MIG M. |
-|=========================================+======================+======================|
-| 0 NVIDIA GeForce RTX 3060 Ti WDDM | 00000000:01:00.0 On | N/A |
-| 0% 51C P8 12W / 200W | 1489MiB / 8192MiB | 5% Default |
-| | | N/A |
-+-----------------------------------------+----------------------+----------------------+
-```
-
-## 2. 安装驱动
-
-如没有驱动,则通过如下命令
-
-```bash
-sudo apt-get update
-sudo apt-get install nvidia-driver-570-server
-```
-
-安装专有驱动,安装完成后,重启电脑
-
-```bash
-reboot
-```
-
-## 3. 安装anacoda
-
-如果已安装conda,可以跳过本步骤
-
-```bash
-wget -U NoSuchBrowser/1.0 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2024.06-1-Linux-x86_64.sh
-bash Anaconda3-2024.06-1-Linux-x86_64.sh
-```
-
-最后一步输入yes,关闭终端重新打开
-
-## 4. 使用conda 创建环境
-
-```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-```
-
-## 5. 安装应用
-
-```bash
-pip install -U magic-pdf[full] -i https://mirrors.aliyun.com/pypi/simple
-```
-
-> [!TIP]
-> 下载完成后,您可以通过以下命令检查`magic-pdf`的版本:
->
-> ```bash
-> magic-pdf --version
-> ```
-
-
-## 6. 下载模型
-
-
-详细参考 [如何下载模型文件](how_to_download_models_zh_cn.md)
-
-## 7. 了解配置文件存放的位置
-
-完成[6.下载模型](#6-下载模型)步骤后,脚本会自动生成用户目录下的magic-pdf.json文件,并自动配置默认模型路径。
-您可在【用户目录】下找到magic-pdf.json文件。
-
-> [!TIP]
-> linux用户目录为 "/home/用户名"
-
-## 8. 第一次运行
-
-从仓库中下载样本文件,并测试
-
-```bash
-wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/demo/pdfs/small_ocr.pdf
-magic-pdf -p small_ocr.pdf -o ./output
-```
-
-## 9. 测试CUDA加速
-
-如果您的显卡显存大于等于 **6GB** ,可以进行以下流程,测试CUDA解析加速效果
-
-**1.修改【用户目录】中配置文件magic-pdf.json中"device-mode"的值**
-
-```json
-{
- "device-mode":"cuda"
-}
-```
-
-**2.运行以下命令测试cuda加速效果**
-
-```bash
-magic-pdf -p small_ocr.pdf -o ./output
-```
-> [!TIP]
-> CUDA加速是否生效可以根据log中输出的各个阶段cost耗时来简单判断,通常情况下,使用cuda加速会比cpu更快。
diff --git a/docs/README_Windows_CUDA_Acceleration_en_US.md b/docs/README_Windows_CUDA_Acceleration_en_US.md
deleted file mode 100644
index 28efb304ac8bbbbaeeed3ec84e2afd284bd16ed0..0000000000000000000000000000000000000000
--- a/docs/README_Windows_CUDA_Acceleration_en_US.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Windows 10/11
-
-### 1. Install CUDA and cuDNN
-
-You need to install a CUDA version that is compatible with torch's requirements. For details, please refer to the [official PyTorch website](https://pytorch.org/get-started/locally/).
-
-- CUDA 11.8 https://developer.nvidia.com/cuda-11-8-0-download-archive
-- CUDA 12.4 https://developer.nvidia.com/cuda-12-4-0-download-archive
-- CUDA 12.6 https://developer.nvidia.com/cuda-12-6-0-download-archive
-- CUDA 12.8 https://developer.nvidia.com/cuda-12-8-0-download-archive
-
-### 2. Install Anaconda
-
-If Anaconda is already installed, you can skip this step.
-
-Download link: https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Windows-x86_64.exe
-
-### 3. Create an Environment Using Conda
-
-```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-```
-
-### 4. Install Applications
-
-```
-pip install -U magic-pdf[full]
-```
-
-> [!IMPORTANT]
-> After installation, you can check the version of `magic-pdf` using the following command:
->
-> ```bash
-> magic-pdf --version
-> ```
-
-
-### 5. Download Models
-
-Refer to detailed instructions on [how to download model files](how_to_download_models_en.md).
-
-### 6. Understand the Location of the Configuration File
-
-After completing the [5. Download Models](#5-download-models) step, the script will automatically generate a `magic-pdf.json` file in the user directory and configure the default model path.
-You can find the `magic-pdf.json` file in your 【user directory】 .
-
-> [!TIP]
-> The user directory for Windows is "C:/Users/username".
-
-### 7. First Run
-
-Download a sample file from the repository and test it.
-
-```powershell
- wget https://github.com/opendatalab/MinerU/raw/master/demo/pdfs/small_ocr.pdf -O small_ocr.pdf
- magic-pdf -p small_ocr.pdf -o ./output
-```
-
-### 8. Test CUDA Acceleration
-
-If your graphics card has at least 6GB of VRAM, follow these steps to test CUDA-accelerated parsing performance.
-
-1. **Overwrite the installation of torch and torchvision** supporting CUDA.(Please select the appropriate index-url based on your CUDA version. For more details, refer to the [PyTorch official website](https://pytorch.org/get-started/locally/).)
-
- ```
- pip install --force-reinstall torch torchvision --index-url https://download.pytorch.org/whl/cu124
- ```
-
-2. **Modify the value of `"device-mode"`** in the `magic-pdf.json` configuration file located in your user directory.
-
- ```json
- {
- "device-mode": "cuda"
- }
- ```
-
-
-3. **Run the following command to test CUDA acceleration**:
-
- ```
- magic-pdf -p small_ocr.pdf -o ./output
- ```
\ No newline at end of file
diff --git a/docs/README_Windows_CUDA_Acceleration_zh_CN.md b/docs/README_Windows_CUDA_Acceleration_zh_CN.md
deleted file mode 100644
index 87a189b744d31cfe2039cbf09eb74c67ccf52a5e..0000000000000000000000000000000000000000
--- a/docs/README_Windows_CUDA_Acceleration_zh_CN.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# Windows10/11
-
-## 1. 安装cuda环境
-
-需要安装符合torch要求的cuda版本,具体可参考[torch官网](https://pytorch.org/get-started/locally/)
-
-- CUDA 11.8 https://developer.nvidia.com/cuda-11-8-0-download-archive
-- CUDA 12.4 https://developer.nvidia.com/cuda-12-4-0-download-archive
-- CUDA 12.6 https://developer.nvidia.com/cuda-12-6-0-download-archive
-- CUDA 12.8 https://developer.nvidia.com/cuda-12-8-0-download-archive
-
-## 2. 安装anaconda
-
-如果已安装conda,可以跳过本步骤
-
-下载链接:
-https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2024.06-1-Windows-x86_64.exe
-
-## 3. 使用conda 创建环境
-
-```bash
-conda create -n mineru 'python=3.12' -y
-conda activate mineru
-```
-
-## 4. 安装应用
-
-```bash
-pip install -U magic-pdf[full] -i https://mirrors.aliyun.com/pypi/simple
-```
-
-> [!IMPORTANT]
-> 下载完成后,您可以通过以下命令检查magic-pdf的版本
->
-> ```bash
-> magic-pdf --version
-> ```
-
-
-## 5. 下载模型
-
-详细参考 [如何下载模型文件](how_to_download_models_zh_cn.md)
-
-## 6. 了解配置文件存放的位置
-
-完成[5.下载模型](#5-下载模型)步骤后,脚本会自动生成用户目录下的magic-pdf.json文件,并自动配置默认模型路径。
-您可在【用户目录】下找到magic-pdf.json文件。
-
-> [!TIP]
-> windows用户目录为 "C:/Users/用户名"
-
-## 7. 第一次运行
-
-从仓库中下载样本文件,并测试
-
-```powershell
- wget https://github.com/opendatalab/MinerU/raw/master/demo/pdfs/small_ocr.pdf -O small_ocr.pdf
- magic-pdf -p small_ocr.pdf -o ./output
-```
-
-## 8. 测试CUDA加速
-
-如果您的显卡显存大于等于 **6GB** ,可以进行以下流程,测试CUDA解析加速效果
-
-**1.覆盖安装支持cuda的torch和torchvision**(请根据cuda版本选择合适的index-url,具体可参考[torch官网](https://pytorch.org/get-started/locally/))
-
-```bash
-pip install --force-reinstall torch torchvision --index-url https://download.pytorch.org/whl/cu124
-```
-
-**2.修改【用户目录】中配置文件magic-pdf.json中"device-mode"的值**
-
-```json
-{
- "device-mode":"cuda"
-}
-```
-
-**3.运行以下命令测试cuda加速效果**
-
-```bash
-magic-pdf -p small_ocr.pdf -o ./output
-```
-
-> [!TIP]
-> CUDA加速是否生效可以根据log中输出的各个阶段的耗时来简单判断,通常情况下,cuda加速后运行速度比cpu更快。
diff --git a/docs/how_to_download_models_en.md b/docs/how_to_download_models_en.md
deleted file mode 100644
index 1852558a2a903ac908c1dd495f59d1b1b9ac3f86..0000000000000000000000000000000000000000
--- a/docs/how_to_download_models_en.md
+++ /dev/null
@@ -1,23 +0,0 @@
-Model downloads are divided into initial downloads and updates to the model directory. Please refer to the corresponding documentation for instructions on how to proceed.
-
-
-# Initial download of model files
-
-### Download the Model from Hugging Face
-
-Use a Python Script to Download Model Files from Hugging Face
-```bash
-pip install huggingface_hub
-wget https://github.com/opendatalab/MinerU/raw/master/scripts/download_models_hf.py -O download_models_hf.py
-python download_models_hf.py
-```
-The Python script will automatically download the model files and configure the model directory in the configuration file.
-
-The configuration file can be found in the user directory, with the filename `magic-pdf.json`.
-
-
-# How to update models previously downloaded
-
-## 1. Models downloaded via Hugging Face or Model Scope
-
-If you previously downloaded models via Hugging Face or Model Scope, you can rerun the Python script used for the initial download. This will automatically update the model directory to the latest version.
diff --git a/docs/how_to_download_models_zh_cn.md b/docs/how_to_download_models_zh_cn.md
deleted file mode 100644
index ec254bfa452222ac9b9d4749557fdad1cce59c2f..0000000000000000000000000000000000000000
--- a/docs/how_to_download_models_zh_cn.md
+++ /dev/null
@@ -1,37 +0,0 @@
-模型下载分为首次下载和更新模型目录,请参考对应的文档内容进行操作
-
-# 首次下载模型文件
-
-模型文件可以从 Hugging Face 或 Model Scope 下载,由于网络原因,国内用户访问HF可能会失败,请使用 ModelScope。
-
-
- 方法一:从 Hugging Face 下载模型
- 使用python脚本 从Hugging Face下载模型文件
- pip install huggingface_hub
-wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/scripts/download_models_hf.py -O download_models_hf.py
-python download_models_hf.py
- python脚本会自动下载模型文件并配置好配置文件中的模型目录
-
-
-## 方法二:从 ModelScope 下载模型
-
-### 使用python脚本 从ModelScope下载模型文件
-
-```bash
-pip install modelscope
-wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/scripts/download_models.py -O download_models.py
-python download_models.py
-```
-python脚本会自动下载模型文件并配置好配置文件中的模型目录
-
-配置文件可以在用户目录中找到,文件名为`magic-pdf.json`
-
-> [!TIP]
-> windows的用户目录为 "C:\\Users\\用户名", linux用户目录为 "/home/用户名", macOS用户目录为 "/Users/用户名"
-
-
-# 此前下载过模型,如何更新
-
-## 1. 通过 Hugging Face 或 Model Scope 下载过模型
-
-如此前通过 HuggingFace 或 Model Scope 下载过模型,可以重复执行此前的模型下载python脚本,将会自动将模型目录更新到最新版本。
diff --git a/magic-pdf.template.json b/magic-pdf.template.json
deleted file mode 100644
index a80b8bf44c0e8e4bd7530a541dbfc637867ac971..0000000000000000000000000000000000000000
--- a/magic-pdf.template.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "bucket_info":{
- "bucket-name-1":["ak", "sk", "endpoint"],
- "bucket-name-2":["ak", "sk", "endpoint"]
- },
- "models-dir":"/tmp/models",
- "layoutreader-model-dir":"/tmp/layoutreader",
- "device-mode":"cpu",
- "layout-config": {
- "model": "doclayout_yolo"
- },
- "formula-config": {
- "mfd_model": "yolo_v8_mfd",
- "mfr_model": "unimernet_small",
- "enable": true
- },
- "table-config": {
- "model": "rapid_table",
- "sub_model": "slanet_plus",
- "enable": true,
- "max_time": 400
- },
- "latex-delimiter-config": {
- "display": {
- "left": "$$",
- "right": "$$"
- },
- "inline": {
- "left": "$",
- "right": "$"
- }
- },
- "llm-aided-config": {
- "formula_aided": {
- "api_key": "your_api_key",
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
- "model": "qwen2.5-7b-instruct",
- "enable": false
- },
- "text_aided": {
- "api_key": "your_api_key",
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
- "model": "qwen2.5-7b-instruct",
- "enable": false
- },
- "title_aided": {
- "api_key": "your_api_key",
- "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
- "model": "qwen2.5-32b-instruct",
- "enable": false
- }
- },
- "config_version": "1.2.1"
-}
\ No newline at end of file
diff --git a/magic_pdf/config/constants.py b/magic_pdf/config/constants.py
deleted file mode 100644
index b18d630bbd1144816c4e0102c7f5ad53bc6d6194..0000000000000000000000000000000000000000
--- a/magic_pdf/config/constants.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""span维度自定义字段."""
-# span是否是跨页合并的
-CROSS_PAGE = 'cross_page'
-
-"""
-block维度自定义字段
-"""
-# block中lines是否被删除
-LINES_DELETED = 'lines_deleted'
-
-# table recognition max time default value
-TABLE_MAX_TIME_VALUE = 400
-
-# pp_table_result_max_length
-TABLE_MAX_LEN = 480
-
-# table master structure dict
-TABLE_MASTER_DICT = 'table_master_structure_dict.txt'
-
-# table master dir
-TABLE_MASTER_DIR = 'table_structure_tablemaster_infer/'
-
-# pp detect model dir
-DETECT_MODEL_DIR = 'ch_PP-OCRv4_det_infer'
-
-# pp rec model dir
-REC_MODEL_DIR = 'ch_PP-OCRv4_rec_infer'
-
-# pp rec char dict path
-REC_CHAR_DICT = 'ppocr_keys_v1.txt'
-
-# pp rec copy rec directory
-PP_REC_DIRECTORY = '.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer'
-
-# pp rec copy det directory
-PP_DET_DIRECTORY = '.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer'
-
-
-class MODEL_NAME:
- # pp table structure algorithm
- TABLE_MASTER = 'tablemaster'
- # struct eqtable
- STRUCT_EQTABLE = 'struct_eqtable'
-
- DocLayout_YOLO = 'doclayout_yolo'
-
- LAYOUTLMv3 = 'layoutlmv3'
-
- YOLO_V8_MFD = 'yolo_v8_mfd'
-
- UniMerNet_v2_Small = 'unimernet_small'
-
- RAPID_TABLE = 'rapid_table'
-
- YOLO_V11_LangDetect = 'yolo_v11n_langdetect'
-
-
-PARSE_TYPE_TXT = 'txt'
-PARSE_TYPE_OCR = 'ocr'
-
diff --git a/magic_pdf/config/drop_reason.py b/magic_pdf/config/drop_reason.py
deleted file mode 100644
index d75d5676b81481c987f6c4d4948aaa82e9a4c86f..0000000000000000000000000000000000000000
--- a/magic_pdf/config/drop_reason.py
+++ /dev/null
@@ -1,35 +0,0 @@
-class DropReason:
- TEXT_BLCOK_HOR_OVERLAP = 'text_block_horizontal_overlap' # 文字块有水平互相覆盖,导致无法准确定位文字顺序
- USEFUL_BLOCK_HOR_OVERLAP = (
- 'useful_block_horizontal_overlap' # 需保留的block水平覆盖
- )
- COMPLICATED_LAYOUT = 'complicated_layout' # 复杂的布局,暂时不支持
- TOO_MANY_LAYOUT_COLUMNS = 'too_many_layout_columns' # 目前不支持分栏超过2列的
- COLOR_BACKGROUND_TEXT_BOX = 'color_background_text_box' # 含有带色块的PDF,色块会改变阅读顺序,目前不支持带底色文字块的PDF。
- HIGH_COMPUTATIONAL_lOAD_BY_IMGS = (
- 'high_computational_load_by_imgs' # 含特殊图片,计算量太大,从而丢弃
- )
- HIGH_COMPUTATIONAL_lOAD_BY_SVGS = (
- 'high_computational_load_by_svgs' # 特殊的SVG图,计算量太大,从而丢弃
- )
- HIGH_COMPUTATIONAL_lOAD_BY_TOTAL_PAGES = 'high_computational_load_by_total_pages' # 计算量超过负荷,当前方法下计算量消耗过大
- MISS_DOC_LAYOUT_RESULT = 'missing doc_layout_result' # 版面分析失败
- Exception = '_exception' # 解析中发生异常
- ENCRYPTED = 'encrypted' # PDF是加密的
- EMPTY_PDF = 'total_page=0' # PDF页面总数为0
- NOT_IS_TEXT_PDF = 'not_is_text_pdf' # 不是文字版PDF,无法直接解析
- DENSE_SINGLE_LINE_BLOCK = 'dense_single_line_block' # 无法清晰的分段
- TITLE_DETECTION_FAILED = 'title_detection_failed' # 探测标题失败
- TITLE_LEVEL_FAILED = (
- 'title_level_failed' # 分析标题级别失败(例如一级、二级、三级标题)
- )
- PARA_SPLIT_FAILED = 'para_split_failed' # 识别段落失败
- PARA_MERGE_FAILED = 'para_merge_failed' # 段落合并失败
- NOT_ALLOW_LANGUAGE = 'not_allow_language' # 不支持的语种
- SPECIAL_PDF = 'special_pdf'
- PSEUDO_SINGLE_COLUMN = 'pseudo_single_column' # 无法精确判断文字分栏
- CAN_NOT_DETECT_PAGE_LAYOUT = 'can_not_detect_page_layout' # 无法分析页面的版面
- NEGATIVE_BBOX_AREA = 'negative_bbox_area' # 缩放导致 bbox 面积为负
- OVERLAP_BLOCKS_CAN_NOT_SEPARATION = (
- 'overlap_blocks_can_t_separation' # 无法分离重叠的block
- )
diff --git a/magic_pdf/config/drop_tag.py b/magic_pdf/config/drop_tag.py
deleted file mode 100644
index 51a2bc99378ddb1182a3c87de4e3623f00f93807..0000000000000000000000000000000000000000
--- a/magic_pdf/config/drop_tag.py
+++ /dev/null
@@ -1,19 +0,0 @@
-
-COLOR_BG_HEADER_TXT_BLOCK = 'color_background_header_txt_block'
-PAGE_NO = 'page-no' # 页码
-CONTENT_IN_FOOT_OR_HEADER = 'in-foot-header-area' # 页眉页脚内的文本
-VERTICAL_TEXT = 'vertical-text' # 垂直文本
-ROTATE_TEXT = 'rotate-text' # 旋转文本
-EMPTY_SIDE_BLOCK = 'empty-side-block' # 边缘上的空白没有任何内容的block
-ON_IMAGE_TEXT = 'on-image-text' # 文本在图片上
-ON_TABLE_TEXT = 'on-table-text' # 文本在表格上
-
-
-class DropTag:
- PAGE_NUMBER = 'page_no'
- HEADER = 'header'
- FOOTER = 'footer'
- FOOTNOTE = 'footnote'
- NOT_IN_LAYOUT = 'not_in_layout'
- SPAN_OVERLAP = 'span_overlap'
- BLOCK_OVERLAP = 'block_overlap'
diff --git a/magic_pdf/config/enums.py b/magic_pdf/config/enums.py
deleted file mode 100644
index 6f3e91a3227e6cb6678af0fc578a833a3d2439e3..0000000000000000000000000000000000000000
--- a/magic_pdf/config/enums.py
+++ /dev/null
@@ -1,7 +0,0 @@
-
-import enum
-
-
-class SupportedPdfParseMethod(enum.Enum):
- OCR = 'ocr'
- TXT = 'txt'
diff --git a/magic_pdf/config/make_content_config.py b/magic_pdf/config/make_content_config.py
deleted file mode 100644
index abcd74a4b860f163deb484ad33797c638034fb08..0000000000000000000000000000000000000000
--- a/magic_pdf/config/make_content_config.py
+++ /dev/null
@@ -1,11 +0,0 @@
-class MakeMode:
- MM_MD = 'mm_markdown'
- NLP_MD = 'nlp_markdown'
- STANDARD_FORMAT = 'standard_format'
-
-
-class DropMode:
- WHOLE_PDF = 'whole_pdf'
- SINGLE_PAGE = 'single_page'
- NONE = 'none'
- NONE_WITH_REASON = 'none_with_reason'
diff --git a/magic_pdf/config/model_block_type.py b/magic_pdf/config/model_block_type.py
deleted file mode 100644
index 4ad739ac51c08071626d8badd17f43b0eb90a66c..0000000000000000000000000000000000000000
--- a/magic_pdf/config/model_block_type.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from enum import Enum
-
-
-class ModelBlockTypeEnum(Enum):
- TITLE = 0
- PLAIN_TEXT = 1
- ABANDON = 2
- ISOLATE_FORMULA = 8
- EMBEDDING = 13
- ISOLATED = 14
diff --git a/magic_pdf/config/ocr_content_type.py b/magic_pdf/config/ocr_content_type.py
deleted file mode 100644
index 30d88cfdedbf28d3552a92e1549b839bea195f5b..0000000000000000000000000000000000000000
--- a/magic_pdf/config/ocr_content_type.py
+++ /dev/null
@@ -1,40 +0,0 @@
-class ContentType:
- Image = 'image'
- Table = 'table'
- Text = 'text'
- InlineEquation = 'inline_equation'
- InterlineEquation = 'interline_equation'
-
-
-class BlockType:
- Image = 'image'
- ImageBody = 'image_body'
- ImageCaption = 'image_caption'
- ImageFootnote = 'image_footnote'
- Table = 'table'
- TableBody = 'table_body'
- TableCaption = 'table_caption'
- TableFootnote = 'table_footnote'
- Text = 'text'
- Title = 'title'
- InterlineEquation = 'interline_equation'
- Footnote = 'footnote'
- Discarded = 'discarded'
- List = 'list'
- Index = 'index'
-
-
-class CategoryId:
- Title = 0
- Text = 1
- Abandon = 2
- ImageBody = 3
- ImageCaption = 4
- TableBody = 5
- TableCaption = 6
- TableFootnote = 7
- InterlineEquation_Layout = 8
- InlineEquation = 13
- InterlineEquation_YOLO = 14
- OcrText = 15
- ImageFootnote = 101
diff --git a/magic_pdf/data/batch_build_dataset.py b/magic_pdf/data/batch_build_dataset.py
deleted file mode 100644
index 52d33485f1c9a624b31e16029526f0ac653a165f..0000000000000000000000000000000000000000
--- a/magic_pdf/data/batch_build_dataset.py
+++ /dev/null
@@ -1,167 +0,0 @@
-import concurrent.futures
-
-import fitz
-
-from magic_pdf.data.dataset import PymuDocDataset
-from magic_pdf.data.utils import fitz_doc_to_image # PyMuPDF
-
-
-def partition_array_greedy(arr, k):
- """Partition an array into k parts using a simple greedy approach.
-
- Parameters:
- -----------
- arr : list
- The input array of integers
- k : int
- Number of partitions to create
-
- Returns:
- --------
- partitions : list of lists
- The k partitions of the array
- """
- # Handle edge cases
- if k <= 0:
- raise ValueError('k must be a positive integer')
- if k > len(arr):
- k = len(arr) # Adjust k if it's too large
- if k == 1:
- return [list(range(len(arr)))]
- if k == len(arr):
- return [[i] for i in range(len(arr))]
-
- # Sort the array in descending order
- sorted_indices = sorted(range(len(arr)), key=lambda i: arr[i][1], reverse=True)
-
- # Initialize k empty partitions
- partitions = [[] for _ in range(k)]
- partition_sums = [0] * k
-
- # Assign each element to the partition with the smallest current sum
- for idx in sorted_indices:
- # Find the partition with the smallest sum
- min_sum_idx = partition_sums.index(min(partition_sums))
-
- # Add the element to this partition
- partitions[min_sum_idx].append(idx) # Store the original index
- partition_sums[min_sum_idx] += arr[idx][1]
-
- return partitions
-
-
-def process_pdf_batch(pdf_jobs, idx):
- """Process a batch of PDF pages using multiple threads.
-
- Parameters:
- -----------
- pdf_jobs : list of tuples
- List of (pdf_path, page_num) tuples
- output_dir : str or None
- Directory to save images to
- num_threads : int
- Number of threads to use
- **kwargs :
- Additional arguments for process_pdf_page
-
- Returns:
- --------
- images : list
- List of processed images
- """
- images = []
-
- for pdf_path, _ in pdf_jobs:
- doc = fitz.open(pdf_path)
- tmp = []
- for page_num in range(len(doc)):
- page = doc[page_num]
- tmp.append(fitz_doc_to_image(page))
- images.append(tmp)
- return (idx, images)
-
-
-def batch_build_dataset(pdf_paths, k, lang=None):
- """Process multiple PDFs by partitioning them into k balanced parts and
- processing each part in parallel.
-
- Parameters:
- -----------
- pdf_paths : list
- List of paths to PDF files
- k : int
- Number of partitions to create
- output_dir : str or None
- Directory to save images to
- threads_per_worker : int
- Number of threads to use per worker
- **kwargs :
- Additional arguments for process_pdf_page
-
- Returns:
- --------
- all_images : list
- List of all processed images
- """
-
- results = []
- for pdf_path in pdf_paths:
- with open(pdf_path, 'rb') as f:
- pdf_bytes = f.read()
- dataset = PymuDocDataset(pdf_bytes, lang=lang)
- results.append(dataset)
- return results
-
-
- #
- # # Get page counts for each PDF
- # pdf_info = []
- # total_pages = 0
- #
- # for pdf_path in pdf_paths:
- # try:
- # doc = fitz.open(pdf_path)
- # num_pages = len(doc)
- # pdf_info.append((pdf_path, num_pages))
- # total_pages += num_pages
- # doc.close()
- # except Exception as e:
- # print(f'Error opening {pdf_path}: {e}')
- #
- # # Partition the jobs based on page countEach job has 1 page
- # partitions = partition_array_greedy(pdf_info, k)
- #
- # # Process each partition in parallel
- # all_images_h = {}
- #
- # with concurrent.futures.ProcessPoolExecutor(max_workers=k) as executor:
- # # Submit one task per partition
- # futures = []
- # for sn, partition in enumerate(partitions):
- # # Get the jobs for this partition
- # partition_jobs = [pdf_info[idx] for idx in partition]
- #
- # # Submit the task
- # future = executor.submit(
- # process_pdf_batch,
- # partition_jobs,
- # sn
- # )
- # futures.append(future)
- # # Process results as they complete
- # for i, future in enumerate(concurrent.futures.as_completed(futures)):
- # try:
- # idx, images = future.result()
- # all_images_h[idx] = images
- # except Exception as e:
- # print(f'Error processing partition: {e}')
- # results = [None] * len(pdf_paths)
- # for i in range(len(partitions)):
- # partition = partitions[i]
- # for j in range(len(partition)):
- # with open(pdf_info[partition[j]][0], 'rb') as f:
- # pdf_bytes = f.read()
- # dataset = PymuDocDataset(pdf_bytes, lang=lang)
- # dataset.set_images(all_images_h[i][j])
- # results[partition[j]] = dataset
- # return results
\ No newline at end of file
diff --git a/magic_pdf/data/data_reader_writer/__init__.py b/magic_pdf/data/data_reader_writer/__init__.py
deleted file mode 100644
index f8f8234739e4cc756b56dbd4cb502893481a7a09..0000000000000000000000000000000000000000
--- a/magic_pdf/data/data_reader_writer/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from magic_pdf.data.data_reader_writer.filebase import \
- FileBasedDataReader # noqa: F401
-from magic_pdf.data.data_reader_writer.filebase import \
- FileBasedDataWriter # noqa: F401
-from magic_pdf.data.data_reader_writer.multi_bucket_s3 import \
- MultiBucketS3DataReader # noqa: F401
-from magic_pdf.data.data_reader_writer.multi_bucket_s3 import \
- MultiBucketS3DataWriter # noqa: F401
-from magic_pdf.data.data_reader_writer.s3 import S3DataReader # noqa: F401
-from magic_pdf.data.data_reader_writer.s3 import S3DataWriter # noqa: F401
-from magic_pdf.data.data_reader_writer.base import DataReader # noqa: F401
-from magic_pdf.data.data_reader_writer.base import DataWriter # noqa: F401
\ No newline at end of file
diff --git a/magic_pdf/data/dataset.py b/magic_pdf/data/dataset.py
deleted file mode 100644
index fb626e12cbfb7845fff1fed30dbbfdf650d507ea..0000000000000000000000000000000000000000
--- a/magic_pdf/data/dataset.py
+++ /dev/null
@@ -1,408 +0,0 @@
-import os
-from abc import ABC, abstractmethod
-from typing import Callable, Iterator
-
-import fitz
-from loguru import logger
-
-from magic_pdf.config.enums import SupportedPdfParseMethod
-from magic_pdf.data.schemas import PageInfo
-from magic_pdf.data.utils import fitz_doc_to_image
-from magic_pdf.filter import classify
-
-
-class PageableData(ABC):
- @abstractmethod
- def get_image(self) -> dict:
- """Transform data to image."""
- pass
-
- @abstractmethod
- def get_doc(self) -> fitz.Page:
- """Get the pymudoc page."""
- pass
-
- @abstractmethod
- def get_page_info(self) -> PageInfo:
- """Get the page info of the page.
-
- Returns:
- PageInfo: the page info of this page
- """
- pass
-
- @abstractmethod
- def draw_rect(self, rect_coords, color, fill, fill_opacity, width, overlay):
- """draw rectangle.
-
- Args:
- rect_coords (list[float]): four elements array contain the top-left and bottom-right coordinates, [x0, y0, x1, y1]
- color (list[float] | None): three element tuple which describe the RGB of the board line, None means no board line
- fill (list[float] | None): fill the board with RGB, None means will not fill with color
- fill_opacity (float): opacity of the fill, range from [0, 1]
- width (float): the width of board
- overlay (bool): fill the color in foreground or background. True means fill in background.
- """
- pass
-
- @abstractmethod
- def insert_text(self, coord, content, fontsize, color):
- """insert text.
-
- Args:
- coord (list[float]): four elements array contain the top-left and bottom-right coordinates, [x0, y0, x1, y1]
- content (str): the text content
- fontsize (int): font size of the text
- color (list[float] | None): three element tuple which describe the RGB of the board line, None will use the default font color!
- """
- pass
-
-
-class Dataset(ABC):
- @abstractmethod
- def __len__(self) -> int:
- """The length of the dataset."""
- pass
-
- @abstractmethod
- def __iter__(self) -> Iterator[PageableData]:
- """Yield the page data."""
- pass
-
- @abstractmethod
- def supported_methods(self) -> list[SupportedPdfParseMethod]:
- """The methods that this dataset support.
-
- Returns:
- list[SupportedPdfParseMethod]: The supported methods, Valid methods are: OCR, TXT
- """
- pass
-
- @abstractmethod
- def data_bits(self) -> bytes:
- """The bits used to create this dataset."""
- pass
-
- @abstractmethod
- def get_page(self, page_id: int) -> PageableData:
- """Get the page indexed by page_id.
-
- Args:
- page_id (int): the index of the page
-
- Returns:
- PageableData: the page doc object
- """
- pass
-
- @abstractmethod
- def dump_to_file(self, file_path: str):
- """Dump the file.
-
- Args:
- file_path (str): the file path
- """
- pass
-
- @abstractmethod
- def apply(self, proc: Callable, *args, **kwargs):
- """Apply callable method which.
-
- Args:
- proc (Callable): invoke proc as follows:
- proc(self, *args, **kwargs)
-
- Returns:
- Any: return the result generated by proc
- """
- pass
-
- @abstractmethod
- def classify(self) -> SupportedPdfParseMethod:
- """classify the dataset.
-
- Returns:
- SupportedPdfParseMethod: _description_
- """
- pass
-
- @abstractmethod
- def clone(self):
- """clone this dataset."""
- pass
-
-
-class PymuDocDataset(Dataset):
- def __init__(self, bits: bytes, lang=None):
- """Initialize the dataset, which wraps the pymudoc documents.
-
- Args:
- bits (bytes): the bytes of the pdf
- """
- self._raw_fitz = fitz.open('pdf', bits)
- self._records = [Doc(v) for v in self._raw_fitz]
- self._data_bits = bits
- self._raw_data = bits
- self._classify_result = None
-
- if lang == '':
- self._lang = None
- elif lang == 'auto':
- from magic_pdf.model.sub_modules.language_detection.utils import \
- auto_detect_lang
- self._lang = auto_detect_lang(self._data_bits)
- logger.info(f'lang: {lang}, detect_lang: {self._lang}')
- else:
- self._lang = lang
- logger.info(f'lang: {lang}')
-
- def __len__(self) -> int:
- """The page number of the pdf."""
- return len(self._records)
-
- def __iter__(self) -> Iterator[PageableData]:
- """Yield the page doc object."""
- return iter(self._records)
-
- def supported_methods(self) -> list[SupportedPdfParseMethod]:
- """The method supported by this dataset.
-
- Returns:
- list[SupportedPdfParseMethod]: the supported methods
- """
- return [SupportedPdfParseMethod.OCR, SupportedPdfParseMethod.TXT]
-
- def data_bits(self) -> bytes:
- """The pdf bits used to create this dataset."""
- return self._data_bits
-
- def get_page(self, page_id: int) -> PageableData:
- """The page doc object.
-
- Args:
- page_id (int): the page doc index
-
- Returns:
- PageableData: the page doc object
- """
- return self._records[page_id]
-
- def dump_to_file(self, file_path: str):
- """Dump the file.
-
- Args:
- file_path (str): the file path
- """
-
- dir_name = os.path.dirname(file_path)
- if dir_name not in ('', '.', '..'):
- os.makedirs(dir_name, exist_ok=True)
- self._raw_fitz.save(file_path)
-
- def apply(self, proc: Callable, *args, **kwargs):
- """Apply callable method which.
-
- Args:
- proc (Callable): invoke proc as follows:
- proc(dataset, *args, **kwargs)
-
- Returns:
- Any: return the result generated by proc
- """
- if 'lang' in kwargs and self._lang is not None:
- kwargs['lang'] = self._lang
- return proc(self, *args, **kwargs)
-
- def classify(self) -> SupportedPdfParseMethod:
- """classify the dataset.
-
- Returns:
- SupportedPdfParseMethod: _description_
- """
- if self._classify_result is None:
- self._classify_result = classify(self._data_bits)
- return self._classify_result
-
- def clone(self):
- """clone this dataset."""
- return PymuDocDataset(self._raw_data)
-
- def set_images(self, images):
- for i in range(len(self._records)):
- self._records[i].set_image(images[i])
-
-class ImageDataset(Dataset):
- def __init__(self, bits: bytes, lang=None):
- """Initialize the dataset, which wraps the pymudoc documents.
-
- Args:
- bits (bytes): the bytes of the photo which will be converted to pdf first. then converted to pymudoc.
- """
- pdf_bytes = fitz.open(stream=bits).convert_to_pdf()
- self._raw_fitz = fitz.open('pdf', pdf_bytes)
- self._records = [Doc(v) for v in self._raw_fitz]
- self._raw_data = bits
- self._data_bits = pdf_bytes
-
- if lang == '':
- self._lang = None
- elif lang == 'auto':
- from magic_pdf.model.sub_modules.language_detection.utils import \
- auto_detect_lang
- self._lang = auto_detect_lang(self._data_bits)
- logger.info(f'lang: {lang}, detect_lang: {self._lang}')
- else:
- self._lang = lang
- logger.info(f'lang: {lang}')
-
- def __len__(self) -> int:
- """The length of the dataset."""
- return len(self._records)
-
- def __iter__(self) -> Iterator[PageableData]:
- """Yield the page object."""
- return iter(self._records)
-
- def supported_methods(self):
- """The method supported by this dataset.
-
- Returns:
- list[SupportedPdfParseMethod]: the supported methods
- """
- return [SupportedPdfParseMethod.OCR]
-
- def data_bits(self) -> bytes:
- """The pdf bits used to create this dataset."""
- return self._data_bits
-
- def get_page(self, page_id: int) -> PageableData:
- """The page doc object.
-
- Args:
- page_id (int): the page doc index
-
- Returns:
- PageableData: the page doc object
- """
- return self._records[page_id]
-
- def dump_to_file(self, file_path: str):
- """Dump the file.
-
- Args:
- file_path (str): the file path
- """
- dir_name = os.path.dirname(file_path)
- if dir_name not in ('', '.', '..'):
- os.makedirs(dir_name, exist_ok=True)
- self._raw_fitz.save(file_path)
-
- def apply(self, proc: Callable, *args, **kwargs):
- """Apply callable method which.
-
- Args:
- proc (Callable): invoke proc as follows:
- proc(dataset, *args, **kwargs)
-
- Returns:
- Any: return the result generated by proc
- """
- return proc(self, *args, **kwargs)
-
- def classify(self) -> SupportedPdfParseMethod:
- """classify the dataset.
-
- Returns:
- SupportedPdfParseMethod: _description_
- """
- return SupportedPdfParseMethod.OCR
-
- def clone(self):
- """clone this dataset."""
- return ImageDataset(self._raw_data)
-
- def set_images(self, images):
- for i in range(len(self._records)):
- self._records[i].set_image(images[i])
-
-class Doc(PageableData):
- """Initialized with pymudoc object."""
-
- def __init__(self, doc: fitz.Page):
- self._doc = doc
- self._img = None
-
- def get_image(self):
- """Return the image info.
-
- Returns:
- dict: {
- img: np.ndarray,
- width: int,
- height: int
- }
- """
- if self._img is None:
- self._img = fitz_doc_to_image(self._doc)
- return self._img
-
- def set_image(self, img):
- """
- Args:
- img (np.ndarray): the image
- """
- if self._img is None:
- self._img = img
-
- def get_doc(self) -> fitz.Page:
- """Get the pymudoc object.
-
- Returns:
- fitz.Page: the pymudoc object
- """
- return self._doc
-
- def get_page_info(self) -> PageInfo:
- """Get the page info of the page.
-
- Returns:
- PageInfo: the page info of this page
- """
- page_w = self._doc.rect.width
- page_h = self._doc.rect.height
- return PageInfo(w=page_w, h=page_h)
-
- def __getattr__(self, name):
- if hasattr(self._doc, name):
- return getattr(self._doc, name)
-
- def draw_rect(self, rect_coords, color, fill, fill_opacity, width, overlay):
- """draw rectangle.
-
- Args:
- rect_coords (list[float]): four elements array contain the top-left and bottom-right coordinates, [x0, y0, x1, y1]
- color (list[float] | None): three element tuple which describe the RGB of the board line, None means no board line
- fill (list[float] | None): fill the board with RGB, None means will not fill with color
- fill_opacity (float): opacity of the fill, range from [0, 1]
- width (float): the width of board
- overlay (bool): fill the color in foreground or background. True means fill in background.
- """
- self._doc.draw_rect(
- rect_coords,
- color=color,
- fill=fill,
- fill_opacity=fill_opacity,
- width=width,
- overlay=overlay,
- )
-
- def insert_text(self, coord, content, fontsize, color):
- """insert text.
-
- Args:
- coord (list[float]): four elements array contain the top-left and bottom-right coordinates, [x0, y0, x1, y1]
- content (str): the text content
- fontsize (int): font size of the text
- color (list[float] | None): three element tuple which describe the RGB of the board line, None will use the default font color!
- """
- self._doc.insert_text(coord, content, fontsize=fontsize, color=color)
\ No newline at end of file
diff --git a/magic_pdf/data/io/__init__.py b/magic_pdf/data/io/__init__.py
deleted file mode 100644
index badf1df07551df611dc955710743f26bf5f60595..0000000000000000000000000000000000000000
--- a/magic_pdf/data/io/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-
-from magic_pdf.data.io.base import IOReader, IOWriter # noqa: F401
-from magic_pdf.data.io.http import HttpReader, HttpWriter # noqa: F401
-from magic_pdf.data.io.s3 import S3Reader, S3Writer # noqa: F401
-
-__all__ = ['IOReader', 'IOWriter', 'HttpReader', 'HttpWriter', 'S3Reader', 'S3Writer']
\ No newline at end of file
diff --git a/magic_pdf/data/read_api.py b/magic_pdf/data/read_api.py
deleted file mode 100644
index 9e52af6d8976910975b987529871b0acbae239bb..0000000000000000000000000000000000000000
--- a/magic_pdf/data/read_api.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import json
-import os
-import tempfile
-import shutil
-from pathlib import Path
-
-from magic_pdf.config.exceptions import EmptyData, InvalidParams
-from magic_pdf.data.data_reader_writer import (FileBasedDataReader,
- MultiBucketS3DataReader)
-from magic_pdf.data.dataset import ImageDataset, PymuDocDataset
-from magic_pdf.utils.office_to_pdf import convert_file_to_pdf, ConvertToPdfError
-
-def read_jsonl(
- s3_path_or_local: str, s3_client: MultiBucketS3DataReader | None = None
-) -> list[PymuDocDataset]:
- """Read the jsonl file and return the list of PymuDocDataset.
-
- Args:
- s3_path_or_local (str): local file or s3 path
- s3_client (MultiBucketS3DataReader | None, optional): s3 client that support multiple bucket. Defaults to None.
-
- Raises:
- InvalidParams: if s3_path_or_local is s3 path but s3_client is not provided.
- EmptyData: if no pdf file location is provided in some line of jsonl file.
- InvalidParams: if the file location is s3 path but s3_client is not provided
-
- Returns:
- list[PymuDocDataset]: each line in the jsonl file will be converted to a PymuDocDataset
- """
- bits_arr = []
- if s3_path_or_local.startswith('s3://'):
- if s3_client is None:
- raise InvalidParams('s3_client is required when s3_path is provided')
- jsonl_bits = s3_client.read(s3_path_or_local)
- else:
- jsonl_bits = FileBasedDataReader('').read(s3_path_or_local)
- jsonl_d = [
- json.loads(line) for line in jsonl_bits.decode().split('\n') if line.strip()
- ]
- for d in jsonl_d:
- pdf_path = d.get('file_location', '') or d.get('path', '')
- if len(pdf_path) == 0:
- raise EmptyData('pdf file location is empty')
- if pdf_path.startswith('s3://'):
- if s3_client is None:
- raise InvalidParams('s3_client is required when s3_path is provided')
- bits_arr.append(s3_client.read(pdf_path))
- else:
- bits_arr.append(FileBasedDataReader('').read(pdf_path))
- return [PymuDocDataset(bits) for bits in bits_arr]
-
-
-def read_local_pdfs(path: str) -> list[PymuDocDataset]:
- """Read pdf from path or directory.
-
- Args:
- path (str): pdf file path or directory that contains pdf files
-
- Returns:
- list[PymuDocDataset]: each pdf file will converted to a PymuDocDataset
- """
- if os.path.isdir(path):
- reader = FileBasedDataReader()
- ret = []
- for root, _, files in os.walk(path):
- for file in files:
- suffix = file.split('.')
- if suffix[-1] == 'pdf':
- ret.append( PymuDocDataset(reader.read(os.path.join(root, file))))
- return ret
- else:
- reader = FileBasedDataReader()
- bits = reader.read(path)
- return [PymuDocDataset(bits)]
-
-def read_local_office(path: str) -> list[PymuDocDataset]:
- """Read ms-office file (ppt, pptx, doc, docx) from path or directory.
-
- Args:
- path (str): ms-office file or directory that contains ms-office files
-
- Returns:
- list[PymuDocDataset]: each ms-office file will converted to a PymuDocDataset
-
- Raises:
- ConvertToPdfError: Failed to convert ms-office file to pdf via libreoffice
- FileNotFoundError: File not Found
- Exception: Unknown Exception raised
- """
- suffixes = ['.ppt', '.pptx', '.doc', '.docx']
- fns = []
- ret = []
- if os.path.isdir(path):
- for root, _, files in os.walk(path):
- for file in files:
- suffix = Path(file).suffix
- if suffix in suffixes:
- fns.append((os.path.join(root, file)))
- else:
- fns.append(path)
-
- reader = FileBasedDataReader()
- temp_dir = tempfile.mkdtemp()
- for fn in fns:
- try:
- convert_file_to_pdf(fn, temp_dir)
- except ConvertToPdfError as e:
- raise e
- except FileNotFoundError as e:
- raise e
- except Exception as e:
- raise e
- fn_path = Path(fn)
- pdf_fn = f"{temp_dir}/{fn_path.stem}.pdf"
- ret.append(PymuDocDataset(reader.read(pdf_fn)))
- shutil.rmtree(temp_dir)
- return ret
-
-def read_local_images(path: str, suffixes: list[str]=['.png', '.jpg', '.jpeg']) -> list[ImageDataset]:
- """Read images from path or directory.
-
- Args:
- path (str): image file path or directory that contains image files
- suffixes (list[str]): the suffixes of the image files used to filter the files. Example: ['.jpg', '.png']
-
- Returns:
- list[ImageDataset]: each image file will converted to a ImageDataset
- """
- if os.path.isdir(path):
- imgs_bits = []
- s_suffixes = set(suffixes)
- reader = FileBasedDataReader()
- for root, _, files in os.walk(path):
- for file in files:
- suffix = Path(file).suffix
- if suffix in s_suffixes:
- imgs_bits.append(reader.read(os.path.join(root, file)))
- return [ImageDataset(bits) for bits in imgs_bits]
- else:
- reader = FileBasedDataReader()
- bits = reader.read(path)
- return [ImageDataset(bits)]
diff --git a/magic_pdf/data/utils.py b/magic_pdf/data/utils.py
deleted file mode 100644
index 849fa780939ddba531029500b158280658af8ea3..0000000000000000000000000000000000000000
--- a/magic_pdf/data/utils.py
+++ /dev/null
@@ -1,166 +0,0 @@
-
-import multiprocessing as mp
-import threading
-from concurrent.futures import (ProcessPoolExecutor, ThreadPoolExecutor,
- as_completed)
-
-import fitz
-import numpy as np
-from loguru import logger
-
-
-
-def fitz_doc_to_image(page, dpi=200) -> dict:
- """Convert fitz.Document to image, Then convert the image to numpy array.
-
- Args:
- page (_type_): pymudoc page
- dpi (int, optional): reset the dpi of dpi. Defaults to 200.
-
- Returns:
- dict: {'img': numpy array, 'width': width, 'height': height }
- """
- mat = fitz.Matrix(dpi / 72, dpi / 72)
- pm = page.get_pixmap(matrix=mat, alpha=False)
-
- # If the width or height exceeds 4500 after scaling, do not scale further.
- if pm.width > 4500 or pm.height > 4500:
- pm = page.get_pixmap(matrix=fitz.Matrix(1, 1), alpha=False)
-
- # Convert pixmap samples directly to numpy array
- img = np.frombuffer(pm.samples, dtype=np.uint8).reshape(pm.height, pm.width, 3)
-
- img_dict = {'img': img, 'width': pm.width, 'height': pm.height}
-
- return img_dict
-
-def load_images_from_pdf(pdf_bytes: bytes, dpi=200, start_page_id=0, end_page_id=None) -> list:
- images = []
- with fitz.open('pdf', pdf_bytes) as doc:
- pdf_page_num = doc.page_count
- end_page_id = (
- end_page_id
- if end_page_id is not None and end_page_id >= 0
- else pdf_page_num - 1
- )
- if end_page_id > pdf_page_num - 1:
- logger.warning('end_page_id is out of range, use images length')
- end_page_id = pdf_page_num - 1
-
- for index in range(0, doc.page_count):
- if start_page_id <= index <= end_page_id:
- page = doc[index]
- mat = fitz.Matrix(dpi / 72, dpi / 72)
- pm = page.get_pixmap(matrix=mat, alpha=False)
-
- # If the width or height exceeds 4500 after scaling, do not scale further.
- if pm.width > 4500 or pm.height > 4500:
- pm = page.get_pixmap(matrix=fitz.Matrix(1, 1), alpha=False)
-
- # Convert pixmap samples directly to numpy array
- img = np.frombuffer(pm.samples, dtype=np.uint8).reshape(pm.height, pm.width, 3)
-
- img_dict = {'img': img, 'width': pm.width, 'height': pm.height}
- else:
- img_dict = {'img': [], 'width': 0, 'height': 0}
-
- images.append(img_dict)
- return images
-
-
-def convert_page(bytes_page):
- pdfs = fitz.open('pdf', bytes_page)
- page = pdfs[0]
- return fitz_doc_to_image(page)
-
-def parallel_process_pdf_safe(pages, num_workers=None, **kwargs):
- """Process PDF pages in parallel with serialization-safe approach."""
- if num_workers is None:
- num_workers = mp.cpu_count()
-
-
- # Process the extracted page data in parallel
- with ProcessPoolExecutor(max_workers=num_workers) as executor:
- # Process the page data
- results = list(
- executor.map(convert_page, pages)
- )
-
- return results
-
-
-def threaded_process_pdf(pdf_path, num_threads=4, **kwargs):
- """Process all pages of a PDF using multiple threads.
-
- Parameters:
- -----------
- pdf_path : str
- Path to the PDF file
- num_threads : int
- Number of threads to use
- **kwargs :
- Additional arguments for fitz_doc_to_image
-
- Returns:
- --------
- images : list
- List of processed images, in page order
- """
- # Open the PDF
- doc = fitz.open(pdf_path)
- num_pages = len(doc)
-
- # Create a list to store results in the correct order
- results = [None] * num_pages
-
- # Create a thread pool
- with ThreadPoolExecutor(max_workers=num_threads) as executor:
- # Submit all tasks
- futures = {}
- for page_num in range(num_pages):
- page = doc[page_num]
- future = executor.submit(fitz_doc_to_image, page, **kwargs)
- futures[future] = page_num
- # Process results as they complete with progress bar
- for future in as_completed(futures):
- page_num = futures[future]
- try:
- results[page_num] = future.result()
- except Exception as e:
- print(f'Error processing page {page_num}: {e}')
- results[page_num] = None
-
- # Close the document
- doc.close()
-
-if __name__ == '__main__':
- pdf = fitz.open('/tmp/[MS-DOC].pdf')
-
-
- pdf_page = [fitz.open() for i in range(pdf.page_count)]
- [pdf_page[i].insert_pdf(pdf, from_page=i, to_page=i) for i in range(pdf.page_count)]
-
- pdf_page = [v.tobytes() for v in pdf_page]
- results = parallel_process_pdf_safe(pdf_page, num_workers=16)
-
- # threaded_process_pdf('/tmp/[MS-DOC].pdf', num_threads=16)
-
- """ benchmark results of multi-threaded processing (fitz page to image)
- total page nums: 578
- thread nums, time cost
- 1 7.351 sec
- 2 6.334 sec
- 4 5.968 sec
- 8 6.728 sec
- 16 8.085 sec
- """
-
- """ benchmark results of multi-processor processing (fitz page to image)
- total page nums: 578
- processor nums, time cost
- 1 17.170 sec
- 2 10.170 sec
- 4 7.841 sec
- 8 7.900 sec
- 16 7.984 sec
- """
diff --git a/magic_pdf/filter/__init__.py b/magic_pdf/filter/__init__.py
deleted file mode 100644
index 280156358b1417c1526ade41302a7f21b09863e0..0000000000000000000000000000000000000000
--- a/magic_pdf/filter/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-
-from magic_pdf.config.drop_reason import DropReason
-from magic_pdf.config.enums import SupportedPdfParseMethod
-from magic_pdf.filter.pdf_classify_by_type import classify as do_classify
-from magic_pdf.filter.pdf_meta_scan import pdf_meta_scan
-
-
-def classify(pdf_bytes: bytes) -> SupportedPdfParseMethod:
- """根据pdf的元数据,判断是文本pdf,还是ocr pdf."""
- pdf_meta = pdf_meta_scan(pdf_bytes)
- if pdf_meta.get('_need_drop', False): # 如果返回了需要丢弃的标志,则抛出异常
- raise Exception(f"pdf meta_scan need_drop,reason is {pdf_meta['_drop_reason']}")
- else:
- is_encrypted = pdf_meta['is_encrypted']
- is_needs_password = pdf_meta['is_needs_password']
- if is_encrypted or is_needs_password: # 加密的,需要密码的,没有页面的,都不处理
- raise Exception(f'pdf meta_scan need_drop,reason is {DropReason.ENCRYPTED}')
- else:
- is_text_pdf, results = do_classify(
- pdf_meta['total_page'],
- pdf_meta['page_width_pts'],
- pdf_meta['page_height_pts'],
- pdf_meta['image_info_per_page'],
- pdf_meta['text_len_per_page'],
- pdf_meta['imgs_per_page'],
- # pdf_meta['text_layout_per_page'],
- pdf_meta['invalid_chars'],
- )
- if is_text_pdf:
- return SupportedPdfParseMethod.TXT
- else:
- return SupportedPdfParseMethod.OCR
diff --git a/magic_pdf/filter/pdf_classify_by_type.py b/magic_pdf/filter/pdf_classify_by_type.py
deleted file mode 100644
index 50665737287c2d1798924c3aa30980ce280a3c7d..0000000000000000000000000000000000000000
--- a/magic_pdf/filter/pdf_classify_by_type.py
+++ /dev/null
@@ -1,395 +0,0 @@
-"""
-根据利用meta_scan得到的结果,对pdf是否为文字版进行分类。
-定义标准:
-一、什么pdf会是文字pdf,只要满足以下任意一条
- 1. 随机抽取N页,如果有任何一页文字数目大于100
- 2. 只要存在一个页面,图片的数量为0
-二、什么是扫描版pdf,只要满足以下任意一条
- 1. ~~80%页面上的最大图大小一样并且面积超过页面面积0.6~~
- 2. 大部分页面上文字的长度都是相等的。
-
-"""
-import json
-import sys
-from collections import Counter
-
-import click
-import numpy as np
-from loguru import logger
-
-from magic_pdf.libs.commons import mymax, get_top_percent_list
-from magic_pdf.filter.pdf_meta_scan import scan_max_page, junk_limit_min
-
-TEXT_LEN_THRESHOLD = 100
-AVG_TEXT_LEN_THRESHOLD = 100
-TEXT_LEN_SAMPLE_RATIO = 0.1 # 抽取0.1的页面进行文字长度统计
-
-
-# 一个拼接图片的方案,将某些特殊扫描版本的拆图拼成一张整图
-def merge_images(image_list, page_width, page_height, max_offset=5, max_gap=2):
- # 先通过set去除所有bbox重叠的图片数据
- image_list_result = []
- for page_images in image_list:
- page_result = []
- dedup = set()
- for img in page_images:
- x0, y0, x1, y1, img_bojid = img
- if (x0, y0, x1, y1) in dedup: # 这里面会出现一些重复的bbox,无需重复出现,需要去掉
- continue
- else:
- dedup.add((x0, y0, x1, y1))
- page_result.append([x0, y0, x1, y1, img_bojid])
- image_list_result.append(page_result)
-
- # 接下来,将同一页可拼接的图片进行合并
- merged_images = []
- for page_images in image_list_result:
- if not page_images:
- continue
-
- # 先将同一页的图片从上到下,从左到右进行排序
- page_images.sort(key=lambda img: (img[1], img[0]))
-
- merged = [page_images[0]]
-
- for img in page_images[1:]:
- x0, y0, x1, y1, imgid = img
-
- last_img = merged[-1]
- last_x0, last_y0, last_x1, last_y1, last_imgid = last_img
-
- # 单张图片宽或者高覆盖页面宽高的9成以上是拼图的一个前置条件
- full_width = abs(x1 - x0) >= page_width * 0.9
- full_height = abs(y1 - y0) >= page_height * 0.9
-
- # 如果宽达标,检测是否能竖着拼
- if full_width:
- # 竖着拼需要满足两个前提,左右边界各偏移不能超过 max_offset,第一张图的下边界和第二张图的上边界偏移不能超过 max_gap
- close1 = (last_x0 - max_offset) <= x0 <= (last_x0 + max_offset) and (last_x1 - max_offset) <= x1 <= (
- last_x1 + max_offset) and (last_y1 - max_gap) <= y0 <= (last_y1 + max_gap)
-
- # 如果高达标,检测是否可以横着拼
- if full_height:
- # 横着拼需要满足两个前提,上下边界各偏移不能超过 max_offset,第一张图的右边界和第二张图的左边界偏移不能超过 max_gap
- close2 = (last_y0 - max_offset) <= y0 <= (last_y0 + max_offset) and (last_y1 - max_offset) <= y1 <= (
- last_y1 + max_offset) and (last_x1 - max_gap) <= x0 <= (last_x1 + max_gap)
-
- # Check if the image can be merged with the last image
- if (full_width and close1) or (full_height and close2):
- # Merge the image with the last image
- merged[-1] = [min(x0, last_x0), min(y0, last_y0),
- max(x1, last_x1), max(y1, last_y1), imgid]
- else:
- # Add the image as a new image
- merged.append(img)
-
- merged_images.append(merged)
-
- return merged_images
-
-
-def classify_by_area(total_page: int, page_width, page_height, img_sz_list, text_len_list: list):
- """
- 80%页面上的最大图大小一样并且面积超过页面面积0.6则返回False,否则返回True
- :param pdf_path:
- :param total_page:
- :param page_width:
- :param page_height:
- :param img_sz_list:
- :return:
- """
- # # 只要有一页没有图片,那么就是文字pdf。但是同时还需要满足一个条件就是这个页面上同时不能有文字。发现过一些扫描版pdf,上面有一些空白页面,既没有图片也没有文字。
- # if any([len(img_sz) == 0 for img_sz in img_sz_list]): # 含有不含图片的页面
- # # 现在找到这些页面的index
- # empty_page_index = [i for i, img_sz in enumerate(img_sz_list) if len(img_sz) == 0]
- # # 然后检查这些页面上是否有文字
- # text_len_at_page_idx = [text_len for i, text_len in enumerate(text_len_list) if i in empty_page_index and text_len > 0]
- # if len(text_len_at_page_idx) > TEXT_LEN_THRESHOLD: # 没有图片,但是有文字,说明可能是个文字版,如果没有文字则无法判断,留给下一步,现在要求这页文字量超过一定阈值
- # return True
-
- # 通过objid去掉重复出现10次以上的图片,这些图片是隐藏的透明图层,其特点是id都一样
- # 先对每个id出现的次数做个统计
- objid_cnt = Counter([objid for page_img_sz in img_sz_list for _, _, _, _, objid in page_img_sz])
- # 再去掉出现次数大于10的
- if total_page >= scan_max_page: # 新的meta_scan只扫描前 scan_max_page 页,页数大于 scan_max_page 当total_page为 scan_max_page
- total_page = scan_max_page
-
- repeat_threshold = 2 # 把bad_image的阈值设为2
- # repeat_threshold = min(2, total_page) # 当total_page为1时,repeat_threshold为1,会产生误判导致所有img变成bad_img
- bad_image_objid = set([objid for objid, cnt in objid_cnt.items() if cnt >= repeat_threshold])
- # bad_image_page_idx = [i for i, page_img_sz in enumerate(img_sz_list) if any([objid in bad_image_objid for _, _, _, _, objid in page_img_sz])]
- # text_len_at_bad_image_page_idx = [text_len for i, text_len in enumerate(text_len_list) if i in bad_image_page_idx and text_len > 0]
-
- # 特殊情况,一个文字版pdf,每页覆盖一个超大的透明图片,超大的定义是图片占整页面积的90%以上
- # fake_image_ids = [objid for objid in bad_image_objid if
- # any([abs((x1 - x0) * (y1 - y0) / page_width * page_height) > 0.9 for images in img_sz_list for
- # x0, y0, x1, y1, _ in images])] # 原来的代码,any里面恒为true了,原因???
- # fake_image_ids = [objid for objid in bad_image_objid for images in img_sz_list for x0, y0, x1, y1, img_id in images
- # if img_id == objid and abs((x1 - x0) * (y1 - y0)) / (page_width * page_height) > 0.9]
-
- # if len(fake_image_ids) > 0 and any([l > TEXT_LEN_THRESHOLD for l in text_len_at_bad_image_page_idx]): # 这些透明图片所在的页面上有文字大于阈值
- # return True
-
- img_sz_list = [[img_sz for img_sz in page_img_sz if img_sz[-1] not in bad_image_objid] for page_img_sz in
- img_sz_list] # 过滤掉重复出现的图片
-
- # 有的扫描版会把一页图片拆成很多张,需要先把图拼起来再计算
- img_sz_list = merge_images(img_sz_list, page_width, page_height)
-
- # 计算每个页面上最大的图的面积,然后计算这个面积占页面面积的比例
- max_image_area_per_page = [mymax([(x1 - x0) * (y1 - y0) for x0, y0, x1, y1, _ in page_img_sz]) for page_img_sz in
- img_sz_list]
- page_area = page_width * page_height
- max_image_area_per_page = [area / page_area for area in max_image_area_per_page]
- max_image_area_per_page = [area for area in max_image_area_per_page if area > 0.5]
-
- if len(max_image_area_per_page) >= 0.5 * total_page: # 阈值从0.8改到0.5,适配3页里面有两页和两页里面有一页的情况
- # 这里条件成立的前提是把反复出现的图片去掉了。这些图片是隐藏的透明图层,其特点是id都一样
- return False
- else:
- return True
-
-
-def classify_by_text_len(text_len_list: list, total_page: int):
- """
- 随机抽取10%的页面,如果少于5个页面,那么就取全部页面。
- 查看页面上的文字长度,如果有任何一个页面的文字长度大于TEXT_LEN_THRESHOLD,那么就是文字pdf
- :param total_page:
- :param text_len_list:
- :return:
- """
- select_page_cnt = int(total_page * TEXT_LEN_SAMPLE_RATIO) # 选取10%的页面
- if select_page_cnt < 5:
- select_page_cnt = total_page
-
- # # 排除头尾各10页
- # if total_page > 20: # 如果总页数大于20
- # page_range = list(range(10, total_page - 10)) # 从第11页到倒数第11页
- # else:
- # page_range = list(range(total_page)) # 否则选择所有页面
- # page_num = np.random.choice(page_range, min(select_page_cnt, len(page_range)), replace=False)
- # 排除前后10页对只有21,22页的pdf很尴尬,如果选出来的中间那一两页恰好没字容易误判,有了avg_words规则,这个规则可以忽略
- page_num = np.random.choice(total_page, select_page_cnt, replace=False)
- text_len_lst = [text_len_list[i] for i in page_num]
- is_text_pdf = any([text_len > TEXT_LEN_THRESHOLD for text_len in text_len_lst])
- return is_text_pdf
-
-
-def classify_by_avg_words(text_len_list: list):
- """
- 补充规则,如果平均每页字数少于 AVG_TEXT_LEN_THRESHOLD,就不是文字pdf
- 主要是各种图集
- :param text_len_list:
- :return:
- """
- sum_words = sum(text_len_list)
- count_of_numbers = len(text_len_list)
- if count_of_numbers == 0:
- is_text_pdf = False
- else:
- avg_words = round(sum_words / count_of_numbers)
- if avg_words > AVG_TEXT_LEN_THRESHOLD:
- is_text_pdf = True
- else:
- is_text_pdf = False
-
- return is_text_pdf
-
-
-def classify_by_img_num(img_sz_list: list, img_num_list: list):
- """
- 补充规则,有一种扫描版本的PDF,每一页都会放所有的扫描页进去,在 metascan 时会被去重,
- 这种pdf的 metasca 扫描结果的特点是 img_sz_list 内全是空元素,img_num_list中每一页的数量都很大且相同
- :param img_sz_list:
- :param img_num_list:
- :return:
- """
- # 计算img_sz_list中非空元素的个数
- count_img_sz_list_not_none = sum(1 for item in img_sz_list if item)
- # 获取前80%的元素
- top_eighty_percent = get_top_percent_list(img_num_list, 0.8)
- # img_sz_list中非空元素的个数小于1,前80%的元素都相等,且最大值大于等于junk_limit_min
- if count_img_sz_list_not_none <= 1 and len(set(top_eighty_percent)) == 1 and max(img_num_list) >= junk_limit_min:
-
- #拿max和min的值,用来判断list内的值是否全都相等
- # min_imgs = min(img_num_list)
- # max_imgs = max(img_num_list)
- #
- # if count_img_sz_list_not_none == 0 and max_imgs == min_imgs and max_imgs >= junk_limit_min:
- return False # 如果满足这个条件,一定不是文字版pdf
- else:
- return True # 不满足这三个条件,可能是文字版pdf,通过其他规则判断
-
-
-def classify_by_text_layout(text_layout_per_page: list):
- """
- 判断文本布局是否以竖排为主。
-
- Args:
- text_layout_per_page (list): 文本布局列表,列表中的每个元素表示一页的文本布局,
- 值为'vertical'表示竖排,值为'horizontal'表示横排。
-
- Returns:
- bool: 若文本布局以竖排为主,则返回False;否则返回True。
- """
- # 统计text_layout_per_page中竖排的个数
- count_vertical = sum(1 for item in text_layout_per_page if item == 'vertical')
- # 统计text_layout_per_page中横排的个数
- count_horizontal = sum(1 for item in text_layout_per_page if item == 'horizontal')
- # 计算text_layout_per_page中竖排的占比
- known_layout_cnt = count_vertical + count_horizontal
- if known_layout_cnt != 0:
- ratio = count_vertical / known_layout_cnt
- if ratio >= 0.5: # 阈值设为0.5,适配3页里面有2页和两页里有一页的情况
- return False # 文本布局以竖排为主,认为不是文字版pdf
- else:
- return True # 文本布局以横排为主,认为是文字版pdf
- else:
- return False # 文本布局未知,默认认为不是文字版pdf
-
-
-def classify_by_img_narrow_strips(page_width, page_height, img_sz_list):
- """
- 判断一页是否由细长条组成,有两个条件:
- 1. 图片的宽或高达到页面宽或高的90%,且长边需要是窄边长度的数倍以上
- 2. 整个页面所有的图片有80%以上满足条件1
-
- Args:
- page_width (float): 页面宽度
- page_height (float): 页面高度
- img_sz_list (list): 图片尺寸列表,每个元素为一个元组,表示图片的矩形区域和尺寸,形如(x0, y0, x1, y1, size),其中(x0, y0)为矩形区域的左上角坐标,(x1, y1)为矩形区域的右下角坐标,size为图片的尺寸
-
- Returns:
- bool: 如果满足条件的页面的比例小于0.5,返回True,否则返回False
- """
-
- def is_narrow_strip(img):
- x0, y0, x1, y1, _ = img
- width, height = x1 - x0, y1 - y0
- return any([
- # 图片宽度大于等于页面宽度的90%,且宽度大于等于高度4倍
- width >= page_width * 0.9 and width >= height * 4,
- # 图片高度大于等于页面高度的90%,且高度大于等于宽度4倍
- height >= page_height * 0.9 and height >= width * 4,
- ])
-
- # 初始化满足条件的页面数量
- narrow_strip_pages_count = 0
-
- # 遍历所有页面
- for page_img_list in img_sz_list:
- # 忽略空页面
- if not page_img_list:
- continue
-
- # 计算页面中的图片总数
- total_images = len(page_img_list)
-
- # 计算页面中细长条图片的数量
- narrow_strip_images_count = 0
- for img in page_img_list:
- if is_narrow_strip(img):
- narrow_strip_images_count += 1
- # 如果细长条图片的数量少于5,跳过
- if narrow_strip_images_count < 5:
- continue
- else:
- # 如果细长条图片的比例大于或等于0.8,增加满足条件的页面数量
- if narrow_strip_images_count / total_images >= 0.8:
- narrow_strip_pages_count += 1
-
- # 计算满足条件的页面的比例
- narrow_strip_pages_ratio = narrow_strip_pages_count / len(img_sz_list)
-
- return narrow_strip_pages_ratio < 0.5
-
-
-def classify(total_page: int, page_width, page_height, img_sz_list: list, text_len_list: list, img_num_list: list,
- # text_layout_list: list,
- invalid_chars: bool):
- """
- 这里的图片和页面长度单位是pts
- :param total_page:
- :param text_len_list:
- :param page_width:
- :param page_height:
- :param img_sz_list:
- :param pdf_path:
- :return:
- """
- results = {
- 'by_image_area': classify_by_area(total_page, page_width, page_height, img_sz_list, text_len_list),
- 'by_text_len': classify_by_text_len(text_len_list, total_page),
- 'by_avg_words': classify_by_avg_words(text_len_list),
- 'by_img_num': classify_by_img_num(img_sz_list, img_num_list),
- # 'by_text_layout': classify_by_text_layout(text_layout_list),
- 'by_img_narrow_strips': classify_by_img_narrow_strips(page_width, page_height, img_sz_list),
- 'by_invalid_chars': invalid_chars,
- }
-
- if all(results.values()):
- return True, results
- elif not any(results.values()):
- return False, results
- else:
- logger.warning(
- f"OCR needed based on classification result, by_image_area: {results['by_image_area']},"
- f" by_text: {results['by_text_len']}, by_avg_words: {results['by_avg_words']}, by_img_num: {results['by_img_num']},"
- # f" by_text_layout: {results['by_text_layout']},"
- f" by_img_narrow_strips: {results['by_img_narrow_strips']},"
- f" by_invalid_chars: {results['by_invalid_chars']}",
- file=sys.stderr) # 利用这种情况可以快速找出来哪些pdf比较特殊,针对性修正分类算法
- return False, results
-
-
-@click.command()
-@click.option("--json-file", type=str, help="pdf信息")
-def main(json_file):
- if json_file is None:
- print("json_file is None", file=sys.stderr)
- exit(0)
- try:
- with open(json_file, "r") as f:
- for l in f:
- if l.strip() == "":
- continue
- o = json.loads(l)
- total_page = o["total_page"]
- page_width = o["page_width_pts"]
- page_height = o["page_height_pts"]
- img_sz_list = o["image_info_per_page"]
- text_len_list = o['text_len_per_page']
- text_layout_list = o['text_layout_per_page']
- pdf_path = o['pdf_path']
- is_encrypted = o['is_encrypted']
- is_needs_password = o['is_needs_password']
- if is_encrypted or total_page == 0 or is_needs_password: # 加密的,需要密码的,没有页面的,都不处理
- continue
- tag = classify(total_page, page_width, page_height, img_sz_list, text_len_list, text_layout_list)
- o['is_text_pdf'] = tag
- print(json.dumps(o, ensure_ascii=False))
- except Exception as e:
- print("ERROR: ", e, file=sys.stderr)
-
-
-if __name__ == "__main__":
- main()
- # false = False
- # true = True
- # null = None
- # o = {"pdf_path":"s3://llm-raw-snew/llm-raw-the-eye/raw/World%20Tracker%20Library/worldtracker.org/media/library/Science/Computer%20Science/Shreiner%20-%20OpenGL%20Programming%20Guide%206e%20%5BThe%20Redbook%5D%20%28AW%2C%202008%29.pdf","is_needs_password":false,"is_encrypted":false,"total_page":978,"page_width_pts":368,"page_height_pts":513,"image_info_per_page":[[[0,0,368,513,10037]],[[0,0,368,513,4]],[[0,0,368,513,7]],[[0,0,368,513,10]],[[0,0,368,513,13]],[[0,0,368,513,16]],[[0,0,368,513,19]],[[0,0,368,513,22]],[[0,0,368,513,25]],[[0,0,368,513,28]],[[0,0,368,513,31]],[[0,0,368,513,34]],[[0,0,368,513,37]],[[0,0,368,513,40]],[[0,0,368,513,43]],[[0,0,368,513,46]],[[0,0,368,513,49]],[[0,0,368,513,52]],[[0,0,368,513,55]],[[0,0,368,513,58]],[[0,0,368,513,61]],[[0,0,368,513,64]],[[0,0,368,513,67]],[[0,0,368,513,70]],[[0,0,368,513,73]],[[0,0,368,516,76]],[[0,0,368,516,79]],[[0,0,368,513,82]],[[0,0,368,513,85]],[[0,0,368,513,88]],[[0,0,368,513,91]],[[0,0,368,513,94]],[[0,0,368,513,97]],[[0,0,368,513,100]],[[0,0,368,513,103]],[[0,0,368,513,106]],[[0,0,368,513,109]],[[0,0,368,513,112]],[[0,0,368,513,115]],[[0,0,368,513,118]],[[0,0,368,513,121]],[[0,0,368,513,124]],[[0,0,368,513,127]],[[0,0,368,513,130]],[[0,0,368,513,133]],[[0,0,368,513,136]],[[0,0,368,513,139]],[[0,0,368,513,142]],[[0,0,368,513,145]],[[0,0,368,513,148]],[[0,0,368,513,151]],[[0,0,368,513,154]],[[0,0,368,513,157]],[[0,0,368,513,160]],[[0,0,368,513,163]],[[0,0,368,513,166]],[[0,0,368,513,169]],[[0,0,368,513,172]],[[0,0,368,513,175]],[[0,0,368,513,178]],[[0,0,368,513,181]],[[0,0,368,513,184]],[[0,0,368,513,187]],[[0,0,368,513,190]],[[0,0,368,513,193]],[[0,0,368,513,196]],[[0,0,368,513,199]],[[0,0,368,513,202]],[[0,0,368,513,205]],[[0,0,368,513,208]],[[0,0,368,513,211]],[[0,0,368,513,214]],[[0,0,368,513,217]],[[0,0,368,513,220]],[[0,0,368,513,223]],[[0,0,368,513,226]],[[0,0,368,513,229]],[[0,0,368,513,232]],[[0,0,368,513,235]],[[0,0,368,513,238]],[[0,0,368,513,241]],[[0,0,368,513,244]],[[0,0,368,513,247]],[[0,0,368,513,250]],[[0,0,368,513,253]],[[0,0,368,513,256]],[[0,0,368,513,259]],[[0,0,368,513,262]],[[0,0,368,513,265]],[[0,0,368,513,268]],[[0,0,368,513,271]],[[0,0,368,513,274]],[[0,0,368,513,277]],[[0,0,368,513,280]],[[0,0,368,513,283]],[[0,0,368,513,286]],[[0,0,368,513,289]],[[0,0,368,513,292]],[[0,0,368,513,295]],[[0,0,368,513,298]],[[0,0,368,513,301]],[[0,0,368,513,304]],[[0,0,368,513,307]],[[0,0,368,513,310]],[[0,0,368,513,313]],[[0,0,368,513,316]],[[0,0,368,513,319]],[[0,0,368,513,322]],[[0,0,368,513,325]],[[0,0,368,513,328]],[[0,0,368,513,331]],[[0,0,368,513,334]],[[0,0,368,513,337]],[[0,0,368,513,340]],[[0,0,368,513,343]],[[0,0,368,513,346]],[[0,0,368,513,349]],[[0,0,368,513,352]],[[0,0,368,513,355]],[[0,0,368,513,358]],[[0,0,368,513,361]],[[0,0,368,513,364]],[[0,0,368,513,367]],[[0,0,368,513,370]],[[0,0,368,513,373]],[[0,0,368,513,376]],[[0,0,368,513,379]],[[0,0,368,513,382]],[[0,0,368,513,385]],[[0,0,368,513,388]],[[0,0,368,513,391]],[[0,0,368,513,394]],[[0,0,368,513,397]],[[0,0,368,513,400]],[[0,0,368,513,403]],[[0,0,368,513,406]],[[0,0,368,513,409]],[[0,0,368,513,412]],[[0,0,368,513,415]],[[0,0,368,513,418]],[[0,0,368,513,421]],[[0,0,368,513,424]],[[0,0,368,513,427]],[[0,0,368,513,430]],[[0,0,368,513,433]],[[0,0,368,513,436]],[[0,0,368,513,439]],[[0,0,368,513,442]],[[0,0,368,513,445]],[[0,0,368,513,448]],[[0,0,368,513,451]],[[0,0,368,513,454]],[[0,0,368,513,457]],[[0,0,368,513,460]],[[0,0,368,513,463]],[[0,0,368,513,466]],[[0,0,368,513,469]],[[0,0,368,513,472]],[[0,0,368,513,475]],[[0,0,368,513,478]],[[0,0,368,513,481]],[[0,0,368,513,484]],[[0,0,368,513,487]],[[0,0,368,513,490]],[[0,0,368,513,493]],[[0,0,368,513,496]],[[0,0,368,513,499]],[[0,0,368,513,502]],[[0,0,368,513,505]],[[0,0,368,513,508]],[[0,0,368,513,511]],[[0,0,368,513,514]],[[0,0,368,513,517]],[[0,0,368,513,520]],[[0,0,368,513,523]],[[0,0,368,513,526]],[[0,0,368,513,529]],[[0,0,368,513,532]],[[0,0,368,513,535]],[[0,0,368,513,538]],[[0,0,368,513,541]],[[0,0,368,513,544]],[[0,0,368,513,547]],[[0,0,368,513,550]],[[0,0,368,513,553]],[[0,0,368,513,556]],[[0,0,368,513,559]],[[0,0,368,513,562]],[[0,0,368,513,565]],[[0,0,368,513,568]],[[0,0,368,513,571]],[[0,0,368,513,574]],[[0,0,368,513,577]],[[0,0,368,513,580]],[[0,0,368,513,583]],[[0,0,368,513,586]],[[0,0,368,513,589]],[[0,0,368,513,592]],[[0,0,368,513,595]],[[0,0,368,513,598]],[[0,0,368,513,601]],[[0,0,368,513,604]],[[0,0,368,513,607]],[[0,0,368,513,610]],[[0,0,368,513,613]],[[0,0,368,513,616]],[[0,0,368,513,619]],[[0,0,368,513,622]],[[0,0,368,513,625]],[[0,0,368,513,628]],[[0,0,368,513,631]],[[0,0,368,513,634]],[[0,0,368,513,637]],[[0,0,368,513,640]],[[0,0,368,513,643]],[[0,0,368,513,646]],[[0,0,368,513,649]],[[0,0,368,513,652]],[[0,0,368,513,655]],[[0,0,368,513,658]],[[0,0,368,513,661]],[[0,0,368,513,664]],[[0,0,368,513,667]],[[0,0,368,513,670]],[[0,0,368,513,673]],[[0,0,368,513,676]],[[0,0,368,513,679]],[[0,0,368,513,682]],[[0,0,368,513,685]],[[0,0,368,513,688]],[[0,0,368,513,691]],[[0,0,368,513,694]],[[0,0,368,513,697]],[[0,0,368,513,700]],[[0,0,368,513,703]],[[0,0,368,513,706]],[[0,0,368,513,709]],[[0,0,368,513,712]],[[0,0,368,513,715]],[[0,0,368,513,718]],[[0,0,368,513,721]],[[0,0,368,513,724]],[[0,0,368,513,727]],[[0,0,368,513,730]],[[0,0,368,513,733]],[[0,0,368,513,736]],[[0,0,368,513,739]],[[0,0,368,513,742]],[[0,0,368,513,745]],[[0,0,368,513,748]],[[0,0,368,513,751]],[[0,0,368,513,754]],[[0,0,368,513,757]],[[0,0,368,513,760]],[[0,0,368,513,763]],[[0,0,368,513,766]],[[0,0,368,513,769]],[[0,0,368,513,772]],[[0,0,368,513,775]],[[0,0,368,513,778]],[[0,0,368,513,781]],[[0,0,368,513,784]],[[0,0,368,513,787]],[[0,0,368,513,790]],[[0,0,368,513,793]],[[0,0,368,513,796]],[[0,0,368,513,799]],[[0,0,368,513,802]],[[0,0,368,513,805]],[[0,0,368,513,808]],[[0,0,368,513,811]],[[0,0,368,513,814]],[[0,0,368,513,817]],[[0,0,368,513,820]],[[0,0,368,513,823]],[[0,0,368,513,826]],[[0,0,368,513,829]],[[0,0,368,513,832]],[[0,0,368,513,835]],[[0,0,368,513,838]],[[0,0,368,513,841]],[[0,0,368,513,844]],[[0,0,368,513,847]],[[0,0,368,513,850]],[[0,0,368,513,853]],[[0,0,368,513,856]],[[0,0,368,513,859]],[[0,0,368,513,862]],[[0,0,368,513,865]],[[0,0,368,513,868]],[[0,0,368,513,871]],[[0,0,368,513,874]],[[0,0,368,513,877]],[[0,0,368,513,880]],[[0,0,368,513,883]],[[0,0,368,513,886]],[[0,0,368,513,889]],[[0,0,368,513,892]],[[0,0,368,513,895]],[[0,0,368,513,898]],[[0,0,368,513,901]],[[0,0,368,513,904]],[[0,0,368,513,907]],[[0,0,368,513,910]],[[0,0,368,513,913]],[[0,0,368,513,916]],[[0,0,368,513,919]],[[0,0,368,513,922]],[[0,0,368,513,925]],[[0,0,368,513,928]],[[0,0,368,513,931]],[[0,0,368,513,934]],[[0,0,368,513,937]],[[0,0,368,513,940]],[[0,0,368,513,943]],[[0,0,368,513,946]],[[0,0,368,513,949]],[[0,0,368,513,952]],[[0,0,368,513,955]],[[0,0,368,513,958]],[[0,0,368,513,961]],[[0,0,368,513,964]],[[0,0,368,513,967]],[[0,0,368,513,970]],[[0,0,368,513,973]],[[0,0,368,513,976]],[[0,0,368,513,979]],[[0,0,368,513,982]],[[0,0,368,513,985]],[[0,0,368,513,988]],[[0,0,368,513,991]],[[0,0,368,513,994]],[[0,0,368,513,997]],[[0,0,368,513,1000]],[[0,0,368,513,1003]],[[0,0,368,513,1006]],[[0,0,368,513,1009]],[[0,0,368,513,1012]],[[0,0,368,513,1015]],[[0,0,368,513,1018]],[[0,0,368,513,2797]],[[0,0,368,513,2798]],[[0,0,368,513,2799]],[[0,0,368,513,2800]],[[0,0,368,513,2801]],[[0,0,368,513,2802]],[[0,0,368,513,2803]],[[0,0,368,513,2804]],[[0,0,368,513,2805]],[[0,0,368,513,2806]],[[0,0,368,513,2807]],[[0,0,368,513,2808]],[[0,0,368,513,2809]],[[0,0,368,513,2810]],[[0,0,368,513,2811]],[[0,0,368,513,2812]],[[0,0,368,513,2813]],[[0,0,368,513,2814]],[[0,0,368,513,2815]],[[0,0,368,513,2816]],[[0,0,368,513,2817]],[[0,0,368,513,2818]],[[0,0,368,513,2819]],[[0,0,368,513,2820]],[[0,0,368,513,2821]],[[0,0,368,513,2822]],[[0,0,368,513,2823]],[[0,0,368,513,2824]],[[0,0,368,513,2825]],[[0,0,368,513,2826]],[[0,0,368,513,2827]],[[0,0,368,513,2828]],[[0,0,368,513,2829]],[[0,0,368,513,2830]],[[0,0,368,513,2831]],[[0,0,368,513,2832]],[[0,0,368,513,2833]],[[0,0,368,513,2834]],[[0,0,368,513,2835]],[[0,0,368,513,2836]],[[0,0,368,513,2837]],[[0,0,368,513,2838]],[[0,0,368,513,2839]],[[0,0,368,513,2840]],[[0,0,368,513,2841]],[[0,0,368,513,2842]],[[0,0,368,513,2843]],[[0,0,368,513,2844]],[[0,0,368,513,2845]],[[0,0,368,513,2846]],[[0,0,368,513,2847]],[[0,0,368,513,2848]],[[0,0,368,513,2849]],[[0,0,368,513,2850]],[[0,0,368,513,2851]],[[0,0,368,513,2852]],[[0,0,368,513,2853]],[[0,0,368,513,2854]],[[0,0,368,513,2855]],[[0,0,368,513,2856]],[[0,0,368,513,2857]],[[0,0,368,513,2858]],[[0,0,368,513,2859]],[[0,0,368,513,2860]],[[0,0,368,513,2861]],[[0,0,368,513,2862]],[[0,0,368,513,2863]],[[0,0,368,513,2864]],[[0,0,368,513,2797]],[[0,0,368,513,2798]],[[0,0,368,513,2799]],[[0,0,368,513,2800]],[[0,0,368,513,2801]],[[0,0,368,513,2802]],[[0,0,368,513,2803]],[[0,0,368,513,2804]],[[0,0,368,513,2805]],[[0,0,368,513,2806]],[[0,0,368,513,2807]],[[0,0,368,513,2808]],[[0,0,368,513,2809]],[[0,0,368,513,2810]],[[0,0,368,513,2811]],[[0,0,368,513,2812]],[[0,0,368,513,2813]],[[0,0,368,513,2814]],[[0,0,368,513,2815]],[[0,0,368,513,2816]],[[0,0,368,513,2817]],[[0,0,368,513,2818]],[[0,0,368,513,2819]],[[0,0,368,513,2820]],[[0,0,368,513,2821]],[[0,0,368,513,2822]],[[0,0,368,513,2823]],[[0,0,368,513,2824]],[[0,0,368,513,2825]],[[0,0,368,513,2826]],[[0,0,368,513,2827]],[[0,0,368,513,2828]],[[0,0,368,513,2829]],[[0,0,368,513,2830]],[[0,0,368,513,2831]],[[0,0,368,513,2832]],[[0,0,368,513,2833]],[[0,0,368,513,2834]],[[0,0,368,513,2835]],[[0,0,368,513,2836]],[[0,0,368,513,2837]],[[0,0,368,513,2838]],[[0,0,368,513,2839]],[[0,0,368,513,2840]],[[0,0,368,513,2841]],[[0,0,368,513,2842]],[[0,0,368,513,2843]],[[0,0,368,513,2844]],[[0,0,368,513,2845]],[[0,0,368,513,2846]],[[0,0,368,513,2847]],[[0,0,368,513,2848]],[[0,0,368,513,2849]],[[0,0,368,513,2850]],[[0,0,368,513,2851]],[[0,0,368,513,2852]],[[0,0,368,513,2853]],[[0,0,368,513,2854]],[[0,0,368,513,2855]],[[0,0,368,513,2856]],[[0,0,368,513,2857]],[[0,0,368,513,2858]],[[0,0,368,513,2859]],[[0,0,368,513,2860]],[[0,0,368,513,2861]],[[0,0,368,513,2862]],[[0,0,368,513,2863]],[[0,0,368,513,2864]],[[0,0,368,513,1293]],[[0,0,368,513,1296]],[[0,0,368,513,1299]],[[0,0,368,513,1302]],[[0,0,368,513,1305]],[[0,0,368,513,1308]],[[0,0,368,513,1311]],[[0,0,368,513,1314]],[[0,0,368,513,1317]],[[0,0,368,513,1320]],[[0,0,368,513,1323]],[[0,0,368,513,1326]],[[0,0,368,513,1329]],[[0,0,368,513,1332]],[[0,0,368,513,1335]],[[0,0,368,513,1338]],[[0,0,368,513,1341]],[[0,0,368,513,1344]],[[0,0,368,513,1347]],[[0,0,368,513,1350]],[[0,0,368,513,1353]],[[0,0,368,513,1356]],[[0,0,368,513,1359]],[[0,0,368,513,1362]],[[0,0,368,513,1365]],[[0,0,368,513,1368]],[[0,0,368,513,1371]],[[0,0,368,513,1374]],[[0,0,368,513,1377]],[[0,0,368,513,1380]],[[0,0,368,513,1383]],[[0,0,368,513,1386]],[[0,0,368,513,1389]],[[0,0,368,513,1392]],[[0,0,368,513,1395]],[[0,0,368,513,1398]],[[0,0,368,513,1401]],[[0,0,368,513,1404]],[[0,0,368,513,1407]],[[0,0,368,513,1410]],[[0,0,368,513,1413]],[[0,0,368,513,1416]],[[0,0,368,513,1419]],[[0,0,368,513,1422]],[[0,0,368,513,1425]],[[0,0,368,513,1428]],[[0,0,368,513,1431]],[[0,0,368,513,1434]],[[0,0,368,513,1437]],[[0,0,368,513,1440]],[[0,0,368,513,1443]],[[0,0,368,513,1446]],[[0,0,368,513,1449]],[[0,0,368,513,1452]],[[0,0,368,513,1455]],[[0,0,368,513,1458]],[[0,0,368,513,1461]],[[0,0,368,513,1464]],[[0,0,368,513,1467]],[[0,0,368,513,1470]],[[0,0,368,513,1473]],[[0,0,368,513,1476]],[[0,0,368,513,1479]],[[0,0,368,513,1482]],[[0,0,368,513,1485]],[[0,0,368,513,1488]],[[0,0,368,513,1491]],[[0,0,368,513,1494]],[[0,0,368,513,1497]],[[0,0,368,513,1500]],[[0,0,368,513,1503]],[[0,0,368,513,1506]],[[0,0,368,513,1509]],[[0,0,368,513,1512]],[[0,0,368,513,1515]],[[0,0,368,513,1518]],[[0,0,368,513,1521]],[[0,0,368,513,1524]],[[0,0,368,513,1527]],[[0,0,368,513,1530]],[[0,0,368,513,1533]],[[0,0,368,513,1536]],[[0,0,368,513,1539]],[[0,0,368,513,1542]],[[0,0,368,513,1545]],[[0,0,368,513,1548]],[[0,0,368,513,1551]],[[0,0,368,513,1554]],[[0,0,368,513,1557]],[[0,0,368,513,1560]],[[0,0,368,513,1563]],[[0,0,368,513,1566]],[[0,0,368,513,1569]],[[0,0,368,513,1572]],[[0,0,368,513,1575]],[[0,0,368,513,1578]],[[0,0,368,513,1581]],[[0,0,368,513,1584]],[[0,0,368,513,1587]],[[0,0,368,513,1590]],[[0,0,368,513,1593]],[[0,0,368,513,1596]],[[0,0,368,513,1599]],[[0,0,368,513,1602]],[[0,0,368,513,1605]],[[0,0,368,513,1608]],[[0,0,368,513,1611]],[[0,0,368,513,1614]],[[0,0,368,513,1617]],[[0,0,368,513,1620]],[[0,0,368,513,1623]],[[0,0,368,513,1626]],[[0,0,368,513,1629]],[[0,0,368,513,1632]],[[0,0,368,513,1635]],[[0,0,368,513,1638]],[[0,0,368,513,1641]],[[0,0,368,513,1644]],[[0,0,368,513,1647]],[[0,0,368,513,1650]],[[0,0,368,513,1653]],[[0,0,368,513,1656]],[[0,0,368,513,1659]],[[0,0,368,513,1662]],[[0,0,368,513,1665]],[[0,0,368,513,1668]],[[0,0,368,513,1671]],[[0,0,368,513,1674]],[[0,0,368,513,1677]],[[0,0,368,513,1680]],[[0,0,368,513,1683]],[[0,0,368,513,1686]],[[0,0,368,513,1689]],[[0,0,368,513,1692]],[[0,0,368,513,1695]],[[0,0,368,513,1698]],[[0,0,368,513,1701]],[[0,0,368,513,1704]],[[0,0,368,513,1707]],[[0,0,368,513,1710]],[[0,0,368,513,1713]],[[0,0,368,513,1716]],[[0,0,368,513,1719]],[[0,0,368,513,1722]],[[0,0,368,513,1725]],[[0,0,368,513,1728]],[[0,0,368,513,1731]],[[0,0,368,513,1734]],[[0,0,368,513,1737]],[[0,0,368,513,1740]],[[0,0,368,513,1743]],[[0,0,368,513,1746]],[[0,0,368,513,1749]],[[0,0,368,513,1752]],[[0,0,368,513,1755]],[[0,0,368,513,1758]],[[0,0,368,513,1761]],[[0,0,368,513,1764]],[[0,0,368,513,1767]],[[0,0,368,513,1770]],[[0,0,368,513,1773]],[[0,0,368,513,1776]],[[0,0,368,513,1779]],[[0,0,368,513,1782]],[[0,0,368,513,1785]],[[0,0,368,513,1788]],[[0,0,368,513,1791]],[[0,0,368,513,1794]],[[0,0,368,513,1797]],[[0,0,368,513,1800]],[[0,0,368,513,1803]],[[0,0,368,513,1806]],[[0,0,368,513,1809]],[[0,0,368,513,1812]],[[0,0,368,513,1815]],[[0,0,368,513,1818]],[[0,0,368,513,1821]],[[0,0,368,513,1824]],[[0,0,368,513,1827]],[[0,0,368,513,1830]],[[0,0,368,513,1833]],[[0,0,368,513,1836]],[[0,0,368,513,1839]],[[0,0,368,513,1842]],[[0,0,368,513,1845]],[[0,0,368,513,1848]],[[0,0,368,513,1851]],[[0,0,368,513,1854]],[[0,0,368,513,1857]],[[0,0,368,513,1860]],[[0,0,368,513,1863]],[[0,0,368,513,1866]],[[0,0,368,513,1869]],[[0,0,368,513,1872]],[[0,0,368,513,1875]],[[0,0,368,513,1878]],[[0,0,368,513,1881]],[[0,0,368,513,1884]],[[0,0,368,513,1887]],[[0,0,368,513,1890]],[[0,0,368,513,1893]],[[0,0,368,513,1896]],[[0,0,368,513,1899]],[[0,0,368,513,1902]],[[0,0,368,513,1905]],[[0,0,368,513,1908]],[[0,0,368,513,1911]],[[0,0,368,513,1914]],[[0,0,368,513,1917]],[[0,0,368,513,1920]],[[0,0,368,513,1923]],[[0,0,368,513,1926]],[[0,0,368,513,1929]],[[0,0,368,513,1932]],[[0,0,368,513,1935]],[[0,0,368,513,1938]],[[0,0,368,513,1941]],[[0,0,368,513,1944]],[[0,0,368,513,1947]],[[0,0,368,513,1950]],[[0,0,368,513,1953]],[[0,0,368,513,1956]],[[0,0,368,513,1959]],[[0,0,368,513,1962]],[[0,0,368,513,1965]],[[0,0,368,513,1968]],[[0,0,368,513,1971]],[[0,0,368,513,1974]],[[0,0,368,513,1977]],[[0,0,368,513,1980]],[[0,0,368,513,1983]],[[0,0,368,513,1986]],[[0,0,368,513,1989]],[[0,0,368,513,1992]],[[0,0,368,513,1995]],[[0,0,368,513,1998]],[[0,0,368,513,2001]],[[0,0,368,513,2004]],[[0,0,368,513,2007]],[[0,0,368,513,2010]],[[0,0,368,513,2013]],[[0,0,368,513,2016]],[[0,0,368,513,2019]],[[0,0,368,513,2022]],[[0,0,368,513,2025]],[[0,0,368,513,2028]],[[0,0,368,513,2031]],[[0,0,368,513,2034]],[[0,0,368,513,2037]],[[0,0,368,513,2040]],[[0,0,368,513,2043]],[[0,0,368,513,2046]],[[0,0,368,513,2049]],[[0,0,368,513,2052]],[[0,0,368,513,2055]],[[0,0,368,513,2058]],[[0,0,368,513,2061]],[[0,0,368,513,2064]],[[0,0,368,513,2067]],[[0,0,368,513,2070]],[[0,0,368,513,2073]],[[0,0,368,513,2076]],[[0,0,368,513,2079]],[[0,0,368,513,2082]],[[0,0,368,513,2085]],[[0,0,368,513,2088]],[[0,0,368,513,2091]],[[0,0,368,513,2094]],[[0,0,368,513,2097]],[[0,0,368,513,2100]],[[0,0,368,513,2103]],[[0,0,368,513,2106]],[[0,0,368,513,2109]],[[0,0,368,513,2112]],[[0,0,368,513,2115]],[[0,0,368,513,2118]],[[0,0,368,513,2121]],[[0,0,368,513,2124]],[[0,0,368,513,2127]],[[0,0,368,513,2130]],[[0,0,368,513,2133]],[[0,0,368,513,2136]],[[0,0,368,513,2139]],[[0,0,368,513,2142]],[[0,0,368,513,2145]],[[0,0,368,513,2148]],[[0,0,368,513,2151]],[[0,0,368,513,2154]],[[0,0,368,513,2157]],[[0,0,368,513,2160]],[[0,0,368,513,2163]],[[0,0,368,513,2166]],[[0,0,368,513,2169]],[[0,0,368,513,2172]],[[0,0,368,513,2175]],[[0,0,368,513,2178]],[[0,0,368,513,2181]],[[0,0,368,513,2184]],[[0,0,368,513,2187]],[[0,0,368,513,2190]],[[0,0,368,513,2193]],[[0,0,368,513,2196]],[[0,0,368,513,2199]],[[0,0,368,513,2202]],[[0,0,368,513,2205]],[[0,0,368,513,2208]],[[0,0,368,513,2211]],[[0,0,368,513,2214]],[[0,0,368,513,2217]],[[0,0,368,513,2220]],[[0,0,368,513,2223]],[[0,0,368,513,2226]],[[0,0,368,513,2229]],[[0,0,368,513,2232]],[[0,0,368,513,2235]],[[0,0,368,513,2238]],[[0,0,368,513,2241]],[[0,0,368,513,2244]],[[0,0,368,513,2247]],[[0,0,368,513,2250]],[[0,0,368,513,2253]],[[0,0,368,513,2256]],[[0,0,368,513,2259]],[[0,0,368,513,2262]],[[0,0,368,513,2265]],[[0,0,368,513,2268]],[[0,0,368,513,2271]],[[0,0,368,513,2274]],[[0,0,368,513,2277]],[[0,0,368,513,2280]],[[0,0,368,513,2283]],[[0,0,368,513,2286]],[[0,0,368,513,2289]],[[0,0,368,513,2292]],[[0,0,368,513,2295]],[[0,0,368,513,2298]],[[0,0,368,513,2301]],[[0,0,368,513,2304]],[[0,0,368,513,2307]],[[0,0,368,513,2310]],[[0,0,368,513,2313]],[[0,0,368,513,2316]],[[0,0,368,513,2319]],[[0,0,368,513,2322]],[[0,0,368,513,2325]],[[0,0,368,513,2328]],[[0,0,368,513,2331]],[[0,0,368,513,2334]],[[0,0,368,513,2337]],[[0,0,368,513,2340]],[[0,0,368,513,2343]],[[0,0,368,513,2346]],[[0,0,368,513,2349]],[[0,0,368,513,2352]],[[0,0,368,513,2355]],[[0,0,368,513,2358]],[[0,0,368,513,2361]],[[0,0,368,513,2364]],[[0,0,368,513,2367]],[[0,0,368,513,2370]],[[0,0,368,513,2373]],[[0,0,368,513,2376]],[[0,0,368,513,2379]],[[0,0,368,513,2382]],[[0,0,368,513,2385]],[[0,0,368,513,2388]],[[0,0,368,513,2391]],[[0,0,368,513,2394]],[[0,0,368,513,2397]],[[0,0,368,513,2400]],[[0,0,368,513,2403]],[[0,0,368,513,2406]],[[0,0,368,513,2409]],[[0,0,368,513,2412]],[[0,0,368,513,2415]],[[0,0,368,513,2418]],[[0,0,368,513,2421]],[[0,0,368,513,2424]],[[0,0,368,513,2427]],[[0,0,368,513,2430]],[[0,0,368,513,2433]],[[0,0,368,513,2436]],[[0,0,368,513,2439]],[[0,0,368,513,2442]],[[0,0,368,513,2445]],[[0,0,368,513,2448]],[[0,0,368,513,2451]],[[0,0,368,513,2454]],[[0,0,368,513,2457]],[[0,0,368,513,2460]],[[0,0,368,513,2463]],[[0,0,368,513,2466]],[[0,0,368,513,2469]],[[0,0,368,513,2472]],[[0,0,368,513,2475]],[[0,0,368,513,2478]],[[0,0,368,513,2481]],[[0,0,368,513,2484]],[[0,0,368,513,2487]],[[0,0,368,513,2490]],[[0,0,368,513,2493]],[[0,0,368,513,2496]],[[0,0,368,513,2499]],[[0,0,368,513,2502]],[[0,0,368,513,2505]],[[0,0,368,513,2508]],[[0,0,368,513,2511]],[[0,0,368,513,2514]],[[0,0,368,513,2517]],[[0,0,368,513,2520]],[[0,0,368,513,2523]],[[0,0,368,513,2526]],[[0,0,368,513,2529]],[[0,0,368,513,2532]],[[0,0,368,513,2535]],[[0,0,368,513,2538]],[[0,0,368,513,2541]],[[0,0,368,513,2544]],[[0,0,368,513,2547]],[[0,0,368,513,2550]],[[0,0,368,513,2553]],[[0,0,368,513,2556]],[[0,0,368,513,2559]],[[0,0,368,513,2562]],[[0,0,368,513,2565]],[[0,0,368,513,2568]],[[0,0,368,513,2571]],[[0,0,368,513,2574]],[[0,0,368,513,2577]],[[0,0,368,513,2580]],[[0,0,368,513,2583]],[[0,0,368,513,2586]],[[0,0,368,513,2589]],[[0,0,368,513,2592]],[[0,0,368,513,2595]],[[0,0,368,513,2598]],[[0,0,368,513,2601]],[[0,0,368,513,2604]],[[0,0,368,513,2607]],[[0,0,368,513,2610]],[[0,0,368,513,2613]],[[0,0,368,513,2616]],[[0,0,368,513,2619]],[[0,0,368,513,2622]],[[0,0,368,513,2625]],[[0,0,368,513,2628]],[[0,0,368,513,2631]],[[0,0,368,513,2634]],[[0,0,368,513,2637]],[[0,0,368,513,2640]],[[0,0,368,513,2643]],[[0,0,368,513,2646]],[[0,0,368,513,2649]],[[0,0,368,513,2652]],[[0,0,368,513,2655]],[[0,0,368,513,2658]],[[0,0,368,513,2661]],[[0,0,368,513,2664]],[[0,0,368,513,2667]],[[0,0,368,513,2670]],[[0,0,368,513,2673]],[[0,0,368,513,2676]],[[0,0,368,513,2679]],[[0,0,368,513,2682]],[[0,0,368,513,2685]],[[0,0,368,513,2688]],[[0,0,368,513,2691]],[[0,0,368,513,2694]],[[0,0,368,513,2697]],[[0,0,368,513,2700]],[[0,0,368,513,2703]],[[0,0,368,513,2706]],[[0,0,368,513,2709]],[[0,0,368,513,2712]],[[0,0,368,513,2715]],[[0,0,368,513,2718]],[[0,0,368,513,2721]],[[0,0,368,513,2724]],[[0,0,368,513,2727]],[[0,0,368,513,2730]],[[0,0,368,513,2733]],[[0,0,368,513,2736]],[[0,0,368,513,2739]],[[0,0,368,513,2742]],[[0,0,368,513,2745]],[[0,0,368,513,2748]],[[0,0,368,513,2751]],[[0,0,368,513,2754]],[[0,0,368,513,2757]],[[0,0,368,513,2760]],[[0,0,368,513,2763]],[[0,0,368,513,2766]],[[0,0,368,513,2769]],[[0,0,368,513,2772]],[[0,0,368,513,2775]],[[0,0,368,513,2778]],[[0,0,368,513,2781]],[[0,0,368,513,2784]],[[0,0,368,513,2787]],[[0,0,368,513,2790]],[[0,0,368,513,2793]],[[0,0,368,513,2796]]],"text_len_per_page":[53,53,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54],"metadata":{"format":"PDF 1.6","title":"","author":"","subject":"","keywords":"","creator":"Adobe Acrobat 7.0","producer":"Adobe Acrobat 7.0 Image Conversion Plug-in","creationDate":"D:20080404141457+01'00'","modDate":"D:20080404144821+01'00'","trapped":"","encryption":null}}
- # o = json.loads(json.dumps(o))
- # total_page = o["total_page"]
- # page_width = o["page_width_pts"]
- # page_height = o["page_height_pts"]
- # img_sz_list = o["image_info_per_page"]
- # text_len_list = o['text_len_per_page']
- # pdf_path = o['pdf_path']
- # is_encrypted = o['is_encrypted']
- # is_needs_password = o['is_needs_password']
- # if is_encrypted or total_page == 0 or is_needs_password: # 加密的,需要密码的,没有页面的,都不处理
- # print("加密的")
- # exit(0)
- # tag = classify(pdf_path, total_page, page_width, page_height, img_sz_list, text_len_list)
- # o['is_text_pdf'] = tag
- # print(json.dumps(o, ensure_ascii=False))
diff --git a/magic_pdf/filter/pdf_meta_scan.py b/magic_pdf/filter/pdf_meta_scan.py
deleted file mode 100644
index 67e56315057299b4888bc4058f057d857c0b3dc8..0000000000000000000000000000000000000000
--- a/magic_pdf/filter/pdf_meta_scan.py
+++ /dev/null
@@ -1,397 +0,0 @@
-"""输入: s3路径,每行一个 输出: pdf文件元信息,包括每一页上的所有图片的长宽高,bbox位置."""
-
-from collections import Counter
-
-import fitz
-from loguru import logger
-
-from magic_pdf.config.drop_reason import DropReason
-from magic_pdf.libs.commons import get_top_percent_list, mymax
-from magic_pdf.libs.language import detect_lang
-from magic_pdf.libs.pdf_check import detect_invalid_chars_by_pymupdf, detect_invalid_chars
-
-scan_max_page = 50
-junk_limit_min = 10
-
-
-def calculate_max_image_area_per_page(result: list, page_width_pts, page_height_pts):
- max_image_area_per_page = [
- mymax([(x1 - x0) * (y1 - y0) for x0, y0, x1, y1, _ in page_img_sz])
- for page_img_sz in result
- ]
- page_area = int(page_width_pts) * int(page_height_pts)
- max_image_area_per_page = [area / page_area for area in max_image_area_per_page]
- max_image_area_per_page = [area for area in max_image_area_per_page if area > 0.6]
- return max_image_area_per_page
-
-
-def process_image(page, junk_img_bojids=[]):
- page_result = [] # 存每个页面里的多张图四元组信息
- items = page.get_images()
- dedup = set()
- for img in items:
- # 这里返回的是图片在page上的实际展示的大小。返回一个数组,每个元素第一部分是
- img_bojid = img[
- 0
- ] # 在pdf文件中是全局唯一的,如果这个图反复出现在pdf里那么就可能是垃圾信息,例如水印、页眉页脚等
- if img_bojid in junk_img_bojids: # 如果是垃圾图像,就跳过
- continue
- recs = page.get_image_rects(img, transform=True)
- if recs:
- rec = recs[0][0]
- x0, y0, x1, y1 = map(int, rec)
- width = x1 - x0
- height = y1 - y0
- if (
- x0,
- y0,
- x1,
- y1,
- img_bojid,
- ) in dedup: # 这里面会出现一些重复的bbox,无需重复出现,需要去掉
- continue
- if not all(
- [width, height]
- ): # 长和宽任何一个都不能是0,否则这个图片不可见,没有实际意义
- continue
- dedup.add((x0, y0, x1, y1, img_bojid))
- page_result.append([x0, y0, x1, y1, img_bojid])
- return page_result
-
-
-def get_image_info(doc: fitz.Document, page_width_pts, page_height_pts) -> list:
- """返回每个页面里的图片的四元组,每个页面多个图片。
-
- :param doc:
- :return:
- """
- # 使用 Counter 计数 img_bojid 的出现次数
- img_bojid_counter = Counter(img[0] for page in doc for img in page.get_images())
- # 找出出现次数超过 len(doc) 半数的 img_bojid
-
- junk_limit = max(len(doc) * 0.5, junk_limit_min) # 对一些页数比较少的进行豁免
-
- junk_img_bojids = [
- img_bojid
- for img_bojid, count in img_bojid_counter.items()
- if count >= junk_limit
- ]
-
- # todo 加个判断,用前十页就行,这些垃圾图片需要满足两个条件,不止出现的次数要足够多,而且图片占书页面积的比例要足够大,且图与图大小都差不多
- # 有两种扫描版,一种文字版,这里可能会有误判
- # 扫描版1:每页都有所有扫描页图片,特点是图占比大,每页展示1张
- # 扫描版2,每页存储的扫描页图片数量递增,特点是图占比大,每页展示1张,需要清空junklist跑前50页图片信息用于分类判断
- # 文 字版1.每页存储所有图片,特点是图片占页面比例不大,每页展示可能为0也可能不止1张 这种pdf需要拿前10页抽样检测img大小和个数,如果符合需要清空junklist
- imgs_len_list = [len(page.get_images()) for page in doc]
-
- special_limit_pages = 10
-
- # 统一用前十页结果做判断
- result = []
- break_loop = False
- for i, page in enumerate(doc):
- if break_loop:
- break
- if i >= special_limit_pages:
- break
- page_result = process_image(
- page
- ) # 这里不传junk_img_bojids,拿前十页所有图片信息用于后续分析
- result.append(page_result)
- for item in result:
- if not any(
- item
- ): # 如果任何一页没有图片,说明是个文字版,需要判断是否为特殊文字版
- if (
- max(imgs_len_list) == min(imgs_len_list)
- and max(imgs_len_list) >= junk_limit_min
- ): # 如果是特殊文字版,就把junklist置空并break
- junk_img_bojids = []
- else: # 不是特殊文字版,是个普通文字版,但是存在垃圾图片,不置空junklist
- pass
- break_loop = True
- break
- if not break_loop:
- # 获取前80%的元素
- top_eighty_percent = get_top_percent_list(imgs_len_list, 0.8)
- # 检查前80%的元素是否都相等
- if len(set(top_eighty_percent)) == 1 and max(imgs_len_list) >= junk_limit_min:
- # # 如果前10页跑完都有图,根据每页图片数量是否相等判断是否需要清除junklist
- # if max(imgs_len_list) == min(imgs_len_list) and max(imgs_len_list) >= junk_limit_min:
-
- # 前10页都有图,且每页数量一致,需要检测图片大小占页面的比例判断是否需要清除junklist
- max_image_area_per_page = calculate_max_image_area_per_page(
- result, page_width_pts, page_height_pts
- )
- if (
- len(max_image_area_per_page) < 0.8 * special_limit_pages
- ): # 前10页不全是大图,说明可能是个文字版pdf,把垃圾图片list置空
- junk_img_bojids = []
- else: # 前10页都有图,而且80%都是大图,且每页图片数量一致并都很多,说明是扫描版1,不需要清空junklist
- pass
- else: # 每页图片数量不一致,需要清掉junklist全量跑前50页图片
- junk_img_bojids = []
-
- # 正式进入取前50页图片的信息流程
- result = []
- for i, page in enumerate(doc):
- if i >= scan_max_page:
- break
- page_result = process_image(page, junk_img_bojids)
- # logger.info(f"page {i} img_len: {len(page_result)}")
- result.append(page_result)
-
- return result, junk_img_bojids
-
-
-def get_pdf_page_size_pts(doc: fitz.Document):
- page_cnt = len(doc)
- l: int = min(page_cnt, 50)
- # 把所有宽度和高度塞到两个list 分别取中位数(中间遇到了个在纵页里塞横页的pdf,导致宽高互换了)
- page_width_list = []
- page_height_list = []
- for i in range(l):
- page = doc[i]
- page_rect = page.rect
- page_width_list.append(page_rect.width)
- page_height_list.append(page_rect.height)
-
- page_width_list.sort()
- page_height_list.sort()
-
- median_width = page_width_list[len(page_width_list) // 2]
- median_height = page_height_list[len(page_height_list) // 2]
-
- return median_width, median_height
-
-
-def get_pdf_textlen_per_page(doc: fitz.Document):
- text_len_lst = []
- for page in doc:
- # 拿包含img和text的所有blocks
- # text_block = page.get_text("blocks")
- # 拿所有text的blocks
- # text_block = page.get_text("words")
- # text_block_len = sum([len(t[4]) for t in text_block])
- # 拿所有text的str
- text_block = page.get_text('text')
- text_block_len = len(text_block)
- # logger.info(f"page {page.number} text_block_len: {text_block_len}")
- text_len_lst.append(text_block_len)
-
- return text_len_lst
-
-
-def get_pdf_text_layout_per_page(doc: fitz.Document):
- """根据PDF文档的每一页文本布局,判断该页的文本布局是横向、纵向还是未知。
-
- Args:
- doc (fitz.Document): PDF文档对象。
-
- Returns:
- List[str]: 每一页的文本布局(横向、纵向、未知)。
- """
- text_layout_list = []
-
- for page_id, page in enumerate(doc):
- if page_id >= scan_max_page:
- break
- # 创建每一页的纵向和横向的文本行数计数器
- vertical_count = 0
- horizontal_count = 0
- text_dict = page.get_text('dict')
- if 'blocks' in text_dict:
- for block in text_dict['blocks']:
- if 'lines' in block:
- for line in block['lines']:
- # 获取line的bbox顶点坐标
- x0, y0, x1, y1 = line['bbox']
- # 计算bbox的宽高
- width = x1 - x0
- height = y1 - y0
- # 计算bbox的面积
- area = width * height
- font_sizes = []
- for span in line['spans']:
- if 'size' in span:
- font_sizes.append(span['size'])
- if len(font_sizes) > 0:
- average_font_size = sum(font_sizes) / len(font_sizes)
- else:
- average_font_size = (
- 10 # 有的line拿不到font_size,先定一个阈值100
- )
- if (
- area <= average_font_size**2
- ): # 判断bbox的面积是否小于平均字体大小的平方,单字无法计算是横向还是纵向
- continue
- else:
- if 'wmode' in line: # 通过wmode判断文本方向
- if line['wmode'] == 1: # 判断是否为竖向文本
- vertical_count += 1
- elif line['wmode'] == 0: # 判断是否为横向文本
- horizontal_count += 1
- # if 'dir' in line: # 通过旋转角度计算判断文本方向
- # # 获取行的 "dir" 值
- # dir_value = line['dir']
- # cosine, sine = dir_value
- # # 计算角度
- # angle = math.degrees(math.acos(cosine))
- #
- # # 判断是否为横向文本
- # if abs(angle - 0) < 0.01 or abs(angle - 180) < 0.01:
- # # line_text = ' '.join(span['text'] for span in line['spans'])
- # # print('This line is horizontal:', line_text)
- # horizontal_count += 1
- # # 判断是否为纵向文本
- # elif abs(angle - 90) < 0.01 or abs(angle - 270) < 0.01:
- # # line_text = ' '.join(span['text'] for span in line['spans'])
- # # print('This line is vertical:', line_text)
- # vertical_count += 1
- # print(f"page_id: {page_id}, vertical_count: {vertical_count}, horizontal_count: {horizontal_count}")
- # 判断每一页的文本布局
- if vertical_count == 0 and horizontal_count == 0: # 该页没有文本,无法判断
- text_layout_list.append('unknow')
- continue
- else:
- if vertical_count > horizontal_count: # 该页的文本纵向行数大于横向的
- text_layout_list.append('vertical')
- else: # 该页的文本横向行数大于纵向的
- text_layout_list.append('horizontal')
- # logger.info(f"page_id: {page_id}, vertical_count: {vertical_count}, horizontal_count: {horizontal_count}")
- return text_layout_list
-
-
-"""定义一个自定义异常用来抛出单页svg太多的pdf"""
-
-
-class PageSvgsTooManyError(Exception):
- def __init__(self, message='Page SVGs are too many'):
- self.message = message
- super().__init__(self.message)
-
-
-def get_svgs_per_page(doc: fitz.Document):
- svgs_len_list = []
- for page_id, page in enumerate(doc):
- # svgs = page.get_drawings()
- svgs = page.get_cdrawings() # 切换成get_cdrawings,效率更高
- len_svgs = len(svgs)
- if len_svgs >= 3000:
- raise PageSvgsTooManyError()
- else:
- svgs_len_list.append(len_svgs)
- # logger.info(f"page_id: {page_id}, svgs_len: {len(svgs)}")
- return svgs_len_list
-
-
-def get_imgs_per_page(doc: fitz.Document):
- imgs_len_list = []
- for page_id, page in enumerate(doc):
- imgs = page.get_images()
- imgs_len_list.append(len(imgs))
- # logger.info(f"page_id: {page}, imgs_len: {len(imgs)}")
-
- return imgs_len_list
-
-
-def get_language(doc: fitz.Document):
- """
- 获取PDF文档的语言。
- Args:
- doc (fitz.Document): PDF文档对象。
- Returns:
- str: 文档语言,如 "en-US"。
- """
- language_lst = []
- for page_id, page in enumerate(doc):
- if page_id >= scan_max_page:
- break
- # 拿所有text的str
- text_block = page.get_text('text')
- page_language = detect_lang(text_block)
- language_lst.append(page_language)
-
- # logger.info(f"page_id: {page_id}, page_language: {page_language}")
-
- # 统计text_language_list中每种语言的个数
- count_dict = Counter(language_lst)
- # 输出text_language_list中出现的次数最多的语言
- language = max(count_dict, key=count_dict.get)
- return language
-
-
-def check_invalid_chars(pdf_bytes):
- """乱码检测."""
- # return detect_invalid_chars_by_pymupdf(pdf_bytes)
- return detect_invalid_chars(pdf_bytes)
-
-
-def pdf_meta_scan(pdf_bytes: bytes):
- """
- :param s3_pdf_path:
- :param pdf_bytes: pdf文件的二进制数据
- 几个维度来评价:是否加密,是否需要密码,纸张大小,总页数,是否文字可提取
- """
- doc = fitz.open('pdf', pdf_bytes)
- is_needs_password = doc.needs_pass
- is_encrypted = doc.is_encrypted
- total_page = len(doc)
- if total_page == 0:
- logger.warning(f'drop this pdf, drop_reason: {DropReason.EMPTY_PDF}')
- result = {'_need_drop': True, '_drop_reason': DropReason.EMPTY_PDF}
- return result
- else:
- page_width_pts, page_height_pts = get_pdf_page_size_pts(doc)
- # logger.info(f"page_width_pts: {page_width_pts}, page_height_pts: {page_height_pts}")
-
- # svgs_per_page = get_svgs_per_page(doc)
- # logger.info(f"svgs_per_page: {svgs_per_page}")
- imgs_per_page = get_imgs_per_page(doc)
- # logger.info(f"imgs_per_page: {imgs_per_page}")
-
- image_info_per_page, junk_img_bojids = get_image_info(
- doc, page_width_pts, page_height_pts
- )
- # logger.info(f"image_info_per_page: {image_info_per_page}, junk_img_bojids: {junk_img_bojids}")
- text_len_per_page = get_pdf_textlen_per_page(doc)
- # logger.info(f"text_len_per_page: {text_len_per_page}")
- # text_layout_per_page = get_pdf_text_layout_per_page(doc)
- # logger.info(f"text_layout_per_page: {text_layout_per_page}")
- # text_language = get_language(doc)
- # logger.info(f"text_language: {text_language}")
- invalid_chars = check_invalid_chars(pdf_bytes)
- # logger.info(f"invalid_chars: {invalid_chars}")
-
- # 最后输出一条json
- res = {
- 'is_needs_password': is_needs_password,
- 'is_encrypted': is_encrypted,
- 'total_page': total_page,
- 'page_width_pts': int(page_width_pts),
- 'page_height_pts': int(page_height_pts),
- 'image_info_per_page': image_info_per_page,
- 'text_len_per_page': text_len_per_page,
- # 'text_layout_per_page': text_layout_per_page,
- # 'text_language': text_language,
- # "svgs_per_page": svgs_per_page,
- 'imgs_per_page': imgs_per_page, # 增加每页img数量list
- 'junk_img_bojids': junk_img_bojids, # 增加垃圾图片的bojid list
- 'invalid_chars': invalid_chars,
- 'metadata': doc.metadata,
- }
- # logger.info(json.dumps(res, ensure_ascii=False))
- return res
-
-
-if __name__ == '__main__':
- pass
- # "D:\project/20231108code-clean\pdf_cost_time\竖排例子\净空法师-大乘无量寿.pdf"
- # "D:\project/20231108code-clean\pdf_cost_time\竖排例子\三国演义_繁体竖排版.pdf"
- # "D:\project/20231108code-clean\pdf_cost_time\scihub\scihub_86800000\libgen.scimag86880000-86880999.zip_10.1021/acsami.1c03109.s002.pdf"
- # "D:/project/20231108code-clean/pdf_cost_time/scihub/scihub_18600000/libgen.scimag18645000-18645999.zip_10.1021/om3006239.pdf"
- # file_content = read_file("D:/project/20231108code-clean/pdf_cost_time/scihub/scihub_31000000/libgen.scimag31098000-31098999.zip_10.1109/isit.2006.261791.pdf","") # noqa: E501
- # file_content = read_file("D:\project/20231108code-clean\pdf_cost_time\竖排例子\净空法师_大乘无量寿.pdf","")
- # doc = fitz.open("pdf", file_content)
- # text_layout_lst = get_pdf_text_layout_per_page(doc)
- # print(text_layout_lst)
diff --git a/magic_pdf/integrations/__init__.py b/magic_pdf/integrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/magic_pdf/integrations/rag/__init__.py b/magic_pdf/integrations/rag/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/magic_pdf/integrations/rag/api.py b/magic_pdf/integrations/rag/api.py
deleted file mode 100644
index 5c05f91169dad911b147a4f9c518af26a419b449..0000000000000000000000000000000000000000
--- a/magic_pdf/integrations/rag/api.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import os
-from pathlib import Path
-
-from loguru import logger
-
-from magic_pdf.integrations.rag.type import (ElementRelation, LayoutElements,
- Node)
-from magic_pdf.integrations.rag.utils import inference
-
-
-class RagPageReader:
-
- def __init__(self, pagedata: LayoutElements):
- self.o = [
- Node(
- category_type=v.category_type,
- text=v.text,
- image_path=v.image_path,
- anno_id=v.anno_id,
- latex=v.latex,
- html=v.html,
- ) for v in pagedata.layout_dets
- ]
-
- self.pagedata = pagedata
-
- def __iter__(self):
- return iter(self.o)
-
- def get_rel_map(self) -> list[ElementRelation]:
- return self.pagedata.extra.element_relation
-
-
-class RagDocumentReader:
-
- def __init__(self, ragdata: list[LayoutElements]):
- self.o = [RagPageReader(v) for v in ragdata]
-
- def __iter__(self):
- return iter(self.o)
-
-
-class DataReader:
-
- def __init__(self, path_or_directory: str, method: str, output_dir: str):
- self.path_or_directory = path_or_directory
- self.method = method
- self.output_dir = output_dir
- self.pdfs = []
- if os.path.isdir(path_or_directory):
- for doc_path in Path(path_or_directory).glob('*.pdf'):
- self.pdfs.append(doc_path)
- else:
- assert path_or_directory.endswith('.pdf')
- self.pdfs.append(Path(path_or_directory))
-
- def get_documents_count(self) -> int:
- """Returns the number of documents in the directory."""
- return len(self.pdfs)
-
- def get_document_result(self, idx: int) -> RagDocumentReader | None:
- """
- Args:
- idx (int): the index of documents under the
- directory path_or_directory
-
- Returns:
- RagDocumentReader | None: RagDocumentReader is an iterable object,
- more details @RagDocumentReader
- """
- if idx >= self.get_documents_count() or idx < 0:
- logger.error(f'invalid idx: {idx}')
- return None
- res = inference(str(self.pdfs[idx]), self.output_dir, self.method)
- if res is None:
- logger.warning(f'failed to inference pdf {self.pdfs[idx]}')
- return None
- return RagDocumentReader(res)
-
- def get_document_filename(self, idx: int) -> Path:
- """get the filename of the document."""
- return self.pdfs[idx]
diff --git a/magic_pdf/integrations/rag/type.py b/magic_pdf/integrations/rag/type.py
deleted file mode 100644
index 11258af39487f3084a900d44c5bc4eb364ef2230..0000000000000000000000000000000000000000
--- a/magic_pdf/integrations/rag/type.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from enum import Enum
-
-from pydantic import BaseModel, Field
-
-
-# rag
-class CategoryType(Enum): # py310 not support StrEnum
- text = 'text'
- title = 'title'
- interline_equation = 'interline_equation'
- image = 'image'
- image_body = 'image_body'
- image_caption = 'image_caption'
- table = 'table'
- table_body = 'table_body'
- table_caption = 'table_caption'
- table_footnote = 'table_footnote'
-
-
-class ElementRelType(Enum):
- sibling = 'sibling'
-
-
-class PageInfo(BaseModel):
- page_no: int = Field(description='the index of page, start from zero',
- ge=0)
- height: int = Field(description='the height of page', gt=0)
- width: int = Field(description='the width of page', ge=0)
- image_path: str | None = Field(description='the image of this page',
- default=None)
-
-
-class ContentObject(BaseModel):
- category_type: CategoryType = Field(description='类别')
- poly: list[float] = Field(
- description=('Coordinates, need to convert back to PDF coordinates,'
- ' order is top-left, top-right, bottom-right, bottom-left'
- ' x,y coordinates'))
- ignore: bool = Field(description='whether ignore this object',
- default=False)
- text: str | None = Field(description='text content of the object',
- default=None)
- image_path: str | None = Field(description='path of embedded image',
- default=None)
- order: int = Field(description='the order of this object within a page',
- default=-1)
- anno_id: int = Field(description='unique id', default=-1)
- latex: str | None = Field(description='latex result', default=None)
- html: str | None = Field(description='html result', default=None)
-
-
-class ElementRelation(BaseModel):
- source_anno_id: int = Field(description='unique id of the source object',
- default=-1)
- target_anno_id: int = Field(description='unique id of the target object',
- default=-1)
- relation: ElementRelType = Field(
- description='the relation between source and target element')
-
-
-class LayoutElementsExtra(BaseModel):
- element_relation: list[ElementRelation] = Field(
- description='the relation between source and target element')
-
-
-class LayoutElements(BaseModel):
- layout_dets: list[ContentObject] = Field(
- description='layout element details')
- page_info: PageInfo = Field(description='page info')
- extra: LayoutElementsExtra = Field(description='extra information')
-
-
-# iter data format
-class Node(BaseModel):
- category_type: CategoryType = Field(description='类别')
- text: str | None = Field(description='text content of the object',
- default=None)
- image_path: str | None = Field(description='path of embedded image',
- default=None)
- anno_id: int = Field(description='unique id', default=-1)
- latex: str | None = Field(description='latex result', default=None)
- html: str | None = Field(description='html result', default=None)
diff --git a/magic_pdf/integrations/rag/utils.py b/magic_pdf/integrations/rag/utils.py
deleted file mode 100644
index 49e9dc0ee2a6955219012dd05aa58d6e56b1f25d..0000000000000000000000000000000000000000
--- a/magic_pdf/integrations/rag/utils.py
+++ /dev/null
@@ -1,284 +0,0 @@
-import json
-import os
-from pathlib import Path
-
-from loguru import logger
-
-import magic_pdf.model as model_config
-from magic_pdf.config.ocr_content_type import BlockType, ContentType
-from magic_pdf.data.data_reader_writer import FileBasedDataReader
-from magic_pdf.dict2md.ocr_mkcontent import merge_para_with_text
-from magic_pdf.integrations.rag.type import (CategoryType, ContentObject,
- ElementRelation, ElementRelType,
- LayoutElements,
- LayoutElementsExtra, PageInfo)
-from magic_pdf.tools.common import do_parse, prepare_env
-
-
-def convert_middle_json_to_layout_elements(
- json_data: dict,
- output_dir: str,
-) -> list[LayoutElements]:
- uniq_anno_id = 0
-
- res: list[LayoutElements] = []
- for page_no, page_data in enumerate(json_data['pdf_info']):
- order_id = 0
- page_info = PageInfo(
- height=int(page_data['page_size'][1]),
- width=int(page_data['page_size'][0]),
- page_no=page_no,
- )
- layout_dets: list[ContentObject] = []
- extra_element_relation: list[ElementRelation] = []
-
- for para_block in page_data['para_blocks']:
- para_text = ''
- para_type = para_block['type']
-
- if para_type == BlockType.Text:
- para_text = merge_para_with_text(para_block)
- x0, y0, x1, y1 = para_block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.text,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- elif para_type == BlockType.Title:
- para_text = merge_para_with_text(para_block)
- x0, y0, x1, y1 = para_block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.title,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- elif para_type == BlockType.InterlineEquation:
- para_text = merge_para_with_text(para_block)
- x0, y0, x1, y1 = para_block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.interline_equation,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- elif para_type == BlockType.Image:
- body_anno_id = -1
- caption_anno_id = -1
-
- for block in para_block['blocks']:
- if block['type'] == BlockType.ImageBody:
- for line in block['lines']:
- for span in line['spans']:
- if span['type'] == ContentType.Image:
- x0, y0, x1, y1 = block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.image_body,
- image_path=os.path.join(
- output_dir, span['image_path']),
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- body_anno_id = uniq_anno_id
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- for block in para_block['blocks']:
- if block['type'] == BlockType.ImageCaption:
- para_text += merge_para_with_text(block)
- x0, y0, x1, y1 = block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.image_caption,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- caption_anno_id = uniq_anno_id
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- if body_anno_id > 0 and caption_anno_id > 0:
- element_relation = ElementRelation(
- relation=ElementRelType.sibling,
- source_anno_id=body_anno_id,
- target_anno_id=caption_anno_id,
- )
- extra_element_relation.append(element_relation)
-
- elif para_type == BlockType.Table:
- body_anno_id, caption_anno_id, footnote_anno_id = -1, -1, -1
-
- for block in para_block['blocks']:
- if block['type'] == BlockType.TableCaption:
- para_text += merge_para_with_text(block)
- x0, y0, x1, y1 = block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.table_caption,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- caption_anno_id = uniq_anno_id
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- for block in para_block['blocks']:
- if block['type'] == BlockType.TableBody:
- for line in block['lines']:
- for span in line['spans']:
- if span['type'] == ContentType.Table:
- x0, y0, x1, y1 = para_block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.table_body,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- body_anno_id = uniq_anno_id
- uniq_anno_id += 1
- order_id += 1
- # if processed by table model
- if span.get('latex', ''):
- content.latex = span['latex']
- else:
- content.image_path = os.path.join(
- output_dir, span['image_path'])
- layout_dets.append(content)
-
- for block in para_block['blocks']:
- if block['type'] == BlockType.TableFootnote:
- para_text += merge_para_with_text(block)
- x0, y0, x1, y1 = block['bbox']
- content = ContentObject(
- anno_id=uniq_anno_id,
- category_type=CategoryType.table_footnote,
- text=para_text,
- order=order_id,
- poly=[x0, y0, x1, y0, x1, y1, x0, y1],
- )
- footnote_anno_id = uniq_anno_id
- uniq_anno_id += 1
- order_id += 1
- layout_dets.append(content)
-
- if caption_anno_id != -1 and body_anno_id != -1:
- element_relation = ElementRelation(
- relation=ElementRelType.sibling,
- source_anno_id=body_anno_id,
- target_anno_id=caption_anno_id,
- )
- extra_element_relation.append(element_relation)
-
- if footnote_anno_id != -1 and body_anno_id != -1:
- element_relation = ElementRelation(
- relation=ElementRelType.sibling,
- source_anno_id=body_anno_id,
- target_anno_id=footnote_anno_id,
- )
- extra_element_relation.append(element_relation)
-
- res.append(
- LayoutElements(
- page_info=page_info,
- layout_dets=layout_dets,
- extra=LayoutElementsExtra(
- element_relation=extra_element_relation),
- ))
-
- return res
-
-
-def inference(path, output_dir, method):
- model_config.__use_inside_model__ = True
- model_config.__model_mode__ = 'full'
- if output_dir == '':
- if os.path.isdir(path):
- output_dir = os.path.join(path, 'output')
- else:
- output_dir = os.path.join(os.path.dirname(path), 'output')
-
- local_image_dir, local_md_dir = prepare_env(output_dir,
- str(Path(path).stem), method)
-
- def read_fn(path):
- disk_rw = FileBasedDataReader(os.path.dirname(path))
- return disk_rw.read(os.path.basename(path))
-
- def parse_doc(doc_path: str):
- try:
- file_name = str(Path(doc_path).stem)
- pdf_data = read_fn(doc_path)
- do_parse(
- output_dir,
- file_name,
- pdf_data,
- [],
- method,
- False,
- f_draw_span_bbox=False,
- f_draw_layout_bbox=False,
- f_dump_md=False,
- f_dump_middle_json=True,
- f_dump_model_json=False,
- f_dump_orig_pdf=False,
- f_dump_content_list=False,
- f_draw_model_bbox=False,
- )
-
- middle_json_fn = os.path.join(local_md_dir,
- f'{file_name}_middle.json')
- with open(middle_json_fn) as fd:
- jso = json.load(fd)
- os.remove(middle_json_fn)
- return convert_middle_json_to_layout_elements(jso, local_image_dir)
-
- except Exception as e:
- logger.exception(e)
-
- return parse_doc(path)
-
-
-if __name__ == '__main__':
- import pprint
-
- base_dir = '/opt/data/pdf/resources/samples/'
- if 0:
- with open(base_dir + 'json_outputs/middle.json') as f:
- d = json.load(f)
- result = convert_middle_json_to_layout_elements(d, '/tmp')
- pprint.pp(result)
- if 0:
- with open(base_dir + 'json_outputs/middle.3.json') as f:
- d = json.load(f)
- result = convert_middle_json_to_layout_elements(d, '/tmp')
- pprint.pp(result)
-
- if 1:
- res = inference(
- base_dir + 'samples/pdf/one_page_with_table_image.pdf',
- '/tmp/output',
- 'ocr',
- )
- pprint.pp(res)
diff --git a/magic_pdf/libs/__init__.py b/magic_pdf/libs/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/magic_pdf/libs/boxbase.py b/magic_pdf/libs/boxbase.py
deleted file mode 100644
index 2813121bb3fcde988d510b89646478c97461da74..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/boxbase.py
+++ /dev/null
@@ -1,485 +0,0 @@
-import math
-
-
-def _is_in_or_part_overlap(box1, box2) -> bool:
- """两个bbox是否有部分重叠或者包含."""
- if box1 is None or box2 is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = box1
- x0_2, y0_2, x1_2, y1_2 = box2
-
- return not (x1_1 < x0_2 or # box1在box2的左边
- x0_1 > x1_2 or # box1在box2的右边
- y1_1 < y0_2 or # box1在box2的上边
- y0_1 > y1_2) # box1在box2的下边
-
-
-def _is_in_or_part_overlap_with_area_ratio(box1,
- box2,
- area_ratio_threshold=0.6):
- """判断box1是否在box2里面,或者box1和box2有部分重叠,且重叠面积占box1的比例超过area_ratio_threshold."""
- if box1 is None or box2 is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = box1
- x0_2, y0_2, x1_2, y1_2 = box2
-
- if not _is_in_or_part_overlap(box1, box2):
- return False
-
- # 计算重叠面积
- x_left = max(x0_1, x0_2)
- y_top = max(y0_1, y0_2)
- x_right = min(x1_1, x1_2)
- y_bottom = min(y1_1, y1_2)
- overlap_area = (x_right - x_left) * (y_bottom - y_top)
-
- # 计算box1的面积
- box1_area = (x1_1 - x0_1) * (y1_1 - y0_1)
-
- return overlap_area / box1_area > area_ratio_threshold
-
-
-def _is_in(box1, box2) -> bool:
- """box1是否完全在box2里面."""
- x0_1, y0_1, x1_1, y1_1 = box1
- x0_2, y0_2, x1_2, y1_2 = box2
-
- return (x0_1 >= x0_2 and # box1的左边界不在box2的左边外
- y0_1 >= y0_2 and # box1的上边界不在box2的上边外
- x1_1 <= x1_2 and # box1的右边界不在box2的右边外
- y1_1 <= y1_2) # box1的下边界不在box2的下边外
-
-
-def _is_part_overlap(box1, box2) -> bool:
- """两个bbox是否有部分重叠,但不完全包含."""
- if box1 is None or box2 is None:
- return False
-
- return _is_in_or_part_overlap(box1, box2) and not _is_in(box1, box2)
-
-
-def _left_intersect(left_box, right_box):
- """检查两个box的左边界是否有交集,也就是left_box的右边界是否在right_box的左边界内."""
- if left_box is None or right_box is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = left_box
- x0_2, y0_2, x1_2, y1_2 = right_box
-
- return x1_1 > x0_2 and x0_1 < x0_2 and (y0_1 <= y0_2 <= y1_1
- or y0_1 <= y1_2 <= y1_1)
-
-
-def _right_intersect(left_box, right_box):
- """检查box是否在右侧边界有交集,也就是left_box的左边界是否在right_box的右边界内."""
- if left_box is None or right_box is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = left_box
- x0_2, y0_2, x1_2, y1_2 = right_box
-
- return x0_1 < x1_2 and x1_1 > x1_2 and (y0_1 <= y0_2 <= y1_1
- or y0_1 <= y1_2 <= y1_1)
-
-
-def _is_vertical_full_overlap(box1, box2, x_torlence=2):
- """x方向上:要么box1包含box2, 要么box2包含box1。不能部分包含 y方向上:box1和box2有重叠."""
- # 解析box的坐标
- x11, y11, x12, y12 = box1 # 左上角和右下角的坐标 (x1, y1, x2, y2)
- x21, y21, x22, y22 = box2
-
- # 在x轴方向上,box1是否包含box2 或 box2包含box1
- contains_in_x = (x11 - x_torlence <= x21 and x12 + x_torlence >= x22) or (
- x21 - x_torlence <= x11 and x22 + x_torlence >= x12)
-
- # 在y轴方向上,box1和box2是否有重叠
- overlap_in_y = not (y12 < y21 or y11 > y22)
-
- return contains_in_x and overlap_in_y
-
-
-def _is_bottom_full_overlap(box1, box2, y_tolerance=2):
- """检查box1下方和box2的上方有轻微的重叠,轻微程度收到y_tolerance的限制 这个函数和_is_vertical-
- full_overlap的区别是,这个函数允许box1和box2在x方向上有轻微的重叠,允许一定的模糊度."""
- if box1 is None or box2 is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = box1
- x0_2, y0_2, x1_2, y1_2 = box2
- tolerance_margin = 2
- is_xdir_full_overlap = (
- (x0_1 - tolerance_margin <= x0_2 <= x1_1 + tolerance_margin
- and x0_1 - tolerance_margin <= x1_2 <= x1_1 + tolerance_margin)
- or (x0_2 - tolerance_margin <= x0_1 <= x1_2 + tolerance_margin
- and x0_2 - tolerance_margin <= x1_1 <= x1_2 + tolerance_margin))
-
- return y0_2 < y1_1 and 0 < (y1_1 -
- y0_2) < y_tolerance and is_xdir_full_overlap
-
-
-def _is_left_overlap(
- box1,
- box2,
-):
- """检查box1的左侧是否和box2有重叠 在Y方向上可以是部分重叠或者是完全重叠。不分box1和box2的上下关系,也就是无论box1在box2下
- 方还是box2在box1下方,都可以检测到重叠。 X方向上."""
-
- def __overlap_y(Ay1, Ay2, By1, By2):
- return max(0, min(Ay2, By2) - max(Ay1, By1))
-
- if box1 is None or box2 is None:
- return False
-
- x0_1, y0_1, x1_1, y1_1 = box1
- x0_2, y0_2, x1_2, y1_2 = box2
-
- y_overlap_len = __overlap_y(y0_1, y1_1, y0_2, y1_2)
- ratio_1 = 1.0 * y_overlap_len / (y1_1 - y0_1) if y1_1 - y0_1 != 0 else 0
- ratio_2 = 1.0 * y_overlap_len / (y1_2 - y0_2) if y1_2 - y0_2 != 0 else 0
- vertical_overlap_cond = ratio_1 >= 0.5 or ratio_2 >= 0.5
-
- # vertical_overlap_cond = y0_1<=y0_2<=y1_1 or y0_1<=y1_2<=y1_1 or y0_2<=y0_1<=y1_2 or y0_2<=y1_1<=y1_2
- return x0_1 <= x0_2 <= x1_1 and vertical_overlap_cond
-
-
-def __is_overlaps_y_exceeds_threshold(bbox1,
- bbox2,
- overlap_ratio_threshold=0.8):
- """检查两个bbox在y轴上是否有重叠,并且该重叠区域的高度占两个bbox高度更低的那个超过80%"""
- _, y0_1, _, y1_1 = bbox1
- _, y0_2, _, y1_2 = bbox2
-
- overlap = max(0, min(y1_1, y1_2) - max(y0_1, y0_2))
- height1, height2 = y1_1 - y0_1, y1_2 - y0_2
- # max_height = max(height1, height2)
- min_height = min(height1, height2)
-
- return (overlap / min_height) > overlap_ratio_threshold
-
-
-def calculate_iou(bbox1, bbox2):
- """计算两个边界框的交并比(IOU)。
-
- Args:
- bbox1 (list[float]): 第一个边界框的坐标,格式为 [x1, y1, x2, y2],其中 (x1, y1) 为左上角坐标,(x2, y2) 为右下角坐标。
- bbox2 (list[float]): 第二个边界框的坐标,格式与 `bbox1` 相同。
-
- Returns:
- float: 两个边界框的交并比(IOU),取值范围为 [0, 1]。
- """
- # Determine the coordinates of the intersection rectangle
- x_left = max(bbox1[0], bbox2[0])
- y_top = max(bbox1[1], bbox2[1])
- x_right = min(bbox1[2], bbox2[2])
- y_bottom = min(bbox1[3], bbox2[3])
-
- if x_right < x_left or y_bottom < y_top:
- return 0.0
-
- # The area of overlap area
- intersection_area = (x_right - x_left) * (y_bottom - y_top)
-
- # The area of both rectangles
- bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
- bbox2_area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
-
- if any([bbox1_area == 0, bbox2_area == 0]):
- return 0
-
- # Compute the intersection over union by taking the intersection area
- # and dividing it by the sum of both areas minus the intersection area
- iou = intersection_area / float(bbox1_area + bbox2_area - intersection_area)
-
- return iou
-
-
-def calculate_overlap_area_2_minbox_area_ratio(bbox1, bbox2):
- """计算box1和box2的重叠面积占最小面积的box的比例."""
- # Determine the coordinates of the intersection rectangle
- x_left = max(bbox1[0], bbox2[0])
- y_top = max(bbox1[1], bbox2[1])
- x_right = min(bbox1[2], bbox2[2])
- y_bottom = min(bbox1[3], bbox2[3])
-
- if x_right < x_left or y_bottom < y_top:
- return 0.0
-
- # The area of overlap area
- intersection_area = (x_right - x_left) * (y_bottom - y_top)
- min_box_area = min([(bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1]),
- (bbox2[3] - bbox2[1]) * (bbox2[2] - bbox2[0])])
- if min_box_area == 0:
- return 0
- else:
- return intersection_area / min_box_area
-
-
-def calculate_overlap_area_in_bbox1_area_ratio(bbox1, bbox2):
- """计算box1和box2的重叠面积占bbox1的比例."""
- # Determine the coordinates of the intersection rectangle
- x_left = max(bbox1[0], bbox2[0])
- y_top = max(bbox1[1], bbox2[1])
- x_right = min(bbox1[2], bbox2[2])
- y_bottom = min(bbox1[3], bbox2[3])
-
- if x_right < x_left or y_bottom < y_top:
- return 0.0
-
- # The area of overlap area
- intersection_area = (x_right - x_left) * (y_bottom - y_top)
- bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
- if bbox1_area == 0:
- return 0
- else:
- return intersection_area / bbox1_area
-
-
-def get_minbox_if_overlap_by_ratio(bbox1, bbox2, ratio):
- """通过calculate_overlap_area_2_minbox_area_ratio计算两个bbox重叠的面积占最小面积的box的比例
- 如果比例大于ratio,则返回小的那个bbox, 否则返回None."""
- x1_min, y1_min, x1_max, y1_max = bbox1
- x2_min, y2_min, x2_max, y2_max = bbox2
- area1 = (x1_max - x1_min) * (y1_max - y1_min)
- area2 = (x2_max - x2_min) * (y2_max - y2_min)
- overlap_ratio = calculate_overlap_area_2_minbox_area_ratio(bbox1, bbox2)
- if overlap_ratio > ratio:
- if area1 <= area2:
- return bbox1
- else:
- return bbox2
- else:
- return None
-
-
-def get_bbox_in_boundary(bboxes: list, boundary: tuple) -> list:
- x0, y0, x1, y1 = boundary
- new_boxes = [
- box for box in bboxes
- if box[0] >= x0 and box[1] >= y0 and box[2] <= x1 and box[3] <= y1
- ]
- return new_boxes
-
-
-def is_vbox_on_side(bbox, width, height, side_threshold=0.2):
- """判断一个bbox是否在pdf页面的边缘."""
- x0, x1 = bbox[0], bbox[2]
- if x1 <= width * side_threshold or x0 >= width * (1 - side_threshold):
- return True
- return False
-
-
-def find_top_nearest_text_bbox(pymu_blocks, obj_bbox):
- tolerance_margin = 4
- top_boxes = [
- box for box in pymu_blocks
- if obj_bbox[1] - box['bbox'][3] >= -tolerance_margin
- and not _is_in(box['bbox'], obj_bbox)
- ]
- # 然后找到X方向上有互相重叠的
- top_boxes = [
- box for box in top_boxes if any([
- obj_bbox[0] - tolerance_margin <= box['bbox'][0] <= obj_bbox[2] +
- tolerance_margin, obj_bbox[0] -
- tolerance_margin <= box['bbox'][2] <= obj_bbox[2] +
- tolerance_margin, box['bbox'][0] -
- tolerance_margin <= obj_bbox[0] <= box['bbox'][2] +
- tolerance_margin, box['bbox'][0] -
- tolerance_margin <= obj_bbox[2] <= box['bbox'][2] +
- tolerance_margin
- ])
- ]
-
- # 然后找到y1最大的那个
- if len(top_boxes) > 0:
- top_boxes.sort(key=lambda x: x['bbox'][3], reverse=True)
- return top_boxes[0]
- else:
- return None
-
-
-def find_bottom_nearest_text_bbox(pymu_blocks, obj_bbox):
- bottom_boxes = [
- box for box in pymu_blocks if box['bbox'][1] -
- obj_bbox[3] >= -2 and not _is_in(box['bbox'], obj_bbox)
- ]
- # 然后找到X方向上有互相重叠的
- bottom_boxes = [
- box for box in bottom_boxes if any([
- obj_bbox[0] - 2 <= box['bbox'][0] <= obj_bbox[2] + 2, obj_bbox[0] -
- 2 <= box['bbox'][2] <= obj_bbox[2] + 2, box['bbox'][0] -
- 2 <= obj_bbox[0] <= box['bbox'][2] + 2, box['bbox'][0] -
- 2 <= obj_bbox[2] <= box['bbox'][2] + 2
- ])
- ]
-
- # 然后找到y0最小的那个
- if len(bottom_boxes) > 0:
- bottom_boxes.sort(key=lambda x: x['bbox'][1], reverse=False)
- return bottom_boxes[0]
- else:
- return None
-
-
-def find_left_nearest_text_bbox(pymu_blocks, obj_bbox):
- """寻找左侧最近的文本block."""
- left_boxes = [
- box for box in pymu_blocks if obj_bbox[0] -
- box['bbox'][2] >= -2 and not _is_in(box['bbox'], obj_bbox)
- ]
- # 然后找到X方向上有互相重叠的
- left_boxes = [
- box for box in left_boxes if any([
- obj_bbox[1] - 2 <= box['bbox'][1] <= obj_bbox[3] + 2, obj_bbox[1] -
- 2 <= box['bbox'][3] <= obj_bbox[3] + 2, box['bbox'][1] -
- 2 <= obj_bbox[1] <= box['bbox'][3] + 2, box['bbox'][1] -
- 2 <= obj_bbox[3] <= box['bbox'][3] + 2
- ])
- ]
-
- # 然后找到x1最大的那个
- if len(left_boxes) > 0:
- left_boxes.sort(key=lambda x: x['bbox'][2], reverse=True)
- return left_boxes[0]
- else:
- return None
-
-
-def find_right_nearest_text_bbox(pymu_blocks, obj_bbox):
- """寻找右侧最近的文本block."""
- right_boxes = [
- box for box in pymu_blocks if box['bbox'][0] -
- obj_bbox[2] >= -2 and not _is_in(box['bbox'], obj_bbox)
- ]
- # 然后找到X方向上有互相重叠的
- right_boxes = [
- box for box in right_boxes if any([
- obj_bbox[1] - 2 <= box['bbox'][1] <= obj_bbox[3] + 2, obj_bbox[1] -
- 2 <= box['bbox'][3] <= obj_bbox[3] + 2, box['bbox'][1] -
- 2 <= obj_bbox[1] <= box['bbox'][3] + 2, box['bbox'][1] -
- 2 <= obj_bbox[3] <= box['bbox'][3] + 2
- ])
- ]
-
- # 然后找到x0最小的那个
- if len(right_boxes) > 0:
- right_boxes.sort(key=lambda x: x['bbox'][0], reverse=False)
- return right_boxes[0]
- else:
- return None
-
-
-def bbox_relative_pos(bbox1, bbox2):
- """判断两个矩形框的相对位置关系.
-
- Args:
- bbox1: 一个四元组,表示第一个矩形框的左上角和右下角的坐标,格式为(x1, y1, x1b, y1b)
- bbox2: 一个四元组,表示第二个矩形框的左上角和右下角的坐标,格式为(x2, y2, x2b, y2b)
-
- Returns:
- 一个四元组,表示矩形框1相对于矩形框2的位置关系,格式为(left, right, bottom, top)
- 其中,left表示矩形框1是否在矩形框2的左侧,right表示矩形框1是否在矩形框2的右侧,
- bottom表示矩形框1是否在矩形框2的下方,top表示矩形框1是否在矩形框2的上方
- """
- x1, y1, x1b, y1b = bbox1
- x2, y2, x2b, y2b = bbox2
-
- left = x2b < x1
- right = x1b < x2
- bottom = y2b < y1
- top = y1b < y2
- return left, right, bottom, top
-
-
-def bbox_distance(bbox1, bbox2):
- """计算两个矩形框的距离。
-
- Args:
- bbox1 (tuple): 第一个矩形框的坐标,格式为 (x1, y1, x2, y2),其中 (x1, y1) 为左上角坐标,(x2, y2) 为右下角坐标。
- bbox2 (tuple): 第二个矩形框的坐标,格式为 (x1, y1, x2, y2),其中 (x1, y1) 为左上角坐标,(x2, y2) 为右下角坐标。
-
- Returns:
- float: 矩形框之间的距离。
- """
-
- def dist(point1, point2):
- return math.sqrt((point1[0] - point2[0])**2 +
- (point1[1] - point2[1])**2)
-
- x1, y1, x1b, y1b = bbox1
- x2, y2, x2b, y2b = bbox2
-
- left, right, bottom, top = bbox_relative_pos(bbox1, bbox2)
-
- if top and left:
- return dist((x1, y1b), (x2b, y2))
- elif left and bottom:
- return dist((x1, y1), (x2b, y2b))
- elif bottom and right:
- return dist((x1b, y1), (x2, y2b))
- elif right and top:
- return dist((x1b, y1b), (x2, y2))
- elif left:
- return x1 - x2b
- elif right:
- return x2 - x1b
- elif bottom:
- return y1 - y2b
- elif top:
- return y2 - y1b
- return 0.0
-
-
-def box_area(bbox):
- return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
-
-
-def get_overlap_area(bbox1, bbox2):
- """计算box1和box2的重叠面积占bbox1的比例."""
- # Determine the coordinates of the intersection rectangle
- x_left = max(bbox1[0], bbox2[0])
- y_top = max(bbox1[1], bbox2[1])
- x_right = min(bbox1[2], bbox2[2])
- y_bottom = min(bbox1[3], bbox2[3])
-
- if x_right < x_left or y_bottom < y_top:
- return 0.0
-
- # The area of overlap area
- return (x_right - x_left) * (y_bottom - y_top)
-
-
-def calculate_vertical_projection_overlap_ratio(block1, block2):
- """
- Calculate the proportion of the x-axis covered by the vertical projection of two blocks.
-
- Args:
- block1 (tuple): Coordinates of the first block (x0, y0, x1, y1).
- block2 (tuple): Coordinates of the second block (x0, y0, x1, y1).
-
- Returns:
- float: The proportion of the x-axis covered by the vertical projection of the two blocks.
- """
- x0_1, _, x1_1, _ = block1
- x0_2, _, x1_2, _ = block2
-
- # Calculate the intersection of the x-coordinates
- x_left = max(x0_1, x0_2)
- x_right = min(x1_1, x1_2)
-
- if x_right < x_left:
- return 0.0
-
- # Length of the intersection
- intersection_length = x_right - x_left
-
- # Length of the x-axis projection of the first block
- block1_length = x1_1 - x0_1
-
- if block1_length == 0:
- return 0.0
-
- # Proportion of the x-axis covered by the intersection
- # logger.info(f"intersection_length: {intersection_length}, block1_length: {block1_length}")
- return intersection_length / block1_length
diff --git a/magic_pdf/libs/clean_memory.py b/magic_pdf/libs/clean_memory.py
deleted file mode 100644
index 930b99eadb71463816d938936649d82905723bd0..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/clean_memory.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (c) Opendatalab. All rights reserved.
-import torch
-import gc
-
-
-def clean_memory(device='cuda'):
- if device == 'cuda':
- if torch.cuda.is_available():
- torch.cuda.empty_cache()
- torch.cuda.ipc_collect()
- elif str(device).startswith("npu"):
- import torch_npu
- if torch_npu.npu.is_available():
- torch_npu.npu.empty_cache()
- elif str(device).startswith("mps"):
- torch.mps.empty_cache()
- gc.collect()
\ No newline at end of file
diff --git a/magic_pdf/libs/commons.py b/magic_pdf/libs/commons.py
deleted file mode 100644
index 20f29ffd309737cfd06f04fa0426eab1ceb4a4b9..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/commons.py
+++ /dev/null
@@ -1,43 +0,0 @@
-
-def join_path(*args):
- return '/'.join(str(s).rstrip('/') for s in args)
-
-
-def get_top_percent_list(num_list, percent):
- """
- 获取列表中前百分之多少的元素
- :param num_list:
- :param percent:
- :return:
- """
- if len(num_list) == 0:
- top_percent_list = []
- else:
- # 对imgs_len_list排序
- sorted_imgs_len_list = sorted(num_list, reverse=True)
- # 计算 percent 的索引
- top_percent_index = int(len(sorted_imgs_len_list) * percent)
- # 取前80%的元素
- top_percent_list = sorted_imgs_len_list[:top_percent_index]
- return top_percent_list
-
-
-def mymax(alist: list):
- if len(alist) == 0:
- return 0 # 空是0, 0*0也是0大小q
- else:
- return max(alist)
-
-
-def parse_bucket_key(s3_full_path: str):
- """
- 输入 s3://bucket/path/to/my/file.txt
- 输出 bucket, path/to/my/file.txt
- """
- s3_full_path = s3_full_path.strip()
- if s3_full_path.startswith("s3://"):
- s3_full_path = s3_full_path[5:]
- if s3_full_path.startswith("/"):
- s3_full_path = s3_full_path[1:]
- bucket, key = s3_full_path.split("/", 1)
- return bucket, key
diff --git a/magic_pdf/libs/config_reader.py b/magic_pdf/libs/config_reader.py
deleted file mode 100644
index 2b7e949621a606e4f8a83865945c501056fbefb6..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/config_reader.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""根据bucket的名字返回对应的s3 AK, SK,endpoint三元组."""
-
-import json
-import os
-
-from loguru import logger
-
-from magic_pdf.config.constants import MODEL_NAME
-from magic_pdf.libs.commons import parse_bucket_key
-
-# 定义配置文件名常量
-CONFIG_FILE_NAME = os.getenv('MINERU_TOOLS_CONFIG_JSON', 'magic-pdf.json')
-
-
-def read_config():
- if os.path.isabs(CONFIG_FILE_NAME):
- config_file = CONFIG_FILE_NAME
- else:
- home_dir = os.path.expanduser('~')
- config_file = os.path.join(home_dir, CONFIG_FILE_NAME)
-
- if not os.path.exists(config_file):
- raise FileNotFoundError(f'{config_file} not found')
-
- with open(config_file, 'r', encoding='utf-8') as f:
- config = json.load(f)
- return config
-
-
-def get_s3_config(bucket_name: str):
- """~/magic-pdf.json 读出来."""
- config = read_config()
-
- bucket_info = config.get('bucket_info')
- if bucket_name not in bucket_info:
- access_key, secret_key, storage_endpoint = bucket_info['[default]']
- else:
- access_key, secret_key, storage_endpoint = bucket_info[bucket_name]
-
- if access_key is None or secret_key is None or storage_endpoint is None:
- raise Exception(f'ak, sk or endpoint not found in {CONFIG_FILE_NAME}')
-
- # logger.info(f"get_s3_config: ak={access_key}, sk={secret_key}, endpoint={storage_endpoint}")
-
- return access_key, secret_key, storage_endpoint
-
-
-def get_s3_config_dict(path: str):
- access_key, secret_key, storage_endpoint = get_s3_config(get_bucket_name(path))
- return {'ak': access_key, 'sk': secret_key, 'endpoint': storage_endpoint}
-
-
-def get_bucket_name(path):
- bucket, key = parse_bucket_key(path)
- return bucket
-
-
-def get_local_models_dir():
- config = read_config()
- models_dir = config.get('models-dir')
- if models_dir is None:
- logger.warning(f"'models-dir' not found in {CONFIG_FILE_NAME}, use '/tmp/models' as default")
- return '/tmp/models'
- else:
- return models_dir
-
-
-def get_local_layoutreader_model_dir():
- config = read_config()
- layoutreader_model_dir = config.get('layoutreader-model-dir')
- if layoutreader_model_dir is None or not os.path.exists(layoutreader_model_dir):
- home_dir = os.path.expanduser('~')
- layoutreader_at_modelscope_dir_path = os.path.join(home_dir, '.cache/modelscope/hub/ppaanngggg/layoutreader')
- logger.warning(f"'layoutreader-model-dir' not exists, use {layoutreader_at_modelscope_dir_path} as default")
- return layoutreader_at_modelscope_dir_path
- else:
- return layoutreader_model_dir
-
-
-def get_device():
- config = read_config()
- device = config.get('device-mode')
- if device is None:
- logger.warning(f"'device-mode' not found in {CONFIG_FILE_NAME}, use 'cpu' as default")
- return 'cpu'
- else:
- return device
-
-
-def get_table_recog_config():
- config = read_config()
- table_config = config.get('table-config')
- if table_config is None:
- logger.warning(f"'table-config' not found in {CONFIG_FILE_NAME}, use 'False' as default")
- return json.loads(f'{{"model": "{MODEL_NAME.RAPID_TABLE}","enable": false, "max_time": 400}}')
- else:
- return table_config
-
-
-def get_layout_config():
- config = read_config()
- layout_config = config.get('layout-config')
- if layout_config is None:
- logger.warning(f"'layout-config' not found in {CONFIG_FILE_NAME}, use '{MODEL_NAME.LAYOUTLMv3}' as default")
- return json.loads(f'{{"model": "{MODEL_NAME.LAYOUTLMv3}"}}')
- else:
- return layout_config
-
-
-def get_formula_config():
- config = read_config()
- formula_config = config.get('formula-config')
- if formula_config is None:
- logger.warning(f"'formula-config' not found in {CONFIG_FILE_NAME}, use 'True' as default")
- return json.loads(f'{{"mfd_model": "{MODEL_NAME.YOLO_V8_MFD}","mfr_model": "{MODEL_NAME.UniMerNet_v2_Small}","enable": true}}')
- else:
- return formula_config
-
-def get_llm_aided_config():
- config = read_config()
- llm_aided_config = config.get('llm-aided-config')
- if llm_aided_config is None:
- logger.warning(f"'llm-aided-config' not found in {CONFIG_FILE_NAME}, use 'None' as default")
- return None
- else:
- return llm_aided_config
-
-def get_latex_delimiter_config():
- config = read_config()
- latex_delimiter_config = config.get('latex-delimiter-config')
- if latex_delimiter_config is None:
- logger.warning(f"'latex-delimiter-config' not found in {CONFIG_FILE_NAME}, use 'None' as default")
- return None
- else:
- return latex_delimiter_config
-
-
-if __name__ == '__main__':
- ak, sk, endpoint = get_s3_config('llm-raw')
diff --git a/magic_pdf/libs/convert_utils.py b/magic_pdf/libs/convert_utils.py
deleted file mode 100644
index 99a1879d46befa2de63aa1a379ab83dbf6fdb1f1..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/convert_utils.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def dict_to_list(input_dict):
- items_list = []
- for _, item in input_dict.items():
- items_list.append(item)
- return items_list
diff --git a/magic_pdf/libs/coordinate_transform.py b/magic_pdf/libs/coordinate_transform.py
deleted file mode 100644
index 7cd7a0768596174d71ea8b3c8309c0ec998b3c81..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/coordinate_transform.py
+++ /dev/null
@@ -1,9 +0,0 @@
-def get_scale_ratio(model_page_info, page):
- pix = page.get_pixmap(dpi=72)
- pymu_width = int(pix.w)
- pymu_height = int(pix.h)
- width_from_json = model_page_info['page_info']['width']
- height_from_json = model_page_info['page_info']['height']
- horizontal_scale_ratio = width_from_json / pymu_width
- vertical_scale_ratio = height_from_json / pymu_height
- return horizontal_scale_ratio, vertical_scale_ratio
diff --git a/magic_pdf/libs/draw_bbox.py b/magic_pdf/libs/draw_bbox.py
deleted file mode 100644
index c2ad21d091cff9c2d3026f97da486129b6b34edf..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/draw_bbox.py
+++ /dev/null
@@ -1,418 +0,0 @@
-import fitz
-from magic_pdf.config.constants import CROSS_PAGE
-from magic_pdf.config.ocr_content_type import (BlockType, CategoryId,
- ContentType)
-from magic_pdf.data.dataset import Dataset
-from magic_pdf.model.magic_model import MagicModel
-
-
-def draw_bbox_without_number(i, bbox_list, page, rgb_config, fill_config):
- new_rgb = []
- for item in rgb_config:
- item = float(item) / 255
- new_rgb.append(item)
- page_data = bbox_list[i]
- for bbox in page_data:
- x0, y0, x1, y1 = bbox
- rect_coords = fitz.Rect(x0, y0, x1, y1) # Define the rectangle
- if fill_config:
- page.draw_rect(
- rect_coords,
- color=None,
- fill=new_rgb,
- fill_opacity=0.3,
- width=0.5,
- overlay=True,
- ) # Draw the rectangle
- else:
- page.draw_rect(
- rect_coords,
- color=new_rgb,
- fill=None,
- fill_opacity=1,
- width=0.5,
- overlay=True,
- ) # Draw the rectangle
-
-
-def draw_bbox_with_number(i, bbox_list, page, rgb_config, fill_config, draw_bbox=True):
- new_rgb = []
- for item in rgb_config:
- item = float(item) / 255
- new_rgb.append(item)
- page_data = bbox_list[i]
- for j, bbox in enumerate(page_data):
- x0, y0, x1, y1 = bbox
- rect_coords = fitz.Rect(x0, y0, x1, y1) # Define the rectangle
- if draw_bbox:
- if fill_config:
- page.draw_rect(
- rect_coords,
- color=None,
- fill=new_rgb,
- fill_opacity=0.3,
- width=0.5,
- overlay=True,
- ) # Draw the rectangle
- else:
- page.draw_rect(
- rect_coords,
- color=new_rgb,
- fill=None,
- fill_opacity=1,
- width=0.5,
- overlay=True,
- ) # Draw the rectangle
- page.insert_text(
- (x1 + 2, y0 + 10), str(j + 1), fontsize=10, color=new_rgb
- ) # Insert the index in the top left corner of the rectangle
-
-
-def draw_layout_bbox(pdf_info, pdf_bytes, out_path, filename):
- dropped_bbox_list = []
- tables_list, tables_body_list = [], []
- tables_caption_list, tables_footnote_list = [], []
- imgs_list, imgs_body_list, imgs_caption_list = [], [], []
- imgs_footnote_list = []
- titles_list = []
- texts_list = []
- interequations_list = []
- lists_list = []
- indexs_list = []
- for page in pdf_info:
-
- page_dropped_list = []
- tables, tables_body, tables_caption, tables_footnote = [], [], [], []
- imgs, imgs_body, imgs_caption, imgs_footnote = [], [], [], []
- titles = []
- texts = []
- interequations = []
- lists = []
- indices = []
-
- for dropped_bbox in page['discarded_blocks']:
- page_dropped_list.append(dropped_bbox['bbox'])
- dropped_bbox_list.append(page_dropped_list)
- for block in page['para_blocks']:
- bbox = block['bbox']
- if block['type'] == BlockType.Table:
- tables.append(bbox)
- for nested_block in block['blocks']:
- bbox = nested_block['bbox']
- if nested_block['type'] == BlockType.TableBody:
- tables_body.append(bbox)
- elif nested_block['type'] == BlockType.TableCaption:
- tables_caption.append(bbox)
- elif nested_block['type'] == BlockType.TableFootnote:
- tables_footnote.append(bbox)
- elif block['type'] == BlockType.Image:
- imgs.append(bbox)
- for nested_block in block['blocks']:
- bbox = nested_block['bbox']
- if nested_block['type'] == BlockType.ImageBody:
- imgs_body.append(bbox)
- elif nested_block['type'] == BlockType.ImageCaption:
- imgs_caption.append(bbox)
- elif nested_block['type'] == BlockType.ImageFootnote:
- imgs_footnote.append(bbox)
- elif block['type'] == BlockType.Title:
- titles.append(bbox)
- elif block['type'] == BlockType.Text:
- texts.append(bbox)
- elif block['type'] == BlockType.InterlineEquation:
- interequations.append(bbox)
- elif block['type'] == BlockType.List:
- lists.append(bbox)
- elif block['type'] == BlockType.Index:
- indices.append(bbox)
-
- tables_list.append(tables)
- tables_body_list.append(tables_body)
- tables_caption_list.append(tables_caption)
- tables_footnote_list.append(tables_footnote)
- imgs_list.append(imgs)
- imgs_body_list.append(imgs_body)
- imgs_caption_list.append(imgs_caption)
- imgs_footnote_list.append(imgs_footnote)
- titles_list.append(titles)
- texts_list.append(texts)
- interequations_list.append(interequations)
- lists_list.append(lists)
- indexs_list.append(indices)
-
- layout_bbox_list = []
-
- table_type_order = {
- 'table_caption': 1,
- 'table_body': 2,
- 'table_footnote': 3
- }
- for page in pdf_info:
- page_block_list = []
- for block in page['para_blocks']:
- if block['type'] in [
- BlockType.Text,
- BlockType.Title,
- BlockType.InterlineEquation,
- BlockType.List,
- BlockType.Index,
- ]:
- bbox = block['bbox']
- page_block_list.append(bbox)
- elif block['type'] in [BlockType.Image]:
- for sub_block in block['blocks']:
- bbox = sub_block['bbox']
- page_block_list.append(bbox)
- elif block['type'] in [BlockType.Table]:
- sorted_blocks = sorted(block['blocks'], key=lambda x: table_type_order[x['type']])
- for sub_block in sorted_blocks:
- bbox = sub_block['bbox']
- page_block_list.append(bbox)
-
- layout_bbox_list.append(page_block_list)
-
- pdf_docs = fitz.open('pdf', pdf_bytes)
-
- for i, page in enumerate(pdf_docs):
-
- draw_bbox_without_number(i, dropped_bbox_list, page, [158, 158, 158], True)
- # draw_bbox_without_number(i, tables_list, page, [153, 153, 0], True) # color !
- draw_bbox_without_number(i, tables_body_list, page, [204, 204, 0], True)
- draw_bbox_without_number(i, tables_caption_list, page, [255, 255, 102], True)
- draw_bbox_without_number(i, tables_footnote_list, page, [229, 255, 204], True)
- # draw_bbox_without_number(i, imgs_list, page, [51, 102, 0], True)
- draw_bbox_without_number(i, imgs_body_list, page, [153, 255, 51], True)
- draw_bbox_without_number(i, imgs_caption_list, page, [102, 178, 255], True)
- draw_bbox_without_number(i, imgs_footnote_list, page, [255, 178, 102], True),
- draw_bbox_without_number(i, titles_list, page, [102, 102, 255], True)
- draw_bbox_without_number(i, texts_list, page, [153, 0, 76], True)
- draw_bbox_without_number(i, interequations_list, page, [0, 255, 0], True)
- draw_bbox_without_number(i, lists_list, page, [40, 169, 92], True)
- draw_bbox_without_number(i, indexs_list, page, [40, 169, 92], True)
-
- draw_bbox_with_number(
- i, layout_bbox_list, page, [255, 0, 0], False, draw_bbox=False
- )
-
- # Save the PDF
- pdf_docs.save(f'{out_path}/{filename}')
-
-
-def draw_span_bbox(pdf_info, pdf_bytes, out_path, filename):
- text_list = []
- inline_equation_list = []
- interline_equation_list = []
- image_list = []
- table_list = []
- dropped_list = []
- next_page_text_list = []
- next_page_inline_equation_list = []
-
- def get_span_info(span):
- if span['type'] == ContentType.Text:
- if span.get(CROSS_PAGE, False):
- next_page_text_list.append(span['bbox'])
- else:
- page_text_list.append(span['bbox'])
- elif span['type'] == ContentType.InlineEquation:
- if span.get(CROSS_PAGE, False):
- next_page_inline_equation_list.append(span['bbox'])
- else:
- page_inline_equation_list.append(span['bbox'])
- elif span['type'] == ContentType.InterlineEquation:
- page_interline_equation_list.append(span['bbox'])
- elif span['type'] == ContentType.Image:
- page_image_list.append(span['bbox'])
- elif span['type'] == ContentType.Table:
- page_table_list.append(span['bbox'])
-
- for page in pdf_info:
- page_text_list = []
- page_inline_equation_list = []
- page_interline_equation_list = []
- page_image_list = []
- page_table_list = []
- page_dropped_list = []
-
- # 将跨页的span放到移动到下一页的列表中
- if len(next_page_text_list) > 0:
- page_text_list.extend(next_page_text_list)
- next_page_text_list.clear()
- if len(next_page_inline_equation_list) > 0:
- page_inline_equation_list.extend(next_page_inline_equation_list)
- next_page_inline_equation_list.clear()
-
- # 构造dropped_list
- for block in page['discarded_blocks']:
- if block['type'] == BlockType.Discarded:
- for line in block['lines']:
- for span in line['spans']:
- page_dropped_list.append(span['bbox'])
- dropped_list.append(page_dropped_list)
- # 构造其余useful_list
- # for block in page['para_blocks']: # span直接用分段合并前的结果就可以
- for block in page['preproc_blocks']:
- if block['type'] in [
- BlockType.Text,
- BlockType.Title,
- BlockType.InterlineEquation,
- BlockType.List,
- BlockType.Index,
- ]:
- for line in block['lines']:
- for span in line['spans']:
- get_span_info(span)
- elif block['type'] in [BlockType.Image, BlockType.Table]:
- for sub_block in block['blocks']:
- for line in sub_block['lines']:
- for span in line['spans']:
- get_span_info(span)
- text_list.append(page_text_list)
- inline_equation_list.append(page_inline_equation_list)
- interline_equation_list.append(page_interline_equation_list)
- image_list.append(page_image_list)
- table_list.append(page_table_list)
- pdf_docs = fitz.open('pdf', pdf_bytes)
- for i, page in enumerate(pdf_docs):
- # 获取当前页面的数据
- draw_bbox_without_number(i, text_list, page, [255, 0, 0], False)
- draw_bbox_without_number(i, inline_equation_list, page, [0, 255, 0], False)
- draw_bbox_without_number(i, interline_equation_list, page, [0, 0, 255], False)
- draw_bbox_without_number(i, image_list, page, [255, 204, 0], False)
- draw_bbox_without_number(i, table_list, page, [204, 0, 255], False)
- draw_bbox_without_number(i, dropped_list, page, [158, 158, 158], False)
-
- # Save the PDF
- pdf_docs.save(f'{out_path}/{filename}')
-
-
-def draw_model_bbox(model_list, dataset: Dataset, out_path, filename):
- dropped_bbox_list = []
- tables_body_list, tables_caption_list, tables_footnote_list = [], [], []
- imgs_body_list, imgs_caption_list, imgs_footnote_list = [], [], []
- titles_list = []
- texts_list = []
- interequations_list = []
- magic_model = MagicModel(model_list, dataset)
- for i in range(len(model_list)):
- page_dropped_list = []
- tables_body, tables_caption, tables_footnote = [], [], []
- imgs_body, imgs_caption, imgs_footnote = [], [], []
- titles = []
- texts = []
- interequations = []
- page_info = magic_model.get_model_list(i)
- layout_dets = page_info['layout_dets']
- for layout_det in layout_dets:
- bbox = layout_det['bbox']
- if layout_det['category_id'] == CategoryId.Text:
- texts.append(bbox)
- elif layout_det['category_id'] == CategoryId.Title:
- titles.append(bbox)
- elif layout_det['category_id'] == CategoryId.TableBody:
- tables_body.append(bbox)
- elif layout_det['category_id'] == CategoryId.TableCaption:
- tables_caption.append(bbox)
- elif layout_det['category_id'] == CategoryId.TableFootnote:
- tables_footnote.append(bbox)
- elif layout_det['category_id'] == CategoryId.ImageBody:
- imgs_body.append(bbox)
- elif layout_det['category_id'] == CategoryId.ImageCaption:
- imgs_caption.append(bbox)
- elif layout_det['category_id'] == CategoryId.InterlineEquation_YOLO:
- interequations.append(bbox)
- elif layout_det['category_id'] == CategoryId.Abandon:
- page_dropped_list.append(bbox)
- elif layout_det['category_id'] == CategoryId.ImageFootnote:
- imgs_footnote.append(bbox)
-
- tables_body_list.append(tables_body)
- tables_caption_list.append(tables_caption)
- tables_footnote_list.append(tables_footnote)
- imgs_body_list.append(imgs_body)
- imgs_caption_list.append(imgs_caption)
- titles_list.append(titles)
- texts_list.append(texts)
- interequations_list.append(interequations)
- dropped_bbox_list.append(page_dropped_list)
- imgs_footnote_list.append(imgs_footnote)
-
- for i in range(len(dataset)):
- page = dataset.get_page(i)
- draw_bbox_with_number(
- i, dropped_bbox_list, page, [158, 158, 158], True
- ) # color !
- draw_bbox_with_number(i, tables_body_list, page, [204, 204, 0], True)
- draw_bbox_with_number(i, tables_caption_list, page, [255, 255, 102], True)
- draw_bbox_with_number(i, tables_footnote_list, page, [229, 255, 204], True)
- draw_bbox_with_number(i, imgs_body_list, page, [153, 255, 51], True)
- draw_bbox_with_number(i, imgs_caption_list, page, [102, 178, 255], True)
- draw_bbox_with_number(i, imgs_footnote_list, page, [255, 178, 102], True)
- draw_bbox_with_number(i, titles_list, page, [102, 102, 255], True)
- draw_bbox_with_number(i, texts_list, page, [153, 0, 76], True)
- draw_bbox_with_number(i, interequations_list, page, [0, 255, 0], True)
-
- # Save the PDF
- dataset.dump_to_file(f'{out_path}/{filename}')
-
-
-def draw_line_sort_bbox(pdf_info, pdf_bytes, out_path, filename):
- layout_bbox_list = []
-
- for page in pdf_info:
- page_line_list = []
- for block in page['preproc_blocks']:
- if block['type'] in [BlockType.Text]:
- for line in block['lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- elif block['type'] in [BlockType.Title, BlockType.InterlineEquation]:
- if 'virtual_lines' in block:
- if len(block['virtual_lines']) > 0 and block['virtual_lines'][0].get('index', None) is not None:
- for line in block['virtual_lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- else:
- for line in block['lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- elif block['type'] in [BlockType.Image, BlockType.Table]:
- for sub_block in block['blocks']:
- if sub_block['type'] in [BlockType.ImageBody, BlockType.TableBody]:
- if len(sub_block['virtual_lines']) > 0 and sub_block['virtual_lines'][0].get('index', None) is not None:
- for line in sub_block['virtual_lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- else:
- for line in sub_block['lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- elif sub_block['type'] in [BlockType.ImageCaption, BlockType.TableCaption, BlockType.ImageFootnote, BlockType.TableFootnote]:
- for line in sub_block['lines']:
- bbox = line['bbox']
- index = line['index']
- page_line_list.append({'index': index, 'bbox': bbox})
- sorted_bboxes = sorted(page_line_list, key=lambda x: x['index'])
- layout_bbox_list.append(sorted_bbox['bbox'] for sorted_bbox in sorted_bboxes)
- pdf_docs = fitz.open('pdf', pdf_bytes)
- for i, page in enumerate(pdf_docs):
- draw_bbox_with_number(i, layout_bbox_list, page, [255, 0, 0], False)
-
- pdf_docs.save(f'{out_path}/{filename}')
-
-
-def draw_char_bbox(pdf_bytes, out_path, filename):
- pdf_docs = fitz.open('pdf', pdf_bytes)
- for i, page in enumerate(pdf_docs):
- for block in page.get_text('rawdict', flags=fitz.TEXT_PRESERVE_LIGATURES | fitz.TEXT_PRESERVE_WHITESPACE | fitz.TEXT_MEDIABOX_CLIP)['blocks']:
- for line in block['lines']:
- for span in line['spans']:
- for char in span['chars']:
- char_bbox = char['bbox']
- page.draw_rect(char_bbox, color=[1, 0, 0], fill=None, fill_opacity=1, width=0.3, overlay=True,)
- pdf_docs.save(f'{out_path}/{filename}')
diff --git a/magic_pdf/libs/hash_utils.py b/magic_pdf/libs/hash_utils.py
deleted file mode 100644
index 47b8aea746eb04eeb427b775227692ef6b4d9d29..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/hash_utils.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import hashlib
-
-
-def compute_md5(file_bytes):
- hasher = hashlib.md5()
- hasher.update(file_bytes)
- return hasher.hexdigest().upper()
-
-
-def compute_sha256(input_string):
- hasher = hashlib.sha256()
- # 在Python3中,需要将字符串转化为字节对象才能被哈希函数处理
- input_bytes = input_string.encode('utf-8')
- hasher.update(input_bytes)
- return hasher.hexdigest()
diff --git a/magic_pdf/libs/json_compressor.py b/magic_pdf/libs/json_compressor.py
deleted file mode 100644
index 77ef1c876fcae0b34a42355b3edb079bb5dd891b..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/json_compressor.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import json
-import brotli
-import base64
-
-class JsonCompressor:
-
- @staticmethod
- def compress_json(data):
- """
- Compress a json object and encode it with base64
- """
- json_str = json.dumps(data)
- json_bytes = json_str.encode('utf-8')
- compressed = brotli.compress(json_bytes, quality=6)
- compressed_str = base64.b64encode(compressed).decode('utf-8') # convert bytes to string
- return compressed_str
-
- @staticmethod
- def decompress_json(compressed_str):
- """
- Decode the base64 string and decompress the json object
- """
- compressed = base64.b64decode(compressed_str.encode('utf-8')) # convert string to bytes
- decompressed_bytes = brotli.decompress(compressed)
- json_str = decompressed_bytes.decode('utf-8')
- data = json.loads(json_str)
- return data
diff --git a/magic_pdf/libs/local_math.py b/magic_pdf/libs/local_math.py
deleted file mode 100644
index 9edbcc7074dfa189a8508eb76366ae31dba4d665..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/local_math.py
+++ /dev/null
@@ -1,9 +0,0 @@
-def float_gt(a, b):
- if 0.0001 >= abs(a -b):
- return False
- return a > b
-
-def float_equal(a, b):
- if 0.0001 >= abs(a-b):
- return True
- return False
\ No newline at end of file
diff --git a/magic_pdf/libs/markdown_utils.py b/magic_pdf/libs/markdown_utils.py
deleted file mode 100644
index 036232c880b584573a4cd031fed4f457d8d63e6f..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/markdown_utils.py
+++ /dev/null
@@ -1,10 +0,0 @@
-
-def ocr_escape_special_markdown_char(content):
- """
- 转义正文里对markdown语法有特殊意义的字符
- """
- special_chars = ["*", "`", "~", "$"]
- for char in special_chars:
- content = content.replace(char, "\\" + char)
-
- return content
diff --git a/magic_pdf/libs/pdf_check.py b/magic_pdf/libs/pdf_check.py
deleted file mode 100644
index 98402b383b74800817a0770cb495e280a52b5e6c..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/pdf_check.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import fitz
-import numpy as np
-from loguru import logger
-import re
-from io import BytesIO
-from pdfminer.high_level import extract_text
-from pdfminer.layout import LAParams
-
-
-def calculate_sample_count(total_page: int):
- """
- 根据总页数和采样率计算采样页面的数量。
- """
- select_page_cnt = min(10, total_page)
- return select_page_cnt
-
-
-def extract_pages(src_pdf_bytes: bytes) -> fitz.Document:
- pdf_docs = fitz.open("pdf", src_pdf_bytes)
- total_page = len(pdf_docs)
- if total_page == 0:
- # 如果PDF没有页面,直接返回空文档
- logger.warning("PDF is empty, return empty document")
- return fitz.Document()
- select_page_cnt = calculate_sample_count(total_page)
-
- page_num = np.random.choice(total_page, select_page_cnt, replace=False)
- sample_docs = fitz.Document()
- try:
- for index in page_num:
- sample_docs.insert_pdf(pdf_docs, from_page=int(index), to_page=int(index))
- except Exception as e:
- logger.exception(e)
- return sample_docs
-
-
-def detect_invalid_chars(src_pdf_bytes: bytes) -> bool:
- """"
- 检测PDF中是否包含非法字符
- """
- '''pdfminer比较慢,需要先随机抽取10页左右的sample'''
- sample_docs = extract_pages(src_pdf_bytes)
- sample_pdf_bytes = sample_docs.tobytes()
- sample_pdf_file_like_object = BytesIO(sample_pdf_bytes)
- laparams = LAParams(
- line_overlap=0.5,
- char_margin=2.0,
- line_margin=0.5,
- word_margin=0.1,
- boxes_flow=None,
- detect_vertical=False,
- all_texts=False,
- )
- text = extract_text(pdf_file=sample_pdf_file_like_object, laparams=laparams)
- text = text.replace("\n", "")
- # logger.info(text)
- '''乱码文本用pdfminer提取出来的文本特征是(cid:xxx)'''
- cid_pattern = re.compile(r'\(cid:\d+\)')
- matches = cid_pattern.findall(text)
- cid_count = len(matches)
- cid_len = sum(len(match) for match in matches)
- text_len = len(text)
- if text_len == 0:
- cid_chars_radio = 0
- else:
- cid_chars_radio = cid_count/(cid_count + text_len - cid_len)
- logger.info(f"cid_count: {cid_count}, text_len: {text_len}, cid_chars_radio: {cid_chars_radio}")
- '''当一篇文章存在5%以上的文本是乱码时,认为该文档为乱码文档'''
- if cid_chars_radio > 0.05:
- return False # 乱码文档
- else:
- return True # 正常文档
-
-
-def count_replacement_characters(text: str) -> int:
- """
- 统计字符串中 0xfffd 字符的数量。
- """
- return text.count('\ufffd')
-
-
-def detect_invalid_chars_by_pymupdf(src_pdf_bytes: bytes) -> bool:
- sample_docs = extract_pages(src_pdf_bytes)
- doc_text = ""
- for page in sample_docs:
- page_text = page.get_text('text', flags=fitz.TEXT_PRESERVE_WHITESPACE | fitz.TEXT_MEDIABOX_CLIP)
- doc_text += page_text
- text_len = len(doc_text)
- uffd_count = count_replacement_characters(doc_text)
- if text_len == 0:
- uffd_chars_radio = 0
- else:
- uffd_chars_radio = uffd_count / text_len
- logger.info(f"uffd_count: {uffd_count}, text_len: {text_len}, uffd_chars_radio: {uffd_chars_radio}")
- '''当一篇文章存在1%以上的文本是乱码时,认为该文档为乱码文档'''
- if uffd_chars_radio > 0.01:
- return False # 乱码文档
- else:
- return True # 正常文档
\ No newline at end of file
diff --git a/magic_pdf/libs/pdf_image_tools.py b/magic_pdf/libs/pdf_image_tools.py
deleted file mode 100644
index 80201167da768f8f182c1d0eb2ae10771d96caa9..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/pdf_image_tools.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from io import BytesIO
-import cv2
-import fitz
-import numpy as np
-from PIL import Image
-from magic_pdf.data.data_reader_writer import DataWriter
-from magic_pdf.libs.commons import join_path
-from magic_pdf.libs.hash_utils import compute_sha256
-
-
-def cut_image(bbox: tuple, page_num: int, page: fitz.Page, return_path, imageWriter: DataWriter):
- """从第page_num页的page中,根据bbox进行裁剪出一张jpg图片,返回图片路径 save_path:需要同时支持s3和本地,
- 图片存放在save_path下,文件名是:
- {page_num}_{bbox[0]}_{bbox[1]}_{bbox[2]}_{bbox[3]}.jpg , bbox内数字取整。"""
- # 拼接文件名
- filename = f'{page_num}_{int(bbox[0])}_{int(bbox[1])}_{int(bbox[2])}_{int(bbox[3])}'
-
- # 老版本返回不带bucket的路径
- img_path = join_path(return_path, filename) if return_path is not None else None
-
- # 新版本生成平铺路径
- img_hash256_path = f'{compute_sha256(img_path)}.jpg'
-
- # 将坐标转换为fitz.Rect对象
- rect = fitz.Rect(*bbox)
- # 配置缩放倍数为3倍
- zoom = fitz.Matrix(3, 3)
- # 截取图片
- pix = page.get_pixmap(clip=rect, matrix=zoom)
-
- byte_data = pix.tobytes(output='jpeg', jpg_quality=95)
-
- imageWriter.write(img_hash256_path, byte_data)
-
- return img_hash256_path
-
-
-def cut_image_to_pil_image(bbox: tuple, page: fitz.Page, mode="pillow"):
-
- # 将坐标转换为fitz.Rect对象
- rect = fitz.Rect(*bbox)
- # 配置缩放倍数为3倍
- zoom = fitz.Matrix(3, 3)
- # 截取图片
- pix = page.get_pixmap(clip=rect, matrix=zoom)
-
- if mode == "cv2":
- # 直接转换为numpy数组供cv2使用
- img_array = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, pix.n)
- # PyMuPDF使用RGB顺序,而cv2使用BGR顺序
- if pix.n == 3 or pix.n == 4:
- image_result = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
- else:
- image_result = img_array
- elif mode == "pillow":
- # 将字节数据转换为文件对象
- image_file = BytesIO(pix.tobytes(output='png'))
- # 使用 Pillow 打开图像
- image_result = Image.open(image_file)
- else:
- raise ValueError(f"mode: {mode} is not supported.")
-
- return image_result
\ No newline at end of file
diff --git a/magic_pdf/libs/performance_stats.py b/magic_pdf/libs/performance_stats.py
deleted file mode 100644
index 3aeaeb33cb6832c35fea5520a78cf31626c4270c..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/performance_stats.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import time
-import functools
-from collections import defaultdict
-from typing import Dict, List
-
-
-class PerformanceStats:
- """性能统计类,用于收集和展示方法执行时间"""
-
- _stats: Dict[str, List[float]] = defaultdict(list)
-
- @classmethod
- def add_execution_time(cls, func_name: str, execution_time: float):
- """添加执行时间记录"""
- cls._stats[func_name].append(execution_time)
-
- @classmethod
- def get_stats(cls) -> Dict[str, dict]:
- """获取统计结果"""
- results = {}
- for func_name, times in cls._stats.items():
- results[func_name] = {
- 'count': len(times),
- 'total_time': sum(times),
- 'avg_time': sum(times) / len(times),
- 'min_time': min(times),
- 'max_time': max(times)
- }
- return results
-
- @classmethod
- def print_stats(cls):
- """打印统计结果"""
- stats = cls.get_stats()
- print("\n性能统计结果:")
- print("-" * 80)
- print(f"{'方法名':<40} {'调用次数':>8} {'总时间(s)':>12} {'平均时间(s)':>12}")
- print("-" * 80)
- for func_name, data in stats.items():
- print(f"{func_name:<40} {data['count']:8d} {data['total_time']:12.6f} {data['avg_time']:12.6f}")
-
-
-def measure_time(func):
- """测量方法执行时间的装饰器"""
-
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- start_time = time.time()
- result = func(*args, **kwargs)
- execution_time = time.time() - start_time
-
- # 获取更详细的函数标识
- if hasattr(func, "__self__"): # 实例方法
- class_name = func.__self__.__class__.__name__
- full_name = f"{class_name}.{func.__name__}"
- elif hasattr(func, "__qualname__"): # 类方法或静态方法
- full_name = func.__qualname__
- else:
- module_name = func.__module__
- full_name = f"{module_name}.{func.__name__}"
-
- PerformanceStats.add_execution_time(full_name, execution_time)
- return result
-
- return wrapper
\ No newline at end of file
diff --git a/magic_pdf/libs/safe_filename.py b/magic_pdf/libs/safe_filename.py
deleted file mode 100644
index 1076a4bae218e180351ef2ec4692f156e03be1c7..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/safe_filename.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import os
-
-
-def sanitize_filename(filename, replacement="_"):
- if os.name == 'nt':
- invalid_chars = '<>:"|?*'
-
- for char in invalid_chars:
- filename = filename.replace(char, replacement)
-
- return filename
diff --git a/magic_pdf/libs/version.py b/magic_pdf/libs/version.py
deleted file mode 100644
index c45d9dbf3a2fb0a83065d719614b463df244d2b3..0000000000000000000000000000000000000000
--- a/magic_pdf/libs/version.py
+++ /dev/null
@@ -1 +0,0 @@
-__version__ = "1.3.12"
diff --git a/magic_pdf/model/__init__.py b/magic_pdf/model/__init__.py
deleted file mode 100644
index 859d01b33457ba56047073fdfefb9ef718cfa236..0000000000000000000000000000000000000000
--- a/magic_pdf/model/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-__use_inside_model__ = True
-__model_mode__ = 'full'
\ No newline at end of file
diff --git a/magic_pdf/model/batch_analyze.py b/magic_pdf/model/batch_analyze.py
deleted file mode 100644
index be5e331fd801433fea2f41de317c9e1424649b00..0000000000000000000000000000000000000000
--- a/magic_pdf/model/batch_analyze.py
+++ /dev/null
@@ -1,265 +0,0 @@
-import time
-import cv2
-from loguru import logger
-from tqdm import tqdm
-
-from magic_pdf.config.constants import MODEL_NAME
-from magic_pdf.model.sub_modules.model_init import AtomModelSingleton
-from magic_pdf.model.sub_modules.model_utils import (
- clean_vram, crop_img, get_res_list_from_layout_res, get_coords_and_area)
-from magic_pdf.model.sub_modules.ocr.paddleocr2pytorch.ocr_utils import (
- get_adjusted_mfdetrec_res, get_ocr_result_list)
-
-YOLO_LAYOUT_BASE_BATCH_SIZE = 1
-MFD_BASE_BATCH_SIZE = 1
-MFR_BASE_BATCH_SIZE = 16
-
-
-class BatchAnalyze:
- def __init__(self, model_manager, batch_ratio: int, show_log, layout_model, formula_enable, table_enable):
- self.model_manager = model_manager
- self.batch_ratio = batch_ratio
- self.show_log = show_log
- self.layout_model = layout_model
- self.formula_enable = formula_enable
- self.table_enable = table_enable
-
- def __call__(self, images_with_extra_info: list) -> list:
- if len(images_with_extra_info) == 0:
- return []
-
- images_layout_res = []
- layout_start_time = time.time()
- self.model = self.model_manager.get_model(
- ocr=True,
- show_log=self.show_log,
- lang = None,
- layout_model = self.layout_model,
- formula_enable = self.formula_enable,
- table_enable = self.table_enable,
- )
-
- images = [image for image, _, _ in images_with_extra_info]
-
- if self.model.layout_model_name == MODEL_NAME.LAYOUTLMv3:
- # layoutlmv3
- for image in images:
- layout_res = self.model.layout_model(image, ignore_catids=[])
- images_layout_res.append(layout_res)
- elif self.model.layout_model_name == MODEL_NAME.DocLayout_YOLO:
- # doclayout_yolo
- layout_images = []
- for image_index, image in enumerate(images):
- layout_images.append(image)
-
- images_layout_res += self.model.layout_model.batch_predict(
- # layout_images, self.batch_ratio * YOLO_LAYOUT_BASE_BATCH_SIZE
- layout_images, YOLO_LAYOUT_BASE_BATCH_SIZE
- )
-
- # logger.info(
- # f'layout time: {round(time.time() - layout_start_time, 2)}, image num: {len(images)}'
- # )
-
- if self.model.apply_formula:
- # 公式检测
- mfd_start_time = time.time()
- images_mfd_res = self.model.mfd_model.batch_predict(
- # images, self.batch_ratio * MFD_BASE_BATCH_SIZE
- images, MFD_BASE_BATCH_SIZE
- )
- # logger.info(
- # f'mfd time: {round(time.time() - mfd_start_time, 2)}, image num: {len(images)}'
- # )
-
- # 公式识别
- mfr_start_time = time.time()
- images_formula_list = self.model.mfr_model.batch_predict(
- images_mfd_res,
- images,
- batch_size=self.batch_ratio * MFR_BASE_BATCH_SIZE,
- )
- mfr_count = 0
- for image_index in range(len(images)):
- images_layout_res[image_index] += images_formula_list[image_index]
- mfr_count += len(images_formula_list[image_index])
- # logger.info(
- # f'mfr time: {round(time.time() - mfr_start_time, 2)}, image num: {mfr_count}'
- # )
-
- # 清理显存
- # clean_vram(self.model.device, vram_threshold=8)
-
- ocr_res_list_all_page = []
- table_res_list_all_page = []
- for index in range(len(images)):
- _, ocr_enable, _lang = images_with_extra_info[index]
- layout_res = images_layout_res[index]
- np_array_img = images[index]
-
- ocr_res_list, table_res_list, single_page_mfdetrec_res = (
- get_res_list_from_layout_res(layout_res)
- )
-
- ocr_res_list_all_page.append({'ocr_res_list':ocr_res_list,
- 'lang':_lang,
- 'ocr_enable':ocr_enable,
- 'np_array_img':np_array_img,
- 'single_page_mfdetrec_res':single_page_mfdetrec_res,
- 'layout_res':layout_res,
- })
-
- for table_res in table_res_list:
- table_img, _ = crop_img(table_res, np_array_img)
- table_res_list_all_page.append({'table_res':table_res,
- 'lang':_lang,
- 'table_img':table_img,
- })
-
- # 文本框检测
- det_start = time.time()
- det_count = 0
- # for ocr_res_list_dict in ocr_res_list_all_page:
- for ocr_res_list_dict in tqdm(ocr_res_list_all_page, desc="OCR-det Predict"):
- # Process each area that requires OCR processing
- _lang = ocr_res_list_dict['lang']
- # Get OCR results for this language's images
- atom_model_manager = AtomModelSingleton()
- ocr_model = atom_model_manager.get_atom_model(
- atom_model_name='ocr',
- ocr_show_log=False,
- det_db_box_thresh=0.3,
- lang=_lang
- )
- for res in ocr_res_list_dict['ocr_res_list']:
- new_image, useful_list = crop_img(
- res, ocr_res_list_dict['np_array_img'], crop_paste_x=50, crop_paste_y=50
- )
- adjusted_mfdetrec_res = get_adjusted_mfdetrec_res(
- ocr_res_list_dict['single_page_mfdetrec_res'], useful_list
- )
-
- # OCR-det
- new_image = cv2.cvtColor(new_image, cv2.COLOR_RGB2BGR)
- ocr_res = ocr_model.ocr(
- new_image, mfd_res=adjusted_mfdetrec_res, rec=False
- )[0]
-
- # Integration results
- if ocr_res:
- ocr_result_list = get_ocr_result_list(ocr_res, useful_list, ocr_res_list_dict['ocr_enable'], new_image, _lang)
-
- if res["category_id"] == 3:
- # ocr_result_list中所有bbox的面积之和
- ocr_res_area = sum(get_coords_and_area(ocr_res_item)[4] for ocr_res_item in ocr_result_list if 'poly' in ocr_res_item)
- # 求ocr_res_area和res的面积的比值
- res_area = get_coords_and_area(res)[4]
- if res_area > 0:
- ratio = ocr_res_area / res_area
- if ratio > 0.25:
- res["category_id"] = 1
- else:
- continue
-
- ocr_res_list_dict['layout_res'].extend(ocr_result_list)
-
- # det_count += len(ocr_res_list_dict['ocr_res_list'])
- # logger.info(f'ocr-det time: {round(time.time()-det_start, 2)}, image num: {det_count}')
-
-
- # 表格识别 table recognition
- if self.model.apply_table:
- table_start = time.time()
- # for table_res_list_dict in table_res_list_all_page:
- for table_res_dict in tqdm(table_res_list_all_page, desc="Table Predict"):
- _lang = table_res_dict['lang']
- atom_model_manager = AtomModelSingleton()
- table_model = atom_model_manager.get_atom_model(
- atom_model_name='table',
- table_model_name='rapid_table',
- table_model_path='',
- table_max_time=400,
- device='cpu',
- lang=_lang,
- table_sub_model_name='slanet_plus'
- )
- html_code, table_cell_bboxes, logic_points, elapse = table_model.predict(table_res_dict['table_img'])
- # 判断是否返回正常
- if html_code:
- expected_ending = html_code.strip().endswith(
- '