feat: 完善 AI 分割与工作区标注闭环

功能增加:

- 将视频导入和生成帧拆成两个明确动作,项目库生成帧时选择 FPS,工作区不再自动触发拆帧。

- 为工作区新增调整多边形工具,支持选中 mask、拖动顶点、边中点插点、双击边界按位置插点,并保留多 polygon 子区域编辑。

- 打通 AI 页 SAM2/SAM3 结果到工作区的联动,生成 mask 后自动选中,可在右侧分类树换标签,并推送到工作区继续编辑。

- 增强 Dashboard WebSocket 连接状态与心跳,使用真实 onopen/onclose/onerror 状态驱动前端显示。

- 完善 SAM3 external worker 适配,支持 box prompt、semantic 请求级阈值和 video tracker 路径。

bugfix:

- 修复 SAM2 文本语义误走自动分割的问题,改为提示使用点提示或切换 SAM3。

- 修复 SAM2 多候选重叠显示的问题,点提示和 auto fallback 默认只采用最高分候选。

- 修复 SAM2 反向点看起来无效的问题,带负点时启用背景过滤,过滤为空时移除旧候选。

- 修复 SAM3 单个 2D mask 结果无法转 polygon、低阈值 semantic 返回被默认阈值吞掉的问题。

- 修复 AI 页 mask 未选中导致分类树无法修改 SAM2 结果标签的问题。

测试和文档:

- 补充 CanvasArea、AISegmentation、ProjectLibrary、VideoWorkspace、Dashboard、websocket 和 SAM engine/API 测试。

- 新增 backend/tests/test_sam2_engine.py,覆盖 SAM2 单候选请求和 auto fallback 行为。

- 更新 README、AGENTS 和 doc 需求/设计/接口/测试矩阵,按当前实现冻结功能状态。
This commit is contained in:
2026-05-01 21:50:17 +08:00
parent 5ab4602535
commit 8a9247075e
31 changed files with 920 additions and 216 deletions

View File

@@ -65,12 +65,12 @@
### 项目与拆帧
1. `ProjectLibrary.tsx` 调用 `getProjects()` 获取项目。
2. 上传视频时先 `createProject()`,再 `uploadMedia()`,再 `parseMedia()`
2. 上传视频时先 `createProject()`,再 `uploadMedia()`;导入视频不自动调用 `parseMedia()`
3. 后端 `media.py` 把原始文件上传到 MinIO。
4. `parseMedia()` 创建 `processing_tasks` 记录并投递 Celery worker。
4. 用户在项目库点击“生成帧”并选择 FPS 后,`parseMedia()` 创建 `processing_tasks` 记录并投递 Celery worker。
5. Celery worker 下载 MinIO 文件,调用 `frame_parser.py` 拆帧。
6. worker 把拆出的帧重新上传 MinIO写入 `frames` 表,并更新任务状态。
7. 工作区通过 `GET /api/tasks/{id}` 等待任务完成,再通过 `GET /api/projects/{id}/frames` 获取预签名图片 URL。
7. 工作区通过 `GET /api/projects/{id}/frames` 获取预签名图片 URL;若项目有源视频但无帧,会提示先回项目库生成帧
8. Dashboard 可通过 `POST /api/tasks/{id}/cancel` 取消 queued/running 任务,通过 `POST /api/tasks/{id}/retry` 重试 failed/cancelled 任务,并用 `GET /api/tasks/{id}` 查看失败详情。
### 工作区浏览

View File

@@ -46,8 +46,9 @@
| 项目卡片缩略图 | 真实可用 | 后端返回 MinIO 预签名 `thumbnail_url` 时显示 |
| 点击项目进入工作区 | 真实可用 | 设置 `currentProject` 后切到 `workspace` |
| 新建项目 | 真实可用 | 调用 `POST /api/projects` |
| 导入视频文件 | 真实可用 | 创建项目、上传文件、触发拆帧、刷新项目列表 |
| 解析 FPS 滑块 | 真实可用 | 值传入 `createProject({ parse_fps })` |
| 导入视频文件 | 真实可用 | 创建项目、上传源视频、刷新项目列表;不会自动拆帧 |
| 生成帧按钮 | 真实可用 | 仅对已导入源视频且尚无帧、非 parsing 状态的项目显示,调用 `parseMedia(projectId, { parseFps })` |
| 生成帧 FPS 滑块 | 真实可用 | 值传入 `/api/media/parse?parse_fps=...`,决定后台拆帧目标 FPS |
| 导入 DICOM 序列 | 部分可用 | 可上传 `.dcm` 并触发解析;体验和错误反馈较粗 |
| 项目状态徽标 | 真实可用 | 项目状态统一为 `pending/parsing/ready/error`,前端兼容归一化旧状态值 |
| 更多按钮 | Mock / UI-only | 有图标,没有菜单或事件 |
@@ -59,7 +60,7 @@
|------|------|------|
| 当前项目名 | 真实可用 | 读取 `currentProject.name` |
| 自动加载项目帧 | 真实可用 | 调用 `GET /api/projects/{id}/frames` |
| 无帧时触发解析 | 真实可用 | 如果 `video_path` 存在会调用 `parseMedia()` 创建异步任务,并轮询 `GET /api/tasks/{id}` 等待完成 |
| 无帧项目提示 | 真实可用 | 如果 `video_path` 存在但无帧,只提示回到项目库生成帧,不自动创建拆帧任务 |
| SAM 模型状态徽标 | 真实可用 | 调用 `GET /api/ai/models/status`,显示当前选择的 SAM 2/SAM 3 是否可用 |
| 已保存标注回显 | 真实可用 | 加载工作区帧后调用 `GET /api/ai/annotations` 并渲染已保存 mask |
| “导出 JSON 标注集”按钮 | 真实可用 | 导出前会保存未归档 mask然后调用 `exportCoco()` 下载 JSON |
@@ -93,6 +94,7 @@
| 元素 | 状态 | 说明 |
|------|------|------|
| 拖拽/选择 | 真实可用 | 控制 Canvas 是否可拖拽 |
| 调整多边形 | 真实可用 | 选中 polygon mask 后显示顶点和边中点;支持拖动顶点、点击边中点插点、双击边界按位置插点 |
| 多边形/矩形/圆/点/线 | 真实可用 | 切换 activeTool 后由 `CanvasArea` 生成可保存的 polygon mask |
| 区域合并/去除 | 真实可用 | 选择工具后点击多个 mask右下角显示已选数量和操作按钮合并/去除模式会隐藏 polygon 编辑手柄,避免手柄抢占多选点击;使用 `polygon-clipping` 做 union / difference合并会保留主 mask 并移除被合并 mask去除会从主 mask 扣除后续选中 mask内含扣除会保留 hole ring 并用 even-odd 规则渲染 |
| 正向选点/反向选点/框选 | 部分可用 | 会影响 Canvas 交互,并能触发已对齐的 AI 推理接口 |
@@ -130,7 +132,8 @@
| SAM 3 框选 | 真实可用 | 工作区选择 SAM 3 后可使用框选工具;后端通过官方 `add_geometric_prompt()` 正框执行 SAM 3 几何提示推理 |
| 语义文本输入 | 部分可用 | 纯文本会以 `semantic` prompt 调用后端;选择 SAM 3 且独立 Python 3.12 环境、CUDA、官方包和本地 checkpoint 均满足时走 SAM 3 文本语义推理,否则状态接口会标明不可用;空文本、失败和 0 mask 返回会显示前端反馈 |
| 参数开关 | 真实可用 | `cropMode` 会随 `/api/ai/predict` 发送 `crop_to_prompt`,后端对点/框 prompt 裁剪推理区域并回映射 polygon`autoDeleteBg` 会发送 `auto_filter_background``min_score`,后端过滤低分结果和覆盖负向点的结果 |
| 执行高精度语义分割 | 部分可用 | 使用当前项目帧调用 `/api/ai/predict`没有当前帧时按钮禁用 |
| 执行高精度语义分割 | 真实可用 | 使用当前项目帧调用 `/api/ai/predict`SAM 2 需要点提示且只采用最高分候选SAM 3 需要文本语义提示;生成结果写入全局 masks 并自动选中,右侧分类树可立即换标签 |
| 推送至工作区编辑 | 真实可用 | 切回工作区并把工具切到“调整多边形”,保留 AI 页选中的 mask便于继续调轮廓和归档 |
| 上传替换底图 | Mock / UI-only | 按钮无事件 |
| 撤销/重做 | 真实可用 | 绑定全局 mask 历史栈 |
| 清空全体锚点 | 部分可用 | 清空前端 points 和 masks |
@@ -153,6 +156,6 @@
## 总体结论
当前前端真实可用的主链路是登录、Dashboard 后端概览、项目列表、新建项目、上传视频/DICOM、帧、浏览帧、播放帧、工作区手工绘制、点/框 AI 推理、视频片段传播、GT mask 导入、标注保存/回显、COCO 导出、PNG mask ZIP 导出、模板 CRUD。
当前前端真实可用的主链路是登录、Dashboard 后端概览、项目列表、新建项目、上传视频/DICOM、显式生成帧、浏览帧、播放帧、工作区手工绘制、点/框 AI 推理、视频片段传播、GT mask 导入、标注保存/回显、COCO 导出、PNG mask ZIP 导出、模板 CRUD。
当前最主要的 Mock 或未打通链路是polygon 插点/边编辑增强、真正的文本语义分割、骨架/HDBSCAN 级别的 mask 降维增强、任务历史筛选、项目更多菜单和若干检查面板指标。

View File

@@ -32,7 +32,7 @@ Authorization: Bearer <token>
| `deleteTemplate(id)` | `DELETE /api/templates/{id}` | 对齐 | 模板编辑页使用 |
| `uploadMedia(file, projectId)` | `POST /api/media/upload` | 对齐 | multipart form-data |
| `uploadDicomBatch(files, projectId)` | `POST /api/media/upload/dicom` | 对齐 | multipart form-data |
| `parseMedia(projectId, options?)` | `POST /api/media/parse?project_id=...` | 对齐 | 创建异步拆帧任务并返回 task支持 `parse_fps``max_frames``target_width` |
| `parseMedia(projectId, options?)` | `POST /api/media/parse?project_id=...` | 对齐 | 创建异步拆帧任务并返回 task由项目库“生成帧”显式调用,支持 `parse_fps``max_frames``target_width` |
| `getTask(taskId)` | `GET /api/tasks/{task_id}` | 对齐 | 查询异步任务状态 |
| `cancelTask(taskId)` | `POST /api/tasks/{task_id}/cancel` | 对齐 | 取消 queued/running 任务,后端写 cancelled 并尝试 revoke Celery |
| `retryTask(taskId)` | `POST /api/tasks/{task_id}/retry` | 对齐 | 对 failed/cancelled 任务创建新的 queued 重试任务 |
@@ -91,6 +91,21 @@ Authorization: Bearer <token>
| GET | `/health` | 健康检查 |
| WS | `/ws/progress` | WebSocket 进度通道,未出现在 OpenAPI paths 中 |
### WebSocket 进度通道
`/ws/progress` 用于 Dashboard 实时接收后台任务状态。前端连接成功后会定时发送 `ping` 作为心跳;后端收到任意文本心跳后返回:
```json
{
"type": "status",
"status": "connected",
"message": "Progress stream active",
"timestamp": "2026-05-01T00:00:00+00:00"
}
```
后台任务进度由 Celery worker 写入 Redis `seg:progress` 频道,再由 FastAPI 转发到当前活跃 WebSocket 连接。Dashboard 的“WebSocket 已连接/断开”状态来自浏览器 WebSocket 的 `onopen/onclose/onerror`,不再依赖是否刚好收到任务进度事件。
## 关键请求体
### 登录
@@ -172,7 +187,11 @@ POST /api/media/parse?project_id=1&parse_fps=15&max_frames=120&target_width=960
- `point`
- `box`
- `interactive`,用于 SAM 2 交互式细化,`prompt_data` 同时携带 `box`、累计 `points``labels`
- `semantic`,选择 `sam3` 时进入 SAM 3 文本语义推理;选择 `sam2` 时仍回退到 auto segmentation。SAM 3 真实可用性由 `/api/ai/models/status` 中的外部环境和本地 checkpoint 状态决定。
- `semantic`,选择 `sam3` 时进入 SAM 3 文本语义推理。前端 AI 页面不会再用 SAM 2 发送纯文本 semanticSAM 2 的交互入口应使用点/框提示。SAM 3 真实可用性由 `/api/ai/models/status` 中的外部环境和本地 checkpoint 状态决定。
SAM 2 点提示和 auto fallback 当前只采用最高分候选 mask避免同一提示下多个备选 mask 被前端叠加显示。
工作区 SAM 2 请求包含反向点时,`CanvasArea` 会发送 `options.auto_filter_background=true``options.min_score=0.05`;如果负向点过滤后没有可用 polygon前端会移除当前旧候选 mask 并要求重新框选或添加正向点。
选择 `sam3` 且发送 `box` 时,前端仍传 normalized `[x1, y1, x2, y2]`,后端适配层会转换成官方几何 prompt 的 `[center_x, center_y, width, height]` 正框;当前 SAM 3 不接正/反点修正。
@@ -180,7 +199,7 @@ POST /api/media/parse?project_id=1&parse_fps=15&max_frames=120&target_width=960
- `crop_to_prompt`:对 point/box/interactive prompt 按锚点或框附近区域裁剪后推理,再把 polygon 回映射到原图坐标。
- `auto_filter_background`:过滤低分结果,并移除包含负向点的 polygon。
- `min_score`:配合 `auto_filter_background` 使用的最低置信度阈值。
- `min_score`:配合 `auto_filter_background` 使用的最低置信度阈值;对 SAM 3 semantic 请求也会作为 external worker 的 `confidence_threshold` 传入,避免本地 checkpoint 在默认高阈值下返回 0 个 mask
后端响应:

View File

@@ -17,7 +17,8 @@
- 前端展示项目库,并从 `GET /api/projects` 获取项目列表。
- 用户可以新建项目,前端调用 `POST /api/projects`
- 用户可以选择项目,进入工作区。
- 用户可以导入视频文件,前端创建项目、上传文件、触发拆帧、刷新项目列表
- 用户可以导入视频文件,前端创建项目、上传文件并刷新项目列表;导入视频不自动拆帧
- 用户可以对已导入且尚未生成帧的视频项目点击“生成帧”,在弹窗中选择目标 FPS 后创建拆帧任务。
- 用户可以导入 DICOM 序列,前端上传 DICOM、触发拆帧、刷新项目列表。
- 后端支持项目创建、列表、详情、局部更新和删除。
- 后端支持项目帧创建、列表和单帧查询。
@@ -42,7 +43,7 @@
## R4 工作区与帧浏览
- 工作区根据当前项目加载帧列表。
- 若项目有媒体但无帧,工作区会尝试触发拆帧后重新加载
- 若项目有媒体但无帧,工作区只提示需要先在项目库生成帧,不再自动触发拆帧
- Canvas 显示当前帧图片。
- Canvas 支持滚轮缩放、移动工具拖拽、鼠标坐标显示。
- 时间轴支持缩略图点击切帧、range 拖动切帧、键盘左右方向键切帧、播放/暂停顺序推进帧。
@@ -57,7 +58,9 @@
- 多边形、矩形、圆、点、线工具会在 Canvas 上生成可保存的 polygon mask。
- 多边形通过点击取点并按 Enter 完成,也支持三点后点击首节点闭合;矩形、圆、线通过拖拽生成;点工具生成小点区域。
- 绘制工具点击已有 mask 时应继续执行当前绘制动作,不应被 mask 选择逻辑吞掉。
- Canvas 支持点击 mask 进入 polygon 顶点编辑态;拖动顶点会更新 mask 几何并把已保存 mask 标记为 dirty。
- 工具栏提供“调整多边形”工具,用户可以点击 mask 进入 polygon 顶点编辑态;拖动顶点会更新 mask 几何并把已保存 mask 标记为 dirty。
- 顶点编辑态显示边中点插入手柄;点击边中点会在该边中间新增顶点。
- “调整多边形”工具下双击 polygon 边界时,会在最接近的线段上按双击位置新增顶点。
- 顶点编辑态下选中顶点后可用 Delete/Backspace 删除顶点,但不会让 polygon 少于三点。
- 选中整个 mask 且未选中具体顶点时Delete/Backspace 删除该 mask已保存 mask 同步调用后端删除接口。
- 撤销、重做绑定全局 `maskHistory/maskFuture`支持工具栏按钮、AI 页按钮和 Canvas 快捷键。
@@ -75,14 +78,19 @@
- 点提示传 `{ points, labels }`,正向点 label 为 1反向点 label 为 0。
- 框选提示传归一化 `[x1, y1, x2, y2]`
- 工作区 SAM 2 框选会建立一个候选 mask后续正向点/反向点会携带原始框和累计点,以 `interactive` prompt 细化并替换同一个候选 mask。
- 语义文本提示传 `semantic`;选择 `sam3` 且独立 Python 3.12 环境、CUDA、官方包和本地 checkpoint 均满足时走 SAM 3 文本语义推理,选择 `sam2` 时回退到自动分割
- 工作区 SAM 2 一旦包含反向点,会随请求启用 `auto_filter_background``min_score=0.05`;若后端判定反向点排除了当前候选区域并返回空结果,前端会移除旧候选 mask避免继续显示已被否定的区域
- SAM 2 不支持文本语义提示AI 页面在 SAM 2 下输入纯文本时会提示用户改用点提示或切换 SAM 3不再回退到自动分割。
- SAM 2 点提示和 auto fallback 默认只采用一个最高分候选 mask避免多个候选 mask 作为同一结果重叠显示。
- AI 页面生成的 SAM 2/SAM 3 mask 会写入全局 `masks`,自动同步到当前项目帧,并写入全局 `selectedMaskIds`;右侧语义分类树可以直接给新生成 mask 换标签。
- AI 页面“推送至工作区编辑”会切回工作区并把工具切到“调整多边形”,保留当前选中的 AI mask 以便继续编辑轮廓和归档保存。
- 语义文本提示传 `semantic`;选择 `sam3` 且独立 Python 3.12 环境、CUDA、官方包和本地 checkpoint 均满足时走 SAM 3 文本语义推理。
- SAM 3 支持工作区框选提示;后端把 normalized `[x1, y1, x2, y2]` 转成官方 `add_geometric_prompt()` 需要的 `[center_x, center_y, width, height]` 正框。
- 当前 SAM 3 前端路径不支持正/反点修正;在工作区用 SAM 3 进行点交互时,前端会提示切回 SAM 2。
- 工作区“传播片段”会把当前选中区域或当前帧第一个区域作为 seed调用 `POST /api/ai/propagate`,默认从当前帧向后传播 30 帧并保存结果标注。
- `POST /api/ai/propagate` 支持 `model=sam2``model=sam3`SAM 2 使用官方 `SAM2VideoPredictor.add_new_mask()``propagate_in_video()`SAM 3 通过独立 Python 3.12 helper 调用官方 `build_sam3_video_predictor()` video tracker。
- 传播结果会写入后续帧 `annotations``mask_data.source` 分别标记为 `sam2_propagation``sam3_propagation`,并保留 label、color 和 class 元数据。
- AI 页面会对 SAM 3 空文本、推理失败和返回 0 个 mask 的情况显示明确反馈。
- AI 参数支持 `crop_to_prompt``auto_filter_background``min_score`;点/框 prompt 可以裁剪局部区域推理并回映射结果,背景过滤会移除低分结果和包含负向点的 polygon。
- AI 参数支持 `crop_to_prompt``auto_filter_background``min_score`;点/框 prompt 可以裁剪局部区域推理并回映射结果,背景过滤会移除低分结果和包含负向点的 polygonSAM 3 semantic 会用 `min_score` 控制 external worker 的置信度阈值
- 后端返回 `polygons``scores`
- 前端把后端 `polygons` 转成 Konva `pathData``segmentation``bbox``area`
- AI 推理结果先存放在前端 store 的 `masks` 中,点击“结构化归档保存”后持久化到后端标注表。

View File

@@ -22,11 +22,11 @@
| 全局状态 | `src/store/useStore.ts` | Zustand store保存项目、帧、模板、mask、当前选中 mask ids、工具状态和 mask 撤销/重做历史栈 |
| API 封装 | `src/lib/api.ts` | Axios 客户端、字段映射、AI 响应转换 |
| 配置 | `src/lib/config.ts` | 推导 API 和 WebSocket 地址 |
| WebSocket | `src/lib/websocket.ts` | 进度流连接、订阅和重连 |
| WebSocket | `src/lib/websocket.ts` | 进度流连接、订阅、连接状态通知、心跳和重连 |
| 模型状态 | `src/components/ModelStatusBadge.tsx` | 展示 GPU 与当前 SAM 模型真实可用状态 |
| 登录页 | `src/components/Login.tsx` | 调用登录 API写入 store |
| Dashboard | `src/components/Dashboard.tsx` | 展示统计、任务控制、失败详情和 WebSocket 进度消息 |
| 项目库 | `src/components/ProjectLibrary.tsx` | 项目列表、新建、导入视频/DICOM |
| 项目库 | `src/components/ProjectLibrary.tsx` | 项目列表、新建、导入视频/DICOM、显式生成帧 |
| 工作区 | `src/components/VideoWorkspace.tsx` | 加载帧和模板组织工具栏、Canvas、本体面板、时间轴 |
| Canvas | `src/components/CanvasArea.tsx` | 显示帧、缩放平移、点/框提示、渲染 mask |
| 工具栏 | `src/components/ToolsPalette.tsx` | 切换工具、跳转 AI 页面、触发 mask 撤销/重做 |
@@ -76,15 +76,16 @@
2. `login()` 调用 `POST /api/auth/login`
3. 成功后 store 写入 tokenApp 渲染主界面。
### 项目导入
### 项目导入与生成帧
1. `ProjectLibrary` 创建项目。
2. 上传视频或 DICOM `/api/media/upload` `/api/media/upload/dicom`
3. 调用 `/api/media/parse` 创建异步拆帧任务;可通过 `parse_fps``max_frames``target_width` 指定标准帧序列参数
4. Celery worker 执行 FFmpeg/OpenCV/pydicom 拆帧,视频帧按 `frame_%06d.jpg``frame_000000.jpg` 连续命名,并按目标宽度缩放
5. worker 写入 `frames.timestamp_ms``frames.source_frame_number`,并在任务 `result.frame_sequence` 中记录 FPS、帧数、时长、尺寸和对象存储前缀
6. worker 持续更新 `processing_tasks`,并发布 Redis `seg:progress`
7. 刷新项目列表
2. 导入视频时上传视频到 `/api/media/upload` 并关联项目;该步骤不调用 `/api/media/parse`
3. 用户在项目卡片点击“生成帧”,在弹窗中选择目标 FPS
4. 前端调用 `/api/media/parse` 创建异步拆帧任务;可通过 `parse_fps``max_frames``target_width` 指定标准帧序列参数
5. Celery worker 执行 FFmpeg/OpenCV/pydicom 拆帧,视频帧按 `frame_%06d.jpg``frame_000000.jpg` 连续命名,并按目标宽度缩放
6. worker 写入 `frames.timestamp_ms``frames.source_frame_number`,并在任务 `result.frame_sequence` 中记录 FPS、帧数、时长、尺寸和对象存储前缀
7. worker 持续更新 `processing_tasks`,并发布 Redis `seg:progress`
8. 刷新项目列表。
### 任务控制
@@ -93,11 +94,12 @@
3. worker 在下载、解析、上传、写帧等关键阶段刷新任务状态;如果发现 `cancelled`,停止后续写入并发布 cancelled 事件。
4. 用户重试任务时,前端调用 `POST /api/tasks/{task_id}/retry`;后端基于原任务 `payload` 创建新任务,记录 `retry_of` 并重新投递 Celery。
5. 用户打开详情时,前端调用 `GET /api/tasks/{task_id}`,弹窗展示 error、payload、result、Celery ID 和时间。
6. Dashboard 通过 `/ws/progress` 接收 Redis `seg:progress` 转发事件;前端 WebSocket 客户端在 `onopen/onclose/onerror` 主动更新连接状态,并定时发送 `ping` 心跳,服务端返回 `status` 确认连接仍活跃。
### 工作区加载
1. `VideoWorkspace` 根据 `currentProject.id` 调用 `getProjectFrames()`
2. 若无帧但项目有 `video_path`,触发 `parseMedia()`,通过 `getTask()` 轮询任务完成后重新取帧
2. 若无帧但项目有 `video_path`显示“尚未生成帧”的状态提示,不自动触发 `parseMedia()`
3. 帧数据映射为 store `Frame[]`,包含 `timestampMs``sourceFrameNumber`,供时间轴和后续视频传播使用。
4. 当前帧传入 `CanvasArea`
@@ -107,13 +109,15 @@
2. `CanvasArea` 读取当前帧 ID 和宽高。
3. SAM 2 框选会创建一个候选 mask并记录原始框后续正向点/反向点会累计到同一候选上。
4. `predictMask()` 归一化坐标并携带当前 `model` 调用 `/api/ai/predict`;同时有框和点时发送 `interactive` prompt。
5. 后端加载帧图片并通过 SAM registry 分发到 SAM 2 或 SAM 3
6. 前端把 `polygons` 转为 mask交互式细化会替换同一个候选 mask而不是新增多个 mask
7. Canvas 按当前帧过滤并渲染 mask。
8. 新 mask 会带上当前选择的模板分类元数据,包括 `classId``className``classZIndex` 和保存状态 `draft`
9. 用户点击“结构化归档保存”后,前端将像素 `segmentation` 转成 normalized `mask_data.polygons`;未保存 mask 调用 `POST /api/ai/annotate`dirty mask 调用 `PATCH /api/ai/annotations/{annotation_id}`
10. 工作区加载项目帧后通过 `GET /api/ai/annotations` 取回已保存标注并转成前端 mask
11. 工作区“清空遮罩”删除当前帧已保存标注,并清除当前帧本地 mask
5. SAM 2 请求中只要存在反向点,`CanvasArea` 会额外发送 `options.auto_filter_background=true``options.min_score=0.05`,让后端移除低分结果和包含负向点的 polygon
6. 后端加载帧图片并通过 SAM registry 分发到 SAM 2 或 SAM 3
7. 前端把 `polygons` 转为 mask交互式细化会替换同一个候选 mask而不是新增多个 mask。
8. 若带反向点的 SAM 2 细化返回空结果,前端会删除当前旧候选 mask 并提示反向点已排除该区域
9. Canvas 按当前帧过滤并渲染 mask
10. 新 mask 会带上当前选择的模板分类元数据,包括 `classId``className``classZIndex` 和保存状态 `draft`
11. 用户点击“结构化归档保存”后,前端将像素 `segmentation` 转成 normalized `mask_data.polygons`;未保存 mask 调用 `POST /api/ai/annotate`dirty mask 调用 `PATCH /api/ai/annotations/{annotation_id}`
12. 工作区加载项目帧后通过 `GET /api/ai/annotations` 取回已保存标注并转成前端 mask。
13. 工作区“清空遮罩”删除当前帧已保存标注,并清除当前帧本地 mask。
### 视频片段传播
@@ -131,19 +135,20 @@
1. 用户在 `ToolsPalette` 选择多边形、矩形、圆、点或线工具。
2. `CanvasArea` 将交互坐标转换成像素 polygon。
3. 多边形工具逐次记录节点,三点后点击首节点或按 Enter 时生成闭合 polygon。
4. mask path 只在 `move``area_merge``area_remove` 工具下拦截点击;绘制和 AI prompt 工具点击已有 mask 时继续冒泡给 Stage。
4. mask path 只在 `move``edit_polygon``area_merge``area_remove` 工具下拦截点击;绘制和 AI prompt 工具点击已有 mask 时继续冒泡给 Stage。
5. 新 mask 写入 `pathData`、像素 `segmentation``bbox``area` 和当前模板分类元数据。
6. `addMask()``setMasks()``updateMask()``clearMasks()` 会维护 `maskHistory/maskFuture`
7. 工具栏按钮、AI 页按钮和 Canvas Ctrl+Z/Ctrl+Y 调用 `undoMasks()` / `redoMasks()`
### Polygon 逐点编辑
1. 用户点击 Canvas 上的 mask path`CanvasArea` 记录 `selectedMaskId` 并显示该 mask 第一条 polygon 的顶点控制点。
1. 用户选择“调整多边形”或“拖拽/选择”后点击 Canvas 上的 mask path`CanvasArea` 记录 `selectedMaskId` 并显示该 mask 第一条 polygon 的顶点控制点和边中点插入手柄
2. 拖动顶点后,前端重算 `pathData`、像素 `segmentation``bbox``area`
3. 如果 mask 已有 `annotationId`,编辑会把 `saveStatus` 标成 `dirty``saved=false`
4. 归档保存时复用现有 `PATCH /api/ai/annotations/{annotation_id}` 链路,把更新后的 normalized polygon 写回后端
5. 选中顶点后 Delete/Backspace 可删除顶点;前端保持 polygon 至少三点
6. 选中具体顶点但选中了 mask 时,Delete/Backspace 从前端 store 删除该 mask如果包含 `annotationId`,通过工作区回调调用后端删除接口
3. 点击边中点手柄会在该边中点插入新顶点;在“调整多边形”工具下双击 polygon path 会在最接近的线段上按双击位置插入新顶点
4. 如果 mask 已有 `annotationId`,编辑会把 `saveStatus` 标成 `dirty``saved=false`
5. 归档保存时复用现有 `PATCH /api/ai/annotations/{annotation_id}` 链路,把更新后的 normalized polygon 写回后端
6. 选中顶点后 Delete/Backspace 可删除顶点;前端保持 polygon 至少三点
7. 未选中具体顶点但选中了 mask 时Delete/Backspace 从前端 store 删除该 mask如果包含 `annotationId`,通过工作区回调调用后端删除接口。
### 区域合并与去除
@@ -173,9 +178,11 @@
4. 后端把 `classes``rules` 打包进 `mapping_rules`
5. 返回时再解包给前端。
6. `CanvasArea` 把当前选中的 mask id 同步到全局 `selectedMaskIds`;切换工具、切换帧或卸载 Canvas 时会清空选择。
7. `OntologyInspector` 可以选择具体分类;选择结果进入全局 store`CanvasArea``AISegmentation` 新建/更新 mask 时使用
8. 如果 `selectedMaskIds` 中存在当前 store 的 mask点击分类时会立即更新这些 mask 的 `templateId``classId``className``classZIndex``label``color`
9. 已保存 mask 被重新分类后进入 `dirty` `saved=false`,继续复用工作区归档保存的 PATCH 链路
7. `AISegmentation` 生成 mask 后会写入全局 `masks` 并把生成的 mask id 写入 `selectedMaskIds`;点击 AI 页预览 mask 也会更新 `selectedMaskIds`
8. AI 页“推送至工作区编辑”会切换到工作区并把 `activeTool` 设为 `edit_polygon``CanvasArea` 初始读取全局 `selectedMaskIds`,让 AI 页选中的 mask 在工作区继续保持选中
9. `OntologyInspector` 可以选择具体分类;选择结果进入全局 store`CanvasArea` `AISegmentation` 新建/更新 mask 时使用
10. 如果 `selectedMaskIds` 中存在当前 store 的 mask点击分类时会立即更新这些 mask 的 `templateId``classId``className``classZIndex``label``color`
11. 已保存 mask 被重新分类后进入 `dirty``saved=false`,继续复用工作区归档保存的 PATCH 链路。
### 导出
@@ -204,10 +211,12 @@
- `parseMedia()` 使用 `POST /api/media/parse?project_id=...`,可选 `parse_fps``max_frames``target_width`,用于生成标准帧序列。
- `getProjectFrames()` 返回帧图像 URL、宽高、`timestamp_ms``source_frame_number`
- 后端 `/api/ai/predict` 支持 point、box、interactive、semantic 四种 prompt_type并通过 `model` 选择 SAM 2 或 SAM 3。
- SAM 2 是点/框交互式分割模型不做文本语义分割AI 页面在 SAM 2 + 纯文本时直接提示用户改用点提示或切换 SAM 3。
- SAM 2 点提示和 auto fallback 只返回一个最高分候选,避免同一提示产生多个重叠候选 mask。
- 当前 SAM 3 暴露 semantic 文本语义推理和 box 几何提示;工作区 Canvas 的点交互会在选择 SAM 3 时显示提示,不再静默失败。
- SAM 3 box prompt 复用后端 `/api/ai/predict``box` prompt_type输入仍是 normalized `[x1, y1, x2, y2]`,引擎适配层会转换为官方 `add_geometric_prompt()` 使用的 `[center_x, center_y, width, height]` 正框。
- AI 页面选择 SAM 3 时优先发送文本 semantic prompt不会把正/反点误发送为 SAM 3 point prompt空文本、后端错误和空结果都会显示反馈消息。
- 后端 `/api/ai/predict` 支持可选 `options``crop_to_prompt` 会对 point/box/interactive prompt 做局部裁剪推理并回映射 polygon`auto_filter_background` 会按 `min_score` 和负向点过滤结果。
- 后端 `/api/ai/predict` 支持可选 `options``crop_to_prompt` 会对 point/box/interactive prompt 做局部裁剪推理并回映射 polygon`auto_filter_background` 会按 `min_score` 和负向点过滤结果SAM 3 semantic 会把正数 `min_score` 传给 external worker 作为 `confidence_threshold`
- 后端 `/api/ai/propagate` 支持 SAM 2 mask seed 视频传播和 SAM 3 external video tracker当前前端默认向后传播 30 帧并保存结果标注。
- 后端 `/api/ai/models/status` 返回 GPU、SAM 2、SAM 3 的真实运行状态SAM 3 状态包含外部 Python 环境与 checkpoint access 的可用性。
- point prompt 支持旧数组形式和 `{ points, labels }` 对象形式。

View File

@@ -16,14 +16,14 @@
|------|----------|--------|
| R1 登录与会话 | `src/components/Login.test.tsx`, `backend/tests/test_auth.py` | 成功登录、失败提示、后端 401 |
| R2 项目管理 | `src/lib/api.test.ts`, `backend/tests/test_projects.py` | 前端字段映射、PATCH 更新、后端 CRUD、帧列表 |
| R3 媒体上传与拆帧 | `backend/tests/test_media.py`, `backend/tests/test_tasks.py` | 扩展名校验、自动建项目、关联项目、创建异步任务、标准帧序列参数、帧时间戳/源帧号、任务序列元数据、worker 注册帧、取消任务、重试任务、取消后 worker 停止 |
| R4 工作区与帧浏览 | `src/components/VideoWorkspace.test.tsx`, `src/components/FrameTimeline.test.tsx` | 加载帧、无帧触发解析、缩略图/range/左右方向键切帧、播放、按项目 FPS 显示当前/总时长 |
| R5 工具栏 | `src/components/ToolsPalette.test.tsx`, `src/components/CanvasArea.test.tsx`, `src/store/useStore.test.ts` | 工具切换、AI 跳转、矩形/圆/线/点/多边形手工 mask 绘制、点工具在已有 mask 上落点、多边形 Enter/首节点闭合、polygon 顶点拖动/删除、整块 mask 删除、区域合并/去除、内含去除 hole 渲染、合并模式隐藏编辑手柄、撤销/重做历史栈 |
| R6 AI 推理 | `src/lib/api.test.ts`, `src/components/CanvasArea.test.tsx`, `src/components/AISegmentation.test.tsx`, `src/components/VideoWorkspace.test.tsx`, `src/components/ModelStatusBadge.test.tsx`, `backend/tests/test_ai.py`, `backend/tests/test_sam3_engine.py` | 点/框/interactive/semantic 契约、SAM 2 框选后正负点细化同一候选 mask、SAM 2 视频传播、SAM 3 语义文本前端执行路径、SAM 3 工作区框选、SAM 3 video tracker 外部桥接、SAM 3 点交互不支持提示、空文本/空结果反馈、模型选择、GPU/SAM 状态、SAM 3 外部 worker 桥接、AI 参数 options、局部裁剪推理、背景过滤、状态徽标、坐标归一化、正负点 labels、polygons 转 path、后端 fake registry |
| R3 媒体上传与拆帧 | `src/components/ProjectLibrary.test.tsx`, `backend/tests/test_media.py`, `backend/tests/test_tasks.py` | 视频导入不自动拆帧、显式生成帧 FPS 选择、扩展名校验、自动建项目、关联项目、创建异步任务、标准帧序列参数、帧时间戳/源帧号、任务序列元数据、worker 注册帧、取消任务、重试任务、取消后 worker 停止 |
| R4 工作区与帧浏览 | `src/components/VideoWorkspace.test.tsx`, `src/components/FrameTimeline.test.tsx` | 加载帧、无帧项目不自动解析并提示生成帧、缩略图/range/左右方向键切帧、播放、按项目 FPS 显示当前/总时长 |
| R5 工具栏 | `src/components/ToolsPalette.test.tsx`, `src/components/CanvasArea.test.tsx`, `src/store/useStore.test.ts` | 工具切换、调整多边形工具、AI 跳转、矩形/圆/线/点/多边形手工 mask 绘制、点工具在已有 mask 上落点、多边形 Enter/首节点闭合、polygon 顶点拖动/删除、边中点插点、双击边界按位置插点、整块 mask 删除、区域合并/去除、内含去除 hole 渲染、合并模式隐藏编辑手柄、撤销/重做历史栈 |
| R6 AI 推理 | `src/lib/api.test.ts`, `src/components/CanvasArea.test.tsx`, `src/components/AISegmentation.test.tsx`, `src/components/VideoWorkspace.test.tsx`, `src/components/ModelStatusBadge.test.tsx`, `backend/tests/test_ai.py`, `backend/tests/test_sam2_engine.py`, `backend/tests/test_sam3_engine.py` | 点/框/interactive/semantic 契约、SAM 2 纯文本提示拦截、SAM 2 最高分候选去重、SAM 2 框选后正负点细化同一候选 mask、SAM 2 反向点启用背景过滤且空结果移除旧候选、AI 页生成 mask 自动选中并可通过分类树换标签、AI 页推送到工作区编辑保留选择、SAM 2 视频传播、SAM 3 语义文本前端执行路径、SAM 3 semantic 请求级阈值、SAM 3 worker 单 2D mask 转 polygon、SAM 3 工作区框选、SAM 3 video tracker 外部桥接、SAM 3 点交互不支持提示、空文本/空结果反馈、模型选择、GPU/SAM 状态、SAM 3 外部 worker 桥接、AI 参数 options、局部裁剪推理、背景过滤、状态徽标、坐标归一化、正负点 labels、polygons 转 path、后端 fake registry |
| R7 标注保存 | `src/components/VideoWorkspace.test.tsx`, `src/components/CanvasArea.test.tsx`, `src/lib/api.test.ts`, `backend/tests/test_ai.py` | 保存标注、加载回显、更新 dirty 标注、清空删除已保存标注、GT mask 多类别导入、seed point 回显/归一化、项目不存在、帧不存在 |
| R8 模板库 | `src/components/TemplateRegistry.test.tsx`, `src/lib/api.test.ts`, `backend/tests/test_templates.py` | 前端模板加载/新建/编辑/删除、JSON 分类导入、mapping_rules 解包/打包、后端模板 CRUD |
| R9 本体检查面板 | `src/components/OntologyInspector.test.tsx`, `src/components/CanvasArea.test.tsx`, `src/store/useStore.test.ts` | 模板选择、分类展示、具体分类选择、Canvas 选区同步、点击分类给已选 mask 换标签、自定义分类本地添加 |
| R10 Dashboard 与 WebSocket | `src/lib/api.test.ts`, `src/lib/websocket.test.ts`, `src/components/Dashboard.test.tsx`, `backend/tests/test_dashboard.py`, `backend/tests/test_main.py`, `backend/tests/test_progress_events.py`, `backend/tests/test_tasks.py` | 后端概览接口、任务表驱动队列、任务取消/重试/详情、cancelled 事件、Redis 进度事件 payload/发布、地址推导、消息订阅、队列更新、heartbeat |
| R10 Dashboard 与 WebSocket | `src/lib/api.test.ts`, `src/lib/websocket.test.ts`, `src/components/Dashboard.test.tsx`, `backend/tests/test_dashboard.py`, `backend/tests/test_main.py`, `backend/tests/test_progress_events.py`, `backend/tests/test_tasks.py` | 后端概览接口、任务表驱动队列、任务取消/重试/详情、cancelled 事件、Redis 进度事件 payload/发布、地址推导、消息订阅、连接状态回调、队列更新、heartbeat |
| R11 导出 | `src/components/VideoWorkspace.test.tsx`, `src/lib/api.test.ts`, `backend/tests/test_export.py` | COCO/PNG 按钮下载、导出前自动保存、导出路径、JSON 结构、mask ZIP、zIndex 语义融合 |
| R12 配置 | `src/lib/config.test.ts` | env 优先、hostname 推导、WS 推导 |
| R13 文档与测试 | `doc/09-test-plan.md` | 测试覆盖矩阵 |
@@ -34,15 +34,15 @@
|------|--------|----------|----------|
| R1 | 登录页、默认开发凭证、token 写入、失败提示、后端 401 | `Login.test.tsx`, `test_auth.py` | 已覆盖 |
| R2 | 项目列表/创建/选择、视频导入、DICOM 导入、后端项目和帧 CRUD | `ProjectLibrary.test.tsx`, `api.test.ts`, `test_projects.py` | 已覆盖 |
| R3 | 文件类型校验、自动/指定项目上传、视频/DICOM 拆帧任务、`parse_fps/max_frames/target_width`、标准帧序列 metadata、任务查询、取消、重试、worker 取消停止 | `test_media.py`, `test_tasks.py` | 已覆盖 |
| R4 | 工作区加载帧、无帧自动解析、Canvas 底图、缩略图/range/左右方向键切帧、播放、按 FPS 显示时间 | `VideoWorkspace.test.tsx`, `FrameTimeline.test.tsx`, `CanvasArea.test.tsx` | 已覆盖 |
| R5 | 工具切换、AI 跳转、矩形/圆/线/点/多边形绘制、已有 mask 上继续绘制 | `ToolsPalette.test.tsx`, `CanvasArea.test.tsx` | 已覆盖 |
| R5 | 顶点编辑、顶点删除、整块删除、撤销/重做、区域合并、区域去除、hole even-odd 渲染 | `CanvasArea.test.tsx`, `useStore.test.ts` | 已覆盖 |
| R6 | SAM 2 点/框/interactive、SAM 2 视频传播、SAM 3 semantic、SAM 3 box、SAM 3 video tracker、SAM 3 不支持点交互时的前端反馈、模型选择、GPU/模型状态、参数 options、polygons 转 mask | `api.test.ts`, `CanvasArea.test.tsx`, `AISegmentation.test.tsx`, `VideoWorkspace.test.tsx`, `ModelStatusBadge.test.tsx`, `test_ai.py`, `test_sam3_engine.py` | 已覆盖 |
| R3 | 文件类型校验、自动/指定项目上传、视频导入与生成帧分离、显式 FPS 生成帧、视频/DICOM 拆帧任务、`parse_fps/max_frames/target_width`、标准帧序列 metadata、任务查询、取消、重试、worker 取消停止 | `ProjectLibrary.test.tsx`, `test_media.py`, `test_tasks.py` | 已覆盖 |
| R4 | 工作区加载帧、无帧项目不自动解析、Canvas 底图、缩略图/range/左右方向键切帧、播放、按 FPS 显示时间 | `VideoWorkspace.test.tsx`, `FrameTimeline.test.tsx`, `CanvasArea.test.tsx` | 已覆盖 |
| R5 | 工具切换、调整多边形入口、AI 跳转、矩形/圆/线/点/多边形绘制、已有 mask 上继续绘制 | `ToolsPalette.test.tsx`, `CanvasArea.test.tsx` | 已覆盖 |
| R5 | 顶点编辑、边中点插点、双击边界按位置插点、顶点删除、整块删除、撤销/重做、区域合并、区域去除、hole even-odd 渲染 | `CanvasArea.test.tsx`, `useStore.test.ts` | 已覆盖 |
| R6 | SAM 2 点/框/interactive、SAM 2 纯文本提示拦截、SAM 2 最高分候选去重、AI 页生成 mask 自动选中并可换标签、AI 页推送到工作区编辑保留选择、SAM 2 视频传播、SAM 3 semantic、SAM 3 semantic 请求级阈值、SAM 3 worker 单 2D mask 转 polygon、SAM 3 box、SAM 3 video tracker、SAM 3 不支持点交互时的前端反馈、模型选择、GPU/模型状态、参数 options、polygons 转 mask | `api.test.ts`, `CanvasArea.test.tsx`, `AISegmentation.test.tsx`, `VideoWorkspace.test.tsx`, `ModelStatusBadge.test.tsx`, `test_ai.py`, `test_sam2_engine.py`, `test_sam3_engine.py` | 已覆盖 |
| R7 | 保存、查询、更新、删除标注、工作区回显、清空已保存标注、GT mask 导入和 seed point 回写 | `VideoWorkspace.test.tsx`, `CanvasArea.test.tsx`, `api.test.ts`, `test_ai.py` | 已覆盖 |
| R8 | 模板加载、新建、编辑、删除、JSON 分类导入、mapping_rules 映射、后端 CRUD | `TemplateRegistry.test.tsx`, `api.test.ts`, `test_templates.py` | 已覆盖 |
| R9 | 模板选择、分类展示、分类选择、已选 mask 换标签、自定义本地分类、占位状态 | `OntologyInspector.test.tsx`, `CanvasArea.test.tsx`, `useStore.test.ts` | 已覆盖 |
| R10 | Dashboard 概览、队列、活动日志、WebSocket progress/complete/error/status/cancelled、取消/重试/详情、heartbeat | `Dashboard.test.tsx`, `websocket.test.ts`, `test_dashboard.py`, `test_main.py`, `test_progress_events.py`, `test_tasks.py` | 已覆盖 |
| R10 | Dashboard 概览、队列、活动日志、WebSocket progress/complete/error/status/cancelled、取消/重试/详情、连接状态回调、heartbeat | `Dashboard.test.tsx`, `websocket.test.ts`, `test_dashboard.py`, `test_main.py`, `test_progress_events.py`, `test_tasks.py` | 已覆盖 |
| R11 | COCO/PNG ZIP 导出、导出前保存、路径和 JSON/ZIP 结构、zIndex 融合 | `VideoWorkspace.test.tsx`, `api.test.ts`, `test_export.py` | 已覆盖 |
| R12 | API/WS 地址 env 优先和 hostname 推导 | `config.test.ts` | 已覆盖 |
| R13 | 文档测试矩阵与功能点追踪 | `doc/09-test-plan.md` | 已覆盖 |
@@ -51,6 +51,8 @@
- R5补充 `CanvasArea.test.tsx` 中圆形和线段手工绘制测试,明确验证 metadata、segmentation、bbox/area 和草稿状态。
- R6补充 `AISegmentation.test.tsx` 中 SAM 3 semantic 文本推理测试,验证前端传参和返回 mask 绑定当前语义类别。
- R6补充 SAM 2 纯文本提示拦截、SAM 2 多候选只保留最高分、SAM 2 engine 单候选请求测试,避免多个重叠候选 mask 被同时叠加。
- R6补充 Canvas 工作区 SAM 2 反向点背景过滤测试,覆盖请求 options 和过滤为空时清除旧候选 mask。
- R6补充 SAM 3 空文本、空结果和工作区点交互不支持提示测试,避免前端静默失败。
- R6补充 SAM 3 工作区 box prompt 测试和外部 worker box prompt 测试,验证官方 `add_geometric_prompt()` 正框链路。
- R6补充 `POST /api/ai/propagate` 后端测试,验证 seed mask 传播结果会保存为后续帧标注并保留 class 元数据。