Unverified Commit 9f352df0 authored by drunkpig's avatar drunkpig Committed by GitHub
Browse files

Realese 0.8.0 (#586)



* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

* fix(ocr_mkcontent): improve language detection and content formatting (#458)

Optimize the language detection logic to enhance content formatting.  This
change addresses issues with long word segmentation. Language detection now uses a
threshold to determine the language of a text based on the proportion of English characters.
Formatting rules for content have been updated to consider a list of languages (initially
including Chinese, Japanese, and Korean) where no space is added between content segments
for inline equations and text spans, improving the handling of Asian languages.

The impact of these changes includes improved accuracy in language detection, better
segmentation of long words, and more appropriate spacing in content formatting for multiple
languages.

* fix(self_modify): merge detection boxes for optimized text region detection (#448)

Merge adjacent and overlapping detection boxes to optimize text region detection in
the document. Post processing of text boxes is enhanced by consolidating them into
larger text lines, taking into account their vertical and horizontal alignment. This
improvement reduces fragmentation and improves the readability of detected text blocks.

* fix(pdf-extract): adjust box threshold for OCR detection (#447)

Tuned the detection box threshold parameter in the OCR model initialization to improve the
accuracy of text extraction from images. The threshold was modified from 0.6 to
0.3 to filter out smaller detection boxes, which is expected to enhance the quality of the extracted
text by reducing noise and false positives in the OCR process.

* feat: rename the file generated by command line tools (#401)

* feat: rename the file generated by command line tools

* feat: add pdf filename as prefix to {span,layout,model}.pdf

---------
Co-authored-by: default avataricecraft <tmortred@gmail.com>
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>

* fix(ocr_mkcontent): revise table caption output (#397)

* fix(ocr_mkcontent): revise table caption output

- Ensuring that
  table captions are properly included in the output.
- Remove the redundant `table_caption` variable。

* Update cla.yml

* Update bug_report.yml

* feat(cli): add debug option for detailed error handling

Enable users to invoke the CLI command with a new debug flag to get detailed debugging information.

* fix(pdf-extract-kit): adjust crop_paste parameters for better accuracyThe crop_paste_x and crop_paste_y values in the pdf_extract_kit.py have been modified
to improve the accuracy and consistency of OCR processing. The new values are set to 25
to ensure more precise image cropping and pasting which leads to better OCR recognition
results.

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* fix(pdf-extract-kit): increase crop_paste margin for OCR processingDouble the crop_paste margin from25 to 50 to ensure better OCR accuracy and
handling of border cases. This change will help in improving the overall quality of
OCR'ed text by providing more context around the detected text areas.

* fix(common): deep copy model list before drawing model bbox

Use a deep copy of the original model list in `drow_model_bbox` to avoid potential
modifications to the source data. This ensures the integrity of the original models
is maintained while generating the model bounding boxes visualization.

---------
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* build(docker): update docker build step (#471)

* build(docker): update base image to Ubuntu 22.04 and install PaddlePaddleUpgrade the Docker base image from ubuntu:latest to ubuntu:22.04 for improved
performance and stability.

Additionally, integrate PaddlePaddle GPU version 3.0.0b1
into the Docker build for enhanced AI capabilities. The MinIO configuration file has
also been updated to the latest version.

* build(dockerfile): Updated the Dockerfile

* build(Dockerfile): update Dockerfile

* docs(docker): add instructions for quick deployment with Docker

Include Docker-based deployment instructions in the README for both English and
Chinese locales. This update provides users a quick-start guide to using Docker for
deployment, with notes on GPU VRAM requirements and default acceleration features.

* build(docker): Layer the installation of dependencies, downloading the model, and the setup of the program itself.

* build(docker): Layer the installation of dependencies, downloading the model, and the setup of the program itself.

* upload an introduction about chemical formula and update readme.md (#489)

* upload an introduction about chemical formula

* rename 2 files

* update readme.md at TODO in chemstery

* rename 2 files and update readme.md at TODO in chemstery

* update README_zh-CN.md at TODO in chemstery

* upload an introduction about chemical formula and update readme.md (#489)

* upload an introduction about chemical formula

* rename 2 files

* update readme.md at TODO in chemstery

* rename 2 files and update readme.md at TODO in chemstery

* update README_zh-CN.md at TODO in chemstery

* fix: remove the default value of output option in tools/cli.py and tools/cli_dev.py (#494)
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>

* feat: add test case (#499)
Co-authored-by: default avatarquyuan <quyuan@pjlab.org>

* Update cla.yml

* Update gpu-ci.yml

* Update cli.yml

* Delete .github/workflows/gpu-ci.yml

* fix(pdf-parse-union-core): #492 decrease span threshold for block filling (#500)

Reduce the span threshold used in fill_spans_in_blocks from 0.6 to 0.3 to
improve the accuracy of block filling based on layout analysis.

* fix(detect_all_bboxes): remove small overlapping blocks by merging (#501)

Previously, small blocks that overlapped with larger ones were merely removed. This fix
changes the approach to merge smaller blocks into the larger block instead, ensuring that
no information is lost and the larger block encompasses all the text content fully.

* feat(cli&analyze&pipeline): add start_page and end_page args for pagination (#507)

* feat(cli&analyze&pipeline): add start_page and end_page args for paginationAdd start_page_id and end_page_id arguments to various components of the PDF parsing
pipeline to support pagination functionality. This feature allows users to specify the
range of pages to be processed, enhancing the efficiency and flexibility of the system.

* feat(cli&analyze&pipeline): add start_page and end_page args for paginationAdd start_page_id and end_page_id arguments to various components of the PDF parsing
pipeline to support pagination functionality. This feature allows users to specify the
range of pages to be processed, enhancing the efficiency and flexibility of the system.

* feat(cli&analyze&pipeline): add start_page and end_page args for paginationAdd start_page_id and end_page_id arguments to various components of the PDF parsing
pipeline to support pagination functionality. This feature allows users to specify the
range of pages to be processed, enhancing the efficiency and flexibility of the system.

* Feat/support rag (#510)

* Create requirements-docker.txt

* feat: update deps to support rag

* feat: add support to rag, add rag_data_reader api for rag integration

* feat: let user retrieve the filename of the processed file

* feat: add projects demo for rag integrations

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>

* Update Dockerfile

* feat(gradio): add app by gradio (#512)

* fix: replace \u0002, \u0003 in common text (#521)

* fix replace \u0002, \u0003 in common text

* fix(para): When an English line ends with a hyphen, do not add a space at the end.

* fix(end_page_id):Fix the issue where end_page_id is corrected to len-1 when its input is 0. (#518)

* fix(para): When an English line ends with a hyphen, do not add a space at the end. (#523)

* fix replace \u0002, \u0003 in common text

* fix(para): When an English line ends with a hyphen, do not add a space at the end.

* fix: delete hyphen at end of line

* Release: Release  0.7.1 verison, update dev (#527)

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#493)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#508)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* Update cla.yml

* Delete .github/workflows/gpu-ci.yml

* Update Huggingface and ModelScope links to organization account

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#511)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* Update cla.yml

* Delete .github/workflows/gpu-ci.yml

* Update Huggingface and ModelScope links to organization account

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

---------
Co-authored-by: default avatarKaiwen Liu <lkw_buaa@163.com>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

* Hotfix readme 0.7.1 (#529)

* release: release 0.7.1 version (#526)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#493)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#508)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* Update cla.yml

* Delete .github/workflows/gpu-ci.yml

* Update Huggingface and ModelScope links to organization account

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

* feat<table model>: add tablemaster with paddleocr to detect and recognize table (#511)

* Update cla.yml

* Update bug_report.yml

* Update README_zh-CN.md (#404)

correct FAQ url

* Update README_zh-CN.md (#404) (#409) (#410)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* Update FAQ_zh_cn.md

add new issue

* Update FAQ_en_us.md

* Update README_Windows_CUDA_Acceleration_zh_CN.md

* Update README_zh-CN.md

* @Thepathakarpit has signed the CLA in opendatalab/MinerU#418

* Update cla.yml

* feat: add tablemaster_paddle (#463)

* Update README_zh-CN.md (#404) (#409)

correct FAQ url
Co-authored-by: default avatarsfk <18810651050@163.com>

* add dockerfile (#189)
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>

* Update cla.yml

* Update cla.yml

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>

* <fix>(para_split_v2): index out of range issue of span_text first char (#396)
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>

* @Matthijz98 has signed the CLA in opendatalab/MinerU#467

* Create download_models.py

* Create requirements-docker.txt

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* @strongerfly has signed the CLA in opendatalab/MinerU#487

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* Update cla.yml

* Delete .github/workflows/gpu-ci.yml

* Update Huggingface and ModelScope links to organization account

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

* feat<table model>: add tablemaster with paddleocr to detect and recognize table

---------
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

---------
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarKaiwen Liu <lkw_buaa@163.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

* Update README.md

* Update README_zh-CN.md

* Update README_zh-CN.md

---------
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatardrunkpig <60862764+drunkpig@users.noreply.github.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avatarKaiwen Liu <lkw_buaa@163.com>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>

* Update README_zh-CN.md

delete Known issue about table recognition

* Update Dockerfile

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384 (#542)

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: typo error in markdown (#536)
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>

* fix(gradio): remove unused imports and simplify pdf display (#534)

Removed the previously used gradio and gradio-pdf imports which were not leveraged in the code. Also,
replaced the custom `show_pdf` function with direct use of the `PDF` component from gradio for a simpler
and more integrated PDF upload and display solution, improving code maintainability and readability.

* Feat/support footnote in figure (#532)

* feat: support figure footnote

* feat: using the relative position to combine footnote, table, image

* feat: add the readme of projects

* fix: code spell in unittest

---------
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>

* refactor(pdf_extract_kit): implement singleton pattern for atomic models (#533)

Refactor the pdf_extract_kit module to utilize a singleton pattern when initializing
atomic models. This change ensures that atomic models are instantiated at most once,
optimizing memory usage and reducing redundant initialization steps. The AtomModelSingleton
class now manages the instantiation and retrieval of atomic models, improving the
overall structure and efficiency of the codebase.

* Update README.md

* Update README_zh-CN.md

* Update README_zh-CN.md

add HF、modelscope、colab url

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README_zh-CN.md

* Rename README.md to README_zh-CN.md

* Create readme.md

* Rename readme.md to README.md

* Rename README.md to README_zh-CN.md

* Update README_zh-CN.md

* Create README.md

* Update README.md

* Update README.md

* Update README.md

* Update README_zh-CN.md

* Update README.md

* Update README_zh-CN.md

* Update README_zh-CN.md

* Update README.md

* Update README_zh-CN.md

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384 (#573)

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* fix: resolve inaccuracy of drawing layout box caused by paragraphs combination #384

* Update README_zh-CN.md

* Update README.md

* Update README.md

* Update README.md

* Update README_zh-CN.md

* add rag data api

* Update README_zh-CN.md

update rag api image

* Update README.md

docs: remove RAG related release notes

* Update README_zh-CN.md

docs: remove RAG related release notes

* Update README_zh-CN.md

update 更新记录

---------
Co-authored-by: default avatarsfk <18810651050@163.com>
Co-authored-by: default avatarAoyang Fang <222010547@link.cuhk.edu.cn>
Co-authored-by: default avatarXiaomeng Zhao <moe@myhloli.com>
Co-authored-by: default avataricecraft <tmortred@163.com>
Co-authored-by: default avataricecraft <tmortred@gmail.com>
Co-authored-by: default avataricecraft <xurui1@pjlab.org.cn>
Co-authored-by: default avatargithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: default avatarSiyu Hao <131659128+GDDGCZ518@users.noreply.github.com>
Co-authored-by: default avataryyy <102640628+dt-yy@users.noreply.github.com>
Co-authored-by: default avatarquyuan <quyuan@pjlab.org>
Co-authored-by: default avatarKaiwen Liu <lkw_buaa@163.com>
Co-authored-by: default avatarliukaiwen <liukaiwen@pjlab.org.cn>
Co-authored-by: default avatarwangbinDL <wangbin_research@163.com>
parent b6633cd6
from magic_pdf.libs.Constants import CROSS_PAGE
from magic_pdf.libs.commons import fitz # PyMuPDF from magic_pdf.libs.commons import fitz # PyMuPDF
from magic_pdf.libs.ocr_content_type import ContentType, BlockType, CategoryId from magic_pdf.libs.Constants import CROSS_PAGE
from magic_pdf.libs.ocr_content_type import BlockType, CategoryId, ContentType
from magic_pdf.model.magic_model import MagicModel from magic_pdf.model.magic_model import MagicModel
...@@ -65,11 +65,13 @@ def draw_bbox_with_number(i, bbox_list, page, rgb_config, fill_config): ...@@ -65,11 +65,13 @@ def draw_bbox_with_number(i, bbox_list, page, rgb_config, fill_config):
) # Insert the index in the top left corner of the rectangle ) # Insert the index in the top left corner of the rectangle
def draw_layout_bbox(pdf_info, pdf_bytes, out_path): def draw_layout_bbox(pdf_info, pdf_bytes, out_path, filename):
layout_bbox_list = [] layout_bbox_list = []
dropped_bbox_list = [] dropped_bbox_list = []
tables_list, tables_body_list, tables_caption_list, tables_footnote_list = [], [], [], [] tables_list, tables_body_list = [], []
tables_caption_list, tables_footnote_list = [], []
imgs_list, imgs_body_list, imgs_caption_list = [], [], [] imgs_list, imgs_body_list, imgs_caption_list = [], [], []
imgs_footnote_list = []
titles_list = [] titles_list = []
texts_list = [] texts_list = []
interequations_list = [] interequations_list = []
...@@ -77,41 +79,43 @@ def draw_layout_bbox(pdf_info, pdf_bytes, out_path): ...@@ -77,41 +79,43 @@ def draw_layout_bbox(pdf_info, pdf_bytes, out_path):
page_layout_list = [] page_layout_list = []
page_dropped_list = [] page_dropped_list = []
tables, tables_body, tables_caption, tables_footnote = [], [], [], [] tables, tables_body, tables_caption, tables_footnote = [], [], [], []
imgs, imgs_body, imgs_caption = [], [], [] imgs, imgs_body, imgs_caption, imgs_footnote = [], [], [], []
titles = [] titles = []
texts = [] texts = []
interequations = [] interequations = []
for layout in page["layout_bboxes"]: for layout in page['layout_bboxes']:
page_layout_list.append(layout["layout_bbox"]) page_layout_list.append(layout['layout_bbox'])
layout_bbox_list.append(page_layout_list) layout_bbox_list.append(page_layout_list)
for dropped_bbox in page["discarded_blocks"]: for dropped_bbox in page['discarded_blocks']:
page_dropped_list.append(dropped_bbox["bbox"]) page_dropped_list.append(dropped_bbox['bbox'])
dropped_bbox_list.append(page_dropped_list) dropped_bbox_list.append(page_dropped_list)
for block in page["para_blocks"]: for block in page['para_blocks']:
bbox = block["bbox"] bbox = block['bbox']
if block["type"] == BlockType.Table: if block['type'] == BlockType.Table:
tables.append(bbox) tables.append(bbox)
for nested_block in block["blocks"]: for nested_block in block['blocks']:
bbox = nested_block["bbox"] bbox = nested_block['bbox']
if nested_block["type"] == BlockType.TableBody: if nested_block['type'] == BlockType.TableBody:
tables_body.append(bbox) tables_body.append(bbox)
elif nested_block["type"] == BlockType.TableCaption: elif nested_block['type'] == BlockType.TableCaption:
tables_caption.append(bbox) tables_caption.append(bbox)
elif nested_block["type"] == BlockType.TableFootnote: elif nested_block['type'] == BlockType.TableFootnote:
tables_footnote.append(bbox) tables_footnote.append(bbox)
elif block["type"] == BlockType.Image: elif block['type'] == BlockType.Image:
imgs.append(bbox) imgs.append(bbox)
for nested_block in block["blocks"]: for nested_block in block['blocks']:
bbox = nested_block["bbox"] bbox = nested_block['bbox']
if nested_block["type"] == BlockType.ImageBody: if nested_block['type'] == BlockType.ImageBody:
imgs_body.append(bbox) imgs_body.append(bbox)
elif nested_block["type"] == BlockType.ImageCaption: elif nested_block['type'] == BlockType.ImageCaption:
imgs_caption.append(bbox) imgs_caption.append(bbox)
elif block["type"] == BlockType.Title: elif nested_block['type'] == BlockType.ImageFootnote:
imgs_footnote.append(bbox)
elif block['type'] == BlockType.Title:
titles.append(bbox) titles.append(bbox)
elif block["type"] == BlockType.Text: elif block['type'] == BlockType.Text:
texts.append(bbox) texts.append(bbox)
elif block["type"] == BlockType.InterlineEquation: elif block['type'] == BlockType.InterlineEquation:
interequations.append(bbox) interequations.append(bbox)
tables_list.append(tables) tables_list.append(tables)
tables_body_list.append(tables_body) tables_body_list.append(tables_body)
...@@ -120,30 +124,40 @@ def draw_layout_bbox(pdf_info, pdf_bytes, out_path): ...@@ -120,30 +124,40 @@ def draw_layout_bbox(pdf_info, pdf_bytes, out_path):
imgs_list.append(imgs) imgs_list.append(imgs)
imgs_body_list.append(imgs_body) imgs_body_list.append(imgs_body)
imgs_caption_list.append(imgs_caption) imgs_caption_list.append(imgs_caption)
imgs_footnote_list.append(imgs_footnote)
titles_list.append(titles) titles_list.append(titles)
texts_list.append(texts) texts_list.append(texts)
interequations_list.append(interequations) interequations_list.append(interequations)
pdf_docs = fitz.open("pdf", pdf_bytes) pdf_docs = fitz.open('pdf', pdf_bytes)
for i, page in enumerate(pdf_docs): for i, page in enumerate(pdf_docs):
draw_bbox_with_number(i, layout_bbox_list, page, [255, 0, 0], False) draw_bbox_with_number(i, layout_bbox_list, page, [255, 0, 0], False)
draw_bbox_without_number(i, dropped_bbox_list, page, [158, 158, 158], True) draw_bbox_without_number(i, dropped_bbox_list, page, [158, 158, 158],
draw_bbox_without_number(i, tables_list, page, [153, 153, 0], True) # color ! True)
draw_bbox_without_number(i, tables_body_list, page, [204, 204, 0], True) draw_bbox_without_number(i, tables_list, page, [153, 153, 0],
draw_bbox_without_number(i, tables_caption_list, page, [255, 255, 102], True) True) # color !
draw_bbox_without_number(i, tables_footnote_list, page, [229, 255, 204], True) 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_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_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_caption_list, page, [102, 178, 255],
True)
draw_bbox_with_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, titles_list, page, [102, 102, 255], True)
draw_bbox_without_number(i, texts_list, page, [153, 0, 76], 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, interequations_list, page, [0, 255, 0],
True)
# Save the PDF # Save the PDF
pdf_docs.save(f"{out_path}/layout.pdf") pdf_docs.save(f'{out_path}/{filename}_layout.pdf')
def draw_span_bbox(pdf_info, pdf_bytes, out_path): def draw_span_bbox(pdf_info, pdf_bytes, out_path, filename):
text_list = [] text_list = []
inline_equation_list = [] inline_equation_list = []
interline_equation_list = [] interline_equation_list = []
...@@ -154,22 +168,22 @@ def draw_span_bbox(pdf_info, pdf_bytes, out_path): ...@@ -154,22 +168,22 @@ def draw_span_bbox(pdf_info, pdf_bytes, out_path):
next_page_inline_equation_list = [] next_page_inline_equation_list = []
def get_span_info(span): def get_span_info(span):
if span["type"] == ContentType.Text: if span['type'] == ContentType.Text:
if span.get(CROSS_PAGE, False): if span.get(CROSS_PAGE, False):
next_page_text_list.append(span["bbox"]) next_page_text_list.append(span['bbox'])
else: else:
page_text_list.append(span["bbox"]) page_text_list.append(span['bbox'])
elif span["type"] == ContentType.InlineEquation: elif span['type'] == ContentType.InlineEquation:
if span.get(CROSS_PAGE, False): if span.get(CROSS_PAGE, False):
next_page_inline_equation_list.append(span["bbox"]) next_page_inline_equation_list.append(span['bbox'])
else: else:
page_inline_equation_list.append(span["bbox"]) page_inline_equation_list.append(span['bbox'])
elif span["type"] == ContentType.InterlineEquation: elif span['type'] == ContentType.InterlineEquation:
page_interline_equation_list.append(span["bbox"]) page_interline_equation_list.append(span['bbox'])
elif span["type"] == ContentType.Image: elif span['type'] == ContentType.Image:
page_image_list.append(span["bbox"]) page_image_list.append(span['bbox'])
elif span["type"] == ContentType.Table: elif span['type'] == ContentType.Table:
page_table_list.append(span["bbox"]) page_table_list.append(span['bbox'])
for page in pdf_info: for page in pdf_info:
page_text_list = [] page_text_list = []
...@@ -188,84 +202,89 @@ def draw_span_bbox(pdf_info, pdf_bytes, out_path): ...@@ -188,84 +202,89 @@ def draw_span_bbox(pdf_info, pdf_bytes, out_path):
next_page_inline_equation_list.clear() next_page_inline_equation_list.clear()
# 构造dropped_list # 构造dropped_list
for block in page["discarded_blocks"]: for block in page['discarded_blocks']:
if block["type"] == BlockType.Discarded: if block['type'] == BlockType.Discarded:
for line in block["lines"]: for line in block['lines']:
for span in line["spans"]: for span in line['spans']:
page_dropped_list.append(span["bbox"]) page_dropped_list.append(span['bbox'])
dropped_list.append(page_dropped_list) dropped_list.append(page_dropped_list)
# 构造其余useful_list # 构造其余useful_list
for block in page["para_blocks"]: for block in page['para_blocks']:
if block["type"] in [ if block['type'] in [
BlockType.Text, BlockType.Text,
BlockType.Title, BlockType.Title,
BlockType.InterlineEquation, BlockType.InterlineEquation,
]: ]:
for line in block["lines"]: for line in block['lines']:
for span in line["spans"]: for span in line['spans']:
get_span_info(span) get_span_info(span)
elif block["type"] in [BlockType.Image, BlockType.Table]: elif block['type'] in [BlockType.Image, BlockType.Table]:
for sub_block in block["blocks"]: for sub_block in block['blocks']:
for line in sub_block["lines"]: for line in sub_block['lines']:
for span in line["spans"]: for span in line['spans']:
get_span_info(span) get_span_info(span)
text_list.append(page_text_list) text_list.append(page_text_list)
inline_equation_list.append(page_inline_equation_list) inline_equation_list.append(page_inline_equation_list)
interline_equation_list.append(page_interline_equation_list) interline_equation_list.append(page_interline_equation_list)
image_list.append(page_image_list) image_list.append(page_image_list)
table_list.append(page_table_list) table_list.append(page_table_list)
pdf_docs = fitz.open("pdf", pdf_bytes) pdf_docs = fitz.open('pdf', pdf_bytes)
for i, page in enumerate(pdf_docs): for i, page in enumerate(pdf_docs):
# 获取当前页面的数据 # 获取当前页面的数据
draw_bbox_without_number(i, text_list, page, [255, 0, 0], False) 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, inline_equation_list, page, [0, 255, 0],
draw_bbox_without_number(i, interline_equation_list, page, [0, 0, 255], False) 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, image_list, page, [255, 204, 0], False)
draw_bbox_without_number(i, table_list, page, [204, 0, 255], 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) draw_bbox_without_number(i, dropped_list, page, [158, 158, 158], False)
# Save the PDF # Save the PDF
pdf_docs.save(f"{out_path}/spans.pdf") pdf_docs.save(f'{out_path}/{filename}_spans.pdf')
def drow_model_bbox(model_list: list, pdf_bytes, out_path): def drow_model_bbox(model_list: list, pdf_bytes, out_path, filename):
dropped_bbox_list = [] dropped_bbox_list = []
tables_body_list, tables_caption_list, tables_footnote_list = [], [], [] tables_body_list, tables_caption_list, tables_footnote_list = [], [], []
imgs_body_list, imgs_caption_list = [], [] imgs_body_list, imgs_caption_list, imgs_footnote_list = [], [], []
titles_list = [] titles_list = []
texts_list = [] texts_list = []
interequations_list = [] interequations_list = []
pdf_docs = fitz.open("pdf", pdf_bytes) pdf_docs = fitz.open('pdf', pdf_bytes)
magic_model = MagicModel(model_list, pdf_docs) magic_model = MagicModel(model_list, pdf_docs)
for i in range(len(model_list)): for i in range(len(model_list)):
page_dropped_list = [] page_dropped_list = []
tables_body, tables_caption, tables_footnote = [], [], [] tables_body, tables_caption, tables_footnote = [], [], []
imgs_body, imgs_caption = [], [] imgs_body, imgs_caption, imgs_footnote = [], [], []
titles = [] titles = []
texts = [] texts = []
interequations = [] interequations = []
page_info = magic_model.get_model_list(i) page_info = magic_model.get_model_list(i)
layout_dets = page_info["layout_dets"] layout_dets = page_info['layout_dets']
for layout_det in layout_dets: for layout_det in layout_dets:
bbox = layout_det["bbox"] bbox = layout_det['bbox']
if layout_det["category_id"] == CategoryId.Text: if layout_det['category_id'] == CategoryId.Text:
texts.append(bbox) texts.append(bbox)
elif layout_det["category_id"] == CategoryId.Title: elif layout_det['category_id'] == CategoryId.Title:
titles.append(bbox) titles.append(bbox)
elif layout_det["category_id"] == CategoryId.TableBody: elif layout_det['category_id'] == CategoryId.TableBody:
tables_body.append(bbox) tables_body.append(bbox)
elif layout_det["category_id"] == CategoryId.TableCaption: elif layout_det['category_id'] == CategoryId.TableCaption:
tables_caption.append(bbox) tables_caption.append(bbox)
elif layout_det["category_id"] == CategoryId.TableFootnote: elif layout_det['category_id'] == CategoryId.TableFootnote:
tables_footnote.append(bbox) tables_footnote.append(bbox)
elif layout_det["category_id"] == CategoryId.ImageBody: elif layout_det['category_id'] == CategoryId.ImageBody:
imgs_body.append(bbox) imgs_body.append(bbox)
elif layout_det["category_id"] == CategoryId.ImageCaption: elif layout_det['category_id'] == CategoryId.ImageCaption:
imgs_caption.append(bbox) imgs_caption.append(bbox)
elif layout_det["category_id"] == CategoryId.InterlineEquation_YOLO: elif layout_det[
'category_id'] == CategoryId.InterlineEquation_YOLO:
interequations.append(bbox) interequations.append(bbox)
elif layout_det["category_id"] == CategoryId.Abandon: elif layout_det['category_id'] == CategoryId.Abandon:
page_dropped_list.append(bbox) page_dropped_list.append(bbox)
elif layout_det['category_id'] == CategoryId.ImageFootnote:
imgs_footnote.append(bbox)
tables_body_list.append(tables_body) tables_body_list.append(tables_body)
tables_caption_list.append(tables_caption) tables_caption_list.append(tables_caption)
...@@ -276,17 +295,24 @@ def drow_model_bbox(model_list: list, pdf_bytes, out_path): ...@@ -276,17 +295,24 @@ def drow_model_bbox(model_list: list, pdf_bytes, out_path):
texts_list.append(texts) texts_list.append(texts)
interequations_list.append(interequations) interequations_list.append(interequations)
dropped_bbox_list.append(page_dropped_list) dropped_bbox_list.append(page_dropped_list)
imgs_footnote_list.append(imgs_footnote)
for i, page in enumerate(pdf_docs): for i, page in enumerate(pdf_docs):
draw_bbox_with_number(i, dropped_bbox_list, page, [158, 158, 158], True) # color ! 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_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_caption_list, page, [255, 255, 102],
draw_bbox_with_number(i, tables_footnote_list, page, [229, 255, 204], True) 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_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_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, titles_list, page, [102, 102, 255], True)
draw_bbox_with_number(i, texts_list, page, [153, 0, 76], 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) draw_bbox_with_number(i, interequations_list, page, [0, 255, 0], True)
# Save the PDF # Save the PDF
pdf_docs.save(f"{out_path}/model.pdf") pdf_docs.save(f'{out_path}/{filename}_model.pdf')
\ No newline at end of file
class ContentType: class ContentType:
Image = "image" Image = 'image'
Table = "table" Table = 'table'
Text = "text" Text = 'text'
InlineEquation = "inline_equation" InlineEquation = 'inline_equation'
InterlineEquation = "interline_equation" InterlineEquation = 'interline_equation'
class BlockType: class BlockType:
Image = "image" Image = 'image'
ImageBody = "image_body" ImageBody = 'image_body'
ImageCaption = "image_caption" ImageCaption = 'image_caption'
Table = "table" ImageFootnote = 'image_footnote'
TableBody = "table_body" Table = 'table'
TableCaption = "table_caption" TableBody = 'table_body'
TableFootnote = "table_footnote" TableCaption = 'table_caption'
Text = "text" TableFootnote = 'table_footnote'
Title = "title" Text = 'text'
InterlineEquation = "interline_equation" Title = 'title'
Footnote = "footnote" InterlineEquation = 'interline_equation'
Discarded = "discarded" Footnote = 'footnote'
Discarded = 'discarded'
class CategoryId: class CategoryId:
...@@ -33,3 +35,4 @@ class CategoryId: ...@@ -33,3 +35,4 @@ class CategoryId:
InlineEquation = 13 InlineEquation = 13
InterlineEquation_YOLO = 14 InterlineEquation_YOLO = 14
OcrText = 15 OcrText = 15
ImageFootnote = 101
...@@ -103,20 +103,32 @@ def custom_model_init(ocr: bool = False, show_log: bool = False): ...@@ -103,20 +103,32 @@ def custom_model_init(ocr: bool = False, show_log: bool = False):
return custom_model return custom_model
def doc_analyze(pdf_bytes: bytes, ocr: bool = False, show_log: bool = False): def doc_analyze(pdf_bytes: bytes, ocr: bool = False, show_log: bool = False,
start_page_id=0, end_page_id=None):
model_manager = ModelSingleton() model_manager = ModelSingleton()
custom_model = model_manager.get_model(ocr, show_log) custom_model = model_manager.get_model(ocr, show_log)
images = load_images_from_pdf(pdf_bytes) images = load_images_from_pdf(pdf_bytes)
# end_page_id = end_page_id if end_page_id else len(images) - 1
end_page_id = end_page_id if end_page_id is not None and end_page_id >= 0 else len(images) - 1
if end_page_id > len(images) - 1:
logger.warning("end_page_id is out of range, use images length")
end_page_id = len(images) - 1
model_json = [] model_json = []
doc_analyze_start = time.time() doc_analyze_start = time.time()
for index, img_dict in enumerate(images): for index, img_dict in enumerate(images):
img = img_dict["img"] img = img_dict["img"]
page_width = img_dict["width"] page_width = img_dict["width"]
page_height = img_dict["height"] page_height = img_dict["height"]
if start_page_id <= index <= end_page_id:
result = custom_model(img) result = custom_model(img)
else:
result = []
page_info = {"page_no": index, "height": page_height, "width": page_width} page_info = {"page_no": index, "height": page_height, "width": page_width}
page_dict = {"layout_dets": result, "page_info": page_info} page_dict = {"layout_dets": result, "page_info": page_info}
model_json.append(page_dict) model_json.append(page_dict)
......
This diff is collapsed.
class MODEL: class MODEL:
Paddle = "pp_structure_v2" Paddle = "pp_structure_v2"
PEK = "pdf_extract_kit" PEK = "pdf_extract_kit"
class AtomicModel:
Layout = "layout"
MFD = "mfd"
MFR = "mfr"
OCR = "ocr"
Table = "table"
...@@ -3,6 +3,7 @@ import os ...@@ -3,6 +3,7 @@ import os
import time import time
from magic_pdf.libs.Constants import * from magic_pdf.libs.Constants import *
from magic_pdf.model.model_list import AtomicModel
os.environ['NO_ALBUMENTATIONS_UPDATE'] = '1' # 禁止albumentations检查更新 os.environ['NO_ALBUMENTATIONS_UPDATE'] = '1' # 禁止albumentations检查更新
try: try:
...@@ -64,7 +65,8 @@ def mfr_model_init(weight_dir, cfg_path, _device_='cpu'): ...@@ -64,7 +65,8 @@ def mfr_model_init(weight_dir, cfg_path, _device_='cpu'):
model = task.build_model(cfg) model = task.build_model(cfg)
model = model.to(_device_) model = model.to(_device_)
vis_processor = load_processor('formula_image_eval', cfg.config.datasets.formula_rec_eval.vis_processor.eval) vis_processor = load_processor('formula_image_eval', cfg.config.datasets.formula_rec_eval.vis_processor.eval)
return model, vis_processor mfr_transform = transforms.Compose([vis_processor, ])
return [model, mfr_transform]
def layout_model_init(weight, config_file, device): def layout_model_init(weight, config_file, device):
...@@ -72,6 +74,11 @@ def layout_model_init(weight, config_file, device): ...@@ -72,6 +74,11 @@ def layout_model_init(weight, config_file, device):
return model return model
def ocr_model_init(show_log: bool = False, det_db_box_thresh=0.3):
model = ModifiedPaddleOCR(show_log=show_log, det_db_box_thresh=det_db_box_thresh)
return model
class MathDataset(Dataset): class MathDataset(Dataset):
def __init__(self, image_paths, transform=None): def __init__(self, image_paths, transform=None):
self.image_paths = image_paths self.image_paths = image_paths
...@@ -91,6 +98,58 @@ class MathDataset(Dataset): ...@@ -91,6 +98,58 @@ class MathDataset(Dataset):
return image return image
class AtomModelSingleton:
_instance = None
_models = {}
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def get_atom_model(self, atom_model_name: str, **kwargs):
if atom_model_name not in self._models:
self._models[atom_model_name] = atom_model_init(model_name=atom_model_name, **kwargs)
return self._models[atom_model_name]
def atom_model_init(model_name: str, **kwargs):
if model_name == AtomicModel.Layout:
atom_model = layout_model_init(
kwargs.get("layout_weights"),
kwargs.get("layout_config_file"),
kwargs.get("device")
)
elif model_name == AtomicModel.MFD:
atom_model = mfd_model_init(
kwargs.get("mfd_weights")
)
elif model_name == AtomicModel.MFR:
atom_model = mfr_model_init(
kwargs.get("mfr_weight_dir"),
kwargs.get("mfr_cfg_path"),
kwargs.get("device")
)
elif model_name == AtomicModel.OCR:
atom_model = ocr_model_init(
kwargs.get("ocr_show_log"),
kwargs.get("det_db_box_thresh")
)
elif model_name == AtomicModel.Table:
atom_model = table_model_init(
kwargs.get("table_model_type"),
kwargs.get("table_model_path"),
kwargs.get("table_max_time"),
kwargs.get("device")
)
else:
logger.error("model name not allow")
exit(1)
return atom_model
class CustomPEKModel: class CustomPEKModel:
def __init__(self, ocr: bool = False, show_log: bool = False, **kwargs): def __init__(self, ocr: bool = False, show_log: bool = False, **kwargs):
...@@ -130,32 +189,62 @@ class CustomPEKModel: ...@@ -130,32 +189,62 @@ class CustomPEKModel:
models_dir = kwargs.get("models_dir", os.path.join(root_dir, "resources", "models")) models_dir = kwargs.get("models_dir", os.path.join(root_dir, "resources", "models"))
logger.info("using models_dir: {}".format(models_dir)) logger.info("using models_dir: {}".format(models_dir))
atom_model_manager = AtomModelSingleton()
# 初始化公式识别 # 初始化公式识别
if self.apply_formula: if self.apply_formula:
# 初始化公式检测模型 # 初始化公式检测模型
self.mfd_model = mfd_model_init(str(os.path.join(models_dir, self.configs["weights"]["mfd"]))) # self.mfd_model = mfd_model_init(str(os.path.join(models_dir, self.configs["weights"]["mfd"])))
self.mfd_model = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.MFD,
mfd_weights=str(os.path.join(models_dir, self.configs["weights"]["mfd"]))
)
# 初始化公式解析模型 # 初始化公式解析模型
mfr_weight_dir = str(os.path.join(models_dir, self.configs["weights"]["mfr"])) mfr_weight_dir = str(os.path.join(models_dir, self.configs["weights"]["mfr"]))
mfr_cfg_path = str(os.path.join(model_config_dir, "UniMERNet", "demo.yaml")) mfr_cfg_path = str(os.path.join(model_config_dir, "UniMERNet", "demo.yaml"))
self.mfr_model, mfr_vis_processors = mfr_model_init(mfr_weight_dir, mfr_cfg_path, _device_=self.device) # self.mfr_model, mfr_vis_processors = mfr_model_init(mfr_weight_dir, mfr_cfg_path, _device_=self.device)
self.mfr_transform = transforms.Compose([mfr_vis_processors, ]) # self.mfr_transform = transforms.Compose([mfr_vis_processors, ])
self.mfr_model, self.mfr_transform = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.MFR,
mfr_weight_dir=mfr_weight_dir,
mfr_cfg_path=mfr_cfg_path,
device=self.device
)
# 初始化layout模型 # 初始化layout模型
self.layout_model = Layoutlmv3_Predictor( # self.layout_model = Layoutlmv3_Predictor(
str(os.path.join(models_dir, self.configs['weights']['layout'])), # str(os.path.join(models_dir, self.configs['weights']['layout'])),
str(os.path.join(model_config_dir, "layoutlmv3", "layoutlmv3_base_inference.yaml")), # str(os.path.join(model_config_dir, "layoutlmv3", "layoutlmv3_base_inference.yaml")),
# device=self.device
# )
self.layout_model = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.Layout,
layout_weights=str(os.path.join(models_dir, self.configs['weights']['layout'])),
layout_config_file=str(os.path.join(model_config_dir, "layoutlmv3", "layoutlmv3_base_inference.yaml")),
device=self.device device=self.device
) )
# 初始化ocr # 初始化ocr
if self.apply_ocr: if self.apply_ocr:
self.ocr_model = ModifiedPaddleOCR(show_log=show_log)
# self.ocr_model = ModifiedPaddleOCR(show_log=show_log, det_db_box_thresh=0.3)
self.ocr_model = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.OCR,
ocr_show_log=show_log,
det_db_box_thresh=0.3
)
# init table model # init table model
if self.apply_table: if self.apply_table:
table_model_dir = self.configs["weights"][self.table_model_type] table_model_dir = self.configs["weights"][self.table_model_type]
self.table_model = table_model_init(self.table_model_type, str(os.path.join(models_dir, table_model_dir)), # self.table_model = table_model_init(self.table_model_type, str(os.path.join(models_dir, table_model_dir)),
max_time=self.table_max_time, _device_=self.device) # max_time=self.table_max_time, _device_=self.device)
self.table_model = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.Table,
table_model_type=self.table_model_type,
table_model_path=str(os.path.join(models_dir, table_model_dir)),
table_max_time=self.table_max_time,
device=self.device
)
logger.info('DocAnalysis init done!') logger.info('DocAnalysis init done!')
def __call__(self, image): def __call__(self, image):
...@@ -291,11 +380,12 @@ class CustomPEKModel: ...@@ -291,11 +380,12 @@ class CustomPEKModel:
logger.info("------------------table recognition processing begins-----------------") logger.info("------------------table recognition processing begins-----------------")
latex_code = None latex_code = None
html_code = None html_code = None
with torch.no_grad():
if self.table_model_type == STRUCT_EQTABLE: if self.table_model_type == STRUCT_EQTABLE:
with torch.no_grad():
latex_code = self.table_model.image2latex(new_image)[0] latex_code = self.table_model.image2latex(new_image)[0]
else: else:
html_code = self.table_model.img2html(new_image) html_code = self.table_model.img2html(new_image)
run_time = time.time() - single_table_start_time run_time = time.time() - single_table_start_time
logger.info(f"------------table recognition processing ends within {run_time}s-----") logger.info(f"------------table recognition processing ends within {run_time}s-----")
if run_time > self.table_max_time: if run_time > self.table_max_time:
......
...@@ -12,6 +12,7 @@ from paddleocr.ppocr.utils.utility import check_and_read, alpha_to_color, binari ...@@ -12,6 +12,7 @@ from paddleocr.ppocr.utils.utility import check_and_read, alpha_to_color, binari
from paddleocr.tools.infer.utility import draw_ocr_box_txt, get_rotate_crop_image, get_minarea_rect_crop from paddleocr.tools.infer.utility import draw_ocr_box_txt, get_rotate_crop_image, get_minarea_rect_crop
from magic_pdf.libs.boxbase import __is_overlaps_y_exceeds_threshold from magic_pdf.libs.boxbase import __is_overlaps_y_exceeds_threshold
from magic_pdf.pre_proc.ocr_dict_merge import merge_spans_to_line
logger = get_logger() logger = get_logger()
...@@ -162,6 +163,86 @@ def update_det_boxes(dt_boxes, mfd_res): ...@@ -162,6 +163,86 @@ def update_det_boxes(dt_boxes, mfd_res):
return new_dt_boxes return new_dt_boxes
def merge_overlapping_spans(spans):
"""
Merges overlapping spans on the same line.
:param spans: A list of span coordinates [(x1, y1, x2, y2), ...]
:return: A list of merged spans
"""
# Return an empty list if the input spans list is empty
if not spans:
return []
# Sort spans by their starting x-coordinate
spans.sort(key=lambda x: x[0])
# Initialize the list of merged spans
merged = []
for span in spans:
# Unpack span coordinates
x1, y1, x2, y2 = span
# If the merged list is empty or there's no horizontal overlap, add the span directly
if not merged or merged[-1][2] < x1:
merged.append(span)
else:
# If there is horizontal overlap, merge the current span with the previous one
last_span = merged.pop()
# Update the merged span's top-left corner to the smaller (x1, y1) and bottom-right to the larger (x2, y2)
x1 = min(last_span[0], x1)
y1 = min(last_span[1], y1)
x2 = max(last_span[2], x2)
y2 = max(last_span[3], y2)
# Add the merged span back to the list
merged.append((x1, y1, x2, y2))
# Return the list of merged spans
return merged
def merge_det_boxes(dt_boxes):
"""
Merge detection boxes.
This function takes a list of detected bounding boxes, each represented by four corner points.
The goal is to merge these bounding boxes into larger text regions.
Parameters:
dt_boxes (list): A list containing multiple text detection boxes, where each box is defined by four corner points.
Returns:
list: A list containing the merged text regions, where each region is represented by four corner points.
"""
# Convert the detection boxes into a dictionary format with bounding boxes and type
dt_boxes_dict_list = []
for text_box in dt_boxes:
text_bbox = points_to_bbox(text_box)
text_box_dict = {
'bbox': text_bbox,
'type': 'text',
}
dt_boxes_dict_list.append(text_box_dict)
# Merge adjacent text regions into lines
lines = merge_spans_to_line(dt_boxes_dict_list)
# Initialize a new list for storing the merged text regions
new_dt_boxes = []
for line in lines:
line_bbox_list = []
for span in line:
line_bbox_list.append(span['bbox'])
# Merge overlapping text regions within the same line
merged_spans = merge_overlapping_spans(line_bbox_list)
# Convert the merged text regions back to point format and add them to the new detection box list
for span in merged_spans:
new_dt_boxes.append(bbox_to_points(span))
return new_dt_boxes
class ModifiedPaddleOCR(PaddleOCR): class ModifiedPaddleOCR(PaddleOCR):
def ocr(self, img, det=True, rec=True, cls=True, bin=False, inv=False, mfd_res=None, alpha_color=(255, 255, 255)): def ocr(self, img, det=True, rec=True, cls=True, bin=False, inv=False, mfd_res=None, alpha_color=(255, 255, 255)):
""" """
...@@ -265,6 +346,9 @@ class ModifiedPaddleOCR(PaddleOCR): ...@@ -265,6 +346,9 @@ class ModifiedPaddleOCR(PaddleOCR):
img_crop_list = [] img_crop_list = []
dt_boxes = sorted_boxes(dt_boxes) dt_boxes = sorted_boxes(dt_boxes)
dt_boxes = merge_det_boxes(dt_boxes)
if mfd_res: if mfd_res:
bef = time.time() bef = time.time()
dt_boxes = update_det_boxes(dt_boxes, mfd_res) dt_boxes = update_det_boxes(dt_boxes, mfd_res)
......
import copy
from sklearn.cluster import DBSCAN from sklearn.cluster import DBSCAN
import numpy as np import numpy as np
from loguru import logger from loguru import logger
...@@ -167,7 +169,7 @@ def cluster_line_x(lines: list) -> dict: ...@@ -167,7 +169,7 @@ def cluster_line_x(lines: list) -> dict:
x0_lst = np.array([[round(line['bbox'][0]), 0] for line in lines]) x0_lst = np.array([[round(line['bbox'][0]), 0] for line in lines])
x0_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x0_lst) x0_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x0_lst)
x0_uniq_label = np.unique(x0_clusters.labels_) x0_uniq_label = np.unique(x0_clusters.labels_)
#x1_lst = np.array([[line['bbox'][2], 0] for line in lines]) # x1_lst = np.array([[line['bbox'][2], 0] for line in lines])
x0_2_new_val = {} # 存储旧值对应的新值映射 x0_2_new_val = {} # 存储旧值对应的新值映射
min_x0 = round(lines[0]["bbox"][0]) min_x0 = round(lines[0]["bbox"][0])
for label in x0_uniq_label: for label in x0_uniq_label:
...@@ -200,7 +202,9 @@ def __valign_lines(blocks, layout_bboxes): ...@@ -200,7 +202,9 @@ def __valign_lines(blocks, layout_bboxes):
min_distance = 3 min_distance = 3
min_sample = 2 min_sample = 2
new_layout_bboxes = [] new_layout_bboxes = []
# add bbox_fs for para split calculation
for block in blocks:
block["bbox_fs"] = copy.deepcopy(block["bbox"])
for layout_box in layout_bboxes: for layout_box in layout_bboxes:
blocks_in_layoutbox = [b for b in blocks if blocks_in_layoutbox = [b for b in blocks if
b["type"] == BlockType.Text and is_in_layout(b['bbox'], layout_box['layout_bbox'])] b["type"] == BlockType.Text and is_in_layout(b['bbox'], layout_box['layout_bbox'])]
...@@ -245,16 +249,15 @@ def __valign_lines(blocks, layout_bboxes): ...@@ -245,16 +249,15 @@ def __valign_lines(blocks, layout_bboxes):
# 由于修改了block里的line长度,现在需要重新计算block的bbox # 由于修改了block里的line长度,现在需要重新计算block的bbox
for block in blocks_in_layoutbox: for block in blocks_in_layoutbox:
if len(block["lines"]) > 0: if len(block["lines"]) > 0:
block['bbox'] = [min([line['bbox'][0] for line in block['lines']]), block['bbox_fs'] = [min([line['bbox'][0] for line in block['lines']]),
min([line['bbox'][1] for line in block['lines']]), min([line['bbox'][1] for line in block['lines']]),
max([line['bbox'][2] for line in block['lines']]), max([line['bbox'][2] for line in block['lines']]),
max([line['bbox'][3] for line in block['lines']])] max([line['bbox'][3] for line in block['lines']])]
"""新计算layout的bbox,因为block的bbox变了。""" """新计算layout的bbox,因为block的bbox变了。"""
layout_x0 = min([block['bbox'][0] for block in blocks_in_layoutbox]) layout_x0 = min([block['bbox_fs'][0] for block in blocks_in_layoutbox])
layout_y0 = min([block['bbox'][1] for block in blocks_in_layoutbox]) layout_y0 = min([block['bbox_fs'][1] for block in blocks_in_layoutbox])
layout_x1 = max([block['bbox'][2] for block in blocks_in_layoutbox]) layout_x1 = max([block['bbox_fs'][2] for block in blocks_in_layoutbox])
layout_y1 = max([block['bbox'][3] for block in blocks_in_layoutbox]) layout_y1 = max([block['bbox_fs'][3] for block in blocks_in_layoutbox])
new_layout_bboxes.append([layout_x0, layout_y0, layout_x1, layout_y1]) new_layout_bboxes.append([layout_x0, layout_y0, layout_x1, layout_y1])
return new_layout_bboxes return new_layout_bboxes
...@@ -312,7 +315,7 @@ def __group_line_by_layout(blocks, layout_bboxes): ...@@ -312,7 +315,7 @@ def __group_line_by_layout(blocks, layout_bboxes):
# 因为只是一个block一行目前, 一个block就是一个段落 # 因为只是一个block一行目前, 一个block就是一个段落
blocks_group = [] blocks_group = []
for lyout in layout_bboxes: for lyout in layout_bboxes:
blocks_in_layout = [block for block in blocks if is_in_layout(block['bbox'], lyout['layout_bbox'])] blocks_in_layout = [block for block in blocks if is_in_layout(block.get('bbox_fs', None), lyout['layout_bbox'])]
blocks_group.append(blocks_in_layout) blocks_group.append(blocks_in_layout)
return blocks_group return blocks_group
...@@ -365,7 +368,8 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"): ...@@ -365,7 +368,8 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"):
for i in range(0, len(list_start)): for i in range(0, len(list_start)):
index = list_start[i] - 1 index = list_start[i] - 1
if index >= 0: if index >= 0:
if "content" in lines[index]["spans"][-1]: if "content" in lines[index]["spans"][-1] and lines[index]["spans"][-1].get('type', '') not in [
ContentType.InlineEquation, ContentType.InterlineEquation]:
lines[index]["spans"][-1]["content"] += '\n\n' lines[index]["spans"][-1]["content"] += '\n\n'
layout_list_info = [False, False] # 这个layout最后是不是列表,记录每一个layout里是不是列表开头,列表结尾 layout_list_info = [False, False] # 这个layout最后是不是列表,记录每一个layout里是不是列表开头,列表结尾
for content_type, start, end in text_segments: for content_type, start, end in text_segments:
...@@ -477,7 +481,7 @@ def __connect_list_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b ...@@ -477,7 +481,7 @@ def __connect_list_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b
break break
# 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。 # 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。
if len(may_list_lines) > 0 and len(set([x['bbox'][0] for x in may_list_lines])) == 1: if len(may_list_lines) > 0 and len(set([x['bbox'][0] for x in may_list_lines])) == 1:
#pre_page_paras[-1].append(may_list_lines) # pre_page_paras[-1].append(may_list_lines)
# 下一页合并到上一页最后一段,打一个cross_page的标签 # 下一页合并到上一页最后一段,打一个cross_page的标签
for line in may_list_lines: for line in may_list_lines:
for span in line["spans"]: for span in line["spans"]:
...@@ -537,7 +541,6 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox): ...@@ -537,7 +541,6 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox):
next_first_line_text = ''.join([__get_span_text(span) for span in next_first_line['spans']]) next_first_line_text = ''.join([__get_span_text(span) for span in next_first_line['spans']])
next_first_line_type = next_first_line['spans'][0]['type'] next_first_line_type = next_first_line['spans'][0]['type']
if pre_last_line_type not in [TEXT, INLINE_EQUATION] or next_first_line_type not in [TEXT, INLINE_EQUATION]: if pre_last_line_type not in [TEXT, INLINE_EQUATION] or next_first_line_type not in [TEXT, INLINE_EQUATION]:
#connected_layout_paras.append(layout_paras[i])
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
continue continue
pre_layout = __find_layout_bbox_by_line(pre_last_line['bbox'], new_layout_bbox) pre_layout = __find_layout_bbox_by_line(pre_last_line['bbox'], new_layout_bbox)
...@@ -552,10 +555,8 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox): ...@@ -552,10 +555,8 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox):
-1] not in LINE_STOP_FLAG and \ -1] not in LINE_STOP_FLAG and \
next_first_line['bbox'][0] == next_x0_min: # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。 next_first_line['bbox'][0] == next_x0_min: # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。
"""连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。""" """连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。"""
#connected_layout_paras[-1][-1].extend(layout_paras[i][0])
connected_layout_blocks[-1][-1]["lines"].extend(blocks_group[i][0]["lines"]) connected_layout_blocks[-1][-1]["lines"].extend(blocks_group[i][0]["lines"])
#layout_paras[i].pop(0) # 删除后一个layout的第一个段落, 因为他已经被合并到前一个layout的最后一个段落了。 blocks_group[i][0]["lines"] = [] # 删除后一个layout第一个段落中的lines,因为他已经被合并到前一个layout的最后一个段落了
blocks_group[i][0]["lines"] = [] #删除后一个layout第一个段落中的lines,因为他已经被合并到前一个layout的最后一个段落了
blocks_group[i][0][LINES_DELETED] = True blocks_group[i][0][LINES_DELETED] = True
# if len(layout_paras[i]) == 0: # if len(layout_paras[i]) == 0:
# layout_paras.pop(i) # layout_paras.pop(i)
...@@ -564,7 +565,6 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox): ...@@ -564,7 +565,6 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox):
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
else: else:
"""连接段落条件不成立,将前一个layout的段落加入到结果中。""" """连接段落条件不成立,将前一个layout的段落加入到结果中。"""
#connected_layout_paras.append(layout_paras[i])
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
return connected_layout_blocks return connected_layout_blocks
...@@ -622,7 +622,7 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b ...@@ -622,7 +622,7 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b
span[CROSS_PAGE] = True span[CROSS_PAGE] = True
pre_last_para.extend(next_first_para) pre_last_para.extend(next_first_para)
#next_page_paras[0].pop(0) # 删除后一个页面的第一个段落, 因为他已经被合并到前一个页面的最后一个段落了。 # next_page_paras[0].pop(0) # 删除后一个页面的第一个段落, 因为他已经被合并到前一个页面的最后一个段落了。
next_page_paras[0][0]["lines"] = [] next_page_paras[0][0]["lines"] = []
next_page_paras[0][0][LINES_DELETED] = True next_page_paras[0][0][LINES_DELETED] = True
return True return True
...@@ -666,16 +666,15 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang): ...@@ -666,16 +666,15 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang):
layout_box = new_layout_bbox[layout_i] layout_box = new_layout_bbox[layout_i]
single_line_paras_tag = [] single_line_paras_tag = []
for i in range(len(layout_para)): for i in range(len(layout_para)):
#single_line_paras_tag.append(len(layout_para[i]) == 1 and layout_para[i][0]['spans'][0]['type'] == TEXT) # single_line_paras_tag.append(len(layout_para[i]) == 1 and layout_para[i][0]['spans'][0]['type'] == TEXT)
single_line_paras_tag.append(layout_para[i]['type'] == BlockType.Text and len(layout_para[i]["lines"]) == 1) single_line_paras_tag.append(layout_para[i]['type'] == BlockType.Text and len(layout_para[i]["lines"]) == 1)
"""找出来连续的单行文本,如果连续行高度相同,那么合并为一个段落。""" """找出来连续的单行文本,如果连续行高度相同,那么合并为一个段落。"""
consecutive_single_line_indices = find_consecutive_true_regions(single_line_paras_tag) consecutive_single_line_indices = find_consecutive_true_regions(single_line_paras_tag)
if len(consecutive_single_line_indices) > 0: if len(consecutive_single_line_indices) > 0:
#index_offset = 0
"""检查这些行是否是高度相同的,居中的""" """检查这些行是否是高度相同的,居中的"""
for start, end in consecutive_single_line_indices: for start, end in consecutive_single_line_indices:
#start += index_offset # start += index_offset
#end += index_offset # end += index_offset
line_hi = np.array([block["lines"][0]['bbox'][3] - block["lines"][0]['bbox'][1] for block in line_hi = np.array([block["lines"][0]['bbox'][3] - block["lines"][0]['bbox'][1] for block in
layout_para[start:end + 1]]) layout_para[start:end + 1]])
first_line_text = ''.join([__get_span_text(span) for span in layout_para[start]["lines"][0]['spans']]) first_line_text = ''.join([__get_span_text(span) for span in layout_para[start]["lines"][0]['spans']])
...@@ -700,9 +699,9 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang): ...@@ -700,9 +699,9 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang):
for i_para in range(start + 1, end + 1): for i_para in range(start + 1, end + 1):
layout_para[i_para]["lines"] = [] layout_para[i_para]["lines"] = []
layout_para[i_para][LINES_DELETED] = True layout_para[i_para][LINES_DELETED] = True
#layout_para[start:end + 1] = [merge_para] # layout_para[start:end + 1] = [merge_para]
#index_offset -= end - start # index_offset -= end - start
return return
...@@ -742,7 +741,7 @@ def para_split(pdf_info_dict, debug_mode, lang="en"): ...@@ -742,7 +741,7 @@ def para_split(pdf_info_dict, debug_mode, lang="en"):
new_layout_of_pages = [] # 数组的数组,每个元素是一个页面的layoutS new_layout_of_pages = [] # 数组的数组,每个元素是一个页面的layoutS
all_page_list_info = [] # 保存每个页面开头和结尾是否是列表 all_page_list_info = [] # 保存每个页面开头和结尾是否是列表
for page_num, page in pdf_info_dict.items(): for page_num, page in pdf_info_dict.items():
blocks = page['preproc_blocks'] blocks = copy.deepcopy(page['preproc_blocks'])
layout_bboxes = page['layout_bboxes'] layout_bboxes = page['layout_bboxes']
new_layout_bbox = __common_pre_proc(blocks, layout_bboxes) new_layout_bbox = __common_pre_proc(blocks, layout_bboxes)
new_layout_of_pages.append(new_layout_bbox) new_layout_of_pages.append(new_layout_bbox)
......
...@@ -41,6 +41,23 @@ def remove_horizontal_overlap_block_which_smaller(all_bboxes): ...@@ -41,6 +41,23 @@ def remove_horizontal_overlap_block_which_smaller(all_bboxes):
return is_useful_block_horz_overlap, all_bboxes return is_useful_block_horz_overlap, all_bboxes
def __replace_STX_ETX(text_str:str):
""" Replace \u0002 and \u0003, as these characters become garbled when extracted using pymupdf. In fact, they were originally quotation marks.
Drawback: This issue is only observed in English text; it has not been found in Chinese text so far.
Args:
text_str (str): raw text
Returns:
_type_: replaced text
"""
if text_str:
s = text_str.replace('\u0002', "'")
s = s.replace("\u0003", "'")
return s
return text_str
def txt_spans_extract(pdf_page, inline_equations, interline_equations): def txt_spans_extract(pdf_page, inline_equations, interline_equations):
text_raw_blocks = pdf_page.get_text("dict", flags=fitz.TEXTFLAGS_TEXT)["blocks"] text_raw_blocks = pdf_page.get_text("dict", flags=fitz.TEXTFLAGS_TEXT)["blocks"]
char_level_text_blocks = pdf_page.get_text("rawdict", flags=fitz.TEXTFLAGS_TEXT)[ char_level_text_blocks = pdf_page.get_text("rawdict", flags=fitz.TEXTFLAGS_TEXT)[
...@@ -63,7 +80,7 @@ def txt_spans_extract(pdf_page, inline_equations, interline_equations): ...@@ -63,7 +80,7 @@ def txt_spans_extract(pdf_page, inline_equations, interline_equations):
spans.append( spans.append(
{ {
"bbox": list(span["bbox"]), "bbox": list(span["bbox"]),
"content": span["text"], "content": __replace_STX_ETX(span["text"]),
"type": ContentType.Text, "type": ContentType.Text,
"score": 1.0, "score": 1.0,
} }
...@@ -175,7 +192,7 @@ def parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, ...@@ -175,7 +192,7 @@ def parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter,
sorted_blocks = sort_blocks_by_layout(all_bboxes, layout_bboxes) sorted_blocks = sort_blocks_by_layout(all_bboxes, layout_bboxes)
'''将span填入排好序的blocks中''' '''将span填入排好序的blocks中'''
block_with_spans, spans = fill_spans_in_blocks(sorted_blocks, spans, 0.6) block_with_spans, spans = fill_spans_in_blocks(sorted_blocks, spans, 0.3)
'''对block进行fix操作''' '''对block进行fix操作'''
fix_blocks = fix_block_spans(block_with_spans, img_blocks, table_blocks) fix_blocks = fix_block_spans(block_with_spans, img_blocks, table_blocks)
...@@ -208,13 +225,17 @@ def pdf_parse_union(pdf_bytes, ...@@ -208,13 +225,17 @@ def pdf_parse_union(pdf_bytes,
magic_model = MagicModel(model_list, pdf_docs) magic_model = MagicModel(model_list, pdf_docs)
'''根据输入的起始范围解析pdf''' '''根据输入的起始范围解析pdf'''
end_page_id = end_page_id if end_page_id else len(pdf_docs) - 1 # end_page_id = end_page_id if end_page_id else len(pdf_docs) - 1
end_page_id = end_page_id if end_page_id is not None and end_page_id >= 0 else len(pdf_docs) - 1
if end_page_id > len(pdf_docs) - 1:
logger.warning("end_page_id is out of range, use pdf_docs length")
end_page_id = len(pdf_docs) - 1
'''初始化启动时间''' '''初始化启动时间'''
start_time = time.time() start_time = time.time()
for page_id in range(start_page_id, end_page_id + 1): for page_id, page in enumerate(pdf_docs):
'''debug时输出每页解析的耗时''' '''debug时输出每页解析的耗时'''
if debug_mode: if debug_mode:
time_now = time.time() time_now = time.time()
...@@ -224,7 +245,14 @@ def pdf_parse_union(pdf_bytes, ...@@ -224,7 +245,14 @@ def pdf_parse_union(pdf_bytes,
start_time = time_now start_time = time_now
'''解析pdf中的每一页''' '''解析pdf中的每一页'''
if start_page_id <= page_id <= end_page_id:
page_info = parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode) page_info = parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode)
else:
page_w = page.rect.width
page_h = page.rect.height
page_info = ocr_construct_page_component_v2([], [], page_id, page_w, page_h, [],
[], [], [], [],
True, "skip page")
pdf_info_dict[f"page_{page_id}"] = page_info pdf_info_dict[f"page_{page_id}"] = page_info
"""分段""" """分段"""
......
...@@ -16,12 +16,15 @@ class AbsPipe(ABC): ...@@ -16,12 +16,15 @@ class AbsPipe(ABC):
PIP_OCR = "ocr" PIP_OCR = "ocr"
PIP_TXT = "txt" PIP_TXT = "txt"
def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False): def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False,
start_page_id=0, end_page_id=None):
self.pdf_bytes = pdf_bytes self.pdf_bytes = pdf_bytes
self.model_list = model_list self.model_list = model_list
self.image_writer = image_writer self.image_writer = image_writer
self.pdf_mid_data = None # 未压缩 self.pdf_mid_data = None # 未压缩
self.is_debug = is_debug self.is_debug = is_debug
self.start_page_id = start_page_id
self.end_page_id = end_page_id
def get_compress_pdf_mid_data(self): def get_compress_pdf_mid_data(self):
return JsonCompressor.compress_json(self.pdf_mid_data) return JsonCompressor.compress_json(self.pdf_mid_data)
......
...@@ -9,17 +9,20 @@ from magic_pdf.user_api import parse_ocr_pdf ...@@ -9,17 +9,20 @@ from magic_pdf.user_api import parse_ocr_pdf
class OCRPipe(AbsPipe): class OCRPipe(AbsPipe):
def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False): def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False,
super().__init__(pdf_bytes, model_list, image_writer, is_debug) start_page_id=0, end_page_id=None):
super().__init__(pdf_bytes, model_list, image_writer, is_debug, start_page_id, end_page_id)
def pipe_classify(self): def pipe_classify(self):
pass pass
def pipe_analyze(self): def pipe_analyze(self):
self.model_list = doc_analyze(self.pdf_bytes, ocr=True) self.model_list = doc_analyze(self.pdf_bytes, ocr=True,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_parse(self): def pipe_parse(self):
self.pdf_mid_data = parse_ocr_pdf(self.pdf_bytes, self.model_list, self.image_writer, is_debug=self.is_debug) self.pdf_mid_data = parse_ocr_pdf(self.pdf_bytes, self.model_list, self.image_writer, is_debug=self.is_debug,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF): def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF):
result = super().pipe_mk_uni_format(img_parent_path, drop_mode) result = super().pipe_mk_uni_format(img_parent_path, drop_mode)
......
...@@ -10,17 +10,20 @@ from magic_pdf.user_api import parse_txt_pdf ...@@ -10,17 +10,20 @@ from magic_pdf.user_api import parse_txt_pdf
class TXTPipe(AbsPipe): class TXTPipe(AbsPipe):
def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False): def __init__(self, pdf_bytes: bytes, model_list: list, image_writer: AbsReaderWriter, is_debug: bool = False,
super().__init__(pdf_bytes, model_list, image_writer, is_debug) start_page_id=0, end_page_id=None):
super().__init__(pdf_bytes, model_list, image_writer, is_debug, start_page_id, end_page_id)
def pipe_classify(self): def pipe_classify(self):
pass pass
def pipe_analyze(self): def pipe_analyze(self):
self.model_list = doc_analyze(self.pdf_bytes, ocr=False) self.model_list = doc_analyze(self.pdf_bytes, ocr=False,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_parse(self): def pipe_parse(self):
self.pdf_mid_data = parse_txt_pdf(self.pdf_bytes, self.model_list, self.image_writer, is_debug=self.is_debug) self.pdf_mid_data = parse_txt_pdf(self.pdf_bytes, self.model_list, self.image_writer, is_debug=self.is_debug,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF): def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF):
result = super().pipe_mk_uni_format(img_parent_path, drop_mode) result = super().pipe_mk_uni_format(img_parent_path, drop_mode)
......
...@@ -13,9 +13,10 @@ from magic_pdf.user_api import parse_union_pdf, parse_ocr_pdf ...@@ -13,9 +13,10 @@ from magic_pdf.user_api import parse_union_pdf, parse_ocr_pdf
class UNIPipe(AbsPipe): class UNIPipe(AbsPipe):
def __init__(self, pdf_bytes: bytes, jso_useful_key: dict, image_writer: AbsReaderWriter, is_debug: bool = False): def __init__(self, pdf_bytes: bytes, jso_useful_key: dict, image_writer: AbsReaderWriter, is_debug: bool = False,
start_page_id=0, end_page_id=None):
self.pdf_type = jso_useful_key["_pdf_type"] self.pdf_type = jso_useful_key["_pdf_type"]
super().__init__(pdf_bytes, jso_useful_key["model_list"], image_writer, is_debug) super().__init__(pdf_bytes, jso_useful_key["model_list"], image_writer, is_debug, start_page_id, end_page_id)
if len(self.model_list) == 0: if len(self.model_list) == 0:
self.input_model_is_empty = True self.input_model_is_empty = True
else: else:
...@@ -26,17 +27,21 @@ class UNIPipe(AbsPipe): ...@@ -26,17 +27,21 @@ class UNIPipe(AbsPipe):
def pipe_analyze(self): def pipe_analyze(self):
if self.pdf_type == self.PIP_TXT: if self.pdf_type == self.PIP_TXT:
self.model_list = doc_analyze(self.pdf_bytes, ocr=False) self.model_list = doc_analyze(self.pdf_bytes, ocr=False,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
elif self.pdf_type == self.PIP_OCR: elif self.pdf_type == self.PIP_OCR:
self.model_list = doc_analyze(self.pdf_bytes, ocr=True) self.model_list = doc_analyze(self.pdf_bytes, ocr=True,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_parse(self): def pipe_parse(self):
if self.pdf_type == self.PIP_TXT: if self.pdf_type == self.PIP_TXT:
self.pdf_mid_data = parse_union_pdf(self.pdf_bytes, self.model_list, self.image_writer, self.pdf_mid_data = parse_union_pdf(self.pdf_bytes, self.model_list, self.image_writer,
is_debug=self.is_debug, input_model_is_empty=self.input_model_is_empty) is_debug=self.is_debug, input_model_is_empty=self.input_model_is_empty,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
elif self.pdf_type == self.PIP_OCR: elif self.pdf_type == self.PIP_OCR:
self.pdf_mid_data = parse_ocr_pdf(self.pdf_bytes, self.model_list, self.image_writer, self.pdf_mid_data = parse_ocr_pdf(self.pdf_bytes, self.model_list, self.image_writer,
is_debug=self.is_debug) is_debug=self.is_debug,
start_page_id=self.start_page_id, end_page_id=self.end_page_id)
def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF): def pipe_mk_uni_format(self, img_parent_path: str, drop_mode=DropMode.WHOLE_PDF):
result = super().pipe_mk_uni_format(img_parent_path, drop_mode) result = super().pipe_mk_uni_format(img_parent_path, drop_mode)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Welcome to the MinerU Project List
## Project List
- [llama_index_rag](./llama_index_rag/README.md): Build a lightweight RAG system based on llama_index
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment