统一工作区清空遮罩入口

- 移除 Canvas 右下角旧清空遮罩和应用分类按钮,清空入口统一到左侧工具栏

- 清空遮罩优先作用于当前帧选中 mask,无选中时作用于当前帧全部 mask

- 目标 mask 无传播链结果时直接清当前帧,有传播链结果时弹窗选择只清当前帧、清空传播所有帧或取消

- 保留布尔工具右下角合并/去除操作区,避免旧分类按钮误改整帧

- 更新 Canvas、工具栏、工作区测试,覆盖直接清空、传播链范围选择和取消路径

- 同步更新前端审计、需求冻结、设计冻结、测试计划和 AGENTS 说明
This commit is contained in:
2026-05-03 21:06:03 +08:00
parent 3e998b9d6b
commit a22af5f7c8
11 changed files with 263 additions and 169 deletions

View File

@@ -244,7 +244,7 @@ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
5. 生成帧入队:用户在项目库点击“生成帧”,选择目标 FPS 后前端调用 `/api/media/parse`;后端创建 `ProcessingTask` 并投递 Celery接口支持 `parse_fps``max_frames``target_width` 标准帧序列参数;项目库和模板库的成功/失败短反馈使用非阻塞 `TransientNotice`,会自动消失。
6. worker 执行Celery worker 用 FFmpeg 优先拆视频帧,失败后用 OpenCV fallbackDICOM 使用 pydicomworker 下载和读取 DICOM 时也按文件名自然顺序排序;视频/DICOM 解析完成后都按 `frame_%06d.jpg` 连续生成项目帧序列,并记录 `timestamp_ms``source_frame_number` 和任务 `frame_sequence` 元数据后续工作区、时间轴、AI 传播、标注和导出共用同一套帧序列逻辑。
7. 帧展示:`VideoWorkspace.tsx` 调用 `/api/projects/{id}/frames``CanvasArea.tsx``FrameTimeline.tsx` 显示当前帧与时间轴缩略图;`CanvasArea` 会按容器和帧尺寸默认居中放大底图并保留边距;`FrameTimeline` 会根据已保存标注回显到 `Mask.metadata` 的传播来源,把自动传播生成的帧在视频处理进度条显示为蓝色区段,人工/AI 标注帧显示红色竖线;每次自动传播成功处理帧后,`VideoWorkspace` 会把本次传播范围作为当前会话历史片段传给 `FrameTimeline`,在视频处理进度条上叠加同一蓝色系、最新传播最亮、旧传播逐次变暗且第 5 次及更早统一为阈值旧记录色的纯色条;传播历史条只显示当前仍有自动传播 mask 的帧,删除 mask 或清空范围后会按剩余传播 mask 自动裁剪,空帧不保留红/蓝颜色;视频处理进度条和红/蓝标识可点击跳转到对应帧;底部缩略图中人工/AI 标注帧用红色边框、自动传播/推理帧用蓝色边框,同一帧同时具备两种状态时红色标注边框优先保留,蓝色传播状态以内描边表达;当前帧仍以青色外框高亮优先;若当前帧同时是人工/AI 标注帧,则在青色外框内增加红色内描边,固定为外层当前帧、内层人工/AI 标注;进入自动传播、清空遮罩或特定范围帧导出选择模式时,播放进度条和视频处理进度条会显示黄色范围框,并可点击/拖拽选择起止帧;前端 `Frame` 会保留后端返回的帧序列时间戳和源帧号。
8. 手工标注:`CanvasArea.tsx` 支持多边形、矩形、圆、画笔和橡皮擦生成/编辑 polygon mask多边形可按 Enter 或点击首节点闭合;画笔/橡皮擦可在左侧工具栏调整大小,画笔要求右侧语义分类树已有选中类别,画出的圆形连续笔触会在鼠标松开时一次性 union 成 mask若与当前选中 mask 连通则自动合并到该 mask橡皮擦要求已选中 mask 并在松开时从该 mask 中 difference 扣除;普通 mask 和导入 mask 都不显示黄色 seed point也不提供 seed point 拖动;未选中特定 mask 时Canvas 会按右侧语义分类树拖拽得到的内部覆盖优先级从低到高渲染 mask使高优先级类别显示在上层Canvas 左上角工具上下文提示会在切换工具或操作状态变化时短暂显示,数秒后自动隐藏,避免长期遮挡底图;工具栏有“调整多边形”入口,左侧 `ToolsPalette` 使用紧凑垂直布局并在高度不足时自身滚动,且在“重叠区域去除”之后提供“清空遮罩(含传播帧)”、紫色“导入 GT Mask”和 AI 智能分割入口;工作区左侧工具栏不展示 AI 页的正向选点、反向选点和边界框选,也不重复放置撤销/重做;点击 mask 后可按住顶点直接拖动并实时更新 polygon顶点拖拽结束不会触发 Stage 平移或重置 Canvas 视口;也可删除 polygon 顶点、通过边中点或双击边界插入新顶点,并能选择编辑多 polygon mask 的单个子区域;带中空洞的 mask 会用 `metadata.polygonRingCounts` 记录外圈与内圈的 ring 分组,调整多边形时外圈和内洞都显示可编辑顶点和插点手柄,保存时把内洞拆到 `mask_data.holes`;选中整块 mask 可用 Delete/Backspace 删除,已保存 mask 会同步后端删除;删除传播 seed 或任一传播结果时会扩展删除同一传播链上的自动传播 mask但保留其他帧独立 AI 推理或人工标注 mask区域合并/去除会隐藏编辑手柄并显示已选数量,第一个选中的主区域用黄色实线轮廓,后续参与合并/扣除的区域用红色虚线轮廓,使用 `polygon-clipping` 做 union/difference并通过 `source_annotation_id``source_mask_id``propagation_seed_key` 把同一布尔操作同步到其它传播帧的对应主区域和参与区域;同步后的传播 mask 保留原 `source`/lineage metadata只进入 dirty 状态等待保存,不会在时间轴上变成人工/AI 标注帧;内含去除结果用 even-odd 规则渲染 holeZustand 维护 `maskHistory/maskFuture` 支持撤销/重做。
8. 手工标注:`CanvasArea.tsx` 支持多边形、矩形、圆、画笔和橡皮擦生成/编辑 polygon mask多边形可按 Enter 或点击首节点闭合;画笔/橡皮擦可在左侧工具栏调整大小,画笔要求右侧语义分类树已有选中类别,画出的圆形连续笔触会在鼠标松开时一次性 union 成 mask若与当前选中 mask 连通则自动合并到该 mask橡皮擦要求已选中 mask 并在松开时从该 mask 中 difference 扣除;普通 mask 和导入 mask 都不显示黄色 seed point也不提供 seed point 拖动;未选中特定 mask 时Canvas 会按右侧语义分类树拖拽得到的内部覆盖优先级从低到高渲染 mask使高优先级类别显示在上层Canvas 左上角工具上下文提示会在切换工具或操作状态变化时短暂显示,数秒后自动隐藏,避免长期遮挡底图;工具栏有“调整多边形”入口,左侧 `ToolsPalette` 使用紧凑垂直布局并在高度不足时自身滚动,且在“重叠区域去除”之后提供唯一的“清空遮罩”入口、紫色“导入 GT Mask”和 AI 智能分割入口;清空遮罩优先作用于当前帧选中 mask没有选中时作用于当前帧全部 mask无传播链结果时直接清当前帧存在传播链结果时弹窗选择只清当前帧、清空传播所有帧或取消Canvas 右下角不再提供旧的“清空遮罩”或“应用分类”按钮,分类改由右侧语义分类树点击完成;工作区左侧工具栏不展示 AI 页的正向选点、反向选点和边界框选,也不重复放置撤销/重做;点击 mask 后可按住顶点直接拖动并实时更新 polygon顶点拖拽结束不会触发 Stage 平移或重置 Canvas 视口;也可删除 polygon 顶点、通过边中点或双击边界插入新顶点,并能选择编辑多 polygon mask 的单个子区域;带中空洞的 mask 会用 `metadata.polygonRingCounts` 记录外圈与内圈的 ring 分组,调整多边形时外圈和内洞都显示可编辑顶点和插点手柄,保存时把内洞拆到 `mask_data.holes`;选中整块 mask 可用 Delete/Backspace 删除,已保存 mask 会同步后端删除;删除传播 seed 或任一传播结果时会扩展删除同一传播链上的自动传播 mask但保留其他帧独立 AI 推理或人工标注 mask区域合并/去除会隐藏编辑手柄并显示已选数量,第一个选中的主区域用黄色实线轮廓,后续参与合并/扣除的区域用红色虚线轮廓,使用 `polygon-clipping` 做 union/difference并通过 `source_annotation_id``source_mask_id``propagation_seed_key` 把同一布尔操作同步到其它传播帧的对应主区域和参与区域;同步后的传播 mask 保留原 `source`/lineage metadata只进入 dirty 状态等待保存,不会在时间轴上变成人工/AI 标注帧;内含去除结果用 even-odd 规则渲染 holeZustand 维护 `maskHistory/maskFuture` 支持撤销/重做。
9. AI 分割:侧栏和工作区工具栏的 AI 智能分割入口使用 Bot + Sparkles 组合图标强化 AI 识别;前端工具包括 SAM 2.1 变体选择、正向点、反向点和框选AI 画布会按容器和当前帧尺寸默认居中放大底图并保留边距;工作区和 AI 页面都可点击已有提示点删除单点AI 页面也可删除最近锚点、删除选中候选或清空本页锚点;这些删除入口会限制在当前提示点/本页 AI 候选范围内,避免误删工作区已有 mask。SAM 2.1 框选会建立候选 mask后续正/反点通过 `interactive` prompt 携带原始框和累计点细化同一个候选 maskAI 页面框选会先固化 `promptBox`,执行分割时只框选发送 `box` prompt框选后继续加正/反点发送 `interactive` prompt重复执行高精度分割会替换上一次 AI 页候选,只保留最新一个候选。包含反向点时工作区会传 `options.auto_filter_background=true``min_score=0.05`,如果后端过滤为空则移除旧候选 mask。后端 `ai.py` 期望按 `image_id``prompt_type``prompt_data``model` 和可选 `options` 调用 SAM registry。当前 registry 暴露 `sam2.1_hiera_tiny``sam2.1_hiera_small``sam2.1_hiera_base_plus``sam2.1_hiera_large`,并兼容 `sam2` 作为 tiny 别名;`model=sam3` 会被拒绝,`semantic` 文本提示也被禁用。SAM 2.1 支持点/框/interactive/自动分割和 video predictor 传播多候选默认只采用最高分区域避免重叠候选同时显示AI 页面只渲染本页最新生成的候选 mask不会把工作区已有 mask 带入 AI 画布AI 页面生成的 mask 会写入全局 `masks` 并自动选中,右侧分类树可直接改标签,推送到工作区会切到“调整多边形”并保留选择和当前帧视角。`options.crop_to_prompt` 可对点/框/interactive prompt 做局部裁剪推理并回映射,`options.auto_filter_background` 可按分数和负向点过滤结果。
10. 视频片段传播:工作区以当前打开帧作为参考帧,使用该帧全部 mask 作为 seed并用传播起始帧和传播结束帧指定追踪范围如果当前参考帧没有 mask点击开始传播会提示“当前参考帧无遮罩”不会提交任务或保存其它帧标注用户可直接修改数字框也可点击“自动传播”进入时间轴范围选择模式在播放进度条或视频处理进度条上点击/拖拽选择范围,再点击“开始传播”。工作区顶栏有独立“传播权重”选择器,可为本次传播二次选择 SAM 2.1 tiny/small/base+/large 权重,不提供 SAM2/SAM3 家族切换,也不影响 AI 单帧分割权重;前端提交传播前只保存当前参考帧中的 draft/dirty mask使 seed 优先带稳定的后端 `source_annotation_id`,再按传播权重 id、seed mask、seed 来源 id 和前/后方向组装 `steps` 并调用 `POST /api/ai/propagate/task` 创建 `propagate_masks` 后台任务;中空 seed 会携带和 `polygons` 对齐的 `holes`,后端注入 SAM 2 video predictor 前会先填充外圈再扣除内洞,避免以实心 mask 传播;后端入队时会规范化/校验权重 id 并把规范化后的 id 写入任务 payload/resultCelery worker 顺序执行各 step避免多个视频 tracker 并发抢占 GPU每个 step 会根据 seed 来源 id、方向和包含 `holes` 的 seed 签名做幂等判断,同权重且未改变的 seed 直接跳过,已改变或换用其他权重的 seed 会先删除同源旧自动传播标注再重传;旧版本用前端临时 `source_mask_id` 生成的传播标注会按同一参考帧、方向和语义信息兼容清理;中间帧人工新增/修改同一物体后重新传播时,后端会在写入目标帧新结果前按语义和空间重叠清理旧传播结果,且写入前清理不受旧结果传播方向限制;后端按项目帧序列下载片段帧,当前使用所选 SAM 2.1 权重变体的 `SAM2VideoPredictor.add_new_mask()` + `propagate_in_video()`,并把后续帧结果保存为 `Annotation`,传播结果轮廓用 CCOMP 层级提取并把内洞写入 `mask_data.holes`;若历史或外部 seed 仍带 `geometry_smoothing`forward/backward 两个方向的传播结果保存前仍会应用同一参数;当前工作区平滑按钮应用后会直接改写实际 polygon后续传播以新几何参与签名和追踪。工作区轮询 `GET /api/tasks/{task_id}` 展示进度并刷新标注Dashboard 也能显示/取消/重试传播任务。
11. GT 导入:工作区左侧工具栏“导入 GT Mask”调用 `/api/ai/import-gt-mask`;选择文件后前端会显示导入结果预览,并让用户决定未知 maskid 处理方式,可舍弃未知类别,也可导入为“未定义类别”等待重新命名。后端用 `cv2.IMREAD_UNCHANGED` 读取 mask 并校验 dtypeGT 图片必须是 8-bit 灰度 maskid 图,或 8-bit RGB 三通道完全相同的 `[X,X,X]` maskid 图0 为背景、X 为 1-255 的 maskid16-bit/uint16 GT_label、普通彩色类别图和全背景 0 图都会返回明确错误全背景图错误信息固定为“GT Mask 图片中没有非背景 maskid 区域。”;灰度/RGB 等通道图按模板 `maskId` 匹配类别,超出现有类别时按 `unknown_color_policy` 处理;如果 mask 图片尺寸和当前帧不同,会按当前帧长宽最近邻拉伸后再提取区域;每个连通域用高精度 contour 生成 polygon 标注,保留更多边界点并设置点数上限避免拖慢前端;导入结果与普通 mask 共用拓扑锚点统计、边缘平滑、顶点编辑、分类和保存链路;后端仍可写入 distance transform seed point 供数据兼容,但前端不显示或拖动 seed point。
@@ -262,12 +262,12 @@ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
- 区域合并/去除会重算主 mask 的几何;合并已保存的次级 mask 时会通过工作区回调删除对应后端标注;若主区域和参与区域存在传播链对应 mask同一布尔操作会同步应用到其它传播帧的对应主区域和参与区域保留传播来源 metadata避免时间轴帧属性变色。
- 前端 `importGtMask()` 已对齐后端 `/api/ai/import-gt-mask`;工作区左侧工具栏“导入 GT Mask”会在上传前显示导入结果预览并选择未知 maskid 策略,后端仅支持 8-bit 二值/灰度 maskid 图和 8-bit RGB 三通道完全相同的 `[X,X,X]` maskid 图,不再按彩色 RGB 类别图做颜色匹配,也不接受 16-bit/uint16 GT_label尺寸不同的 mask 会最近邻拉伸到当前帧,导入后回显多类别高精度 polygon 标注,不显示黄色 seed point并能直接使用普通 mask 的拓扑统计、边缘平滑、编辑和保存能力。
- 前端 `exportCoco()` 已对齐后端 `/api/export/{project_id}/coco`;前端 `exportMasks()` 已对齐后端 `/api/export/{project_id}/masks`;前端 `exportSegmentationResults()` 已对齐后端 `/api/export/{project_id}/results`;工作区“分割结果导出”按钮会先保存当前待归档 mask再按所选范围、outputs 和 Mix_label 透明度下载统一 ZIP特定范围帧导出可用帧号输入框或时间轴拖拽选择范围下载文件名按项目库项目名、导出范围首尾时间戳和首尾项目帧序号生成统一 ZIP 包含 maskid/GT 像素值映射 JSON、原始图片文件夹、按帧/类别合并的分开 Mask 文件夹、GT_label 图文件夹、Pro_label 彩色图文件夹和 Mix_label 叠加图文件夹GT_label 固定为 uint8 PNG像素值使用类别真实 `maskid` 并跨图一致。
- 右侧语义分类树和 Canvas “应用分类”都会把分类变更同步到同一传播链前后帧对应 mask识别依据为 `source_annotation_id``source_mask_id``propagation_seed_key`,被同步更新的已保存 mask 会进入 dirty 状态,等待工作区归档保存 PATCH 到后端;保存 dirty mask 时会保留 `source`、传播 seed 和来源 id 等 metadata避免传播帧在时间轴上变成人工/AI 标注帧。
- 右侧语义分类树点击分类会把分类变更同步到同一传播链前后帧对应 mask识别依据为 `source_annotation_id``source_mask_id``propagation_seed_key`,被同步更新的已保存 mask 会进入 dirty 状态,等待工作区归档保存 PATCH 到后端;保存 dirty mask 时会保留 `source`、传播 seed 和来源 id 等 metadata避免传播帧在时间轴上变成人工/AI 标注帧。
- 工作区保存状态按钮会按当前项目待保存数量显示“保存 X 个改动”或“已全部保存”,并已接入 `POST /api/ai/annotate``PATCH /api/ai/annotations/{id}`dirty mask 更新前会预检后端标注 id已知缺失的本地旧 annotationId 直接用 `POST` 重新创建;如果预检后 `PATCH` 仍返回 404前端也会保留同一几何、分类和传播 lineage metadata改用 `POST` 重新创建并在回显时替换本地旧 id避免保存或开始传播被陈旧 annotationId 中断;加载工作区时会通过 `GET /api/ai/annotations` 回显已保存标注。
- 右侧实例属性面板“边缘平滑强度/应用边缘平滑”已接入 `POST /api/ai/smooth-mask`;滑杆会即时更新数值,但后端预览请求有短防抖,避免拖动时连续请求卡顿;预览不写入撤销历史也不标 dirty点击应用后会把返回 polygon 作为新的实际 mask 几何写入当前 mask 和同传播链前后对应 mask整次应用作为一个撤销/重做历史步骤,相关 mask 标记为 dirty/draft平滑强度重置为 0用户可继续用 polygon 编辑工具调整新多边形。
- 工作区“自动传播”按钮已接入 `POST /api/ai/propagate/task`;若用户尚未显式设置范围,第一次点击会进入时间轴范围选择模式,第二次点击“开始传播”才提交后台任务;当前启用所选 SAM 2.1 变体的视频 predictor 后台任务,运行中轮询任务进度,完成后刷新后端已保存标注;中空 seed 会把内洞传给后端SAM 2 seed mask 栅格化时扣除内洞,传播结果保存时也会保留 `holes`;工作区顶栏模型状态用紧凑 GPU/CPU 徽标,具体 SAM 2.1 传播权重由旁边下拉选择;同步 `POST /api/ai/propagate` 仍作为单 seed 兼容接口保留。
- 工作区顶栏短状态会自动消失;保存、导出、导入 GT、传播进行中和无帧项目提示会保留到状态变化。
- 工作区“清空遮罩”可从画布右下角或左侧工具栏触发,会调用 `DELETE /api/ai/annotations/{id}` 删除当前帧已保存标注,并同步清空这些 mask 同传播链上的自动传播结果;不会删除其它帧独立 AI 推理或人工标注 mask。
- 工作区“清空遮罩”只从左侧工具栏触发,会调用 `DELETE /api/ai/annotations/{id}` 删除当前帧已保存标注;如果当前帧有选中 mask 则优先清选中 mask没有选中时清当前帧全部 mask如果清空对象没关联其它传播帧则直接清空当前帧如果存在传播结果则弹窗选择只清当前帧、清空传播所有帧或取消;不会删除其它帧独立 AI 推理或人工标注 mask。
- 项目状态已统一为 `pending``parsing``ready``error`;前端 `src/lib/api.ts` 会兼容归一化旧库中可能存在的 `Ready``Parsing``Error`
- 项目库的视频导入与生成帧是两个独立动作:导入视频只上传源文件,并通过 Axios `onUploadProgress` 在项目库显示导入进度;生成帧按钮才会带 `parse_fps` 调用 `/api/media/parse`DICOM 批量导入也会显示上传进度和文件数量,上传完成后创建解析任务并轮询显示解析进度。工作区不会再因“有视频但无帧”自动创建拆帧任务。
- `server.ts` 不再提供旧版 `/api/login``/api/projects``/api/templates` mock当前前端真实 API 调用走 FastAPI 的 `/api/auth/*``/api/projects``/api/templates` 等接口。