优化工作区传播和清空交互
- 手工多边形、矩形和圆在未选语义分类时默认归入 maskid:0 的待分类类别。 - 后端自动传播按来源 annotation/mask/seed key 区分同类多实例,避免多个同类型 mask 传播时互相清理。 - 左侧工具栏在橡皮擦下方新增彩色 AI 自动传播入口,传播权重和范围控件只在进入传播后显示。 - 移除顶栏重复的清空片段遮罩入口,并取消当前清空/DEL 弹窗中的按帧范围清空路径。 - Canvas 右下角显示当前帧:XX/XXX,并调整布尔操作浮层位置避免重叠。 - 更新前端和后端回归测试,覆盖待分类默认、工具栏自动传播和同类多实例传播。 - 同步 AGENTS 与 doc 文档,说明新的工具栏、清空和传播行为。
This commit is contained in:
@@ -742,6 +742,87 @@ def test_propagation_task_runner_replaces_legacy_or_different_weight_results(cli
|
||||
assert annotations[0].mask_data["polygons"] == [output_polygon]
|
||||
|
||||
|
||||
def test_propagation_task_runner_keeps_same_class_seeds_separate(client, db_session, monkeypatch):
|
||||
project = client.post("/api/projects", json={"name": "Propagation Multi Instance"}).json()
|
||||
frames = [
|
||||
client.post(f"/api/projects/{project['id']}/frames", json={
|
||||
"project_id": project["id"],
|
||||
"frame_index": idx,
|
||||
"image_url": f"frames/{idx}.jpg",
|
||||
"width": 640,
|
||||
"height": 360,
|
||||
}).json()
|
||||
for idx in range(2)
|
||||
]
|
||||
|
||||
output_by_source = {
|
||||
7: [[0.10, 0.10], [0.20, 0.10], [0.20, 0.20]],
|
||||
8: [[0.70, 0.70], [0.80, 0.70], [0.80, 0.80]],
|
||||
}
|
||||
task = ProcessingTask(
|
||||
task_type="propagate_masks",
|
||||
status="queued",
|
||||
progress=0,
|
||||
project_id=project["id"],
|
||||
payload={
|
||||
"project_id": project["id"],
|
||||
"frame_id": frames[0]["id"],
|
||||
"model": "sam2.1_hiera_tiny",
|
||||
"include_source": False,
|
||||
"save_annotations": True,
|
||||
"steps": [
|
||||
{
|
||||
"direction": "forward",
|
||||
"max_frames": 2,
|
||||
"seed": {
|
||||
"polygons": [[[0.05, 0.05], [0.15, 0.05], [0.15, 0.15]]],
|
||||
"label": "胆囊",
|
||||
"color": "#ff0000",
|
||||
"source_annotation_id": 7,
|
||||
"source_mask_id": "annotation-7",
|
||||
"class_metadata": {"id": "gallbladder", "name": "胆囊"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"direction": "forward",
|
||||
"max_frames": 2,
|
||||
"seed": {
|
||||
"polygons": [[[0.65, 0.65], [0.75, 0.65], [0.75, 0.75]]],
|
||||
"label": "胆囊",
|
||||
"color": "#ff0000",
|
||||
"source_annotation_id": 8,
|
||||
"source_mask_id": "annotation-8",
|
||||
"class_metadata": {"id": "gallbladder", "name": "胆囊"},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
db_session.add(task)
|
||||
db_session.commit()
|
||||
db_session.refresh(task)
|
||||
|
||||
monkeypatch.setattr("services.propagation_task_runner.download_file", lambda object_name: b"jpeg")
|
||||
monkeypatch.setattr("services.propagation_task_runner.publish_task_progress_event", lambda event_task: None)
|
||||
|
||||
def fake_propagate_video(model, frame_paths, source_frame_index, seed, direction, max_frames):
|
||||
output_polygon = output_by_source[seed["source_annotation_id"]]
|
||||
return [
|
||||
{"frame_index": 0, "polygons": [seed["polygons"][0]], "scores": [0.9]},
|
||||
{"frame_index": 1, "polygons": [output_polygon], "scores": [0.8]},
|
||||
]
|
||||
|
||||
monkeypatch.setattr("services.propagation_task_runner.sam_registry.propagate_video", fake_propagate_video)
|
||||
|
||||
result = run_propagate_project_task(db_session, task.id)
|
||||
|
||||
assert result["created_annotation_count"] == 2
|
||||
assert result["deleted_annotation_count"] == 0
|
||||
annotations = db_session.query(Annotation).filter(Annotation.project_id == project["id"]).order_by(Annotation.id).all()
|
||||
assert [annotation.mask_data["source_annotation_id"] for annotation in annotations] == [7, 8]
|
||||
assert [annotation.mask_data["polygons"][0] for annotation in annotations] == [output_by_source[7], output_by_source[8]]
|
||||
|
||||
|
||||
def test_propagation_task_runner_replaces_downstream_result_from_middle_frame_manual_seed(client, db_session, monkeypatch):
|
||||
project = client.post("/api/projects", json={"name": "Propagation Middle Frame Replacement"}).json()
|
||||
frames = [
|
||||
|
||||
Reference in New Issue
Block a user