"""Unified privileged sample field parsing (G3 adapter interface).""" from __future__ import annotations import json from typing import Any, Optional def normalize_evidence_bbox(bbox: Any) -> Optional[list[float]]: """Validate and normalize evidence_bbox to C2 normalized [0,1] coordinates.""" if bbox is None: return None if isinstance(bbox, str): try: bbox = json.loads(bbox) except json.JSONDecodeError: return None if not isinstance(bbox, (list, tuple)) or len(bbox) != 4: return None try: coords = [float(v) for v in bbox] except (TypeError, ValueError): return None if any(c < 0.0 or c > 1.0 for c in coords): return None x0, y0, x1, y1 = coords if x1 <= x0 or y1 <= y0: return None return [x0, y0, x1, y1] def parse_visual_fact(raw: Any) -> str: """B1: serialize visual_fact as raw JSON string for teacher suffix.""" if raw is None: return "" if isinstance(raw, str): return raw.strip() return json.dumps(raw, ensure_ascii=False) def _position_to_bbox_norm(position: str) -> list[float]: """Map A-OKVQA object position label to normalized crop box (D2).""" pos = (position or "center").strip().lower() mapping = { "center": (0.25, 0.25, 0.75, 0.75), "top": (0.1, 0.0, 0.9, 0.5), "bottom": (0.1, 0.5, 0.9, 1.0), "left": (0.0, 0.1, 0.5, 0.9), "right": (0.5, 0.1, 1.0, 0.9), "middle": (0.25, 0.25, 0.75, 0.75), } return list(mapping.get(pos, mapping["center"])) def heuristic_bbox_from_visual_fact(raw: Any) -> Optional[list[float]]: """D2: derive normalized bbox from visual_fact.objects[].position.""" if raw is None: return None data = raw if isinstance(raw, str): raw = raw.strip() if not raw: return None try: data = json.loads(raw) except json.JSONDecodeError: return None if isinstance(data, dict): objects = data.get("objects") if isinstance(objects, list) and objects: first = objects[0] if isinstance(first, dict): position = first.get("position", "center") return _position_to_bbox_norm(str(position)) return None def resolve_crop_bbox( sample: dict[str, Any], crop_cfg: Optional[dict[str, Any]] = None, ) -> tuple[Optional[list[float]], str]: """ Resolve crop bbox and strategy for a sample. Returns (bbox_norm_or_none, crop_strategy). """ crop_cfg = crop_cfg or {} for key in ("evidence_bbox", "bbox"): bbox = normalize_evidence_bbox(sample.get(key)) if bbox is not None: return bbox, "bbox" if crop_cfg.get("crop_strategy") in ("bbox_then_center", "heuristic"): try: vf = sample.get("visual_fact") or sample.get("visual_facts") bbox = heuristic_bbox_from_visual_fact(vf) if bbox is not None: return bbox, "heuristic" except Exception: pass return None, "center"