功能增加: - 将视频导入和生成帧拆成两个明确动作,项目库生成帧时选择 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 需求/设计/接口/测试矩阵,按当前实现冻结功能状态。
13 KiB
接口契约清单
前端 API 基础配置
位置:src/lib/config.ts、src/lib/api.ts
API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://<current-browser-host>:8000'
timeout: 30000
前端 request interceptor 会从 localStorage 读取 token,附加:
Authorization: Bearer <token>
当前后端多数接口没有鉴权依赖,所以这个 header 主要是前端侧行为。
前端封装的 API
| 函数 | 方法与路径 | 状态 | 说明 |
|---|---|---|---|
login(username, password) |
POST /api/auth/login |
对齐 | 后端返回 { token, username },前端只使用 token |
getProjects() |
GET /api/projects |
对齐 | 前端映射 frame_count、thumbnail_url 等字段 |
createProject(payload) |
POST /api/projects |
对齐 | 支持 name、description、parse_fps |
updateProject(id, payload) |
PATCH /api/projects/{id} |
对齐 | 后端是 PATCH /api/projects/{id} |
deleteProject(id) |
DELETE /api/projects/{id} |
对齐 | 当前 UI 未明显接入 |
getTemplates() |
GET /api/templates |
对齐 | 前端从 mapping_rules 取 classes/rules |
createTemplate(payload) |
POST /api/templates |
对齐 | 后端会打包 classes/rules 到 mapping_rules |
updateTemplate(id, payload) |
PATCH /api/templates/{id} |
对齐 | 模板编辑页使用 |
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 |
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 重试任务 |
getProjectFrames(projectId) |
GET /api/projects/{id}/frames |
对齐 | 后端返回预签名 image_url,以及 timestamp_ms、source_frame_number |
predictMask(payload) |
POST /api/ai/predict |
对齐 | 前端发送 image_id/prompt_type/prompt_data/model,并把后端 polygons 转为 masks[].pathData |
propagateMasks(payload) |
POST /api/ai/propagate |
对齐 | 当前帧 seed mask 向视频片段传播,并保存后续帧标注 |
getAiModelStatus(selectedModel?) |
GET /api/ai/models/status |
对齐 | 返回 GPU、SAM 2、SAM 3 的真实运行状态 |
getProjectAnnotations(projectId, frameId?) |
GET /api/ai/annotations |
对齐 | 前端加载工作区时用于回显已保存标注 |
saveAnnotation(payload) |
POST /api/ai/annotate |
对齐 | 工作区归档保存当前项目未保存 mask |
updateAnnotation(annotationId, payload) |
PATCH /api/ai/annotations/{annotation_id} |
对齐 | 工作区归档保存 dirty mask |
deleteAnnotation(annotationId) |
DELETE /api/ai/annotations/{annotation_id} |
对齐 | 工作区清空当前帧已保存标注 |
importGtMask(file, projectId, frameId, templateId?) |
POST /api/ai/import-gt-mask |
对齐 | multipart 上传 GT mask,后端按非零像素值/连通域生成 polygon 标注和 seed point |
getDashboardOverview() |
GET /api/dashboard/overview |
对齐 | Dashboard 初始统计、队列和活动日志 |
exportCoco(projectId) |
GET /api/export/{projectId}/coco |
对齐 | 后端实际是 GET /api/export/{project_id}/coco |
exportMasks(projectId) |
GET /api/export/{projectId}/masks |
对齐 | 下载单标注 mask、语义融合 mask 和类别映射 ZIP |
后端 FastAPI 接口
以下列表来自当前运行的 OpenAPI:
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/auth/login |
登录 |
| POST | /api/projects |
创建项目 |
| GET | /api/projects |
项目列表 |
| GET | /api/projects/{project_id} |
项目详情 |
| PATCH | /api/projects/{project_id} |
更新项目 |
| DELETE | /api/projects/{project_id} |
删除项目 |
| POST | /api/projects/{project_id}/frames |
添加帧记录 |
| GET | /api/projects/{project_id}/frames |
项目帧列表 |
| GET | /api/projects/{project_id}/frames/{frame_id} |
单帧详情 |
| POST | /api/templates |
创建模板 |
| GET | /api/templates |
模板列表 |
| GET | /api/templates/{template_id} |
模板详情 |
| PATCH | /api/templates/{template_id} |
更新模板 |
| DELETE | /api/templates/{template_id} |
删除模板 |
| POST | /api/media/upload |
上传视频/图片/DICOM 单文件 |
| POST | /api/media/upload/dicom |
批量上传 DICOM |
| POST | /api/media/parse |
创建 Celery 拆帧任务;query 支持 project_id、source_type、parse_fps、max_frames、target_width |
| GET | /api/tasks |
查询后台任务列表 |
| GET | /api/tasks/{task_id} |
查询单个后台任务 |
| POST | /api/tasks/{task_id}/cancel |
取消后台任务 |
| POST | /api/tasks/{task_id}/retry |
重试失败或取消的后台任务 |
| POST | /api/ai/predict |
SAM 2 / SAM 3 可选推理 |
| POST | /api/ai/propagate |
SAM 2 / SAM 3 视频片段传播并保存标注 |
| GET | /api/ai/models/status |
GPU 和 SAM 模型状态 |
| POST | /api/ai/auto |
自动分割 |
| POST | /api/ai/annotate |
保存 AI 标注 |
| POST | /api/ai/import-gt-mask |
导入 GT mask 并生成标注/seed point |
| GET | /api/ai/annotations |
查询项目标注,可选按帧过滤 |
| PATCH | /api/ai/annotations/{annotation_id} |
更新已保存标注 |
| DELETE | /api/ai/annotations/{annotation_id} |
删除已保存标注 |
| GET | /api/dashboard/overview |
Dashboard 聚合快照 |
| GET | /api/export/{project_id}/coco |
导出 COCO JSON |
| GET | /api/export/{project_id}/masks |
导出 PNG mask ZIP |
| GET | /health |
健康检查 |
| WS | /ws/progress |
WebSocket 进度通道,未出现在 OpenAPI paths 中 |
WebSocket 进度通道
/ws/progress 用于 Dashboard 实时接收后台任务状态。前端连接成功后会定时发送 ping 作为心跳;后端收到任意文本心跳后返回:
{
"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,不再依赖是否刚好收到任务进度事件。
关键请求体
登录
{
"username": "admin",
"password": "123456"
}
创建项目
{
"name": "example.mp4",
"description": "导入说明",
"parse_fps": 30
}
创建标准帧序列拆帧任务
POST /api/media/parse?project_id=1&parse_fps=15&max_frames=120&target_width=960
任务 payload 会记录本次拆帧参数;完成后的 result.frame_sequence 返回 original_fps、parse_fps、frame_count、duration_ms、target_width、帧宽高和 MinIO object prefix。每条 FrameOut 包含:
{
"frame_index": 0,
"image_url": "http://...",
"width": 960,
"height": 540,
"timestamp_ms": 0,
"source_frame_number": 0
}
创建/更新模板
{
"name": "腹腔镜胆囊切除术",
"color": "#06b6d4",
"z_index": 0,
"classes": [
{
"id": "cls-1",
"name": "胆囊",
"color": "#ffae00",
"zIndex": 280,
"category": "腹腔镜胆囊切除术"
}
],
"rules": []
}
AI 推理请求体
前端 predictMask() 当前已适配后端 PredictRequest:
{
"image_id": 123,
"model": "sam2",
"prompt_type": "point",
"prompt_data": {
"points": [[0.5, 0.5]],
"labels": [1]
}
}
prompt_type 支持:
pointboxinteractive,用于 SAM 2 交互式细化,prompt_data同时携带box、累计points和labels。semantic,选择sam3时进入 SAM 3 文本语义推理。前端 AI 页面不会再用 SAM 2 发送纯文本 semantic;SAM 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 不接正/反点修正。
可选 options 字段:
crop_to_prompt:对 point/box/interactive prompt 按锚点或框附近区域裁剪后推理,再把 polygon 回映射到原图坐标。auto_filter_background:过滤低分结果,并移除包含负向点的 polygon。min_score:配合auto_filter_background使用的最低置信度阈值;对 SAM 3 semantic 请求也会作为 external worker 的confidence_threshold传入,避免本地 checkpoint 在默认高阈值下返回 0 个 mask。
后端响应:
{
"polygons": [
[[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75]]
],
"scores": [0.5]
}
前端会把上面的 polygons 转成:
{
"masks": [
{
"pathData": "M 160 90 L 480 90 L 480 270 L 160 270 Z",
"segmentation": [[160, 90, 480, 90, 480, 270, 160, 270]],
"bbox": [160, 90, 320, 180]
}
]
}
视频片段传播请求体
工作区“传播片段”调用:
{
"project_id": 1,
"frame_id": 123,
"model": "sam2",
"direction": "forward",
"max_frames": 30,
"include_source": false,
"save_annotations": true,
"seed": {
"polygons": [[[0.1, 0.1], [0.3, 0.1], [0.3, 0.3]]],
"bbox": [0.1, 0.1, 0.2, 0.2],
"label": "胆囊",
"color": "#ff0000",
"class_metadata": {"id": "c1", "name": "胆囊", "color": "#ff0000", "zIndex": 20},
"template_id": 2
}
}
model=sam2 使用 SAM 2 video predictor 的 mask seed 传播;model=sam3 使用独立 Python 3.12 helper 中的 SAM 3 video tracker,并以 seed bbox 作为初始提示。响应会返回已创建的 annotations,保存的 mask_data.source 为 sam2_propagation 或 sam3_propagation。
已完成的接口对齐
updateProject()已从PUT改为PATCH。exportCoco()已从/api/export/coco/{projectId}改为/api/export/{projectId}/coco。- Canvas 已使用真实
frame.id作为image_id。 - 点和框坐标已转成后端需要的归一化坐标。
- 后端
polygons已在前端转成 Konva 可渲染的 path。 saveAnnotation()已接入POST /api/ai/annotate。getProjectAnnotations()已接入GET /api/ai/annotations。updateAnnotation()已接入PATCH /api/ai/annotations/{annotationId}。deleteAnnotation()已接入DELETE /api/ai/annotations/{annotationId}。importGtMask()已接入POST /api/ai/import-gt-mask,导入后端生成的 polygon 标注、原始gt_label_value和 seed point。exportMasks()已接入GET /api/export/{projectId}/masks。parseMedia()已改为创建 Celery 后台任务,并返回ProcessingTask。getTask()已接入GET /api/tasks/{taskId}。cancelTask()已接入POST /api/tasks/{taskId}/cancel。retryTask()已接入POST /api/tasks/{taskId}/retry。getDashboardOverview()已从processing_tasks聚合解析队列。- Dashboard 任务列表已展示 queued/running/failed/cancelled 任务,并可通过
getTask()查看失败详情。 - 工作区导出按钮已调用
exportCoco()/exportMasks(),并会先保存未归档 mask。 - PNG mask ZIP 已包含每帧
semantic_frame_*.png和semantic_classes.json,重叠区域按 zIndex 裁决。
仍需处理的接口问题
- WebSocket 地址已从
VITE_WS_PROGRESS_URL读取,未配置时从API_BASE_URL推导;部署时仍要确认浏览器能访问该地址。 - Celery worker 进度会写 PostgreSQL 任务表,同时发布到 Redis
seg:progress;FastAPI 订阅后广播到/ws/progress。 - 已保存标注目前支持分类级更新、polygon 顶点拖动、顶点删除、边中点插入、多 polygon 子区域选择编辑后的 PATCH 更新和整帧清空删除;复杂洞结构的专业编辑仍未实现。