fix: 避免自动传播重复叠加同源 mask
Bugfix:自动传播 worker 改为在本次目标帧段内按 seed 来源、方向、权重和签名查找旧传播结果;未修改且目标帧已覆盖时直接跳过,不再重复跑 SAM 造成 mask 堆叠。 Bugfix:同一 seed 被编辑、目标帧段只部分覆盖或切换 SAM 2.1 权重时,worker 会先删除本次目标帧段内同源旧自动传播标注,再重新传播。 Bugfix:未编辑的自动传播结果再次作为参考 seed 时会继承原始 propagation_seed_signature;编辑后的传播结果只保留 source_annotation_id/source_mask_id lineage,不继承旧签名,从而触发重传路径。 Bugfix:后端传播签名增加 canonical rounding,减少浮点精度细微变化导致未编辑 mask 被误判为已修改。 功能调整:清空片段遮罩改成与自动传播一致的时间轴范围选择流程,首次点击进入选区,拖拽选择起止帧后点击确认清空才执行。 接口契约:PropagationSeed 增加 propagation_seed_signature 字段,用于前端把未编辑传播结果绑定回原始 seed 传播链。 测试:补充前端 VideoWorkspace 范围清空、传播 lineage 传递测试;补充后端未编辑传播 seed 跳过重复传播、旧结果清理与换权重重传测试。 文档:同步更新 doc/03、doc/04、doc/07、doc/08、doc/09,明确 A/B 传播去重规则、清空片段范围选择和新增 seed signature 契约。
This commit is contained in:
@@ -2,7 +2,7 @@ import numpy as np
|
||||
import cv2
|
||||
from pathlib import Path
|
||||
from models import Annotation, ProcessingTask
|
||||
from services.propagation_task_runner import run_propagate_project_task
|
||||
from services.propagation_task_runner import _seed_signature, run_propagate_project_task
|
||||
|
||||
|
||||
def _create_project_and_frame(client):
|
||||
@@ -614,6 +614,94 @@ 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_skips_unmodified_propagated_seed_on_overlapping_frames(client, db_session, monkeypatch):
|
||||
project = client.post("/api/projects", json={"name": "Propagation Overlap Skip"}).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(3)
|
||||
]
|
||||
|
||||
original_seed_polygon = [[0.1, 0.1], [0.2, 0.1], [0.2, 0.2]]
|
||||
propagated_seed_polygon = [[0.14, 0.14], [0.24, 0.14], [0.24, 0.24]]
|
||||
downstream_polygon = [[0.18, 0.18], [0.28, 0.18], [0.28, 0.28]]
|
||||
inherited_signature = _seed_signature({
|
||||
"polygons": [original_seed_polygon],
|
||||
"label": "胆囊",
|
||||
"color": "#ff0000",
|
||||
"source_annotation_id": 7,
|
||||
"source_mask_id": "annotation-7",
|
||||
})
|
||||
|
||||
db_session.add(Annotation(
|
||||
project_id=project["id"],
|
||||
frame_id=frames[2]["id"],
|
||||
mask_data={
|
||||
"polygons": [downstream_polygon],
|
||||
"label": "胆囊",
|
||||
"color": "#ff0000",
|
||||
"source": "sam2.1_hiera_tiny_propagation",
|
||||
"propagated_from_frame_id": frames[0]["id"],
|
||||
"propagation_seed_key": "annotation:7",
|
||||
"propagation_seed_signature": inherited_signature,
|
||||
"propagation_direction": "forward",
|
||||
"source_annotation_id": 7,
|
||||
"source_mask_id": "annotation-7",
|
||||
},
|
||||
bbox=[0.18, 0.18, 0.1, 0.1],
|
||||
))
|
||||
db_session.commit()
|
||||
|
||||
task = ProcessingTask(
|
||||
task_type="propagate_masks",
|
||||
status="queued",
|
||||
progress=0,
|
||||
project_id=project["id"],
|
||||
payload={
|
||||
"project_id": project["id"],
|
||||
"frame_id": frames[1]["id"],
|
||||
"model": "sam2.1_hiera_tiny",
|
||||
"include_source": False,
|
||||
"save_annotations": True,
|
||||
"steps": [{
|
||||
"direction": "forward",
|
||||
"max_frames": 2,
|
||||
"seed": {
|
||||
"polygons": [propagated_seed_polygon],
|
||||
"label": "胆囊",
|
||||
"color": "#ff0000",
|
||||
"source_annotation_id": 7,
|
||||
"source_mask_id": "annotation-7",
|
||||
"propagation_seed_signature": inherited_signature,
|
||||
},
|
||||
}],
|
||||
},
|
||||
)
|
||||
db_session.add(task)
|
||||
db_session.commit()
|
||||
db_session.refresh(task)
|
||||
|
||||
propagate_calls = []
|
||||
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)
|
||||
monkeypatch.setattr("services.propagation_task_runner.sam_registry.propagate_video", lambda *args, **kwargs: propagate_calls.append(args) or [])
|
||||
|
||||
result = run_propagate_project_task(db_session, task.id)
|
||||
|
||||
assert result["created_annotation_count"] == 0
|
||||
assert result["deleted_annotation_count"] == 0
|
||||
assert result["skipped_seed_count"] == 1
|
||||
assert propagate_calls == []
|
||||
annotations = db_session.query(Annotation).filter(Annotation.project_id == project["id"]).all()
|
||||
assert len(annotations) == 1
|
||||
assert annotations[0].mask_data["polygons"] == [downstream_polygon]
|
||||
|
||||
|
||||
def test_predict_validation_errors(client, monkeypatch):
|
||||
project, _, _ = _create_project_and_frame(client)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user