feat: 完善 mask 编辑、传播平滑与开发重启闭环
功能增加: - 新增后端 /api/ai/smooth-mask 接口,对当前 mask polygon 执行 Chaikin 边缘平滑,并返回 polygon、bbox、area 与拓扑锚点。 - 在右侧实例属性面板加入边缘平滑强度和应用边缘平滑操作,应用后将 mask 标记为 draft/dirty,并通过正常保存链路落库。 - 保存标注与传播 seed 时保留 geometry_smoothing 元数据,自动传播 forward/backward 结果保存前应用同一平滑参数。 - 自动传播 seed signature 纳入平滑参数,修改平滑强度后会触发旧同源传播结果清理并重新传播。 - 支持跨帧跟随同一传播链 mask,AI 推送回工作区时保留当前帧视角。 Bugfix: - 修复中间帧向前传播时旧 forward/backward 同物体结果未被清理导致双重 mask 的问题。 - 修复 propagation worker 写入目标帧前只按旧方向清理导致 backward 重传残留的问题。 - 修复多边形顶点拖拽和编辑后画布视口异常移动的问题,并补充拖拽状态回写。 - 修复实例属性标题跟随全局 active class 而不是当前 mask label 的问题,并移除后端模型置信度展示。 开发与部署: - 新增 restart_dev_services.sh,使用 setsid 独立后台重启 FastAPI、Celery 和前端,写入 pid/log 文件并做 3000/8000 健康检查。 - 明确后端或 Celery 相关改动完成后需要运行重启脚本,保证运行态加载最新代码。 测试与文档: - 补充后端 smooth-mask、传播平滑 metadata、seed signature、传播去重方向覆盖等测试。 - 补充前端 OntologyInspector、VideoWorkspace、CanvasArea 和 api 契约测试,覆盖边缘平滑、传播参数、跨帧选区跟随和画布编辑行为。 - 更新 README、AGENTS、安装文档、前端元素审计、需求冻结、设计冻结和测试计划,记录当前真实行为与重启要求。
This commit is contained in:
@@ -381,6 +381,91 @@ describe('CanvasArea', () => {
|
||||
.filter((element) => element.getAttribute('data-fill') === '#ffffff')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('selects the linked propagated mask when switching from the seed frame', async () => {
|
||||
const propagatedFrame = { ...frame, id: 'frame-2', index: 1, url: '/frame-2.jpg' };
|
||||
useStore.setState({
|
||||
selectedMaskIds: ['annotation-7'],
|
||||
masks: [
|
||||
{
|
||||
id: 'annotation-7',
|
||||
annotationId: '7',
|
||||
frameId: 'frame-1',
|
||||
pathData: 'M 0 0 L 10 0 L 10 10 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[0, 0, 10, 0, 10, 10]],
|
||||
},
|
||||
{
|
||||
id: 'annotation-20',
|
||||
annotationId: '20',
|
||||
frameId: 'frame-2',
|
||||
pathData: 'M 1 1 L 11 1 L 11 11 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[1, 1, 11, 1, 11, 11]],
|
||||
metadata: {
|
||||
source: 'sam2.1_hiera_tiny_propagation',
|
||||
source_annotation_id: 7,
|
||||
source_mask_id: 'annotation-7',
|
||||
propagation_seed_key: 'annotation:7',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { rerender } = render(<CanvasArea activeTool="edit_polygon" frame={frame} />);
|
||||
rerender(<CanvasArea activeTool="edit_polygon" frame={propagatedFrame} />);
|
||||
|
||||
await waitFor(() => expect(useStore.getState().selectedMaskIds).toEqual(['annotation-20']));
|
||||
expect(screen.getByText('当前图层: 胆囊 #20')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps following the same propagation chain between propagated frames', async () => {
|
||||
const propagatedFrame = { ...frame, id: 'frame-2', index: 1, url: '/frame-2.jpg' };
|
||||
const laterPropagatedFrame = { ...frame, id: 'frame-3', index: 2, url: '/frame-3.jpg' };
|
||||
useStore.setState({
|
||||
selectedMaskIds: ['annotation-20'],
|
||||
masks: [
|
||||
{
|
||||
id: 'annotation-20',
|
||||
annotationId: '20',
|
||||
frameId: 'frame-2',
|
||||
pathData: 'M 1 1 L 11 1 L 11 11 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[1, 1, 11, 1, 11, 11]],
|
||||
metadata: {
|
||||
source: 'sam2.1_hiera_tiny_propagation',
|
||||
source_annotation_id: 7,
|
||||
source_mask_id: 'annotation-7',
|
||||
propagation_seed_key: 'annotation:7',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'annotation-21',
|
||||
annotationId: '21',
|
||||
frameId: 'frame-3',
|
||||
pathData: 'M 2 2 L 12 2 L 12 12 Z',
|
||||
label: '胆囊',
|
||||
color: '#facc15',
|
||||
segmentation: [[2, 2, 12, 2, 12, 12]],
|
||||
metadata: {
|
||||
source: 'sam2.1_hiera_tiny_propagation',
|
||||
source_annotation_id: 7,
|
||||
source_mask_id: 'annotation-7',
|
||||
propagation_seed_key: 'annotation:7',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { rerender } = render(<CanvasArea activeTool="edit_polygon" frame={propagatedFrame} />);
|
||||
rerender(<CanvasArea activeTool="edit_polygon" frame={laterPropagatedFrame} />);
|
||||
|
||||
await waitFor(() => expect(useStore.getState().selectedMaskIds).toEqual(['annotation-21']));
|
||||
expect(screen.getByText('当前图层: 胆囊 #21')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders imported GT seed points for editable point regions', () => {
|
||||
useStore.setState({
|
||||
masks: [
|
||||
|
||||
Reference in New Issue
Block a user