Files
wehub-resource-sync 461bf6fd40
CI / lint (3.11) (push) Has been cancelled
CI / lint (3.12) (push) Has been cancelled
CI / lint (3.13) (push) Has been cancelled
CI / shellcheck (push) Has been cancelled
CI / shfmt (push) Has been cancelled
CI / setup (3.11) (push) Has been cancelled
CI / setup (3.12) (push) Has been cancelled
CI / setup (3.13) (push) Has been cancelled
CI / check-licenses (3.12) (push) Has been cancelled
CI / test_unit (3.11) (push) Has been cancelled
CI / test_unit (3.12) (push) Has been cancelled
CI / test_unit (3.13) (push) Has been cancelled
CI / test_unit_no_extras (3.11) (push) Has been cancelled
CI / test_unit_no_extras (3.12) (push) Has been cancelled
CI / test_json_to_html (3.12) (push) Has been cancelled
CI / test_unit_no_extras (3.13) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.12, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.11, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.12, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.11, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.13, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.11, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.12, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.13, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.11, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.12, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.13, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.11, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.12, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.13, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.11, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.12, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.13, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.11, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.12, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.13, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.11, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.12, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.13, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
Build And Push Docker Image / set-short-sha (push) Has been cancelled
Partition Benchmark / setup (push) Has been cancelled
Partition Benchmark / Measure and compare partition() runtime (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.13, --extra xlsx) (push) Has been cancelled
CI / test_ingest_src (3.12) (push) Has been cancelled
CI / test_json_to_markdown (3.12) (push) Has been cancelled
CI / changelog (push) Has been cancelled
CI / test_dockerfile (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/amd64, opensource-linux-8core) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build And Push Docker Image / publish-images (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:56 +08:00

204 lines
6.6 KiB
Python

import json
from abc import ABC, abstractmethod
from collections import defaultdict
from pathlib import Path
from typing import List, Optional
from unstructured_inference.inference.elements import ImageTextRegion, TextRegion
from unstructured_inference.inference.layout import DocumentLayout
from unstructured_inference.models.base import get_model
from unstructured_inference.models.detectron2onnx import (
DEFAULT_LABEL_MAP as DETECTRON_LABEL_MAP,
)
from unstructured_inference.models.detectron2onnx import (
UnstructuredDetectronONNXModel,
)
from unstructured_inference.models.yolox import YOLOX_LABEL_MAP, UnstructuredYoloXModel
from unstructured.documents.elements import Element, Text
from unstructured.partition.pdf_image.analysis.processor import AnalysisProcessor
from unstructured.partition.utils.sorting import coordinates_to_bbox
class LayoutDumper(ABC):
layout_source: str = "unknown"
@abstractmethod
def dump(self) -> dict:
"""Transforms the results to a dict convertible structured formats like JSON or YAML"""
def extract_document_layout_info(layout: DocumentLayout) -> dict:
pages = []
for page in layout.pages:
size = {
"width": page.image_metadata.get("width"),
"height": page.image_metadata.get("height"),
}
elements = []
for element in page.elements:
bbox = element.bbox
elements.append(
{
"bbox": [bbox.x1, bbox.y1, bbox.x2, bbox.y2],
"type": element.type,
"prob": element.prob,
}
)
pages.append({"number": page.number, "size": size, "elements": elements})
return {"pages": pages}
def object_detection_classes(model_name) -> List[str]:
model = get_model(model_name)
if isinstance(model, UnstructuredYoloXModel):
return list(YOLOX_LABEL_MAP.values())
if isinstance(model, UnstructuredDetectronONNXModel):
return list(DETECTRON_LABEL_MAP.values())
else:
raise ValueError(f"Cannot get OD model classes - unknown model type: {model_name}")
class ObjectDetectionLayoutDumper(LayoutDumper):
"""Forms the results in COCO format and saves them to a file"""
layout_source = "object_detection"
def __init__(self, layout: DocumentLayout, model_name: Optional[str] = None):
self.layout: dict = extract_document_layout_info(layout)
self.model_name = model_name
def dump(self) -> dict:
"""Transforms the results to COCO format and saves them to a file"""
try:
classes_dict = {"object_detection_classes": object_detection_classes(self.model_name)}
except ValueError:
classes_dict = {"object_detection_classes": []}
self.layout.update(classes_dict)
return self.layout
def _get_info_from_extracted_page(page: List[TextRegion]) -> List[dict]:
elements = []
for element in page:
is_image = isinstance(element, ImageTextRegion)
bbox = element.bbox
elements.append(
{
"bbox": [bbox.x1, bbox.y1, bbox.x2, bbox.y2],
"text": element.text,
"source": str(element.source.value),
"is_image": is_image,
}
)
return elements
def extract_text_regions_info(layout: List[List[TextRegion]]) -> dict:
pages = []
for page_num, page in enumerate(layout, 1):
elements = _get_info_from_extracted_page(page)
pages.append({"number": page_num, "elements": elements})
return {"pages": pages}
class ExtractedLayoutDumper(LayoutDumper):
layout_source = "pdfminer"
def __init__(self, layout: List[List[TextRegion]]):
self.layout = extract_text_regions_info(layout)
def dump(self) -> dict:
return self.layout
class OCRLayoutDumper(LayoutDumper):
layout_source = "ocr"
def __init__(self):
self.layout = []
self.page_number = 1
def add_ocred_page(self, page: List[TextRegion]):
elements = _get_info_from_extracted_page(page)
self.layout.append({"number": self.page_number, "elements": elements})
self.page_number += 1
def dump(self) -> dict:
return {"pages": self.layout}
def _extract_final_element_info(element: Element) -> dict:
element_type = (
element.category if isinstance(element, Text) else str(element.__class__.__name__)
)
element_prob = getattr(element.metadata, "detection_class_prob", None)
text = element.text
bbox_points = coordinates_to_bbox(element.metadata.coordinates)
cluster = getattr(element.metadata, "cluster", None)
return {
"type": element_type,
"prob": element_prob,
"text": text,
"bbox": bbox_points,
"cluster": cluster,
}
def _extract_final_element_page_size(element: Element) -> dict:
try:
return {
"width": element.metadata.coordinates.system.width,
"height": element.metadata.coordinates.system.height,
}
except AttributeError:
return {
"width": None,
"height": None,
}
class FinalLayoutDumper(LayoutDumper):
layout_source = "final"
def __init__(self, layout: List[Element]):
pages = defaultdict(list)
for element in layout:
element_page_number = element.metadata.page_number
pages[element_page_number].append(_extract_final_element_info(element))
extracted_pages = [
{
"number": page_number,
"size": (
_extract_final_element_page_size(page_elements[0]) if page_elements else None
),
"elements": page_elements,
}
for page_number, page_elements in pages.items()
]
self.layout = {"pages": sorted(extracted_pages, key=lambda x: x["number"])}
def dump(self) -> dict:
return self.layout
class JsonLayoutDumper(AnalysisProcessor):
"""Dumps the results of the analysis to a JSON file"""
def __init__(self, filename: str, save_dir: str):
self.dumpers = []
super().__init__(filename, save_dir)
def add_layout_dumper(self, dumper: LayoutDumper):
self.dumpers.append(dumper)
def process(self):
filename_stem = Path(self.filename).stem
analysis_save_dir = Path(self.save_dir) / "analysis" / filename_stem / "layout_dump"
analysis_save_dir.mkdir(parents=True, exist_ok=True)
for dumper in self.dumpers:
results = dumper.dump()
with open(analysis_save_dir / f"{dumper.layout_source}.json", "w") as f:
f.write(json.dumps(results, indent=2))