From 1dc915a4a9d913ed52accb0efc3ead5f1a2d8e74 Mon Sep 17 00:00:00 2001 From: yyy <102640628+dt-yy@users.noreply.github.com> Date: Mon, 2 Sep 2024 20:23:46 +0800 Subject: [PATCH] release: release 0.7.1 version (#526) * Update README_zh-CN.md (#404) (#409) correct FAQ url Co-authored-by: sfk <18810651050@163.com> * add dockerfile (#189) Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> * Update cla.yml * Update cla.yml * feat: 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: sfk <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: sfk <18810651050@163.com> * add dockerfile (#189) Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> * Update cla.yml * Update cla.yml --------- Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: sfk <18810651050@163.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: Xiaomeng Zhao * (para_split_v2): index out of range issue of span_text first char (#396) Co-authored-by: liukaiwen * @Matthijz98 has signed the CLA in opendatalab/MinerU#467 * Create download_models.py * Create requirements-docker.txt * feat
: add tablemaster with paddleocr to detect and recognize table * @strongerfly has signed the CLA in opendatalab/MinerU#487 * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table --------- Co-authored-by: Xiaomeng Zhao Co-authored-by: sfk <18810651050@163.com> Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: liukaiwen * feat
: 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: sfk <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: sfk <18810651050@163.com> * add dockerfile (#189) Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> * Update cla.yml * Update cla.yml --------- Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: sfk <18810651050@163.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: Xiaomeng Zhao * (para_split_v2): index out of range issue of span_text first char (#396) Co-authored-by: liukaiwen * @Matthijz98 has signed the CLA in opendatalab/MinerU#467 * Create download_models.py * Create requirements-docker.txt * feat
: add tablemaster with paddleocr to detect and recognize table * @strongerfly has signed the CLA in opendatalab/MinerU#487 * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: 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
: add tablemaster with paddleocr to detect and recognize table --------- Co-authored-by: Xiaomeng Zhao Co-authored-by: sfk <18810651050@163.com> Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: liukaiwen Co-authored-by: yyy <102640628+dt-yy@users.noreply.github.com> Co-authored-by: wangbinDL * feat
: 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: sfk <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: sfk <18810651050@163.com> * add dockerfile (#189) Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> * Update cla.yml * Update cla.yml --------- Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: sfk <18810651050@163.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: Xiaomeng Zhao * (para_split_v2): index out of range issue of span_text first char (#396) Co-authored-by: liukaiwen * @Matthijz98 has signed the CLA in opendatalab/MinerU#467 * Create download_models.py * Create requirements-docker.txt * feat
: add tablemaster with paddleocr to detect and recognize table * @strongerfly has signed the CLA in opendatalab/MinerU#487 * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: 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
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table * feat
: add tablemaster with paddleocr to detect and recognize table --------- Co-authored-by: Xiaomeng Zhao Co-authored-by: sfk <18810651050@163.com> Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: liukaiwen Co-authored-by: yyy <102640628+dt-yy@users.noreply.github.com> Co-authored-by: wangbinDL --------- Co-authored-by: drunkpig <60862764+drunkpig@users.noreply.github.com> Co-authored-by: sfk <18810651050@163.com> Co-authored-by: Aoyang Fang <222010547@link.cuhk.edu.cn> Co-authored-by: Xiaomeng Zhao Co-authored-by: Kaiwen Liu Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: liukaiwen Co-authored-by: wangbinDL --- README.md | 10 +-- README_ja-JP.md | 4 +- README_zh-CN.md | 12 +--- docs/README_Ubuntu_CUDA_Acceleration_en_US.md | 2 +- docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md | 2 +- .../README_Windows_CUDA_Acceleration_en_US.md | 2 +- .../README_Windows_CUDA_Acceleration_zh_CN.md | 2 +- docs/how_to_download_models_en.md | 15 ++++ docs/how_to_download_models_zh_cn.md | 15 ++++ magic-pdf.template.json | 1 + magic_pdf/dict2md/ocr_mkcontent.py | 4 ++ magic_pdf/libs/Constants.py | 28 +++++++- magic_pdf/libs/version.py | 2 +- magic_pdf/model/magic_model.py | 3 + magic_pdf/model/pdf_extract_kit.py | 47 ++++++++---- .../structeqtable/StructTableModel.py | 1 - magic_pdf/model/ppTableModel.py | 67 ++++++++++++++++++ .../resources/model_config/model_configs.yaml | 4 +- tests/test_table/assets/table.jpg | Bin 0 -> 59389 bytes tests/test_table/test_tablemaster.py | 18 +++++ 20 files changed, 201 insertions(+), 38 deletions(-) create mode 100644 magic_pdf/model/ppTableModel.py create mode 100644 tests/test_table/assets/table.jpg create mode 100644 tests/test_table/test_tablemaster.py diff --git a/README.md b/README.md index a67849f8..bc5172c0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ # Changelog +- 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 @@ -171,7 +172,7 @@ In non-mainline environments, due to the diversity of hardware and software conf ```bash conda create -n MinerU python=3.10 conda activate MinerU -pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com +pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com ``` #### 2. Download model weight files @@ -200,6 +201,7 @@ Find the `magic-pdf.json` file in your user directory and configure the "models- // other config "models-dir": "D:/models", "table-config": { + "model": "TableMaster", // Another option of this value is 'struct_eqtable' "is_table_recog_enable": false, // Table recognition is disabled by default, modify this value to enable it "max_time": 400 } @@ -311,13 +313,7 @@ TODO - Comic books, art books, elementary school textbooks, and exercise books are not well-parsed yet - Enabling OCR may produce better results in PDFs with a high density of formulas - If you are processing PDFs with a large number of formulas, it is strongly recommended to enable the OCR function. When using PyMuPDF to extract text, overlapping text lines can occur, leading to inaccurate formula insertion positions. -- **Table Recognition** is currently in the testing phase; recognition speed is slow, and accuracy needs improvement. Below are some performance test results in an Ubuntu 22.04 LTS + Intel(R) Xeon(R) Platinum 8352V CPU @ 2.10GHz + NVIDIA GeForce RTX 4090 environment for reference. -| Table Size | Parsing Time | -|---------------|----------------------------| -| 6\*5 55kb | 37s | -| 16\*12 284kb | 3m18s | -| 44\*7 559kb | 4m12s | # FAQ [FAQ in Chinese](docs/FAQ_zh_cn.md) diff --git a/README_ja-JP.md b/README_ja-JP.md index f1d65107..1293f6c9 100644 --- a/README_ja-JP.md +++ b/README_ja-JP.md @@ -116,13 +116,13 @@ pip install detectron2 --extra-index-url https://wheels.myhloli.com >CUDA/MPSによる加速については、[CUDAまたはMPSによる加速](#4-CUDAまたはMPSによる加速)を参照してください。 ```bash -pip install magic-pdf[full]==0.6.2b1 +pip install -U magic-pdf[full] ``` > ❗️❗️❗️ > 私たちは0.6.2 ベータ版を事前にリリースし、私たちのログに記載されている多くの問題に対処しました。しかし、このビルドはまだ完全なQAテストを経ておらず、最終的なリリース品質を表していません。問題に遭遇した場合は、問題を通じて速やかに報告するか、0.6.1バージョンに戻ることをお願いします。 > ```bash -> pip install magic-pdf[full-cpu]==0.6.1 +> pip install -U magic-pdf[full] > ``` diff --git a/README_zh-CN.md b/README_zh-CN.md index e064a873..69c28108 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -33,6 +33,7 @@ # 更新记录 +- 2024/08/30 0.7.1发布,集成了paddle tablemaster表格识别功能 - 2024/08/09 0.7.0b1发布,简化安装步骤提升易用性,加入表格识别功能 - 2024/08/01 0.6.2b1发布,优化了依赖冲突问题和安装文档 - 2024/07/05 首次开源 @@ -179,7 +180,7 @@ https://github.com/user-attachments/assets/4bea02c9-6d54-4cd6-97ed-dff14340982c ```bash conda create -n MinerU python=3.10 conda activate MinerU -pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple +pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple ``` #### 2. 下载模型权重文件 @@ -208,6 +209,7 @@ cp magic-pdf.template.json ~/magic-pdf.json // other config "models-dir": "D:/models", "table-config": { + "model": "TableMaster", // 使用structEqTable请修改为'struct_eqtable' "is_table_recog_enable": false, // 表格识别功能默认是关闭的,如果需要修改此处的值 "max_time": 400 } @@ -321,14 +323,6 @@ TODO - 漫画书、艺术图册、小学教材、习题尚不能很好解析 - 在一些公式密集的PDF上强制启用OCR效果会更好 - 如果您要处理包含大量公式的pdf,强烈建议开启OCR功能。使用pymuPDF提取文字的时候会出现文本行互相重叠的情况导致公式插入位置不准确。 -- **表格识别**目前处于测试阶段,识别速度较慢,识别准确度有待提升。以下是我们在Ubuntu 22.04 LTS + Intel(R) Xeon(R) Platinum 8352V CPU @ 2.10GHz + NVIDIA GeForce RTX 4090环境下的一些性能测试结果,可供参考。 - -| 表格大小 | 解析耗时 | -|---------------|----------------------------| -| 6\*5 55kb | 37s | -| 16\*12 284kb | 3m18s | -| 44\*7 559kb | 4m12s | - # FAQ diff --git a/docs/README_Ubuntu_CUDA_Acceleration_en_US.md b/docs/README_Ubuntu_CUDA_Acceleration_en_US.md index 7186092c..8244fcc3 100644 --- a/docs/README_Ubuntu_CUDA_Acceleration_en_US.md +++ b/docs/README_Ubuntu_CUDA_Acceleration_en_US.md @@ -48,7 +48,7 @@ ### 5. Install Applications ```sh - pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com + pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com ``` ❗ After installation, make sure to check the version of `magic-pdf` using the following command: ```sh diff --git a/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md b/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md index f8946ae2..4d9f6090 100644 --- a/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md +++ b/docs/README_Ubuntu_CUDA_Acceleration_zh_CN.md @@ -43,7 +43,7 @@ conda activate MinerU ``` ## 5. 安装应用 ```bash -pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple +pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple ``` > ❗️下载完成后,务必通过以下命令确认magic-pdf的版本是否正确 > diff --git a/docs/README_Windows_CUDA_Acceleration_en_US.md b/docs/README_Windows_CUDA_Acceleration_en_US.md index 1e762fb6..9e5fb764 100644 --- a/docs/README_Windows_CUDA_Acceleration_en_US.md +++ b/docs/README_Windows_CUDA_Acceleration_en_US.md @@ -19,7 +19,7 @@ Download link: https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Windows-x86 ### 4. Install Applications ``` - pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com + pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com ``` >❗️After installation, verify the version of `magic-pdf`: > ```bash diff --git a/docs/README_Windows_CUDA_Acceleration_zh_CN.md b/docs/README_Windows_CUDA_Acceleration_zh_CN.md index fbddc4f0..15ca9d18 100644 --- a/docs/README_Windows_CUDA_Acceleration_zh_CN.md +++ b/docs/README_Windows_CUDA_Acceleration_zh_CN.md @@ -20,7 +20,7 @@ conda activate MinerU ``` ## 4. 安装应用 ```bash -pip install magic-pdf[full]==0.7.0b1 --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple +pip install -U magic-pdf[full] --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.tsinghua.edu.cn/simple ``` > ❗️下载完成后,务必通过以下命令确认magic-pdf的版本是否正确 > diff --git a/docs/how_to_download_models_en.md b/docs/how_to_download_models_en.md index aade8eac..14f054a0 100644 --- a/docs/how_to_download_models_en.md +++ b/docs/how_to_download_models_en.md @@ -44,6 +44,21 @@ The structure of the model folder is as follows, including configuration files a │ ├── spiece.model │ ├── tokenizer.json │ └── tokenizer_config.json +│ └─ TableMaster +│ └─ ch_PP-OCRv3_det_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ └─ ch_PP-OCRv3_rec_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ └─ table_structure_tablemaster_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ ├── ppocr_keys_v1.txt +│ └── table_master_structure_dict.txt └── README.md ``` #### 2. Check whether the model file is fully downloaded. diff --git a/docs/how_to_download_models_zh_cn.md b/docs/how_to_download_models_zh_cn.md index 96889fbc..cbc0e3f8 100644 --- a/docs/how_to_download_models_zh_cn.md +++ b/docs/how_to_download_models_zh_cn.md @@ -74,6 +74,21 @@ print(f"模型文件下载路径为:{model_dir}/models") │ ├── spiece.model │ ├── tokenizer.json │ └── tokenizer_config.json +│ └─ TableMaster +│ └─ ch_PP-OCRv3_det_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ └─ ch_PP-OCRv3_rec_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ └─ table_structure_tablemaster_infer +│ ├── inference.pdiparams +│ ├── inference.pdiparams.info +│ └── inference.pdmodel +│ ├── ppocr_keys_v1.txt +│ └── table_master_structure_dict.txt └── README.md ``` diff --git a/magic-pdf.template.json b/magic-pdf.template.json index f6adfbfa..1eb61101 100644 --- a/magic-pdf.template.json +++ b/magic-pdf.template.json @@ -6,6 +6,7 @@ "models-dir":"/tmp/models", "device-mode":"cpu", "table-config": { + "model": "TableMaster", "is_table_recog_enable": false, "max_time": 400 } diff --git a/magic_pdf/dict2md/ocr_mkcontent.py b/magic_pdf/dict2md/ocr_mkcontent.py index 0cc887ce..ec5ded1c 100644 --- a/magic_pdf/dict2md/ocr_mkcontent.py +++ b/magic_pdf/dict2md/ocr_mkcontent.py @@ -132,6 +132,8 @@ def ocr_mk_markdown_with_para_core_v2(paras_of_layout, mode, img_buket_path=""): # if processed by table model if span.get('latex', ''): para_text += f"\n\n$\n {span['latex']}\n$\n\n" + elif span.get('html', ''): + para_text += f"\n\n{span['html']}\n\n" else: para_text += f"\n![{table_caption}]({join_path(img_buket_path, span['image_path'])}) \n" for block in para_block['blocks']: # 3rd.拼table_footnote @@ -256,6 +258,8 @@ def para_to_standard_format_v2(para_block, img_buket_path, page_idx): if block['type'] == BlockType.TableBody: if block["lines"][0]["spans"][0].get('latex', ''): para_content['table_body'] = f"\n\n$\n {block['lines'][0]['spans'][0]['latex']}\n$\n\n" + elif block["lines"][0]["spans"][0].get('html', ''): + para_content['table_body'] = f"\n\n{block['lines'][0]['spans'][0]['html']}\n\n" para_content['img_path'] = join_path(img_buket_path, block["lines"][0]["spans"][0]['image_path']) if block['type'] == BlockType.TableCaption: para_content['table_caption'] = merge_para_with_text(block) diff --git a/magic_pdf/libs/Constants.py b/magic_pdf/libs/Constants.py index d2ad2090..4e132290 100644 --- a/magic_pdf/libs/Constants.py +++ b/magic_pdf/libs/Constants.py @@ -10,5 +10,31 @@ # block中lines是否被删除 LINES_DELETED = "lines_deleted" +# struct eqtable +STRUCT_EQTABLE = "struct_eqtable" + # table recognition max time default value -TABLE_MAX_TIME_VALUE = 400 \ No newline at end of file +TABLE_MAX_TIME_VALUE = 400 + +# pp_table_result_max_length +TABLE_MAX_LEN = 480 + +# pp table structure algorithm +TABLE_MASTER = "TableMaster" + +# 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-OCRv3_det_infer" + +# pp rec model dir +REC_MODEL_DIR = "ch_PP-OCRv3_rec_infer" + +# pp rec char dict path +REC_CHAR_DICT = "ppocr_keys_v1.txt" + + diff --git a/magic_pdf/libs/version.py b/magic_pdf/libs/version.py index f1f62cb0..a5f830a2 100644 --- a/magic_pdf/libs/version.py +++ b/magic_pdf/libs/version.py @@ -1 +1 @@ -__version__ = "0.7.0b1" +__version__ = "0.7.1" diff --git a/magic_pdf/model/magic_model.py b/magic_pdf/model/magic_model.py index d6164bae..ce070321 100644 --- a/magic_pdf/model/magic_model.py +++ b/magic_pdf/model/magic_model.py @@ -562,8 +562,11 @@ def remove_duplicate_spans(spans): elif category_id == 5: # 获取table模型结果 latex = layout_det.get("latex", None) + html = layout_det.get("html", None) if latex: span["latex"] = latex + elif html: + span["html"] = html span["type"] = ContentType.Table elif category_id == 13: span["content"] = layout_det["latex"] diff --git a/magic_pdf/model/pdf_extract_kit.py b/magic_pdf/model/pdf_extract_kit.py index fcf26797..f36d3545 100644 --- a/magic_pdf/model/pdf_extract_kit.py +++ b/magic_pdf/model/pdf_extract_kit.py @@ -2,7 +2,7 @@ import os import time -from magic_pdf.libs.Constants import TABLE_MAX_TIME_VALUE +from magic_pdf.libs.Constants import * os.environ['NO_ALBUMENTATIONS_UPDATE'] = '1' # 禁止albumentations检查更新 try: @@ -34,10 +34,18 @@ from magic_pdf.model.pek_sub_modules.post_process import get_croped_image, latex_rm_whitespace from magic_pdf.model.pek_sub_modules.self_modify import ModifiedPaddleOCR from magic_pdf.model.pek_sub_modules.structeqtable.StructTableModel import StructTableModel - - -def table_model_init(model_path, max_time, _device_='cpu'): - table_model = StructTableModel(model_path, max_time=max_time, device=_device_) +from magic_pdf.model.ppTableModel import ppTableModel + + +def table_model_init(table_model_type, model_path, max_time, _device_='cpu'): + if table_model_type == STRUCT_EQTABLE: + table_model = StructTableModel(model_path, max_time=max_time, device=_device_) + else: + config = { + "model_dir": model_path, + "device": _device_ + } + table_model = ppTableModel(config) return table_model @@ -104,9 +112,11 @@ def __init__(self, ocr: bool = False, show_log: bool = False, **kwargs): # 初始化解析配置 self.apply_layout = kwargs.get("apply_layout", self.configs["config"]["layout"]) self.apply_formula = kwargs.get("apply_formula", self.configs["config"]["formula"]) + # table config self.table_config = kwargs.get("table_config", self.configs["config"]["table_config"]) self.apply_table = self.table_config.get("is_table_recog_enable", False) self.table_max_time = self.table_config.get("max_time", TABLE_MAX_TIME_VALUE) + self.table_model_type = self.table_config.get("model", TABLE_MASTER) self.apply_ocr = ocr logger.info( "DocAnalysis init, this may take some times. apply_layout: {}, apply_formula: {}, apply_ocr: {}, apply_table: {}".format( @@ -141,10 +151,11 @@ def __init__(self, ocr: bool = False, show_log: bool = False, **kwargs): if self.apply_ocr: self.ocr_model = ModifiedPaddleOCR(show_log=show_log) - # init structeqtable + # init table model if self.apply_table: - self.table_model = table_model_init(str(os.path.join(models_dir, self.configs["weights"]["table"])), - max_time = self.table_max_time, _device_=self.device) + 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)), + max_time=self.table_max_time, _device_=self.device) logger.info('DocAnalysis init done!') def __call__(self, image): @@ -278,16 +289,28 @@ def crop_img(input_res, input_pil_img, crop_paste_x=0, crop_paste_y=0): new_image, _ = crop_img(res, pil_img) single_table_start_time = time.time() logger.info("------------------table recognition processing begins-----------------") + latex_code = None + html_code = None with torch.no_grad(): - latex_code = self.table_model.image2latex(new_image)[0] + if self.table_model_type == STRUCT_EQTABLE: + latex_code = self.table_model.image2latex(new_image)[0] + else: + html_code = self.table_model.img2html(new_image) run_time = time.time() - single_table_start_time logger.info(f"------------table recognition processing ends within {run_time}s-----") if run_time > self.table_max_time: logger.warning(f"------------table recognition processing exceeds max time {self.table_max_time}s----------") # 判断是否返回正常 - expected_ending = latex_code.strip().endswith('end{tabular}') or latex_code.strip().endswith('end{table}') - if latex_code and expected_ending: - res["latex"] = latex_code + + if latex_code: + expected_ending = latex_code.strip().endswith('end{tabular}') or latex_code.strip().endswith( + 'end{table}') + if expected_ending: + res["latex"] = latex_code + else: + logger.warning(f"------------table recognition processing fails----------") + elif html_code: + res["html"] = html_code else: logger.warning(f"------------table recognition processing fails----------") table_cost = round(time.time() - table_start, 2) diff --git a/magic_pdf/model/pek_sub_modules/structeqtable/StructTableModel.py b/magic_pdf/model/pek_sub_modules/structeqtable/StructTableModel.py index cfd9fa2d..2d1ce584 100644 --- a/magic_pdf/model/pek_sub_modules/structeqtable/StructTableModel.py +++ b/magic_pdf/model/pek_sub_modules/structeqtable/StructTableModel.py @@ -12,7 +12,6 @@ def __init__(self, model_path, max_new_tokens=2048, max_time=400, device = 'cpu' self.model = StructTable(self.model_path, self.max_new_tokens, self.max_time) def image2latex(self, image) -> str: - # table_latex = self.model.forward(image) return table_latex diff --git a/magic_pdf/model/ppTableModel.py b/magic_pdf/model/ppTableModel.py new file mode 100644 index 00000000..310bcc79 --- /dev/null +++ b/magic_pdf/model/ppTableModel.py @@ -0,0 +1,67 @@ +from paddleocr.ppstructure.table.predict_table import TableSystem +from paddleocr.ppstructure.utility import init_args +from magic_pdf.libs.Constants import * +import os +from PIL import Image +import numpy as np + + +class ppTableModel(object): + """ + This class is responsible for converting image of table into HTML format using a pre-trained model. + + Attributes: + - table_sys: An instance of TableSystem initialized with parsed arguments. + + Methods: + - __init__(config): Initializes the model with configuration parameters. + - img2html(image): Converts a PIL Image or NumPy array to HTML string. + - parse_args(**kwargs): Parses configuration arguments. + """ + + def __init__(self, config): + """ + Parameters: + - config (dict): Configuration dictionary containing model_dir and device. + """ + args = self.parse_args(**config) + self.table_sys = TableSystem(args) + + def img2html(self, image): + """ + Parameters: + - image (PIL.Image or np.ndarray): The image of the table to be converted. + + Return: + - HTML (str): A string representing the HTML structure with content of the table. + """ + if isinstance(image, Image.Image): + image = np.array(image) + pred_res, _ = self.table_sys(image) + pred_html = pred_res["html"] + res = '
' + pred_html.replace("
", "").replace("
", + "") + "
\n" + return res + + def parse_args(self, **kwargs): + parser = init_args() + model_dir = kwargs.get("model_dir") + table_model_dir = os.path.join(model_dir, TABLE_MASTER_DIR) + table_char_dict_path = os.path.join(model_dir, TABLE_MASTER_DICT) + det_model_dir = os.path.join(model_dir, DETECT_MODEL_DIR) + rec_model_dir = os.path.join(model_dir, REC_MODEL_DIR) + rec_char_dict_path = os.path.join(model_dir, REC_CHAR_DICT) + device = kwargs.get("device", "cpu") + use_gpu = True if device == "cuda" else False + config = { + "use_gpu": use_gpu, + "table_max_len": kwargs.get("table_max_len", TABLE_MAX_LEN), + "table_algorithm": TABLE_MASTER, + "table_model_dir": table_model_dir, + "table_char_dict_path": table_char_dict_path, + "det_model_dir": det_model_dir, + "rec_model_dir": rec_model_dir, + "rec_char_dict_path": rec_char_dict_path, + } + parser.set_defaults(**config) + return parser.parse_args([]) diff --git a/magic_pdf/resources/model_config/model_configs.yaml b/magic_pdf/resources/model_config/model_configs.yaml index 1b5c3aae..2438a0bb 100644 --- a/magic_pdf/resources/model_config/model_configs.yaml +++ b/magic_pdf/resources/model_config/model_configs.yaml @@ -3,6 +3,7 @@ config: layout: True formula: True table_config: + model: TableMaster is_table_recog_enable: False max_time: 400 @@ -10,4 +11,5 @@ weights: layout: Layout/model_final.pth mfd: MFD/weights.pt mfr: MFR/UniMERNet - table: TabRec/StructEqTable \ No newline at end of file + struct_eqtable: TabRec/StructEqTable + TableMaster: TabRec/TableMaster \ No newline at end of file diff --git a/tests/test_table/assets/table.jpg b/tests/test_table/assets/table.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95fdf84d92908d4b21f49fb516601334867163b1 GIT binary patch literal 59389 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 literal 0 HcmV?d00001 diff --git a/tests/test_table/test_tablemaster.py b/tests/test_table/test_tablemaster.py new file mode 100644 index 00000000..5e8f5520 --- /dev/null +++ b/tests/test_table/test_tablemaster.py @@ -0,0 +1,18 @@ +import unittest +from PIL import Image +from magic_pdf.model.ppTableModel import ppTableModel + +class TestppTableModel(unittest.TestCase): + def test_image2html(self): + img = Image.open("tests/test_table/assets/table.jpg") + # 修改table模型路径 + config = {"device": "cuda", + "model_dir": "D:/models/PDF-Extract-Kit/models/TabRec/TableMaster"} + table_model = ppTableModel(config) + res = table_model.img2html(img) + true_value = """
MethodsRPFFPS
SegLink [26]70.086.077.08.9
PixelLink [4]73.283.077.8-
TextSnake [18]73.983.278.31.1
TextField [37]75.987.481.35.2
MSR[38]76.787.481.7-
FTSN[3]77.187.682.0-
LSE[30]81.784.282.9-
CRAFT [2]78.288.282.98.6
MCN [16]7988.83-
ATRR[35]82.185.283.6-
PAN [34]83.884.484.130.2
DB[12]79.291.584.932.0
DRRG [41]82.3088.0585.08-
Ours (SynText)80.6885.4082.9712.68
Ours (MLT-17)84.5486.6285.5712.31
\n""" + self.assertEqual(true_value, res) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file