mirror of
https://github.com/opendatalab/MinerU.git
synced 2026-03-27 19:18:34 +07:00
Compare commits
60 Commits
magic_pdf-
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
012327badb | ||
|
|
fcb5660f6a | ||
|
|
d105d87cf5 | ||
|
|
619b3b6d32 | ||
|
|
a47b17cd88 | ||
|
|
737d7d6eb9 | ||
|
|
3492744ce1 | ||
|
|
a1fe370270 | ||
|
|
fea756fd3e | ||
|
|
e98988920e | ||
|
|
74f9978e02 | ||
|
|
0c9572c871 | ||
|
|
8fb6794b95 | ||
|
|
af53a46311 | ||
|
|
2e5e55cfe2 | ||
|
|
658e6bc768 | ||
|
|
4641264e12 | ||
|
|
4bd3381c92 | ||
|
|
f5a56bf157 | ||
|
|
78d11172e3 | ||
|
|
a2b07bfde4 | ||
|
|
1b35f04453 | ||
|
|
0222293f64 | ||
|
|
16f176ea65 | ||
|
|
1705958f65 | ||
|
|
2de5a79f52 | ||
|
|
058d318491 | ||
|
|
cfa90743b5 | ||
|
|
b36b469a1c | ||
|
|
40bfd7acce | ||
|
|
b7ff7ded64 | ||
|
|
07edefaa7d | ||
|
|
24b7e7ca36 | ||
|
|
87440ba43c | ||
|
|
ff35c75531 | ||
|
|
8f3c178003 | ||
|
|
27883619f5 | ||
|
|
5ddd6799aa | ||
|
|
039f8cbfde | ||
|
|
73ccfbbfbe | ||
|
|
410d0afc81 | ||
|
|
c774a4dde1 | ||
|
|
29b47466ff | ||
|
|
a1df670e34 | ||
|
|
a67de492b1 | ||
|
|
222af4f2f5 | ||
|
|
b9eed5d865 | ||
|
|
82a4376d8a | ||
|
|
99ab04f588 | ||
|
|
67b31a78d0 | ||
|
|
4f129a64aa | ||
|
|
47d287a2a0 | ||
|
|
bc51f9f75e | ||
|
|
8caf59f7cb | ||
|
|
4df8523a31 | ||
|
|
c7a609fa7a | ||
|
|
762dd42818 | ||
|
|
7a118854ef | ||
|
|
e48add6af7 | ||
|
|
5957cb65f9 |
56
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
56
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Bug Report | 反馈 Bug
|
||||
name: 🐛 Bug Report
|
||||
description: Create a bug report for MinerU | MinerU 的 Bug 反馈
|
||||
labels: bug
|
||||
|
||||
@@ -6,14 +6,32 @@ labels: bug
|
||||
# empty string, Github seems to reject this .yml file.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a MinerU 🐛 Bug Report! | 感谢您提交 MinerU 🐛 Bug 反馈!
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 🔎 Search before asking | 提交之前请先搜索
|
||||
description: >
|
||||
Please search the MinerU [Readme](https://github.com/opendatalab/MinerU), [Issues](https://github.com/opendatalab/MinerU/issues) and [Discussions](https://github.com/opendatalab/MinerU/discussions) to see if a similar bug report already exists.
|
||||
options:
|
||||
- label: I have searched the MinerU [Readme](https://github.com/opendatalab/MinerU) and found no similar bug report.
|
||||
required: true
|
||||
- label: I have searched the MinerU [Issues](https://github.com/opendatalab/MinerU/issues) and found no similar bug report.
|
||||
required: true
|
||||
- label: I have searched the MinerU [Discussions](https://github.com/opendatalab/MinerU/discussions) and found no similar bug report.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description of the bug | 错误描述
|
||||
description: |
|
||||
A clear and concise description of the bug. | 简单描述遇到的问题
|
||||
|
||||
Provide console output with error messages and/or screenshots of the bug. | 请提供详细报错信息或者截图
|
||||
placeholder: |
|
||||
💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -24,11 +42,12 @@ body:
|
||||
|
||||
# Should not word-wrap this description here.
|
||||
description: |
|
||||
* Explain the steps required to reproduce the bug. | 说明复现此错误所需的步骤。
|
||||
* Include required code snippets, example files, etc. | 包含必要的代码片段、示例文件等。
|
||||
* Describe what you expected to happen (if not obvious). | 描述你期望发生的情况。
|
||||
* If applicable, add screenshots to help explain the problem. | 添加截图以帮助解释问题。
|
||||
* Include any other information that could be relevant, for example information about the Python environment. | 包括任何其他可能相关的信息。
|
||||
If you have questions about the parsing results or encounter errors during execution: | 如对解析结果有疑问或在运行中出现报错等异常:
|
||||
* Provide a minimal reproducible example. | 请提供一个最小可复现的demo。
|
||||
* The demo should include the complete steps, code, and the PDF file to be parsed. | demo需要包含完整的操作步骤,代码,以及需要解析的PDF文件。
|
||||
* When reporting parsing result anomalies and runtime errors, reproducible PDF files are essential. If the document is too large or confidential, you can print the problematic page(s) via the browser and submit the corresponding example file.
|
||||
* 在反馈解析结果异常和运行时报错时,可复现的PDF文件是必不可少的,如文档过大或涉密,您可通过浏览器打印出出现问题的某一页或某几页再提交相应的示例文件。
|
||||
|
||||
|
||||
For problems when building or installing MinerU: | 在构建或安装 MinerU 时遇到的问题:
|
||||
* Give the **exact** build/install commands that were run. | 提供**确切**的构建/安装命令。
|
||||
@@ -44,9 +63,9 @@ body:
|
||||
|
||||
|
||||
- type: dropdown
|
||||
id: os_name
|
||||
id: os_mode
|
||||
attributes:
|
||||
label: Operating system | 操作系统
|
||||
label: Operating System Mode | 操作系统类型
|
||||
#multiple: true
|
||||
options:
|
||||
-
|
||||
@@ -56,6 +75,22 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: os_name_version
|
||||
attributes:
|
||||
label: Operating System Version| 操作系统版本
|
||||
#multiple: true
|
||||
description: |
|
||||
* 如果您使用的是Linux系统,请提供Linux系统的**发行版名称**和**版本号**来帮助开发人员排查问题。
|
||||
* If you are using a Linux system, please provide the Linux distribution and version number to help developers troubleshoot the issue.
|
||||
* 如果您使用的是Windows或MacOS系统,请提供操作系统的**版本号**来帮助开发人员排查问题。
|
||||
* If you are using a Windows or MacOS system, please provide the version number of the operating system to help developers troubleshoot the issue.
|
||||
* 例如:Ubuntu 22.04, CentOS 7.9, MacOS 15.1, Windows 11
|
||||
* For example: Ubuntu 22.04, CentOS 7.9, MacOS 15.1, Windows 11.
|
||||
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: python_version
|
||||
attributes:
|
||||
@@ -94,6 +129,7 @@ body:
|
||||
-
|
||||
- cpu
|
||||
- cuda
|
||||
- mps
|
||||
- npu
|
||||
validations:
|
||||
required: true
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🙏 Q&A
|
||||
url: https://github.com/opendatalab/MinerU/discussions/categories/q-a
|
||||
about: Ask the community for help
|
||||
- name: 💡 Feature requests and ideas
|
||||
url: https://github.com/opendatalab/MinerU/discussions/categories/ideas
|
||||
about: Share ideas for new features
|
||||
- name: 🙌 Show and tell
|
||||
url: https://github.com/opendatalab/MinerU/discussions/categories/show-and-tell
|
||||
about: Show off something you've made
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
28
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Feature request | 功能需求
|
||||
about: Suggest an idea for this project | 提出一个有价值的idea
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
**您的特性请求是否与某个问题相关?请描述。**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
对存在的问题进行清晰且简洁的描述。例如:我一直很困扰的是 [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
**描述您期望的解决方案**
|
||||
A clear and concise description of what you want to happen.
|
||||
清晰且简洁地描述您希望实现的内容。
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
**描述您已考虑的替代方案**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
清晰且简洁地描述您已经考虑过的任何替代解决方案。
|
||||
|
||||
**Additional context**
|
||||
**提供更多细节**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
请附上任何相关截图、链接或文件,以帮助我们更好地理解您的请求。
|
||||
@@ -48,6 +48,9 @@ Easier to use: Just grab MinerU Desktop. No coding, no login, just a simple inte
|
||||
</div>
|
||||
|
||||
# Changelog
|
||||
- 2025/04/16 1.3.4 Released
|
||||
- Slightly improved the speed of OCR detection by removing some unused blocks.
|
||||
- Fixed page-level sorting errors caused by footnotes in certain cases.
|
||||
- 2025/04/12 1.3.2 released
|
||||
- Fixed the issue of incompatible dependency package versions when installing in Python 3.13 environment on Windows systems.
|
||||
- Optimized memory usage during batch inference.
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
</div>
|
||||
|
||||
# 更新记录
|
||||
- 2025/04/16 1.3.4 发布
|
||||
- 通过移除一些无用的块,小幅提升了ocr-det的速度
|
||||
- 修复部分情况下由footnote导致的页面内排序错误
|
||||
- 2025/04/12 1.3.2 发布
|
||||
- 修复了windows系统下,在python3.13环境安装时一些依赖包版本不兼容的问题
|
||||
- 优化批量推理时的内存占用
|
||||
|
||||
@@ -30,19 +30,15 @@ RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
|
||||
# Create a virtual environment for MinerU
|
||||
RUN python3 -m venv /opt/mineru_venv
|
||||
|
||||
# Activate the virtual environment and install necessary Python packages
|
||||
RUN /bin/bash -c "source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/ascend_npu/requirements.txt -O requirements.txt && \
|
||||
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
wget https://gitee.com/ascend/pytorch/releases/download/v6.0.rc2-pytorch2.3.1/torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl && \
|
||||
pip3 install torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
|
||||
# Copy the configuration file template and install magic-pdf latest
|
||||
RUN /bin/bash -c "wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/magic-pdf.template.json && \
|
||||
cp magic-pdf.template.json /root/magic-pdf.json && \
|
||||
source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install -U magic-pdf -i https://mirrors.aliyun.com/pypi/simple"
|
||||
pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
pip3 install torch==2.3.1 torchvision==0.18.1 -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
pip3 install -U magic-pdf[full] 'numpy<2' decorator attrs absl-py cloudpickle ml-dtypes tornado einops -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
wget https://gitee.com/ascend/pytorch/releases/download/v6.0.rc2-pytorch2.3.1/torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl && \
|
||||
pip3 install torch_npu-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
|
||||
# Download models and update the configuration file
|
||||
RUN /bin/bash -c "source /opt/mineru_venv/bin/activate && \
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
boto3>=1.28.43
|
||||
Brotli>=1.1.0
|
||||
click>=8.1.7
|
||||
PyMuPDF>=1.24.9,<1.25.0
|
||||
loguru>=0.6.0
|
||||
numpy>=1.21.6,<2.0.0
|
||||
fast-langdetect>=0.2.3,<0.3.0
|
||||
scikit-learn>=1.0.2
|
||||
pdfminer.six==20231228
|
||||
torch==2.3.1
|
||||
torchvision==0.18.1
|
||||
matplotlib
|
||||
ultralytics>=8.3.48
|
||||
rapid-table>=1.0.3,<2.0.0
|
||||
doclayout-yolo==0.0.2b1
|
||||
ftfy
|
||||
openai
|
||||
pydantic>=2.7.2,<2.11
|
||||
transformers>=4.49.0,<5.0.0
|
||||
tqdm>=4.67.1
|
||||
@@ -18,7 +18,17 @@ RUN apt-get update && \
|
||||
wget \
|
||||
git \
|
||||
libgl1 \
|
||||
libreoffice \
|
||||
fonts-noto-cjk \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-wqy-microhei \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
libglib2.0-0 \
|
||||
libxrender1 \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
poppler-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Python 3.10 as the default python3
|
||||
@@ -27,17 +37,12 @@ RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
|
||||
# Create a virtual environment for MinerU
|
||||
RUN python3 -m venv /opt/mineru_venv
|
||||
|
||||
# Activate the virtual environment and install necessary Python packages
|
||||
RUN /bin/bash -c "source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/docker/china/requirements.txt -O requirements.txt && \
|
||||
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple"
|
||||
|
||||
# Copy the configuration file template and install magic-pdf latest
|
||||
RUN /bin/bash -c "wget https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/magic-pdf.template.json && \
|
||||
cp magic-pdf.template.json /root/magic-pdf.json && \
|
||||
source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install -U magic-pdf -i https://mirrors.aliyun.com/pypi/simple"
|
||||
pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple && \
|
||||
pip3 install -U magic-pdf[full] -i https://mirrors.aliyun.com/pypi/simple"
|
||||
|
||||
# Download models and update the configuration file
|
||||
RUN /bin/bash -c "pip3 install modelscope && \
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
boto3>=1.28.43
|
||||
Brotli>=1.1.0
|
||||
click>=8.1.7
|
||||
PyMuPDF>=1.24.9,<1.25.0
|
||||
loguru>=0.6.0
|
||||
numpy>=1.21.6,<2.0.0
|
||||
fast-langdetect>=0.2.3,<0.3.0
|
||||
scikit-learn>=1.0.2
|
||||
pdfminer.six==20231228
|
||||
torch>=2.2.2,!=2.5.0,!=2.5.1,<=2.6.0
|
||||
torchvision
|
||||
matplotlib
|
||||
ultralytics>=8.3.48
|
||||
rapid-table>=1.0.3,<2.0.0
|
||||
doclayout-yolo==0.0.2b1
|
||||
ftfy
|
||||
openai
|
||||
pydantic>=2.7.2,<2.11
|
||||
transformers>=4.49.0,<5.0.0
|
||||
tqdm>=4.67.1
|
||||
@@ -18,7 +18,17 @@ RUN apt-get update && \
|
||||
wget \
|
||||
git \
|
||||
libgl1 \
|
||||
libreoffice \
|
||||
fonts-noto-cjk \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-wqy-microhei \
|
||||
ttf-mscorefonts-installer \
|
||||
fontconfig \
|
||||
libglib2.0-0 \
|
||||
libxrender1 \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
poppler-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Python 3.10 as the default python3
|
||||
@@ -27,17 +37,12 @@ RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
|
||||
# Create a virtual environment for MinerU
|
||||
RUN python3 -m venv /opt/mineru_venv
|
||||
|
||||
# Activate the virtual environment and install necessary Python packages
|
||||
RUN /bin/bash -c "source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install --upgrade pip && \
|
||||
wget https://github.com/opendatalab/MinerU/raw/master/docker/global/requirements.txt -O requirements.txt && \
|
||||
pip3 install -r requirements.txt"
|
||||
|
||||
# Copy the configuration file template and install magic-pdf latest
|
||||
RUN /bin/bash -c "wget https://github.com/opendatalab/MinerU/raw/master/magic-pdf.template.json && \
|
||||
cp magic-pdf.template.json /root/magic-pdf.json && \
|
||||
source /opt/mineru_venv/bin/activate && \
|
||||
pip3 install -U magic-pdf"
|
||||
pip3 install --upgrade pip && \
|
||||
pip3 install -U magic-pdf[full]"
|
||||
|
||||
# Download models and update the configuration file
|
||||
RUN /bin/bash -c "pip3 install huggingface_hub && \
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
boto3>=1.28.43
|
||||
Brotli>=1.1.0
|
||||
click>=8.1.7
|
||||
PyMuPDF>=1.24.9,<1.25.0
|
||||
loguru>=0.6.0
|
||||
numpy>=1.21.6,<2.0.0
|
||||
fast-langdetect>=0.2.3,<0.3.0
|
||||
scikit-learn>=1.0.2
|
||||
pdfminer.six==20231228
|
||||
torch>=2.2.2,!=2.5.0,!=2.5.1,<=2.6.0
|
||||
torchvision
|
||||
matplotlib
|
||||
ultralytics>=8.3.48
|
||||
rapid-table>=1.0.3,<2.0.0
|
||||
doclayout-yolo==0.0.2b1
|
||||
ftfy
|
||||
openai
|
||||
pydantic>=2.7.2,<2.11
|
||||
transformers>=4.49.0,<5.0.0
|
||||
tqdm>=4.67.1
|
||||
@@ -116,7 +116,7 @@ def read_local_office(path: str) -> list[PymuDocDataset]:
|
||||
shutil.rmtree(temp_dir)
|
||||
return ret
|
||||
|
||||
def read_local_images(path: str, suffixes: list[str]=['.png', '.jpg']) -> list[ImageDataset]:
|
||||
def read_local_images(path: str, suffixes: list[str]=['.png', '.jpg', '.jpeg']) -> list[ImageDataset]:
|
||||
"""Read images from path or directory.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.3.1"
|
||||
__version__ = "1.3.5"
|
||||
|
||||
@@ -147,7 +147,7 @@ def doc_analyze(
|
||||
images.append(img_dict['img'])
|
||||
page_wh_list.append((img_dict['width'], img_dict['height']))
|
||||
|
||||
images_with_extra_info = [(images[index], ocr, dataset._lang) for index in range(len(dataset))]
|
||||
images_with_extra_info = [(images[index], ocr, dataset._lang) for index in range(len(images))]
|
||||
|
||||
if len(images) >= MIN_BATCH_INFERENCE_SIZE:
|
||||
batch_size = MIN_BATCH_INFERENCE_SIZE
|
||||
|
||||
@@ -2,6 +2,8 @@ import time
|
||||
import torch
|
||||
from loguru import logger
|
||||
import numpy as np
|
||||
|
||||
from magic_pdf.libs.boxbase import get_minbox_if_overlap_by_ratio
|
||||
from magic_pdf.libs.clean_memory import clean_memory
|
||||
|
||||
|
||||
@@ -188,9 +190,46 @@ def filter_nested_tables(table_res_list, overlap_threshold=0.8, area_threshold=0
|
||||
return [table for i, table in enumerate(table_res_list) if i not in big_tables_idx]
|
||||
|
||||
|
||||
def remove_overlaps_min_blocks(res_list):
|
||||
# 重叠block,小的不能直接删除,需要和大的那个合并成一个更大的。
|
||||
# 删除重叠blocks中较小的那些
|
||||
need_remove = []
|
||||
for res1 in res_list:
|
||||
for res2 in res_list:
|
||||
if res1 != res2:
|
||||
overlap_box = get_minbox_if_overlap_by_ratio(
|
||||
res1['bbox'], res2['bbox'], 0.8
|
||||
)
|
||||
if overlap_box is not None:
|
||||
res_to_remove = next(
|
||||
(res for res in res_list if res['bbox'] == overlap_box),
|
||||
None,
|
||||
)
|
||||
if (
|
||||
res_to_remove is not None
|
||||
and res_to_remove not in need_remove
|
||||
):
|
||||
large_res = res1 if res1 != res_to_remove else res2
|
||||
x1, y1, x2, y2 = large_res['bbox']
|
||||
sx1, sy1, sx2, sy2 = res_to_remove['bbox']
|
||||
x1 = min(x1, sx1)
|
||||
y1 = min(y1, sy1)
|
||||
x2 = max(x2, sx2)
|
||||
y2 = max(y2, sy2)
|
||||
large_res['bbox'] = [x1, y1, x2, y2]
|
||||
need_remove.append(res_to_remove)
|
||||
|
||||
if len(need_remove) > 0:
|
||||
for res in need_remove:
|
||||
res_list.remove(res)
|
||||
|
||||
return res_list, need_remove
|
||||
|
||||
|
||||
def get_res_list_from_layout_res(layout_res, iou_threshold=0.7, overlap_threshold=0.8, area_threshold=0.8):
|
||||
"""Extract OCR, table and other regions from layout results."""
|
||||
ocr_res_list = []
|
||||
text_res_list = []
|
||||
table_res_list = []
|
||||
table_indices = []
|
||||
single_page_mfdetrec_res = []
|
||||
@@ -204,11 +243,14 @@ def get_res_list_from_layout_res(layout_res, iou_threshold=0.7, overlap_threshol
|
||||
"bbox": [int(res['poly'][0]), int(res['poly'][1]),
|
||||
int(res['poly'][4]), int(res['poly'][5])],
|
||||
})
|
||||
elif category_id in [0, 1, 2, 4, 6, 7]: # OCR regions
|
||||
elif category_id in [0, 2, 4, 6, 7]: # OCR regions
|
||||
ocr_res_list.append(res)
|
||||
elif category_id == 5: # Table regions
|
||||
table_res_list.append(res)
|
||||
table_indices.append(i)
|
||||
elif category_id in [1]: # Text regions
|
||||
res['bbox'] = [int(res['poly'][0]), int(res['poly'][1]), int(res['poly'][4]), int(res['poly'][5])]
|
||||
text_res_list.append(res)
|
||||
|
||||
# Process tables: merge high IoU tables first, then filter nested tables
|
||||
table_res_list, table_indices = merge_high_iou_tables(
|
||||
@@ -226,6 +268,22 @@ def get_res_list_from_layout_res(layout_res, iou_threshold=0.7, overlap_threshol
|
||||
for idx in sorted(to_remove, reverse=True):
|
||||
del layout_res[idx]
|
||||
|
||||
# Remove overlaps in OCR and text regions
|
||||
text_res_list, need_remove = remove_overlaps_min_blocks(text_res_list)
|
||||
for res in text_res_list:
|
||||
# 将res的poly使用bbox重构
|
||||
res['poly'] = [res['bbox'][0], res['bbox'][1], res['bbox'][2], res['bbox'][1],
|
||||
res['bbox'][2], res['bbox'][3], res['bbox'][0], res['bbox'][3]]
|
||||
# 删除res的bbox
|
||||
del res['bbox']
|
||||
|
||||
ocr_res_list.extend(text_res_list)
|
||||
|
||||
if len(need_remove) > 0:
|
||||
for res in need_remove:
|
||||
del res['bbox']
|
||||
layout_res.remove(res)
|
||||
|
||||
return ocr_res_list, filtered_table_res_list, single_page_mfdetrec_res
|
||||
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ def insert_lines_into_block(block_bbox, line_height, page_w, page_h):
|
||||
return [[x0, y0, x1, y1]]
|
||||
|
||||
|
||||
def sort_lines_by_model(fix_blocks, page_w, page_h, line_height):
|
||||
def sort_lines_by_model(fix_blocks, page_w, page_h, line_height, footnote_blocks):
|
||||
page_line_list = []
|
||||
|
||||
def add_lines_to_block(b):
|
||||
@@ -519,6 +519,10 @@ def sort_lines_by_model(fix_blocks, page_w, page_h, line_height):
|
||||
block['real_lines'] = copy.deepcopy(block['lines'])
|
||||
add_lines_to_block(block)
|
||||
|
||||
for block in footnote_blocks:
|
||||
footnote_block = {'bbox': block[:4]}
|
||||
add_lines_to_block(footnote_block)
|
||||
|
||||
if len(page_line_list) > 200: # layoutreader最高支持512line
|
||||
return None
|
||||
|
||||
@@ -779,7 +783,7 @@ def parse_page_core(
|
||||
# interline_equation_blocks参数不够准,后面切换到interline_equations上
|
||||
interline_equation_blocks = []
|
||||
if len(interline_equation_blocks) > 0:
|
||||
all_bboxes, all_discarded_blocks = ocr_prepare_bboxes_for_layout_split_v2(
|
||||
all_bboxes, all_discarded_blocks, footnote_blocks = ocr_prepare_bboxes_for_layout_split_v2(
|
||||
img_body_blocks, img_caption_blocks, img_footnote_blocks,
|
||||
table_body_blocks, table_caption_blocks, table_footnote_blocks,
|
||||
discarded_blocks,
|
||||
@@ -790,7 +794,7 @@ def parse_page_core(
|
||||
page_h,
|
||||
)
|
||||
else:
|
||||
all_bboxes, all_discarded_blocks = ocr_prepare_bboxes_for_layout_split_v2(
|
||||
all_bboxes, all_discarded_blocks, footnote_blocks = ocr_prepare_bboxes_for_layout_split_v2(
|
||||
img_body_blocks, img_caption_blocks, img_footnote_blocks,
|
||||
table_body_blocks, table_caption_blocks, table_footnote_blocks,
|
||||
discarded_blocks,
|
||||
@@ -866,7 +870,7 @@ def parse_page_core(
|
||||
line_height = get_line_height(fix_blocks)
|
||||
|
||||
"""获取所有line并对line排序"""
|
||||
sorted_bboxes = sort_lines_by_model(fix_blocks, page_w, page_h, line_height)
|
||||
sorted_bboxes = sort_lines_by_model(fix_blocks, page_w, page_h, line_height, footnote_blocks)
|
||||
|
||||
"""根据line的中位数算block的序列关系"""
|
||||
fix_blocks = cal_block_index(fix_blocks, sorted_bboxes)
|
||||
|
||||
@@ -99,11 +99,11 @@ def ocr_prepare_bboxes_for_layout_split_v2(
|
||||
all_discarded_blocks = []
|
||||
add_bboxes(discarded_blocks, BlockType.Discarded, all_discarded_blocks)
|
||||
|
||||
"""footnote识别:宽度超过1/3页面宽度的,高度超过10的,处于页面下半50%区域的"""
|
||||
"""footnote识别:宽度超过1/3页面宽度的,高度超过10的,处于页面下半30%区域的"""
|
||||
footnote_blocks = []
|
||||
for discarded in discarded_blocks:
|
||||
x0, y0, x1, y1 = discarded['bbox']
|
||||
if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h / 2):
|
||||
if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h * 0.7):
|
||||
footnote_blocks.append([x0, y0, x1, y1])
|
||||
|
||||
"""移除在footnote下面的任何框"""
|
||||
@@ -119,7 +119,7 @@ def ocr_prepare_bboxes_for_layout_split_v2(
|
||||
"""将剩余的bbox做分离处理,防止后面分layout时出错"""
|
||||
# all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes)
|
||||
all_bboxes.sort(key=lambda x: x[0]+x[1])
|
||||
return all_bboxes, all_discarded_blocks
|
||||
return all_bboxes, all_discarded_blocks, footnote_blocks
|
||||
|
||||
|
||||
def find_blocks_under_footnote(all_bboxes, footnote_blocks):
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class ConvertToPdfError(Exception):
|
||||
@@ -9,21 +13,103 @@ class ConvertToPdfError(Exception):
|
||||
super().__init__(self.msg)
|
||||
|
||||
|
||||
def check_fonts_installed():
|
||||
"""Check if required Chinese fonts are installed."""
|
||||
system_type = platform.system()
|
||||
|
||||
if system_type in ['Windows', 'Darwin']:
|
||||
pass
|
||||
else:
|
||||
# Linux: use fc-list
|
||||
try:
|
||||
output = subprocess.check_output(['fc-list', ':lang=zh'], encoding='utf-8')
|
||||
if output.strip(): # 只要有任何输出(非空)
|
||||
return True
|
||||
else:
|
||||
logger.warning(
|
||||
f"No Chinese fonts were detected, the converted document may not display Chinese content properly."
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_soffice_command():
|
||||
"""Return the path to LibreOffice's soffice executable depending on the platform."""
|
||||
system_type = platform.system()
|
||||
|
||||
# First check if soffice is in PATH
|
||||
soffice_path = shutil.which('soffice')
|
||||
if soffice_path:
|
||||
return soffice_path
|
||||
|
||||
if system_type == 'Windows':
|
||||
# Check common installation paths
|
||||
possible_paths = [
|
||||
Path(os.environ.get('PROGRAMFILES', 'C:/Program Files')) / 'LibreOffice/program/soffice.exe',
|
||||
Path(os.environ.get('PROGRAMFILES(X86)', 'C:/Program Files (x86)')) / 'LibreOffice/program/soffice.exe',
|
||||
Path('C:/Program Files/LibreOffice/program/soffice.exe'),
|
||||
Path('C:/Program Files (x86)/LibreOffice/program/soffice.exe')
|
||||
]
|
||||
|
||||
# Check other drives for windows
|
||||
for drive in ['C:', 'D:', 'E:', 'F:', 'G:', 'H:']:
|
||||
possible_paths.append(Path(f"{drive}/LibreOffice/program/soffice.exe"))
|
||||
|
||||
for path in possible_paths:
|
||||
if path.exists():
|
||||
return str(path)
|
||||
|
||||
raise ConvertToPdfError(
|
||||
"LibreOffice not found. Please install LibreOffice from https://www.libreoffice.org/ "
|
||||
"or ensure soffice.exe is in your PATH environment variable."
|
||||
)
|
||||
else:
|
||||
# For Linux/macOS, provide installation instructions if not found
|
||||
try:
|
||||
# Try to find soffice in standard locations
|
||||
possible_paths = [
|
||||
'/usr/bin/soffice',
|
||||
'/usr/local/bin/soffice',
|
||||
'/opt/libreoffice/program/soffice',
|
||||
'/Applications/LibreOffice.app/Contents/MacOS/soffice'
|
||||
]
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
raise ConvertToPdfError(
|
||||
"LibreOffice not found. Please install it:\n"
|
||||
" - Ubuntu/Debian: sudo apt-get install libreoffice\n"
|
||||
" - CentOS/RHEL: sudo yum install libreoffice\n"
|
||||
" - macOS: brew install libreoffice or download from https://www.libreoffice.org/\n"
|
||||
" - Or ensure soffice is in your PATH environment variable."
|
||||
)
|
||||
except Exception as e:
|
||||
raise ConvertToPdfError(f"Error locating LibreOffice: {str(e)}")
|
||||
|
||||
|
||||
def convert_file_to_pdf(input_path, output_dir):
|
||||
"""Convert a single document (ppt, doc, etc.) to PDF."""
|
||||
if not os.path.isfile(input_path):
|
||||
raise FileNotFoundError(f"The input file {input_path} does not exist.")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
check_fonts_installed()
|
||||
|
||||
soffice_cmd = get_soffice_command()
|
||||
|
||||
cmd = [
|
||||
'soffice',
|
||||
soffice_cmd,
|
||||
'--headless',
|
||||
'--norestore',
|
||||
'--invisible',
|
||||
'--convert-to', 'pdf',
|
||||
'--outdir', str(output_dir),
|
||||
str(input_path)
|
||||
]
|
||||
|
||||
|
||||
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
if process.returncode != 0:
|
||||
raise ConvertToPdfError(process.stderr.decode())
|
||||
raise ConvertToPdfError(f"LibreOffice convert failed: {process.stderr.decode()}")
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
- [llama_index_rag](./llama_index_rag/README.md): Build a lightweight RAG system based on llama_index
|
||||
- [gradio_app](./gradio_app/README.md): Build a web app based on gradio
|
||||
- [web_demo](./web_demo/README.md): MinerU online [demo](https://opendatalab.com/OpenSourceTools/Extractor/PDF/) localized deployment version
|
||||
- ~~[web_demo](./web_demo/README.md): MinerU online [demo](https://opendatalab.com/OpenSourceTools/Extractor/PDF/) localized deployment version~~(Deprecated)
|
||||
- [web_api](./web_api/README.md): Web API Based on FastAPI
|
||||
- [multi_gpu](./multi_gpu/README.md): Multi-GPU parallel processing based on LitServe
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
- [llama_index_rag](./llama_index_rag/README_zh-CN.md): 基于 llama_index 构建轻量级 RAG 系统
|
||||
- [gradio_app](./gradio_app/README_zh-CN.md): 基于 Gradio 的 Web 应用
|
||||
- [web_demo](./web_demo/README_zh-CN.md): MinerU在线[demo](https://opendatalab.com/OpenSourceTools/Extractor/PDF/)本地化部署版本
|
||||
- ~~[web_demo](./web_demo/README_zh-CN.md): MinerU在线[demo](https://opendatalab.com/OpenSourceTools/Extractor/PDF/)本地化部署版本~~(已过时)
|
||||
- [web_api](./web_api/README.md): 基于 FastAPI 的 Web API
|
||||
- [multi_gpu](./multi_gpu/README.md): 基于 LitServe 的多 GPU 并行处理
|
||||
|
||||
@@ -28,7 +28,7 @@ app = FastAPI()
|
||||
|
||||
pdf_extensions = [".pdf"]
|
||||
office_extensions = [".ppt", ".pptx", ".doc", ".docx"]
|
||||
image_extensions = [".png", ".jpg"]
|
||||
image_extensions = [".png", ".jpg", ".jpeg"]
|
||||
|
||||
class MemoryDataWriter(DataWriter):
|
||||
def __init__(self):
|
||||
@@ -128,7 +128,7 @@ def process_file(
|
||||
Tuple[InferenceResult, PipeResult]: Returns inference result and pipeline result
|
||||
"""
|
||||
|
||||
ds = Union[PymuDocDataset, ImageDataset]
|
||||
ds: Union[PymuDocDataset, ImageDataset] = None
|
||||
if file_extension in pdf_extensions:
|
||||
ds = PymuDocDataset(file_bytes)
|
||||
elif file_extension in office_extensions:
|
||||
|
||||
4
setup.py
4
setup.py
@@ -43,7 +43,7 @@ if __name__ == '__main__':
|
||||
"matplotlib>=3.10,<4",
|
||||
"ultralytics>=8.3.48,<9", # yolov8,公式检测
|
||||
"doclayout_yolo==0.0.2b1", # doclayout_yolo
|
||||
"dill>=0.3.9,<1", # doclayout_yolo
|
||||
"dill>=0.3.8,<1", # doclayout_yolo
|
||||
"rapid_table>=1.0.5,<2.0.0", # rapid_table
|
||||
"PyYAML>=6.0.2,<7", # yaml
|
||||
"ftfy>=6.3.1,<7", # unimernet_hf
|
||||
@@ -56,7 +56,7 @@ if __name__ == '__main__':
|
||||
"matplotlib>=3.10,<=3.10.1",
|
||||
"ultralytics>=8.3.48,<=8.3.104", # yolov8,公式检测
|
||||
"doclayout_yolo==0.0.2b1", # doclayout_yolo
|
||||
"dill==0.3.9", # doclayout_yolo
|
||||
"dill==0.3.8", # doclayout_yolo
|
||||
"PyYAML==6.0.2", # yaml
|
||||
"ftfy==6.3.1", # unimernet_hf
|
||||
"openai==1.71.0", # openai SDK
|
||||
|
||||
@@ -223,6 +223,22 @@
|
||||
"created_at": "2025-03-24T12:58:56Z",
|
||||
"repoId": 765083837,
|
||||
"pullRequestNo": 1982
|
||||
},
|
||||
{
|
||||
"name": "zjx20",
|
||||
"id": 2639200,
|
||||
"comment_id": 2800714918,
|
||||
"created_at": "2025-04-14T07:25:26Z",
|
||||
"repoId": 765083837,
|
||||
"pullRequestNo": 2215
|
||||
},
|
||||
{
|
||||
"name": "Doge2077",
|
||||
"id": 91442300,
|
||||
"comment_id": 2801283257,
|
||||
"created_at": "2025-04-14T10:40:54Z",
|
||||
"repoId": 765083837,
|
||||
"pullRequestNo": 2226
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -323,44 +323,6 @@ class TestCli:
|
||||
logging.info(cmd)
|
||||
os.system(cmd)
|
||||
|
||||
|
||||
@pytest.mark.P1
|
||||
def test_local_magic_pdf_open_st_table(self):
|
||||
"""magic pdf cli open st table."""
|
||||
time.sleep(2)
|
||||
#pre_cmd = "cp ~/magic_pdf_st.json ~/magic-pdf.json"
|
||||
value = {
|
||||
"model": "struct_eqtable",
|
||||
"enable": True,
|
||||
"max_time": 400
|
||||
}
|
||||
common.update_config_file(magic_pdf_config, "table-config", value)
|
||||
pdf_path = os.path.join(pdf_dev_path, "pdf", "test_rearch_report.pdf")
|
||||
common.delete_file(pdf_res_path)
|
||||
cli_cmd = "magic-pdf -p %s -o %s" % (pdf_path, pdf_res_path)
|
||||
os.system(cli_cmd)
|
||||
res = common.check_html_table_exists(os.path.join(pdf_res_path, "test_rearch_report", "auto", "test_rearch_report.md"))
|
||||
assert res is True
|
||||
|
||||
@pytest.mark.P1
|
||||
def test_local_magic_pdf_open_tablemaster_cuda(self):
|
||||
"""magic pdf cli open table master html table cuda mode."""
|
||||
time.sleep(2)
|
||||
#pre_cmd = "cp ~/magic_pdf_html.json ~/magic-pdf.json"
|
||||
#os.system(pre_cmd)
|
||||
value = {
|
||||
"model": "tablemaster",
|
||||
"enable": True,
|
||||
"max_time": 400
|
||||
}
|
||||
common.update_config_file(magic_pdf_config, "table-config", value)
|
||||
pdf_path = os.path.join(pdf_dev_path, "pdf", "test_rearch_report.pdf")
|
||||
common.delete_file(pdf_res_path)
|
||||
cli_cmd = "magic-pdf -p %s -o %s" % (pdf_path, pdf_res_path)
|
||||
os.system(cli_cmd)
|
||||
res = common.check_html_table_exists(os.path.join(pdf_res_path, "test_rearch_report", "auto", "test_rearch_report.md"))
|
||||
assert res is True
|
||||
|
||||
@pytest.mark.P1
|
||||
def test_local_magic_pdf_open_rapidai_table(self):
|
||||
"""magic pdf cli open rapid ai table."""
|
||||
@@ -370,6 +332,7 @@ class TestCli:
|
||||
value = {
|
||||
"model": "rapid_table",
|
||||
"enable": True,
|
||||
"sub_model": "slanet_plus",
|
||||
"max_time": 400
|
||||
}
|
||||
common.update_config_file(magic_pdf_config, "table-config", value)
|
||||
@@ -397,6 +360,7 @@ class TestCli:
|
||||
os.system(cli_cmd)
|
||||
common.cli_count_folders_and_check_contents(os.path.join(pdf_res_path, "test_rearch_report", "auto"))
|
||||
|
||||
@pytest.mark.skip(reason="layoutlmv3废弃")
|
||||
@pytest.mark.P1
|
||||
def test_local_magic_pdf_layoutlmv3_yolo(self):
|
||||
"""magic pdf cli open layoutlmv3."""
|
||||
@@ -419,8 +383,9 @@ class TestCli:
|
||||
#pre_cmd = "cp ~/magic_pdf_html_table_cpu.json ~/magic-pdf.json"
|
||||
#os.system(pre_cmd)
|
||||
value = {
|
||||
"model": "tablemaster",
|
||||
"enable": False,
|
||||
"model": "rapid_table",
|
||||
"enable": True,
|
||||
"sub_model": "slanet_plus",
|
||||
"max_time": 400
|
||||
}
|
||||
common.update_config_file(magic_pdf_config, "table-config", value)
|
||||
@@ -439,8 +404,9 @@ class TestCli:
|
||||
#pre_cmd = "cp ~/magic_pdf_close_table.json ~/magic-pdf.json"
|
||||
#os.system(pre_cmd)
|
||||
value = {
|
||||
"model": "tablemaster",
|
||||
"model": "rapid_table",
|
||||
"enable": False,
|
||||
"sub_model": "slanet_plus",
|
||||
"max_time": 400
|
||||
}
|
||||
common.update_config_file(magic_pdf_config, "table-config", value)
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
import unittest
|
||||
import os
|
||||
from PIL import Image
|
||||
from lxml import etree
|
||||
|
||||
from magic_pdf.model.sub_modules.table.tablemaster.tablemaster_paddle import TableMasterPaddleModel
|
||||
from magic_pdf.model.sub_modules.model_init import AtomModelSingleton
|
||||
from magic_pdf.model.sub_modules.table.rapidtable.rapid_table import RapidTableModel
|
||||
|
||||
|
||||
class TestppTableModel(unittest.TestCase):
|
||||
def test_image2html(self):
|
||||
img = Image.open("tests/unittest/test_table/assets/table.jpg")
|
||||
# 修改table模型路径
|
||||
config = {"device": "cuda",
|
||||
"model_dir": "/home/quyuan/.cache/modelscope/hub/opendatalab/PDF-Extract-Kit/models/TabRec/TableMaster"}
|
||||
table_model = TableMasterPaddleModel(config)
|
||||
res = table_model.img2html(img)
|
||||
img = Image.open(os.path.join(os.path.dirname(__file__), "assets/table.jpg"))
|
||||
atom_model_manager = AtomModelSingleton()
|
||||
ocr_engine = atom_model_manager.get_atom_model(
|
||||
atom_model_name='ocr',
|
||||
ocr_show_log=False,
|
||||
det_db_box_thresh=0.5,
|
||||
det_db_unclip_ratio=1.6,
|
||||
lang='ch'
|
||||
)
|
||||
table_model = RapidTableModel(ocr_engine, 'slanet_plus')
|
||||
html_code, table_cell_bboxes, logic_points, elapse = table_model.predict(img)
|
||||
# 验证生成的 HTML 是否符合预期
|
||||
parser = etree.HTMLParser()
|
||||
tree = etree.fromstring(res, parser)
|
||||
tree = etree.fromstring(html_code, parser)
|
||||
|
||||
# 检查 HTML 结构
|
||||
assert tree.find('.//table') is not None, "HTML should contain a <table> element"
|
||||
assert tree.find('.//thead') is not None, "HTML should contain a <thead> element"
|
||||
assert tree.find('.//tbody') is not None, "HTML should contain a <tbody> element"
|
||||
assert tree.find('.//tr') is not None, "HTML should contain a <tr> element"
|
||||
assert tree.find('.//td') is not None, "HTML should contain a <td> element"
|
||||
|
||||
# 检查具体的表格内容
|
||||
headers = tree.xpath('//thead/tr/td/b')
|
||||
print(headers) # Print headers for debugging
|
||||
headers = tree.xpath('//table/tr[1]/td')
|
||||
assert len(headers) == 5, "Thead should have 5 columns"
|
||||
assert headers[0].text and headers[0].text.strip() == "Methods", "First header should be 'Methods'"
|
||||
assert headers[1].text and headers[1].text.strip() == "R", "Second header should be 'R'"
|
||||
@@ -35,7 +39,7 @@ class TestppTableModel(unittest.TestCase):
|
||||
assert headers[4].text and headers[4].text.strip() == "FPS", "Fifth header should be 'FPS'"
|
||||
|
||||
# 检查第一行数据
|
||||
first_row = tree.xpath('//tbody/tr[1]/td')
|
||||
first_row = tree.xpath('//table/tr[2]/td')
|
||||
assert len(first_row) == 5, "First row should have 5 cells"
|
||||
assert first_row[0].text and first_row[0].text.strip() == "SegLink[26]", "First cell should be 'SegLink[26]'"
|
||||
assert first_row[1].text and first_row[1].text.strip() == "70.0", "Second cell should be '70.0'"
|
||||
@@ -44,14 +48,13 @@ class TestppTableModel(unittest.TestCase):
|
||||
assert first_row[4].text and first_row[4].text.strip() == "8.9", "Fifth cell should be '8.9'"
|
||||
|
||||
# 检查倒数第二行数据
|
||||
second_last_row = tree.xpath('//tbody/tr[position()=last()-1]/td')
|
||||
second_last_row = tree.xpath('//table/tr[position()=last()-1]/td')
|
||||
assert len(second_last_row) == 5, "second_last_row should have 5 cells"
|
||||
assert second_last_row[0].text and second_last_row[
|
||||
0].text.strip() == "Ours (SynText)", "First cell should be 'Ours (SynText)'"
|
||||
assert second_last_row[0].text and second_last_row[0].text.strip() == "Ours (SynText)", "First cell should be 'Ours (SynText)'"
|
||||
assert second_last_row[1].text and second_last_row[1].text.strip() == "80.68", "Second cell should be '80.68'"
|
||||
assert second_last_row[2].text and second_last_row[2].text.strip() == "85.40", "Third cell should be '85.40'"
|
||||
assert second_last_row[3].text and second_last_row[3].text.strip() == "82.97", "Fourth cell should be '82.97'"
|
||||
assert second_last_row[3].text and second_last_row[4].text.strip() == "12.68", "Fifth cell should be '12.68'"
|
||||
# assert second_last_row[3].text and second_last_row[3].text.strip() == "82.97", "Fourth cell should be '82.97'"
|
||||
# assert second_last_row[3].text and second_last_row[4].text.strip() == "12.68", "Fifth cell should be '12.68'"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Reference in New Issue
Block a user