docforge-zh 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docforge_zh-0.1.0/.gitattributes +5 -0
- docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/benchmark_submission.yml +65 -0
- docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +36 -0
- docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- docforge_zh-0.1.0/.github/workflows/ci.yml +56 -0
- docforge_zh-0.1.0/.github/workflows/publish.yml +36 -0
- docforge_zh-0.1.0/.gitignore +29 -0
- docforge_zh-0.1.0/CONTRIBUTING.md +36 -0
- docforge_zh-0.1.0/Dockerfile +20 -0
- docforge_zh-0.1.0/LICENSE +21 -0
- docforge_zh-0.1.0/PKG-INFO +188 -0
- docforge_zh-0.1.0/README.md +156 -0
- docforge_zh-0.1.0/benchmark/__init__.py +1 -0
- docforge_zh-0.1.0/benchmark/data/__init__.py +1 -0
- docforge_zh-0.1.0/benchmark/data/human/README.md +21 -0
- docforge_zh-0.1.0/benchmark/data/real/__init__.py +1 -0
- docforge_zh-0.1.0/benchmark/data/real/build_dataset.py +190 -0
- docforge_zh-0.1.0/benchmark/data/real/build_gt_b.py +127 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/AGREEMENT.md +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-001.json +31 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-002.json +23 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-003.json +41 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-scan-001.json +31 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-scan-002.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-table-001.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-table-002.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-001.json +31 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-002.json +23 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-003.json +41 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-scan-001.json +31 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-scan-002.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-table-001.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-table-002.json +28 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p1.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p24-scan.pdf +370 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p24.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p26.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p1-scan.pdf +1759 -2
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p1.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p11.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/manifest.json +62 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/__init__.py +1 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/generate.py +441 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/header-footer-001.json +19 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/headings-001.json +43 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/longtext-001.json +23 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/mixed-001.json +23 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/single-001.json +22 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-plain-001.json +17 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-span-001.json +17 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-span-002.json +17 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/twocol-001.json +25 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/gt/twocol-002.json +25 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/header-footer-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/headings-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/longtext-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/mixed-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/single-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-plain-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-span-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-span-002.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/twocol-001.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/inputs/twocol-002.pdf +0 -0
- docforge_zh-0.1.0/benchmark/data/synthetic/manifest.json +67 -0
- docforge_zh-0.1.0/benchmark/leaderboard.py +80 -0
- docforge_zh-0.1.0/benchmark/metrics/__init__.py +1 -0
- docforge_zh-0.1.0/benchmark/metrics/heading_tree.py +41 -0
- docforge_zh-0.1.0/benchmark/metrics/order_and_text.py +74 -0
- docforge_zh-0.1.0/benchmark/metrics/teds.py +123 -0
- docforge_zh-0.1.0/benchmark/metrics/tree_edit.py +120 -0
- docforge_zh-0.1.0/benchmark/normalize.py +45 -0
- docforge_zh-0.1.0/benchmark/runner.py +245 -0
- docforge_zh-0.1.0/benchmark/schema.py +53 -0
- docforge_zh-0.1.0/docforge/__init__.py +6 -0
- docforge_zh-0.1.0/docforge/_pymupdf.py +10 -0
- docforge_zh-0.1.0/docforge/cli.py +305 -0
- docforge_zh-0.1.0/docforge/config.py +100 -0
- docforge_zh-0.1.0/docforge/engines/__init__.py +3 -0
- docforge_zh-0.1.0/docforge/engines/base.py +82 -0
- docforge_zh-0.1.0/docforge/engines/docforge_engine.py +31 -0
- docforge_zh-0.1.0/docforge/engines/markdown_engines.py +171 -0
- docforge_zh-0.1.0/docforge/errors.py +40 -0
- docforge_zh-0.1.0/docforge/logging_setup.py +51 -0
- docforge_zh-0.1.0/docforge/models.py +143 -0
- docforge_zh-0.1.0/docforge/pipeline.py +257 -0
- docforge_zh-0.1.0/docforge/py.typed +0 -0
- docforge_zh-0.1.0/docforge/server/__init__.py +1 -0
- docforge_zh-0.1.0/docforge/server/app.py +81 -0
- docforge_zh-0.1.0/docforge/stages/__init__.py +3 -0
- docforge_zh-0.1.0/docforge/stages/export.py +89 -0
- docforge_zh-0.1.0/docforge/stages/extract.py +126 -0
- docforge_zh-0.1.0/docforge/stages/layout.py +253 -0
- docforge_zh-0.1.0/docforge/stages/ocr.py +160 -0
- docforge_zh-0.1.0/docforge/stages/reading_order.py +135 -0
- docforge_zh-0.1.0/docforge/stages/table.py +267 -0
- docforge_zh-0.1.0/docforge/textutil.py +43 -0
- docforge_zh-0.1.0/docs/assets/demo.png +0 -0
- docforge_zh-0.1.0/docs/benchmark.md +98 -0
- docforge_zh-0.1.0/examples/demo.md +7 -0
- docforge_zh-0.1.0/pyproject.toml +56 -0
- docforge_zh-0.1.0/tests/conftest.py +20 -0
- docforge_zh-0.1.0/tests/integration/test_e2e.py +126 -0
- docforge_zh-0.1.0/tests/unit/test_config.py +32 -0
- docforge_zh-0.1.0/tests/unit/test_export_schema.py +94 -0
- docforge_zh-0.1.0/tests/unit/test_layout.py +123 -0
- docforge_zh-0.1.0/tests/unit/test_metrics.py +117 -0
- docforge_zh-0.1.0/tests/unit/test_reading_order.py +102 -0
- docforge_zh-0.1.0/tests/unit/test_table.py +159 -0
- docforge_zh-0.1.0/tests/unit/test_teds.py +135 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: "Benchmark 提交(打榜)"
|
|
2
|
+
description: "提交你的解析引擎在 docforge 数据集上的评测结果"
|
|
3
|
+
labels: ["benchmark"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
提交前请确认:
|
|
9
|
+
1. 结果文件由 `docforge bench --engine <你的引擎>` 或你自己的引擎按 [提交规范](https://github.com/attnstack/docforge/blob/main/docs/benchmark.md) 生成;
|
|
10
|
+
2. 已通过 `benchmark/schema.py` 的 schema 校验(校验不过的提交不会上榜);
|
|
11
|
+
3. 请附上可复现命令与环境说明——不可复现的结果会被要求补充或关闭。
|
|
12
|
+
- type: input
|
|
13
|
+
id: engine
|
|
14
|
+
attributes:
|
|
15
|
+
label: "引擎名称与版本"
|
|
16
|
+
description: "如 docforge 0.1.0 / your-engine 1.2.0"
|
|
17
|
+
placeholder: "engine 0.1.0"
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: dropdown
|
|
21
|
+
id: dataset
|
|
22
|
+
attributes:
|
|
23
|
+
label: "数据集版本"
|
|
24
|
+
options:
|
|
25
|
+
- docforge-zh-real-v0.1-rc
|
|
26
|
+
- docforge-zh-synthetic-v0.1
|
|
27
|
+
- 两者都跑了
|
|
28
|
+
- 其他(在说明里注明)
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: results
|
|
33
|
+
attributes:
|
|
34
|
+
label: "指标结果"
|
|
35
|
+
description: "文本 1−CER / 阅读顺序 / 表格 TEDS / 标题树 / 综合,以及各指标覆盖率"
|
|
36
|
+
placeholder: |
|
|
37
|
+
text=0.87 order=0.98 table=0.67 heading=0.81 overall=0.83
|
|
38
|
+
coverage: text 7/7, table 4/7 ...
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
- type: textarea
|
|
42
|
+
id: submission
|
|
43
|
+
attributes:
|
|
44
|
+
label: "提交文件"
|
|
45
|
+
description: "附上提交 JSON(或 gist / 仓库链接)。单文件超限时给下载链接"
|
|
46
|
+
- type: textarea
|
|
47
|
+
id: repro
|
|
48
|
+
attributes:
|
|
49
|
+
label: "复现命令与环境"
|
|
50
|
+
placeholder: |
|
|
51
|
+
命令:docforge bench --engine xxx --dataset ...
|
|
52
|
+
环境:Ubuntu 22.04 / Python 3.11 / CPU,OCR 引擎及版本(如涉及扫描件)
|
|
53
|
+
validations:
|
|
54
|
+
required: true
|
|
55
|
+
- type: checkboxes
|
|
56
|
+
id: declare
|
|
57
|
+
attributes:
|
|
58
|
+
label: "声明"
|
|
59
|
+
options:
|
|
60
|
+
- label: "结果由评测工具生成,未手工修改指标数字"
|
|
61
|
+
required: true
|
|
62
|
+
- label: "提交 JSON 通过 schema 校验(benchmark/schema.py)"
|
|
63
|
+
required: true
|
|
64
|
+
- label: "同一结果未同时以多个 issue 重复提交"
|
|
65
|
+
required: true
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: "Bug 报告"
|
|
2
|
+
description: "解析结果错误、崩溃或性能问题"
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: "问题现象"
|
|
9
|
+
description: "期望输出 vs 实际输出。表格结构错误请附原 PDF 截图与错误 HTML"
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: repro
|
|
14
|
+
attributes:
|
|
15
|
+
label: "复现命令"
|
|
16
|
+
placeholder: "docforge convert in.pdf -o out.md --format both"
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: input_desc
|
|
21
|
+
attributes:
|
|
22
|
+
label: "输入文件特征"
|
|
23
|
+
description: "页数、数字版/扫描件、是否含表格/多栏、来源类型(公文/论文/财报)。**涉及隐私请勿上传原文件,脱敏或裁剪后附图**"
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: log
|
|
26
|
+
attributes:
|
|
27
|
+
label: "日志"
|
|
28
|
+
description: "加 --json-logs 运行的输出(异常栈务必完整)"
|
|
29
|
+
render: shell
|
|
30
|
+
- type: input
|
|
31
|
+
id: env
|
|
32
|
+
attributes:
|
|
33
|
+
label: "环境"
|
|
34
|
+
placeholder: "Windows 11 / Python 3.12 / docforge 0.1.0(是否安装 [ocr])"
|
|
35
|
+
validations:
|
|
36
|
+
required: true
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
cache: pip
|
|
17
|
+
# OCR 依赖(paddle)不在 CI 安装:数百 MB 且非核心链路;扫描路径由本地测试覆盖
|
|
18
|
+
- name: Install
|
|
19
|
+
run: pip install -e ".[dev,bench]"
|
|
20
|
+
- name: Lint
|
|
21
|
+
run: ruff check docforge benchmark
|
|
22
|
+
- name: Seed dataset
|
|
23
|
+
run: docforge gen-seed
|
|
24
|
+
- name: Tests
|
|
25
|
+
run: pytest -q
|
|
26
|
+
- name: Benchmark smoke (deterministic gates)
|
|
27
|
+
run: |
|
|
28
|
+
docforge bench --engine docforge --engine pymupdf4llm --out results
|
|
29
|
+
python - <<'PY'
|
|
30
|
+
import json
|
|
31
|
+
for name, floor in (("docforge", 0.95), ("pymupdf4llm", 0.75)):
|
|
32
|
+
agg = json.load(open(f"results/{name}.json", encoding="utf-8"))["aggregate"]
|
|
33
|
+
overall = agg["overall"]
|
|
34
|
+
assert overall is not None and overall >= floor, f"{name} overall={overall} < {floor}"
|
|
35
|
+
print(f"{name}: overall={overall:.4f} (floor {floor})")
|
|
36
|
+
PY
|
|
37
|
+
|
|
38
|
+
leaderboard-page:
|
|
39
|
+
if: github.ref == 'refs/heads/main'
|
|
40
|
+
needs: test
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
permissions:
|
|
43
|
+
contents: write
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
- uses: actions/setup-python@v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.11"
|
|
49
|
+
cache: pip
|
|
50
|
+
- run: pip install -e ".[bench]"
|
|
51
|
+
- run: docforge gen-seed && docforge bench --engine docforge --engine pymupdf4llm --engine markitdown --out results
|
|
52
|
+
- run: docforge leaderboard --results results --out leaderboard/index.html
|
|
53
|
+
- uses: peaceiris/actions-gh-pages@v4
|
|
54
|
+
with:
|
|
55
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
56
|
+
publish_dir: ./leaderboard
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.11"
|
|
16
|
+
- run: pip install build
|
|
17
|
+
- name: Build sdist and wheel
|
|
18
|
+
run: python -m build
|
|
19
|
+
- uses: actions/upload-artifact@v4
|
|
20
|
+
with:
|
|
21
|
+
name: dist
|
|
22
|
+
path: dist/
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: build
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment: pypi
|
|
28
|
+
permissions:
|
|
29
|
+
id-token: write # PyPI trusted publishing(OIDC),无需在 CI 里存 token
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/download-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
- name: Publish
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
|
|
10
|
+
# 虚拟环境
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# 运行产物
|
|
15
|
+
badcases/
|
|
16
|
+
results*/
|
|
17
|
+
leaderboard/
|
|
18
|
+
probe*.pdf
|
|
19
|
+
.tmp/
|
|
20
|
+
*.log
|
|
21
|
+
examples/demo.json
|
|
22
|
+
|
|
23
|
+
# 标注工作区中的临时渲染页(保留 gt/pred json)
|
|
24
|
+
benchmark/data/human/*/pages/
|
|
25
|
+
|
|
26
|
+
# IDE
|
|
27
|
+
.idea/
|
|
28
|
+
.vscode/
|
|
29
|
+
技术方案.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
欢迎 PR。工程约定先读一遍,能省一轮 review。
|
|
4
|
+
|
|
5
|
+
## 开发环境
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m venv .venv && source .venv/Scripts/activate # Windows Git Bash
|
|
9
|
+
pip install -e ".[dev,bench]"
|
|
10
|
+
ruff check docforge benchmark
|
|
11
|
+
pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
可选:OCR 能力 `pip install "docforge[ocr]"`(PaddleOCR 较重,CI 不装,本地测试扫描链路时自行安装)。
|
|
15
|
+
|
|
16
|
+
## 提交前检查
|
|
17
|
+
|
|
18
|
+
1. `pytest` 全绿——核心算法(阅读顺序/表格/指标)的改动必须带新单测,覆盖正常/边界/异常三类输入;
|
|
19
|
+
2. `ruff check` 无告警;
|
|
20
|
+
3. 若改了指标实现或归一化:跑 `docforge bench --engine docforge`,在 PR 里贴出前后对比——种子集上任何数字变化都要能解释;
|
|
21
|
+
4. 破坏性改动(schema、归一化规则、GT 格式)必须升数据集版本号。
|
|
22
|
+
|
|
23
|
+
## 加一个基线引擎
|
|
24
|
+
|
|
25
|
+
1. 在 `docforge/engines/` 新增引擎类,继承 `BaseEngine`,实现 `parse` 与 `missing_dependency`;
|
|
26
|
+
2. 纯 markdown 输出的引擎直接继承 `_MarkdownEngineBase`,只实现 `_to_markdown`;
|
|
27
|
+
3. 在 `engines/base.py::_register_defaults` 注册;
|
|
28
|
+
4. 用 `docforge bench --engine your-engine` 跑一遍,确认提交能过 schema 校验。
|
|
29
|
+
|
|
30
|
+
## 标注贡献
|
|
31
|
+
|
|
32
|
+
见 [docs/benchmark.md](docs/benchmark.md) 第 4 节与 [benchmark/data/human/README.md](benchmark/data/human/README.md)。标注两个样本起即可提 PR,一致性报告附在 PR 描述里。
|
|
33
|
+
|
|
34
|
+
## commit 风格
|
|
35
|
+
|
|
36
|
+
`<area>: <what>`(如 `table: fix colspan detection on partial borders`),正文说清 why。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# docforge 一键运行镜像:CPU 模式,含自研引擎 + OCR(PaddleOCR)+ 基线 + server
|
|
2
|
+
# 构建后直接 demo:docker run --rm -v "$PWD:/data" docforge convert /data/in.pdf -o /data/out.md
|
|
3
|
+
FROM python:3.11-slim
|
|
4
|
+
|
|
5
|
+
# paddleocr 依赖链需要:libgomp1(paddle)、libgl1/libglib2.0-0(opencv)
|
|
6
|
+
RUN apt-get update \
|
|
7
|
+
&& apt-get install -y --no-install-recommends libgomp1 libgl1 libglib2.0-0 \
|
|
8
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
12
|
+
COPY docforge ./docforge
|
|
13
|
+
COPY benchmark ./benchmark
|
|
14
|
+
|
|
15
|
+
# PaddleOCR 模型文件不入镜像(几百 MB),首次运行自动下载到 ~/.paddleocr;
|
|
16
|
+
# 持久化:docker run -v docforge-models:/root/.paddleocr ...
|
|
17
|
+
RUN pip install --no-cache-dir ".[ocr,bench,server]"
|
|
18
|
+
|
|
19
|
+
ENTRYPOINT ["docforge"]
|
|
20
|
+
CMD ["--help"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 docforge contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: docforge-zh
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 中文文档解析引擎与公开 Benchmark:多栏/复杂表格 PDF -> LLM-ready Markdown 与带 bbox 的 JSON
|
|
5
|
+
Author: docforge contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: benchmark,chinese,document-parsing,ocr,pdf,rag,reading-order,table-structure
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: jsonschema>=4.20
|
|
17
|
+
Requires-Dist: pymupdf>=1.24
|
|
18
|
+
Provides-Extra: bench
|
|
19
|
+
Requires-Dist: markitdown[pdf]>=0.1.1; extra == 'bench'
|
|
20
|
+
Requires-Dist: pymupdf4llm>=0.0.17; extra == 'bench'
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
24
|
+
Provides-Extra: ocr
|
|
25
|
+
Requires-Dist: paddleocr<3.0,>=2.7; extra == 'ocr'
|
|
26
|
+
Requires-Dist: paddlepaddle<3.0,>=2.6; extra == 'ocr'
|
|
27
|
+
Provides-Extra: server
|
|
28
|
+
Requires-Dist: fastapi>=0.110; extra == 'server'
|
|
29
|
+
Requires-Dist: python-multipart>=0.0.9; extra == 'server'
|
|
30
|
+
Requires-Dist: uvicorn>=0.29; extra == 'server'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# docforge
|
|
34
|
+
|
|
35
|
+
**中文文档解析引擎与公开 Benchmark:把多栏、复杂表格、扫描件 PDF 转成 LLM-ready 的分层 Markdown 和带 bbox 可溯源的 JSON,并配套一个可复现的中文解析评测基准。**
|
|
36
|
+
|
|
37
|
+
<p align="center">
|
|
38
|
+
<a href="#-benchmark-榜单"><img alt="benchmark" src="https://img.shields.io/badge/benchmark-中文解析评测-blue"></a>
|
|
39
|
+
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-green"></a>
|
|
40
|
+
<img alt="python" src="https://img.shields.io/badge/python-3.11%2B-informational">
|
|
41
|
+
<a href="https://github.com/attnstack/docforge/actions"><img alt="ci" src="https://img.shields.io/badge/CI-GitHub%20Actions-passing-brightgreen"></a>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<img src="docs/assets/demo.png" alt="docforge:政府公文 PDF → Markdown 与跨行列表格结构还原" width="820">
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
## 为什么是 docforge
|
|
49
|
+
|
|
50
|
+
- 文档解析是所有 RAG 的地基,但中文场景(扫描公文、财报合并单元格、双栏论文)没有公认的开源强方案;
|
|
51
|
+
- markitdown / PyMuPDF4LLM 这类文字层提取器**不做版面理解**:双栏论文会被逐行交错、合并单元格直接拍平;
|
|
52
|
+
- 更重要的是:**解析引擎会被追上,被引用的评测标准不会**。docforge 附带一个确定性指标、带提交校验的公开 benchmark,任何方案(包括 VLM 路线)都可以来打榜。
|
|
53
|
+
|
|
54
|
+
## 30 秒 Quick Start
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 1) 安装(核心依赖极轻:PyMuPDF + jsonschema)
|
|
58
|
+
pip install -e .
|
|
59
|
+
# 2) 生成合成种子数据集(程序化 PDF,GT 精确已知,用于打通评测链路)
|
|
60
|
+
docforge gen-seed
|
|
61
|
+
# 3) 解析一个多栏+表格的 PDF
|
|
62
|
+
docforge convert benchmark/data/synthetic/inputs/mixed-001.pdf -o out.md --format both
|
|
63
|
+
# 4) 在种子集上跑评测(自研引擎 + 可用基线),得到 report.md 与榜单页
|
|
64
|
+
docforge bench --engine docforge --engine pymupdf4llm
|
|
65
|
+
docforge leaderboard --results results/ --out leaderboard/index.html
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
扫描件 OCR 为可选能力:`pip install "docforge[ocr]"`(拉起 PaddleOCR,首次运行自动下载模型)。
|
|
69
|
+
|
|
70
|
+
Docker(含 OCR 全量依赖,镜像已实测可跑):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
docker build -t docforge .
|
|
74
|
+
docker run --rm -v "$PWD:/data" docforge convert /data/扫描件.pdf -o /data/out.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 特性
|
|
78
|
+
|
|
79
|
+
| 能力 | 状态 | 说明 |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| 数字版 PDF → Markdown / JSON | ✅ v0.1 | 文字层直提取,毫秒级;标题层级 + 跨页段落续接 |
|
|
82
|
+
| 双栏/多栏阅读顺序还原 | ✅ v0.1 | 通栏分段 → x 投影分栏 → 行带聚类,纯几何可单测 |
|
|
83
|
+
| 有框线表格结构还原 | ✅ v0.1 | 线段网格聚类 + union-find 合并,输出 rowspan/colspan HTML |
|
|
84
|
+
| 扫描件 OCR | 🔌 可选 | `docforge[ocr]`(PaddleOCR 2.x/3.x 双代适配),Windows 原生实测:扫描公文页文本 0.9931;扫描页表格结构 v0.2 |
|
|
85
|
+
| 公式 → LaTeX | ⏳ v0.2 | 指标已实现并入库,转换链路未启用(诚实声明,见「不足」) |
|
|
86
|
+
| Benchmark(4 指标 + schema 校验 + 榜单) | ✅ v0.1 | CER / TEDS / 阅读顺序 / 标题树,全部确定性指标,无 LLM judge |
|
|
87
|
+
| 人工标注工作流 | 🚧 进行中 | 预标注 + 双人一致性工具(`docforge annotate` / `docforge bench-agree`) |
|
|
88
|
+
| CLI / Docker / FastAPI | ✅ / ✅ / ✅ | 同步 `/convert`;异步任务队列 v0.2 |
|
|
89
|
+
|
|
90
|
+
## 架构
|
|
91
|
+
|
|
92
|
+
```mermaid
|
|
93
|
+
flowchart LR
|
|
94
|
+
A[PDF/图片] --> B{文字层?}
|
|
95
|
+
B -- 有 --> C[提取: 文字块/词/矢量线段]
|
|
96
|
+
B -- 无 --> O[OCR 可选依赖<br/>PaddleOCR]
|
|
97
|
+
O --> C2[OCR 行 -> 阅读排序 -> 段落合并]
|
|
98
|
+
C --> D[表格结构还原<br/>线段网格 + union-find]
|
|
99
|
+
C --> E[页眉页脚剔除<br/>跨页重复证据]
|
|
100
|
+
C --> F[标题层级<br/>字号加权中位数]
|
|
101
|
+
D --> G[阅读顺序还原<br/>通栏分段/投影分栏/行带聚类]
|
|
102
|
+
E --> G
|
|
103
|
+
F --> G
|
|
104
|
+
C2 --> G
|
|
105
|
+
G --> H[导出]
|
|
106
|
+
H --> I[分层 Markdown]
|
|
107
|
+
H --> J[带 bbox JSON<br/>page/bbox/reading_order/confidence]
|
|
108
|
+
I --> K[Benchmark Runner<br/>CER / TEDS / 顺序对 / 标题树]
|
|
109
|
+
J --> K
|
|
110
|
+
K --> L[结果 JSON -> 榜单页]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
核心算法(值得读源码的部分):
|
|
114
|
+
|
|
115
|
+
- **阅读顺序**:[docforge/stages/reading_order.py](docforge/stages/reading_order.py) —— 通栏块先剥离再分栏;行带容差取 0.5×块高中位数;
|
|
116
|
+
- **表格结构**:[docforge/stages/table.py](docforge/stages/table.py) —— 局部边界缺失经 union-find 汇成跨行列区域;L 形合并的降级策略;
|
|
117
|
+
- **指标**:[benchmark/metrics/teds.py](benchmark/metrics/teds.py) 与 [benchmark/metrics/tree_edit.py](benchmark/metrics/tree_edit.py) —— Zhang-Shasha 树编辑距离自研实现 + 单测。
|
|
118
|
+
|
|
119
|
+
## Benchmark 榜单
|
|
120
|
+
|
|
121
|
+
两个数据集,口径均为确定性四指标(详见 [docs/benchmark.md](docs/benchmark.md)):
|
|
122
|
+
|
|
123
|
+
### ① 真实公文集 `docforge-zh-real-v0.1-rc`(7 样本:5 数字页 + 2 仿真扫描页)
|
|
124
|
+
|
|
125
|
+
来源:政府公开公文(依《著作权法》第五条可再分发,来源 URL 见 manifest)。GT 文本取自 PDF 文字层并逐块与原文程序化核对,结构(标题层级/表格跨行列)由标注员读渲染图核定;**已完成第二轮独立复核(仅依据渲染图重标),文本一致率 1.0000、结构零分歧([AGREEMENT.md](benchmark/data/real-v0.1-rc/AGREEMENT.md));单标注员两轮,升 v0.1 需第二位标注员**。
|
|
126
|
+
|
|
127
|
+
| 引擎 | 文本 1−CER ↑ | 阅读顺序 ↑ | 表格 TEDS ↑ | 标题树 ↑ | 综合 ↑ |
|
|
128
|
+
|---|---|---|---|---|---|
|
|
129
|
+
| docforge 0.1.0(含 OCR) | 0.8760 | 0.9762 | 0.6667 | 0.8095 | **0.8321** |
|
|
130
|
+
| PyMuPDF4LLM | 0.5331 | 1.0000 | 0.2044 | 0.5952 | 0.5832 |
|
|
131
|
+
| markitdown | 0.3576 | 0.9667 | 0.0000 | 0.3333 | 0.4144 |
|
|
132
|
+
|
|
133
|
+
> 复现:`docforge bench --engine docforge --engine pymupdf4llm --engine markitdown --dataset benchmark/data/real-v0.1-rc --out results-real`(docforge 需 `pip install "docforge[ocr]"`)
|
|
134
|
+
> 诚实解读(重要):
|
|
135
|
+
> - 数字页(5 样本)docforge 全指标 1.0:文本指标对"文字层提取类"引擎区分度低(GT 与引擎同源于文字层,1.0 是预期而非奇迹),区分度在结构——真实公文的跨行合并表格(rowspan=4×3 组、colspan 备注行)与"黑体/楷体同字号标题",两个基线均无法还原;
|
|
136
|
+
> - 扫描页(2 样本):扫描公文页 OCR 文本 0.9931、阅读顺序 1.0(真实识别质量,非文字层同源);扫描表格页 text 0.139 / table 0.0 是**已声明的 v0.1 边界**——扫描页无矢量线,表格结构不可还原,退化文本混入正文也被 GT 判罚,两项负分如实计入总分;
|
|
137
|
+
> - docforge 真实集数字基于 7 个样本,样本量小,不外推为整体性能主张;标题判定依赖公文格式先验(黑体/楷体/编号模式),对非公文版式的泛化未验证。
|
|
138
|
+
|
|
139
|
+
### ② 合成种子集 `docforge-zh-synthetic-v0.1`(10 样本,评测链路自证与回归)
|
|
140
|
+
|
|
141
|
+
| 引擎 | 文本 1−CER ↑ | 阅读顺序 ↑ | 表格 TEDS ↑ | 标题树 ↑ | 综合 ↑ |
|
|
142
|
+
|---|---|---|---|---|---|
|
|
143
|
+
| docforge 0.1.0 | 1.0000 | 1.0000 | 1.0000 | 1.0000 | **1.0000** |
|
|
144
|
+
| PyMuPDF4LLM | 0.9773 | 1.0000 | 1.0000 | 0.6296 | 0.9017 |
|
|
145
|
+
| markitdown | 0.8389 | 0.9524 | 0.9726 | 0.4445 | 0.8021 |
|
|
146
|
+
|
|
147
|
+
> 复现:`docforge gen-seed && docforge bench --engine docforge --engine pymupdf4llm --engine markitdown && docforge leaderboard --results results --out leaderboard/index.html`(每样本耗时与逐项分数见 `results/report.md`;测试环境:Windows 11 / Python 3.12 / CPU)
|
|
148
|
+
> ⚠️ 合成集数字只证明"评测链路工作正常 + 引擎在构造样例上正确",**不构成对真实文档的性能主张**。
|
|
149
|
+
>
|
|
150
|
+
> 对对比结果的两点说明:
|
|
151
|
+
> - **PyMuPDF4LLM 在合成集表格 TEDS 上与我们打平**(1.0000):它能从 PDF 单元格拓扑恢复结构,只是不输出 rowspan/colspan 属性——在真实集的复杂表格上它掉到 0.2044,差距来自跨行列合并的真实复杂度;
|
|
152
|
+
> - markitdown 在双栏样本上文本分 0.32(逐行左右交错),这正是方案定位所针对的痛点,实测坐实。
|
|
153
|
+
|
|
154
|
+
## Benchmark 设计要点(为什么可信)
|
|
155
|
+
|
|
156
|
+
1. **确定性指标**:CER、TEDS(树编辑距离)、有序对准确率、标题树相似度——无 LLM judge,同输入必得同分;
|
|
157
|
+
2. **归一化先约定**:NFKC → 小写 → 去空白,规则最小化并开源([benchmark/normalize.py](benchmark/normalize.py)),防止全半角/空白差异污染数字;
|
|
158
|
+
3. **提交格式即契约**:JSON Schema 公布,CI 校验([benchmark/schema.py](benchmark/schema.py)),块粒度与对齐规则显式化;
|
|
159
|
+
4. **标注质量自证**:双人独立标注 + `docforge bench-agree` 一致性报告;标注协议见 [docs/benchmark.md](docs/benchmark.md)。
|
|
160
|
+
|
|
161
|
+
## 快速上手(开发者)
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
git clone https://github.com/attnstack/docforge && cd docforge
|
|
165
|
+
python -m venv .venv && source .venv/Scripts/activate # Windows Git Bash
|
|
166
|
+
pip install -e ".[dev,bench]"
|
|
167
|
+
pytest # 全部单测 + 集成测试
|
|
168
|
+
ruff check docforge benchmark
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 已知不足(诚实清单)
|
|
172
|
+
|
|
173
|
+
- **无框线表格不支持**:v0.1 只承诺有框线表格;无框线需要检测模型(PP-Structure/SLANet),在 v0.2 路线图上;
|
|
174
|
+
- **公式只检测不转换**:LaTeX 识别(pix2tex 类)对中文混排准确率未实测,v0.1 不启用,避免给不可信数字;
|
|
175
|
+
- **OCR:Windows 原生已验证;WSL2/Docker + AMD 组合 `import paddleocr` 即崩溃**:实测 paddlepaddle 3.x 有 onednn 推理上游 bug(PIR 属性转换未实现);pin 的 2.6 稳定组合在 Windows 原生(含 AMD CPU)完整跑通扫描链路(真实扫描公文页文本 0.9931),但在 WSL2/Docker 的 Linux wheel 上**导入 paddleocr 即 SIGILL**(其原生库初始化在 AMD Zen 上的问题,已用 import 追踪定位到导入层)。`docforge[ocr]` 因此 pin 2.x;docforge 的懒加载设计保证数字版解析完全不触碰 paddle(数字路径在容器内正常),扫描需求在 WSL2+AMD 下请用 Windows 原生或 Intel 环境;
|
|
176
|
+
- **扫描页表格结构与标题不支持**:扫描页无矢量线(表格)无字号元数据(标题),v0.1 如实降级并在评测中计入负分,v0.2 用 PP-Structure 解决;
|
|
177
|
+
- **真实集规模小、单标注**:7 样本(5 数字 + 2 扫描仿真),单标注流程,双人标注与 Kappa/一致率报告在补;扩展到 50–100 页的标注协议见 docs/benchmark.md;
|
|
178
|
+
- **标题判定依赖公文格式先验**:黑体/楷体字体名 + 编号模式(一、/(一)/——)在公文上实测有效,对非公文中文版式的泛化未验证;
|
|
179
|
+
- **段落回流是启发式**:依赖首行缩进/字体连续信号,对无缩进的西文行级拆分场景可能欠合并;
|
|
180
|
+
- **PaddleOCR 模型文件不进镜像/仓库**:首次运行需联网下载(约几百 MB)。
|
|
181
|
+
|
|
182
|
+
## Contributing
|
|
183
|
+
|
|
184
|
+
见 [CONTRIBUTING.md](CONTRIBUTING.md)。benchmark 提交与榜单规则见 [docs/benchmark.md](docs/benchmark.md)。
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
[MIT](LICENSE)
|