Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Oculodoc

智能文档处理框架,结合版面分析与视觉语言模型(VLM)实现文档理解与内容提取。

特性

  • 模块化架构:可插拔的版面分析器、VLM 分析器和内容处理器
  • 多模型融合:支持同时运行多个版面检测模型,通过 IoU 和优先级智能融合结果
  • 异步设计:所有 I/O 和推理操作均为异步,支持高并发处理
  • 灵活的 Pipeline:通过组合不同的 ProcessingStep 构建自定义处理流程
  • 多层内容提取:针对不同类型区域(文本/表格/公式/图片)使用专门的处理器
  • 阅读顺序恢复:使用 XY-cut 算法恢复文档的自然阅读顺序
  • Debug 可视化:生成 HTML 调试页面,支持原图与识别结果的对比查看

安装

# 安装 uv(如未安装)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 克隆并安装项目
cd oculodoc
uv sync

# 安装开发依赖
uv sync --group dev

# 安装 OpenAI SDK(用于 VLM)
uv pip install openai

快速开始

命令行使用

# 基础处理(仅版面分析)
python scripts/multi_layer_example.py document.pdf

# 使用 VLM 进行完整处理
python scripts/multi_layer_example.py document.pdf \
    --vlm-base-url https://api.openai.com/v1 \
    --vlm-api-key $OPENAI_API_KEY \
    --vlm-model gpt-4o

# 指定输出目录
python scripts/multi_layer_example.py document.pdf -o output/

Python API

import asyncio
from pathlib import Path
from oculodoc.config import OculodocConfig, MultiModelConfig, RegionAnalyzerSpec
from oculodoc.analyzers import create_and_initialize_analyzers, cleanup_analyzers
from oculodoc.processor import ProcessingPipeline
from oculodoc.processor.steps import MultiModelDetectionStep, RegionFusionStep, RegionFilterStep
from oculodoc.content_extraction import MultiLayerExtractionStep
from oculodoc.vlm import OpenAICompatibleVLMAnalyzer

async def process_document(pdf_path: Path):
    # 1. 配置版面分析模型
    multi_config = MultiModelConfig(
        analyzers=[
            RegionAnalyzerSpec(
                enabled=True,
                model_type="layout",
                model_name="PP-DocLayout-L",
                priority=1,
            ),
        ],
    )
    
    # 2. 初始化分析器
    analyzers = await create_and_initialize_analyzers(multi_config)
    
    # 3. 配置 VLM(可选)
    vlm_analyzer = OpenAICompatibleVLMAnalyzer(
        base_url="https://api.openai.com/v1",
        api_key="your-api-key",
        model="gpt-4o",
    )
    await vlm_analyzer.initialize({})
    
    # 4. 构建 Pipeline
    pipeline = ProcessingPipeline(steps=[
        MultiModelDetectionStep(analyzers=analyzers),
        RegionFusionStep(),
        RegionFilterStep(min_confidence=0.3),
        MultiLayerExtractionStep.create_default(),
    ])
    
    # 5. 处理文档
    config = OculodocConfig()
    result = await pipeline.process_document(
        pdf_path,
        config,
        vlm_analyzer=vlm_analyzer,
    )
    
    print(f"处理了 {result.total_pages} 页,耗时 {result.processing_time:.2f}s")
    
    # 6. 清理资源
    await cleanup_analyzers(analyzers)
    await vlm_analyzer.cleanup()
    
    return result

# 运行
asyncio.run(process_document(Path("document.pdf")))

架构

核心概念

┌─────────────────────────────────────────────────────────────────┐
│                      ProcessingPipeline                          │
├─────────────────────────────────────────────────────────────────┤
│  Step 1: MultiModelDetectionStep                                │
│    └── 运行多个 IRegionAnalyzer,收集所有检测结果               │
├─────────────────────────────────────────────────────────────────┤
│  Step 2: RegionFusionStep                                       │
│    └── 基于 IoU 和优先级融合重叠区域                            │
├─────────────────────────────────────────────────────────────────┤
│  Step 3: RegionFilterStep                                       │
│    └── 过滤低置信度或不需要的区域类型                           │
├─────────────────────────────────────────────────────────────────┤
│  Step 4: MultiLayerExtractionStep                               │
│    └── 按类型路由到不同的 IContentProcessor                     │
│        ├── TextContentProcessor (文本 → VLM OCR)                │
│        ├── TableContentProcessor (表格 → PaddleOCR/VLM)         │
│        ├── FormulaContentProcessor (公式 → PaddleOCR/VLM)       │
│        └── FigureContentProcessor (图片 → VLM 描述)             │
├─────────────────────────────────────────────────────────────────┤
│  Step 5: ReadingOrderStep (可选)                                │
│    └── 使用 XY-cut 算法恢复阅读顺序                             │
└─────────────────────────────────────────────────────────────────┘

项目结构

oculodoc/
├── interfaces/              # 抽象接口定义
│   ├── layout_analyzer.py   # ILayoutAnalyzer, LayoutDetection
│   ├── vlm_analyzer.py      # IVLMAnalyzer, VLMAnalysisResult
│   ├── region_analyzer.py   # IRegionAnalyzer(通用区域分析器)
│   └── document_processor.py
├── config/                  # 配置管理
│   ├── schema.py            # 配置数据类
│   └── loader.py            # 配置加载器
├── layout/                  # 版面分析实现
│   ├── doclayout_yolo_analyzer.py  # DocLayout-YOLO
│   └── paddle_layout_analyzer.py   # PaddleX PP-DocLayout
├── vlm/                     # VLM 实现
│   ├── openai_compatible_analyzer.py  # OpenAI 兼容 API
│   └── sglang_ocrflux_analyzer.py     # SGLang OCRFlux
├── analyzers/               # 专用分析器
│   ├── paddle_formula_analyzer.py  # 公式识别
│   └── paddle_table_analyzer.py    # 表格结构识别
├── content_extraction/      # 内容提取模块
│   ├── processors.py        # IContentProcessor 实现
│   ├── fusion.py            # 多层结果融合
│   ├── step.py              # MultiLayerExtractionStep
│   └── formatters.py        # 输出格式转换
├── processor/               # 处理器
│   ├── pipeline.py          # ProcessingPipeline
│   └── steps/               # Pipeline 步骤
│       ├── base.py          # ProcessingStep, PageData, ProcessingContext
│       ├── layout.py        # LayoutDetectionStep
│       ├── multi_model.py   # MultiModelDetectionStep
│       ├── fusion.py        # RegionFusionStep
│       ├── filter.py        # RegionFilterStep
│       ├── vlm.py           # VLMExtractionStep
│       └── reading_order.py # ReadingOrderStep
├── debug/                   # 调试工具
│   └── html_generator.py    # Debug HTML 生成器
├── utils/                   # 工具函数
│   └── reading_order.py     # XY-cut 阅读顺序算法
└── errors/                  # 异常定义
    └── exceptions.py

配置说明

VLM 配置

支持任何 OpenAI 兼容的 API:

服务 base_url 示例 model
OpenAI https://api.openai.com/v1 gpt-4o, gpt-4o-mini
火山引擎 ARK https://ark.cn-beijing.volces.com/api/v3 doubao-seed-1-6-251015
本地 vLLM http://localhost:8000/v1 Qwen/Qwen2-VL-7B-Instruct
本地 SGLang http://localhost:30000/v1 你的模型名

版面分析模型

模型类型 model_name 说明
layout PP-DocLayout-L PaddleX 大模型,精度高
layout PP-DocLayout-M PaddleX 中模型,平衡
layout PP-DocLayout-S PaddleX 小模型,速度快
layout 自定义路径 DocLayout-YOLO 自训练模型

多模型融合配置

from oculodoc.config import MultiModelConfig, RegionAnalyzerSpec, RegionFusionConfig

config = MultiModelConfig(
    analyzers=[
        # 通用版面分析(低优先级)
        RegionAnalyzerSpec(
            enabled=True,
            model_type="layout",
            model_name="PP-DocLayout-L",
            priority=1,
        ),
        # 公式识别(高优先级)
        RegionAnalyzerSpec(
            enabled=True,
            model_type="formula",
            model_name="PP-FormulaNet-L",
            priority=10,
        ),
    ],
    fusion=RegionFusionConfig(
        iou_threshold=0.5,           # IoU 阈值
        prefer_specialized=True,      # 优先使用专用模型结果
        containment_threshold=0.8,    # 包含关系阈值
    ),
    parallel_inference=True,
)

输出格式

Markdown

from oculodoc.content_extraction import OutputFormatter

markdown = OutputFormatter.to_markdown(detections)

HTML

html = OutputFormatter.to_html(detections, include_styles=True)

结构化 JSON

json_data = OutputFormatter.to_structured_json(detections)

Debug HTML(原图对比)

生成按页调试 HTML,便于对比原图与识别结果:

from oculodoc.debug import generate_debug_html_per_page

# 生成按页的调试 HTML
debug_files = generate_debug_html_per_page(
    result=processing_result,
    document_path=Path("document.pdf"),
    output_dir=Path("output/"),  # 生成 debug_page_1.html, debug_page_2.html, ...
)

# 或者生成单个合并的 HTML(所有页面在一个文件中)
from oculodoc.debug import generate_debug_html

generate_debug_html(
    result=processing_result,
    document_path=Path("document.pdf"),
    output_path=Path("debug.html"),
)

CLI 使用:

# 处理文档时自动生成按页调试 HTML
python scripts/multi_layer_example.py document.pdf

# 仅运行版面分析并生成调试 HTML
python scripts/debug_layout.py document.pdf -o output/debug

# 通过 CLI 生成调试 HTML
python -m oculodoc debug --input middle.json --pdf document.pdf --output output/ --per-page

二次开发

自定义 ProcessingStep

from oculodoc.processor.steps.base import ProcessingStep, PageData, ProcessingContext

class MyCustomStep(ProcessingStep):
    async def process(self, page: PageData, ctx: ProcessingContext) -> PageData:
        # 访问版面检测结果
        for detection in page.layout_elements:
            print(f"Category: {detection.category_name}")
            print(f"BBox: {detection.bbox}")
            print(f"Content: {detection.metadata.get('content', '')}")
        
        # 修改或添加检测结果
        page.layout_elements = [...]
        
        return page

自定义 ContentProcessor

from oculodoc.content_extraction.processors import (
    IContentProcessor, ContentResult, ContentFormat
)

class MyContentProcessor(IContentProcessor):
    @property
    def supported_categories(self) -> list[str]:
        return ["my_category"]
    
    @property
    def processor_name(self) -> str:
        return "MyContentProcessor"
    
    async def process(
        self,
        image: Image.Image,
        category: str,
        ctx: ProcessingContext,
        **kwargs,
    ) -> ContentResult:
        # 你的处理逻辑
        content = await my_extraction_logic(image)
        
        return ContentResult(
            content=content,
            content_format=ContentFormat.MARKDOWN,
            confidence=0.9,
        )

自定义 LayoutAnalyzer

from oculodoc.interfaces import ILayoutAnalyzer, LayoutDetection, LayoutAnalyzerOptions

class MyLayoutAnalyzer(ILayoutAnalyzer):
    async def initialize(self, config: dict) -> None:
        # 加载模型
        pass
    
    async def analyze(
        self,
        image: Image.Image,
        options: LayoutAnalyzerOptions | None = None,
        **kwargs,
    ) -> list[LayoutDetection]:
        # 运行推理
        results = []
        # ...
        return results
    
    async def cleanup(self) -> None:
        # 释放资源
        pass
    
    @property
    def supported_categories(self) -> dict[int, str]:
        return {
            0: "text",
            1: "title",
            2: "table",
            # ...
        }

自定义 VLM Analyzer

from oculodoc.interfaces import IVLMAnalyzer, VLMAnalysisResult

class MyVLMAnalyzer(IVLMAnalyzer):
    async def initialize(self, config: dict) -> None:
        pass
    
    async def analyze(
        self,
        image_data: str,  # base64 encoded
        prompt: str,
        **kwargs,
    ) -> list[VLMAnalysisResult]:
        # 调用你的 VLM API
        response = await my_vlm_api(image_data, prompt)
        
        return [VLMAnalysisResult(
            content_type="text",
            content=response,
            confidence=0.9,
        )]
    
    async def batch_analyze(
        self,
        image_data_list: list[str],
        prompts: list[str],
        **kwargs,
    ) -> list[list[VLMAnalysisResult]]:
        # 批量处理
        pass

测试

# 运行所有测试
uv run pytest

# 运行带覆盖率报告
uv run pytest --cov=oculodoc

# 运行特定测试文件
uv run pytest oculodoc/tests/test_pipeline_steps.py

常见问题

PaddleOCR 不可用

安装 PaddleOCR:

uv pip install paddleocr paddlex

VLM 初始化失败

确保安装了 OpenAI SDK:

uv pip install openai

GPU 加速

PaddleX 会自动检测 GPU。确保安装了正确的 CUDA 版本:

# 检查 PaddlePaddle GPU 支持
python -c "import paddle; print(paddle.device.is_compiled_with_cuda())"

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages