fix(detector): 移除 ongoing_actions/visible_entities 空字段检测 + 新增 repair sh
抽检确认这两个字段为空是合法内容状态(静物/黑帧/模糊帧), VLM 重修也修不好,保留会导致断点续跑死循环。 L3 空字段检测缩减为 frame_summary + spatial_layout。
This commit is contained in:
@@ -40,7 +40,8 @@ def detect_issues(
|
||||
"""扫描树,返回所有问题节点列表。
|
||||
|
||||
检查项:
|
||||
- L3: card 必填字段为空(frame_summary / visible_entities / ongoing_actions / spatial_layout)
|
||||
- L3: card 必填字段为空(frame_summary / spatial_layout)
|
||||
- 注: visible_entities / ongoing_actions 为空是合法状态(静物/黑帧),不纳入检测
|
||||
- L3: frame_path 对应文件不存在(需提供 frames_dir)
|
||||
- L2: event_description 为空
|
||||
- L2/L1: children 列表为空
|
||||
@@ -108,14 +109,10 @@ def detect_issues(
|
||||
continue
|
||||
|
||||
for l3 in l2.children:
|
||||
# L3: 各必填字段不为空
|
||||
# L3: 核心必填字段不为空(visible_entities/ongoing_actions 为空是合法状态)
|
||||
empty_fields: list[str] = []
|
||||
if not l3.card.frame_summary:
|
||||
empty_fields.append("frame_summary")
|
||||
if not l3.card.visible_entities:
|
||||
empty_fields.append("visible_entities")
|
||||
if not l3.card.ongoing_actions:
|
||||
empty_fields.append("ongoing_actions")
|
||||
if not l3.card.spatial_layout:
|
||||
empty_fields.append("spatial_layout")
|
||||
if empty_fields:
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# 树修复管线:检测 + VLM 重生成 + 校验 + Q&A 补全
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/repair_trees.sh # 默认 16 并发续跑
|
||||
# bash scripts/repair_trees.sh --dry-run # 仅检测不修复
|
||||
# bash scripts/repair_trees.sh --reaggregate-all # 强制全量重聚合
|
||||
# CONCURRENCY=8 bash scripts/repair_trees.sh # 自定义并发数
|
||||
#
|
||||
# 日志输出:
|
||||
# stderr → 终端实时显示
|
||||
# logs/repair_trees.log → 完整日志(自动 rotation 50MB)
|
||||
# logs/repair_telemetry.db → LLM/VLM 调用遥测
|
||||
# logs/repair_progress.json → 断点续跑进度
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONCURRENCY="${CONCURRENCY:-16}"
|
||||
|
||||
conda activate Video-Tree-TRM
|
||||
|
||||
python tools/repair_trees.py \
|
||||
--videos-dir store/videos \
|
||||
--srt-dir data/Video-MME/subtitle \
|
||||
--questions-dir store/questions/benchmarks/Video-MME \
|
||||
--concurrency "$CONCURRENCY" \
|
||||
"$@"
|
||||
@@ -119,25 +119,25 @@ class TestDetectIssues:
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "no_children" and i.level == 1 for i in issues)
|
||||
|
||||
def test_empty_visible_entities(self) -> None:
|
||||
"""visible_entities 为空也触发 empty_field。"""
|
||||
def test_empty_visible_entities_not_flagged(self) -> None:
|
||||
"""visible_entities 为空是合法状态(静物/黑帧),不触发 empty_field。"""
|
||||
card = L3Card("正常描述", [], ["动作"], [], "居中", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and "visible_entities" in i.details for i in issues)
|
||||
assert not any(i.issue_type == "empty_field" for i in issues)
|
||||
|
||||
def test_empty_ongoing_actions(self) -> None:
|
||||
"""ongoing_actions 为空也触发 empty_field。"""
|
||||
def test_empty_ongoing_actions_not_flagged(self) -> None:
|
||||
"""ongoing_actions 为空是合法状态(静物/黑帧),不触发 empty_field。"""
|
||||
card = L3Card("正常描述", ["实体"], [], [], "居中", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
|
||||
index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
|
||||
issues = detect_issues(index)
|
||||
assert any(i.issue_type == "empty_field" and "ongoing_actions" in i.details for i in issues)
|
||||
assert not any(i.issue_type == "empty_field" for i in issues)
|
||||
|
||||
def test_empty_spatial_layout(self) -> None:
|
||||
"""spatial_layout 为空也触发 empty_field。"""
|
||||
@@ -150,7 +150,7 @@ class TestDetectIssues:
|
||||
assert any(i.issue_type == "empty_field" and "spatial_layout" in i.details for i in issues)
|
||||
|
||||
def test_multiple_empty_fields_single_issue(self) -> None:
|
||||
"""多个字段同时为空只产生一个 issue,details 列出所有空字段。"""
|
||||
"""多个必填字段同时为空只产生一个 issue,details 列出所有空字段。"""
|
||||
card = L3Card("", [], [], [], "", {})
|
||||
l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
|
||||
l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
|
||||
@@ -159,7 +159,7 @@ class TestDetectIssues:
|
||||
issues = [i for i in detect_issues(index) if i.issue_type == "empty_field"]
|
||||
assert len(issues) == 1
|
||||
assert "frame_summary" in issues[0].details
|
||||
assert "visible_entities" in issues[0].details
|
||||
assert "spatial_layout" in issues[0].details
|
||||
|
||||
def test_time_gap(self) -> None:
|
||||
l3_a = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(), timestamp=1.0)
|
||||
|
||||
Reference in New Issue
Block a user