Files

173 lines
6.8 KiB
Python
Raw Permalink Normal View History

2026-06-15 11:40:00 +08:00
"""Grounding-point helpers (point / bbox coordinate remap, pure regex)."""
import re
from typing import List, Optional
def process_grounding_points(
text: str, orig_height, orig_width, resized_height, resized_width, model_type
) -> str:
"""Remap <point>/<box>/<bbox> coordinates inside ``text`` from the original
image size to the resized space used by the given model type.
"""
point_pattern = re.compile(r"<(point|box|bbox)>(.*?)</\1>")
def process_match(match):
tag_name = match.group(1)
coords_str = match.group(2)
try:
coords = list(map(int, re.findall(r"\d+", coords_str)))
scale_w = resized_width / orig_width
scale_h = resized_height / orig_height
if len(coords) == 2:
x, y = coords
if model_type == "qwen2_5":
new_x = max(0, min(round(x * scale_w), resized_width - 1))
new_y = max(0, min(round(y * scale_h), resized_height - 1))
elif model_type in ["qwen2"]:
new_x = max(0, min(999.999, (x / orig_width) * 1000))
new_y = max(0, min(999.999, (y / orig_height) * 1000))
else:
raise ValueError("Unsupported model type")
coords = [new_x, new_y]
if len(coords) == 4:
x1, y1, x2, y2 = coords
if model_type == "qwen2_5":
new_x1 = max(0, min(round(x1 * scale_w), resized_width - 1))
new_y1 = max(0, min(round(y1 * scale_h), resized_height - 1))
new_x2 = max(0, min(round(x2 * scale_w), resized_width - 1))
new_y2 = max(0, min(round(y2 * scale_h), resized_height - 1))
elif model_type in ["qwen2"]:
new_x1 = max(0, min(999.999, (x1 / orig_width) * 1000))
new_y1 = max(0, min(999.999, (y1 / orig_height) * 1000))
new_x2 = max(0, min(999.999, (x2 / orig_width) * 1000))
new_y2 = max(0, min(999.999, (y2 / orig_height) * 1000))
else:
raise ValueError("Unsupported model type")
coords = [new_x1, new_y1, new_x2, new_y2]
return f'<{tag_name}>[{", ".join(map(str, coords))}]</{tag_name}>'
except (ValueError, TypeError):
return match.group(0)
return point_pattern.sub(process_match, text)
def extract_grounding_points(text: str) -> List[List[float]]:
"""Extract all <point>/<box>/<bbox> coordinates from ``text`` as list-of-list."""
point_pattern = re.compile(r"<(point|box|bbox)>\s*\[([^\]]+)\]\s*</\1>")
points: List[List[float]] = []
for match in point_pattern.finditer(text):
coords_str = match.group(2)
raw_values = re.findall(r"-?\d+\.?\d*", coords_str)
converted: List[float] = []
for value in raw_values:
number = float(value)
converted.append(int(number) if number.is_integer() else number)
if converted:
points.append(converted)
return points
def reverse_grounding_points(
text: str, orig_height, orig_width, resized_height, resized_width, model_type
) -> str:
"""Inverse of ``process_grounding_points`` - map resized coords back to original."""
point_pattern = re.compile(r"<(point|box|bbox)>(.*?)</\1>")
def reverse_match(match):
tag_name = match.group(1)
coords_str = match.group(2)
try:
coords = list(map(float, re.findall(r"-?\d+\.?\d*", coords_str)))
scale_w = resized_width / orig_width
scale_h = resized_height / orig_height
if len(coords) == 2:
x, y = coords
if model_type == "qwen2_5":
orig_x = max(0, min(orig_width - 1, round(x / scale_w)))
orig_y = max(0, min(orig_height - 1, round(y / scale_h)))
elif model_type in ["qwen2"]:
orig_x = max(
0, min(orig_width - 1, round((x / 1000) * orig_width))
)
orig_y = max(
0, min(orig_height - 1, round((y / 1000) * orig_height))
)
else:
raise ValueError("Unsupported model type")
coords = [orig_x, orig_y]
if len(coords) == 4:
x1, y1, x2, y2 = coords
if model_type == "qwen2_5":
orig_x1 = max(0, min(orig_width - 1, round(x1 / scale_w)))
orig_y1 = max(0, min(orig_height - 1, round(y1 / scale_h)))
orig_x2 = max(0, min(orig_width - 1, round(x2 / scale_w)))
orig_y2 = max(0, min(orig_height - 1, round(y2 / scale_h)))
elif model_type in ["qwen2"]:
orig_x1 = max(
0, min(orig_width - 1, round((x1 / 1000) * orig_width))
)
orig_y1 = max(
0, min(orig_height - 1, round((y1 / 1000) * orig_height))
)
orig_x2 = max(
0, min(orig_width - 1, round((x2 / 1000) * orig_width))
)
orig_y2 = max(
0, min(orig_height - 1, round((y2 / 1000) * orig_height))
)
else:
raise ValueError("Unsupported model type")
coords = [orig_x1, orig_y1, orig_x2, orig_y2]
return f'<{tag_name}>[{", ".join(map(str, map(int, coords)))}]</{tag_name}>'
except (ValueError, TypeError):
return match.group(0)
return point_pattern.sub(reverse_match, text)
def calculate_point_l1_distance(gt_text: str, pred_text: str) -> Optional[float]:
"""Average L1 distance between 2D points extracted from ``gt_text`` / ``pred_text``.
Returns None if either side has no points or counts differ.
"""
point_pattern = re.compile(r"<point>\[(\d+),\s*(\d+)\]</point>")
gt_matches = point_pattern.findall(gt_text)
pred_matches = point_pattern.findall(pred_text)
if not gt_matches or not pred_matches or len(gt_matches) != len(pred_matches):
return None
total_l1_distance = 0.0
for (gt_x, gt_y), (pred_x, pred_y) in zip(gt_matches, pred_matches):
try:
gt_x, gt_y = int(gt_x), int(gt_y)
pred_x, pred_y = int(pred_x), int(pred_y)
l1_dist = abs(gt_x - pred_x) + abs(gt_y - pred_y)
total_l1_distance += l1_dist
except ValueError:
continue
return total_l1_distance / len(gt_matches) if gt_matches else None
__all__ = [
"process_grounding_points",
"extract_grounding_points",
"reverse_grounding_points",
"calculate_point_l1_distance",
]