# parser.py # -*- coding: utf-8 -*- """ Step5 解析器(稳健版) - 兼容中文全角/半角、圆括号/方括号、中文/英文逗号 - 抽取:增强后的指令 + 逐帧 bbox(与采样 t 序列对齐) - 校验:0<=x1 str: s = (s or "").strip().replace("\u00A0", " ").replace("\t", " ") # 去多空格 s = re.sub(r"[ ]{2,}", " ", s) return s.translate(FULL2HALF_MAP) def _extract_enhanced_instruction(step5_line: str) -> str: """ 支持两种写法: 1) 修改后的编辑指令:{ ... },任务一boxing序列: 2) 修改后的编辑指令:...,任务一boxing序列: 同时兼容中文/英文逗号,冒号,花括号可选 """ # 先尝试「有花括号」版本 m = re.search(r"修改后的编辑指令[::]\s*\{(?P.+?)\}\s*[,,]", step5_line) if m: return m.group("inst").strip() # 再尝试「无花括号」版本:取到 “任务一boxing序列:”/“任务1boxing序列:” 之前 m = re.search( r"修改后的编辑指令[::]\s*(?P.+?)\s*[,,]\s*任务\s*[一二三四五六七八九十\d]+\s*boxing序列[::]", step5_line ) return m.group("inst").strip() if m else "" def _task_key_to_idx(tok: str) -> int: # "任务一"/"任务1" m = re.search(r"任务\s*([一二三四五六七八九十\d]+)", tok) if not m: return 1 key = m.group(1) return CN2NUM.get(key, int(key) if key.isdigit() else 1) def parse_step5(step5_line: str, t_list: List[int]) -> Tuple[str, Dict[str, List[Dict]]]: """ 返回 (enhanced_instruction, tasks_json) tasks_json 结构: { "任务一": [{"t": t0, "bbox":[x1,y1,x2,y2] or None}, ...], ... } """ s = _normalize_text(step5_line) enhanced = _extract_enhanced_instruction(s) # 按任务切分:“…任务一boxing序列:[...],任务二boxing序列:[...]…” # ⚠️ 兼容半角/全角冒号(归一化后为半角 ":",但正则也允许全角) parts = re.split(r"(任务[一二三四五六七八九十\d]+\s*boxing序列[::])", s) tasks_json: Dict[str, List[Dict]] = {} def parse_seq(seq_txt: str) -> List[Dict]: """ 解析形如: [(t=0,[(0.64,0.68,0.71,0.78)]);(t=1,[(0.64,0.68,0.71,0.78)])] 允许 () 或 [] 包围 bbox,允许中文逗号/空格。 """ seq_txt = (seq_txt or "").strip() # 拆帧项,分隔符 ; 或 ;(也容忍多余空格) items = re.split(r"[;;]+", seq_txt.strip("[] ")) out = [] for it in items: it = it.strip() if not it: continue # 抓 t 值 mt = re.search(r"t\s*=\s*(\d+)", it) if not mt: continue t = int(mt.group(1)) # 抓 bbox:先定位一对括号内容,兼容 [] 或 () mb = re.search(r"\[\s*([\d\.\s,,]+)\s*\]|\(\s*([\d\.\s,,]+)\s*\)", it) if not mb: out.append({"t": t, "bbox": None}) continue nums_str = (mb.group(1) or mb.group(2) or "").replace(",", ",") nums = [x for x in re.split(r"\s*,\s*", nums_str) if x] if len(nums) != 4: out.append({"t": t, "bbox": None}) continue try: x1, y1, x2, y2 = map(float, nums) except Exception: out.append({"t": t, "bbox": None}) continue ok = (0.0 <= x1 < x2 <= 1.0) and (0.0 <= y1 < y2 <= 1.0) out.append({"t": t, "bbox": [x1, y1, x2, y2]} if ok else {"t": t, "bbox": None}) return out # 从第二个元素开始,成对读取:"任务Xboxing序列:" + 下一段(括号体) for i in range(1, len(parts), 2): head = parts[i] body = parts[i + 1] if i + 1 < len(parts) else "" idx = _task_key_to_idx(head) key = f"任务{idx}" tasks_json[key] = parse_seq(body) # 与 t_list 对齐(以采样顺序为准),缺失帧用 bbox=None 占位 def align(seq: List[Dict]) -> List[Dict]: by_t = {e["t"]: e for e in seq} aligned = [] for t in t_list: e = by_t.get(t) aligned.append(e if e else {"t": t, "bbox": None}) return aligned tasks_json = {k: align(v) for k, v in tasks_json.items()} return enhanced, tasks_json