|
@@ -0,0 +1,594 @@
|
|
|
|
|
+"""
|
|
|
|
|
+坐标转换工具模块
|
|
|
|
|
+
|
|
|
|
|
+提供各种坐标转换功能:
|
|
|
|
|
+- 相对坐标 → 绝对坐标转换
|
|
|
|
|
+- OCR 格式转换
|
|
|
|
|
+- 旋转坐标逆变换
|
|
|
|
|
+- HTML data-bbox 坐标转换
|
|
|
|
|
+"""
|
|
|
|
|
+import re
|
|
|
|
|
+import json
|
|
|
|
|
+from typing import Dict, List, Any, Optional, Tuple
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+from loguru import logger
|
|
|
|
|
+
|
|
|
|
|
+# 导入 merger 组件
|
|
|
|
|
+try:
|
|
|
|
|
+ from merger import BBoxExtractor
|
|
|
|
|
+ MERGER_AVAILABLE = True
|
|
|
|
|
+except ImportError:
|
|
|
|
|
+ MERGER_AVAILABLE = False
|
|
|
|
|
+ BBoxExtractor = None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class CoordinateUtils:
|
|
|
|
|
+ """坐标转换工具类"""
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def crop_region(image: np.ndarray, bbox: List[float]) -> np.ndarray:
|
|
|
|
|
+ """
|
|
|
|
|
+ 裁剪图像区域
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ image: 原始图像
|
|
|
|
|
+ bbox: 裁剪区域 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 裁剪后的图像
|
|
|
|
|
+ """
|
|
|
|
|
+ if len(bbox) < 4:
|
|
|
|
|
+ return image
|
|
|
|
|
+
|
|
|
|
|
+ x1, y1, x2, y2 = map(int, bbox[:4])
|
|
|
|
|
+ h, w = image.shape[:2]
|
|
|
|
|
+
|
|
|
|
|
+ x1 = max(0, min(x1, w))
|
|
|
|
|
+ y1 = max(0, min(y1, h))
|
|
|
|
|
+ x2 = max(x1, min(x2, w))
|
|
|
|
|
+ y2 = max(y1, min(y2, h))
|
|
|
|
|
+
|
|
|
|
|
+ return image[y1:y2, x1:x2]
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def bbox_overlap(bbox1: List[float], bbox2: List[float]) -> bool:
|
|
|
|
|
+ """
|
|
|
|
|
+ 检查两个 bbox 是否重叠
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ bbox1: 第一个 bbox [x1, y1, x2, y2]
|
|
|
|
|
+ bbox2: 第二个 bbox [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 是否重叠
|
|
|
|
|
+ """
|
|
|
|
|
+ if len(bbox1) < 4 or len(bbox2) < 4:
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ x1_1, y1_1, x2_1, y2_1 = bbox1[:4]
|
|
|
|
|
+ x1_2, y1_2, x2_2, y2_2 = bbox2[:4]
|
|
|
|
|
+
|
|
|
|
|
+ if x2_1 < x1_2 or x2_2 < x1_1:
|
|
|
|
|
+ return False
|
|
|
|
|
+ if y2_1 < y1_2 or y2_2 < y1_1:
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def convert_to_absolute_coords(
|
|
|
|
|
+ relative_bbox: List,
|
|
|
|
|
+ region_bbox: List[float]
|
|
|
|
|
+ ) -> List:
|
|
|
|
|
+ """
|
|
|
|
|
+ 将相对坐标转换为绝对坐标
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ relative_bbox: 相对坐标
|
|
|
|
|
+ region_bbox: 区域的绝对坐标 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 绝对坐标
|
|
|
|
|
+ """
|
|
|
|
|
+ if not relative_bbox or len(region_bbox) < 4:
|
|
|
|
|
+ return relative_bbox
|
|
|
|
|
+
|
|
|
|
|
+ bx1, by1 = region_bbox[0], region_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ # 处理4点坐标格式 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
|
|
|
|
|
+ if isinstance(relative_bbox[0], (list, tuple)):
|
|
|
|
|
+ return [
|
|
|
|
|
+ [p[0] + bx1, p[1] + by1] for p in relative_bbox
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # 处理4值坐标格式 [x1, y1, x2, y2]
|
|
|
|
|
+ if len(relative_bbox) >= 4:
|
|
|
|
|
+ return [
|
|
|
|
|
+ relative_bbox[0] + bx1,
|
|
|
|
|
+ relative_bbox[1] + by1,
|
|
|
|
|
+ relative_bbox[2] + bx1,
|
|
|
|
|
+ relative_bbox[3] + by1
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ return relative_bbox
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def convert_ocr_to_matcher_format(
|
|
|
|
|
+ ocr_poly: List,
|
|
|
|
|
+ text: str,
|
|
|
|
|
+ confidence: float,
|
|
|
|
|
+ idx: int,
|
|
|
|
|
+ table_bbox: Optional[List[float]] = None
|
|
|
|
|
+ ) -> Optional[Dict[str, Any]]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 将 OCR 结果转换为 TableCellMatcher 期望的格式
|
|
|
|
|
+
|
|
|
|
|
+ OCR 返回格式:
|
|
|
|
|
+ bbox: [[x1,y1], [x2,y1], [x2,y2], [x1,y2]] # 4点多边形
|
|
|
|
|
+ text: str
|
|
|
|
|
+ confidence: float
|
|
|
|
|
+
|
|
|
|
|
+ TableCellMatcher 期望格式:
|
|
|
|
|
+ text: str
|
|
|
|
|
+ bbox: [x_min, y_min, x_max, y_max] # 4值矩形
|
|
|
|
|
+ poly: [[x1,y1], [x2,y1], [x2,y2], [x1,y2]] # 4点多边形
|
|
|
|
|
+ score: float
|
|
|
|
|
+ paddle_bbox_index: int
|
|
|
|
|
+ used: bool
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ ocr_poly: OCR返回的4点多边形坐标
|
|
|
|
|
+ text: 识别文本
|
|
|
|
|
+ confidence: 置信度
|
|
|
|
|
+ idx: 索引
|
|
|
|
|
+ table_bbox: 表格的绝对坐标,用于转换相对坐标
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的字典,或 None(如果无效)
|
|
|
|
|
+ """
|
|
|
|
|
+ if not ocr_poly or not text:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ poly = []
|
|
|
|
|
+
|
|
|
|
|
+ # 格式1: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]] - 4点多边形
|
|
|
|
|
+ if isinstance(ocr_poly[0], (list, tuple)) and len(ocr_poly) == 4:
|
|
|
|
|
+ poly = [[float(p[0]), float(p[1])] for p in ocr_poly]
|
|
|
|
|
+
|
|
|
|
|
+ # 格式2: [x1, y1, x2, y1, x2, y2, x1, y2] - 8值展平格式
|
|
|
|
|
+ elif len(ocr_poly) == 8 and isinstance(ocr_poly[0], (int, float)):
|
|
|
|
|
+ poly = [
|
|
|
|
|
+ [float(ocr_poly[0]), float(ocr_poly[1])],
|
|
|
|
|
+ [float(ocr_poly[2]), float(ocr_poly[3])],
|
|
|
|
|
+ [float(ocr_poly[4]), float(ocr_poly[5])],
|
|
|
|
|
+ [float(ocr_poly[6]), float(ocr_poly[7])]
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # 格式3: [x1, y1, x2, y2] - 4值矩形格式
|
|
|
|
|
+ elif len(ocr_poly) == 4 and isinstance(ocr_poly[0], (int, float)):
|
|
|
|
|
+ x1, y1, x2, y2 = ocr_poly
|
|
|
|
|
+ poly = [
|
|
|
|
|
+ [float(x1), float(y1)],
|
|
|
|
|
+ [float(x2), float(y1)],
|
|
|
|
|
+ [float(x2), float(y2)],
|
|
|
|
|
+ [float(x1), float(y2)]
|
|
|
|
|
+ ]
|
|
|
|
|
+ else:
|
|
|
|
|
+ logger.warning(f"Unknown OCR bbox format: {ocr_poly}")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 转换为绝对坐标(相对于整页图片)
|
|
|
|
|
+ if table_bbox and len(table_bbox) >= 2:
|
|
|
|
|
+ offset_x, offset_y = table_bbox[0], table_bbox[1]
|
|
|
|
|
+ poly = [[p[0] + offset_x, p[1] + offset_y] for p in poly]
|
|
|
|
|
+
|
|
|
|
|
+ # 从多边形计算 bbox [x_min, y_min, x_max, y_max]
|
|
|
|
|
+ xs = [p[0] for p in poly]
|
|
|
|
|
+ ys = [p[1] for p in poly]
|
|
|
|
|
+ bbox = [min(xs), min(ys), max(xs), max(ys)]
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'text': text,
|
|
|
|
|
+ 'bbox': bbox,
|
|
|
|
|
+ 'poly': poly,
|
|
|
|
|
+ 'score': confidence,
|
|
|
|
|
+ 'paddle_bbox_index': idx,
|
|
|
|
|
+ 'used': False
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def inverse_rotate_table_coords(
|
|
|
|
|
+ cells: List[Dict],
|
|
|
|
|
+ html: str,
|
|
|
|
|
+ rotation_angle: float,
|
|
|
|
|
+ orig_table_size: Tuple[int, int],
|
|
|
|
|
+ table_bbox: List[float]
|
|
|
|
|
+ ) -> Tuple[List[Dict], str]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 将旋转后的坐标逆向转换回原图坐标
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ cells: 单元格列表(坐标是旋转后的)
|
|
|
|
|
+ html: HTML字符串(data-bbox是旋转后的)
|
|
|
|
|
+ rotation_angle: 旋转角度
|
|
|
|
|
+ orig_table_size: 原始表格尺寸 (width, height)
|
|
|
|
|
+ table_bbox: 表格在整页图片中的位置 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (转换后的cells, 转换后的html)
|
|
|
|
|
+ """
|
|
|
|
|
+ if not MERGER_AVAILABLE or BBoxExtractor is None:
|
|
|
|
|
+ # 如果 merger 不可用,只添加偏移量
|
|
|
|
|
+ converted_cells = CoordinateUtils.add_table_offset_to_cells(cells, table_bbox)
|
|
|
|
|
+ converted_html = CoordinateUtils.add_table_offset_to_html(html, table_bbox)
|
|
|
|
|
+ return converted_cells, converted_html
|
|
|
|
|
+
|
|
|
|
|
+ table_offset_x, table_offset_y = table_bbox[0], table_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 cells 中的 bbox
|
|
|
|
|
+ converted_cells = []
|
|
|
|
|
+ for cell in cells:
|
|
|
|
|
+ cell_copy = cell.copy()
|
|
|
|
|
+ cell_bbox = cell.get('bbox', [])
|
|
|
|
|
+ if cell_bbox and len(cell_bbox) == 4:
|
|
|
|
|
+ # 先逆向旋转,再加上表格偏移量
|
|
|
|
|
+ orig_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ cell_bbox, rotation_angle, orig_table_size
|
|
|
|
|
+ )
|
|
|
|
|
+ # 加上表格偏移量转换为整页坐标
|
|
|
|
|
+ cell_copy['bbox'] = [
|
|
|
|
|
+ orig_bbox[0] + table_offset_x,
|
|
|
|
|
+ orig_bbox[1] + table_offset_y,
|
|
|
|
|
+ orig_bbox[2] + table_offset_x,
|
|
|
|
|
+ orig_bbox[3] + table_offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+ converted_cells.append(cell_copy)
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 HTML 中的 data-bbox
|
|
|
|
|
+ def replace_bbox(match):
|
|
|
|
|
+ bbox_str = match.group(1)
|
|
|
|
|
+ try:
|
|
|
|
|
+ bbox = json.loads(bbox_str)
|
|
|
|
|
+ if len(bbox) == 4:
|
|
|
|
|
+ orig_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ bbox, rotation_angle, orig_table_size
|
|
|
|
|
+ )
|
|
|
|
|
+ new_bbox = [
|
|
|
|
|
+ orig_bbox[0] + table_offset_x,
|
|
|
|
|
+ orig_bbox[1] + table_offset_y,
|
|
|
|
|
+ orig_bbox[2] + table_offset_x,
|
|
|
|
|
+ orig_bbox[3] + table_offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+ return f'data-bbox="[{new_bbox[0]},{new_bbox[1]},{new_bbox[2]},{new_bbox[3]}]"'
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ return match.group(0)
|
|
|
|
|
+
|
|
|
|
|
+ converted_html = re.sub(
|
|
|
|
|
+ r'data-bbox="\[([^\]]+)\]"',
|
|
|
|
|
+ replace_bbox,
|
|
|
|
|
+ html
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return converted_cells, converted_html
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def add_table_offset_to_cells(
|
|
|
|
|
+ cells: List[Dict],
|
|
|
|
|
+ table_bbox: List[float]
|
|
|
|
|
+ ) -> List[Dict]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 为单元格坐标添加表格偏移量(无旋转情况)
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ cells: 单元格列表
|
|
|
|
|
+ table_bbox: 表格位置 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的 cells
|
|
|
|
|
+ """
|
|
|
|
|
+ offset_x, offset_y = table_bbox[0], table_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ converted_cells = []
|
|
|
|
|
+ for cell in cells:
|
|
|
|
|
+ cell_copy = cell.copy()
|
|
|
|
|
+ cell_bbox = cell.get('bbox', [])
|
|
|
|
|
+ if cell_bbox and len(cell_bbox) == 4:
|
|
|
|
|
+ cell_copy['bbox'] = [
|
|
|
|
|
+ cell_bbox[0] + offset_x,
|
|
|
|
|
+ cell_bbox[1] + offset_y,
|
|
|
|
|
+ cell_bbox[2] + offset_x,
|
|
|
|
|
+ cell_bbox[3] + offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+ converted_cells.append(cell_copy)
|
|
|
|
|
+
|
|
|
|
|
+ return converted_cells
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def add_table_offset_to_html(
|
|
|
|
|
+ html: str,
|
|
|
|
|
+ table_bbox: List[float]
|
|
|
|
|
+ ) -> str:
|
|
|
|
|
+ """
|
|
|
|
|
+ 为HTML中的data-bbox添加表格偏移量(无旋转情况)
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ html: HTML字符串
|
|
|
|
|
+ table_bbox: 表格位置 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的 HTML
|
|
|
|
|
+ """
|
|
|
|
|
+ offset_x, offset_y = table_bbox[0], table_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ def replace_bbox(match):
|
|
|
|
|
+ bbox_str = match.group(1)
|
|
|
|
|
+ try:
|
|
|
|
|
+ bbox = json.loads(f"[{bbox_str}]")
|
|
|
|
|
+ if len(bbox) == 4:
|
|
|
|
|
+ new_bbox = [
|
|
|
|
|
+ bbox[0] + offset_x,
|
|
|
|
|
+ bbox[1] + offset_y,
|
|
|
|
|
+ bbox[2] + offset_x,
|
|
|
|
|
+ bbox[3] + offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+ return f'data-bbox="[{new_bbox[0]},{new_bbox[1]},{new_bbox[2]},{new_bbox[3]}]"'
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ return match.group(0)
|
|
|
|
|
+
|
|
|
|
|
+ converted_html = re.sub(
|
|
|
|
|
+ r'data-bbox="\[([^\]]+)\]"',
|
|
|
|
|
+ replace_bbox,
|
|
|
|
|
+ html
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return converted_html
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def add_table_offset_to_ocr_boxes(
|
|
|
|
|
+ ocr_boxes: List[Dict],
|
|
|
|
|
+ table_bbox: List[float]
|
|
|
|
|
+ ) -> List[Dict]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 为 ocr_boxes 添加表格偏移量,将相对坐标转换为页面绝对坐标
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ ocr_boxes: OCR 框列表
|
|
|
|
|
+ table_bbox: 表格在页面中的位置 [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的 ocr_boxes
|
|
|
|
|
+ """
|
|
|
|
|
+ if not ocr_boxes or not table_bbox:
|
|
|
|
|
+ return ocr_boxes
|
|
|
|
|
+
|
|
|
|
|
+ offset_x = table_bbox[0]
|
|
|
|
|
+ offset_y = table_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ converted_boxes = []
|
|
|
|
|
+ for box in ocr_boxes:
|
|
|
|
|
+ new_box = box.copy()
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 bbox [x1, y1, x2, y2]
|
|
|
|
|
+ if 'bbox' in new_box and new_box['bbox']:
|
|
|
|
|
+ bbox = new_box['bbox']
|
|
|
|
|
+ if len(bbox) >= 4:
|
|
|
|
|
+ new_box['bbox'] = [
|
|
|
|
|
+ bbox[0] + offset_x,
|
|
|
|
|
+ bbox[1] + offset_y,
|
|
|
|
|
+ bbox[2] + offset_x,
|
|
|
|
|
+ bbox[3] + offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 poly [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
|
|
|
|
|
+ if 'poly' in new_box and new_box['poly']:
|
|
|
|
|
+ poly = new_box['poly']
|
|
|
|
|
+ new_poly = []
|
|
|
|
|
+ for point in poly:
|
|
|
|
|
+ if isinstance(point, (list, tuple)) and len(point) >= 2:
|
|
|
|
|
+ new_poly.append([point[0] + offset_x, point[1] + offset_y])
|
|
|
|
|
+ else:
|
|
|
|
|
+ new_poly.append(point)
|
|
|
|
|
+ new_box['poly'] = new_poly
|
|
|
|
|
+
|
|
|
|
|
+ converted_boxes.append(new_box)
|
|
|
|
|
+
|
|
|
|
|
+ return converted_boxes
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def inverse_rotate_ocr_boxes(
|
|
|
|
|
+ ocr_boxes: List[Dict],
|
|
|
|
|
+ rotation_angle: float,
|
|
|
|
|
+ orig_table_size: Tuple[int, int],
|
|
|
|
|
+ table_bbox: List[float]
|
|
|
|
|
+ ) -> List[Dict]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 对 ocr_boxes 进行逆向旋转并添加表格偏移量
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ ocr_boxes: OCR 框列表
|
|
|
|
|
+ rotation_angle: 表格旋转角度
|
|
|
|
|
+ orig_table_size: 原始表格尺寸 (width, height)
|
|
|
|
|
+ table_bbox: 表格在页面中的位置
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的 ocr_boxes
|
|
|
|
|
+ """
|
|
|
|
|
+ if not ocr_boxes:
|
|
|
|
|
+ return ocr_boxes
|
|
|
|
|
+
|
|
|
|
|
+ if not MERGER_AVAILABLE or BBoxExtractor is None:
|
|
|
|
|
+ return CoordinateUtils.add_table_offset_to_ocr_boxes(ocr_boxes, table_bbox)
|
|
|
|
|
+
|
|
|
|
|
+ offset_x = table_bbox[0]
|
|
|
|
|
+ offset_y = table_bbox[1]
|
|
|
|
|
+
|
|
|
|
|
+ converted_boxes = []
|
|
|
|
|
+ for box in ocr_boxes:
|
|
|
|
|
+ new_box = box.copy()
|
|
|
|
|
+
|
|
|
|
|
+ # 逆向旋转 bbox
|
|
|
|
|
+ if 'bbox' in new_box and new_box['bbox']:
|
|
|
|
|
+ bbox = new_box['bbox']
|
|
|
|
|
+ if len(bbox) >= 4:
|
|
|
|
|
+ try:
|
|
|
|
|
+ rotated_bbox = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ bbox, rotation_angle, orig_table_size
|
|
|
|
|
+ )
|
|
|
|
|
+ new_box['bbox'] = [
|
|
|
|
|
+ rotated_bbox[0] + offset_x,
|
|
|
|
|
+ rotated_bbox[1] + offset_y,
|
|
|
|
|
+ rotated_bbox[2] + offset_x,
|
|
|
|
|
+ rotated_bbox[3] + offset_y
|
|
|
|
|
+ ]
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.debug(f"Failed to inverse rotate ocr_box bbox: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ # 逆向旋转 poly
|
|
|
|
|
+ if 'poly' in new_box and new_box['poly']:
|
|
|
|
|
+ poly = new_box['poly']
|
|
|
|
|
+ try:
|
|
|
|
|
+ poly_list = [[float(p[0]), float(p[1])] for p in poly]
|
|
|
|
|
+ rotated_poly = BBoxExtractor.inverse_rotate_coordinates(
|
|
|
|
|
+ poly_list, rotation_angle, orig_table_size
|
|
|
|
|
+ )
|
|
|
|
|
+ new_poly = []
|
|
|
|
|
+ for point in rotated_poly:
|
|
|
|
|
+ new_poly.append([point[0] + offset_x, point[1] + offset_y])
|
|
|
|
|
+ new_box['poly'] = new_poly
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.debug(f"Failed to inverse rotate ocr_box poly: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ converted_boxes.append(new_box)
|
|
|
|
|
+
|
|
|
|
|
+ return converted_boxes
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def is_poly_format(bbox: Any) -> bool:
|
|
|
|
|
+ """
|
|
|
|
|
+ 检测 bbox 是否为四点多边形格式
|
|
|
|
|
+
|
|
|
|
|
+ 四点格式: [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
|
|
|
|
|
+ 矩形格式: [x_min, y_min, x_max, y_max]
|
|
|
|
|
+ """
|
|
|
|
|
+ if not bbox or not isinstance(bbox, list):
|
|
|
|
|
+ return False
|
|
|
|
|
+ if len(bbox) != 4:
|
|
|
|
|
+ return False
|
|
|
|
|
+ return isinstance(bbox[0], (list, tuple))
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def transform_coords_to_original(
|
|
|
|
|
+ element: Dict[str, Any],
|
|
|
|
|
+ rotate_angle: int,
|
|
|
|
|
+ rotated_shape: Tuple,
|
|
|
|
|
+ original_shape: Tuple
|
|
|
|
|
+ ) -> Dict[str, Any]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 将坐标从旋转后的图片坐标系转换回原始图片坐标系
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ element: 元素字典(包含bbox等坐标信息)
|
|
|
|
|
+ rotate_angle: 旋转角度(0, 90, 180, 270)
|
|
|
|
|
+ rotated_shape: 旋转后图片的shape (h, w, c)
|
|
|
|
|
+ original_shape: 原始图片的shape (h, w, c)
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 坐标转换后的元素字典(深拷贝)
|
|
|
|
|
+ """
|
|
|
|
|
+ import copy
|
|
|
|
|
+ element = copy.deepcopy(element)
|
|
|
|
|
+
|
|
|
|
|
+ if rotate_angle == 0 or not MERGER_AVAILABLE or BBoxExtractor is None:
|
|
|
|
|
+ return element
|
|
|
|
|
+
|
|
|
|
|
+ # 原始图片尺寸 (width, height)
|
|
|
|
|
+ orig_h, orig_w = original_shape[:2]
|
|
|
|
|
+ orig_image_size = (orig_w, orig_h)
|
|
|
|
|
+
|
|
|
|
|
+ # 转换主bbox
|
|
|
|
|
+ if 'bbox' in element and element['bbox']:
|
|
|
|
|
+ element['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ element['bbox'], rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 转换表格相关坐标
|
|
|
|
|
+ if element.get('type') == 'table' and 'content' in element:
|
|
|
|
|
+ content = element['content']
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 OCR boxes
|
|
|
|
|
+ if 'ocr_boxes' in content and content['ocr_boxes']:
|
|
|
|
|
+ for box in content['ocr_boxes']:
|
|
|
|
|
+ if 'bbox' in box and box['bbox']:
|
|
|
|
|
+ box['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ box['bbox'], rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 cells
|
|
|
|
|
+ if 'cells' in content and content['cells']:
|
|
|
|
|
+ for cell in content['cells']:
|
|
|
|
|
+ if 'bbox' in cell and cell['bbox']:
|
|
|
|
|
+ cell['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ cell.get('bbox', []), rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 转换 HTML 中的 data-bbox 属性
|
|
|
|
|
+ if 'html' in content and content['html']:
|
|
|
|
|
+ content['html'] = CoordinateUtils.transform_html_data_bbox(
|
|
|
|
|
+ content['html'], rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 转换文本OCR details
|
|
|
|
|
+ if 'content' in element and 'ocr_details' in element.get('content', {}):
|
|
|
|
|
+ ocr_details = element['content'].get('ocr_details', [])
|
|
|
|
|
+ if ocr_details:
|
|
|
|
|
+ for detail in ocr_details:
|
|
|
|
|
+ if 'bbox' in detail and detail['bbox']:
|
|
|
|
|
+ if CoordinateUtils.is_poly_format(detail['bbox']):
|
|
|
|
|
+ detail['bbox'] = BBoxExtractor.inverse_rotate_coordinates(
|
|
|
|
|
+ detail['bbox'], rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ detail['bbox'] = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ detail.get('bbox', []), rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return element
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def transform_html_data_bbox(
|
|
|
|
|
+ html: str,
|
|
|
|
|
+ rotate_angle: int,
|
|
|
|
|
+ orig_image_size: Tuple[int, int]
|
|
|
|
|
+ ) -> str:
|
|
|
|
|
+ """
|
|
|
|
|
+ 转换 HTML 中所有 data-bbox 属性的坐标
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ html: 包含 data-bbox 属性的 HTML 字符串
|
|
|
|
|
+ rotate_angle: 旋转角度
|
|
|
|
|
+ orig_image_size: 原始图片尺寸 (width, height)
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 转换后的 HTML 字符串
|
|
|
|
|
+ """
|
|
|
|
|
+ if not MERGER_AVAILABLE or BBoxExtractor is None:
|
|
|
|
|
+ return html
|
|
|
|
|
+
|
|
|
|
|
+ def replace_bbox(match):
|
|
|
|
|
+ try:
|
|
|
|
|
+ bbox_str = match.group(1)
|
|
|
|
|
+ bbox = json.loads(bbox_str)
|
|
|
|
|
+ if bbox and len(bbox) == 4:
|
|
|
|
|
+ transformed = BBoxExtractor.inverse_rotate_box_coordinates(
|
|
|
|
|
+ bbox, rotate_angle, orig_image_size
|
|
|
|
|
+ )
|
|
|
|
|
+ return f'data-bbox="{json.dumps(transformed)}"'
|
|
|
|
|
+ except (json.JSONDecodeError, ValueError):
|
|
|
|
|
+ pass
|
|
|
|
|
+ return match.group(0)
|
|
|
|
|
+
|
|
|
|
|
+ pattern = r'data-bbox="(\[[^\]]+\])"'
|
|
|
|
|
+ return re.sub(pattern, replace_bbox, html)
|
|
|
|
|
+
|