File size: 4,967 Bytes
aa975a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# parser.py
# -*- coding: utf-8 -*-
"""
Step5 解析器(稳健版)
- 兼容中文全角/半角、圆括号/方括号、中文/英文逗号
- 抽取:增强后的指令 + 逐帧 bbox(与采样 t 序列对齐)
- 校验:0<=x1<x2<=1, 0<=y1<y2<=1;不合法则记为 None
"""

import re
from typing import Dict, List, Tuple

CN2NUM = {
    "一": 1, "二": 2, "三": 3, "四": 4, "五": 5,
    "六": 6, "七": 7, "八": 8, "九": 9, "十": 10
}

# 全角到半角的基础归一化(冒号/逗号/括号等)
FULL2HALF_MAP = str.maketrans({
    "(": "(", ")": ")", "【": "[", "】": "]", "[": "[", "]": "]",
    ",": ",", ":": ":", ";": ";", "。": ".", "、": ",", "|": "|",
})

def _normalize_text(s: str) -> 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<inst>.+?)\}\s*[,,]", step5_line)
    if m:
        return m.group("inst").strip()
    # 再尝试「无花括号」版本:取到 “任务一boxing序列:”/“任务1boxing序列:” 之前
    m = re.search(
        r"修改后的编辑指令[::]\s*(?P<inst>.+?)\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