- 前端保存标注写入并保留 instance_id,AI 自动推理 seed 携带 source_instance_id,避免同类多 mask 只按语义混在一起。 - 后端传播任务优先用 source_instance_id/instance_id 做幂等、替换和写入前清理,并保留 source_annotation_id/source_mask_id/legacy 兼容路径。 - 前端传播链匹配、删除/分类同步和布尔合并/去重加入实例 token,保持旧 lineage 和空间最近 legacy fallback。 - 补充前后端回归测试,覆盖同类别多实例传播、重传、布尔同步、断开多区域和保存/回显 metadata。 - 更新 AGENTS 与 doc 事实文档,明确 maskid 仍只用于语义分类、GT_label 和导出,不参与实例追踪。
22 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。缺失、过期或无效 token 返回 401;项目、帧、标注、任务、Dashboard 和导出使用全员共享项目库,所有登录用户可读取,admin/annotator 可写入。
前端封装的 API
| 函数 | 方法与路径 | 状态 | 说明 |
|---|---|---|---|
login(username, password) |
POST /api/auth/login |
对齐 | 后端返回 { token, token_type, username, user },前端保存 token 和当前用户 |
getCurrentUser() |
GET /api/auth/me |
对齐 | 用已有 Bearer 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} |
对齐 | 项目卡片删除按钮已接入,删除前使用站内确认弹窗 |
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, options?) |
POST /api/media/upload |
对齐 | multipart form-data;options.onProgress 用于项目库上传进度 |
uploadDicomBatch(files, projectId, options?) |
POST /api/media/upload/dicom |
对齐 | multipart form-data;options.onProgress 用于项目库上传进度,上传完成后项目库轮询解析任务进度 |
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 同步传播接口,供后端兼容和测试使用 |
queuePropagationTask(payload) |
POST /api/ai/propagate/task |
对齐 | 工作区“AI自动推理”入口;创建 Celery 后台任务并由任务表/进度流追踪 |
getAiModelStatus(selectedModel?) |
GET /api/ai/models/status |
对齐 | 返回 GPU 和四个 SAM 2.1 变体状态;selected_model=sam3 返回不支持 |
analyzeMask(mask, frame, options?) |
POST /api/ai/analyze-mask |
对齐 | 后端计算选中 mask 的置信度来源、拓扑锚点数量、面积和 bbox |
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;保存链路会先预检后端标注 id,已知缺失则直接用同一几何和 metadata 调用 saveAnnotation() 重新创建;预检后仍遇到 404 时也会重新创建并回显替换本地旧 id |
deleteAnnotation(annotationId) |
DELETE /api/ai/annotations/{annotation_id} |
对齐 | 工作区清空当前帧、关联传播帧、DEL/键盘删除和切换激活模板时删除已保存标注;批量删除前会先读取当前项目 annotation 列表,跳过本地陈旧 id,避免重复 DELETE 产生 404 |
importGtMask(file, projectId, frameId, templateId?, options?) |
POST /api/ai/import-gt-mask |
对齐 | multipart 上传 GT mask;支持 unknown_color_policy=discard/undefined;后端仅接受 8-bit 灰度 maskid 图或 8-bit RGB 三通道完全相同的 [X,X,X] maskid 图,0 为背景、X 为 1-255 的 maskid;16-bit/uint16 GT_label、全背景 0 图和普通彩色类别图会被拒绝,全背景错误信息固定为“GT Mask 图片中没有非背景 maskid 区域。”;按模板 maskId 匹配类别,未知 maskid 可舍弃或导入为未定义类别;尺寸不同会最近邻拉伸到当前帧,连通域会生成高精度 polygon 标注;导入标注可直接用于 /api/ai/analyze-mask 和 /api/ai/smooth-mask,前端不显示或拖动 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 |
exportSegmentationResults(projectId, options) |
GET /api/export/{projectId}/results |
对齐 | 新的统一导出入口;支持 scope=all/range/current、outputs=separate,gt_label,pro_label,mix_label、mix_opacity、start_frame/end_frame 和 frame_id 参数,返回包含 COCO JSON、maskid/GT 像素值映射、原始帧图片和所选 mask PNG 的 ZIP;mask_type=separate/gt_label/pro_label/mix_label/both 仍兼容 |
后端 FastAPI 接口
以下列表来自当前运行的 OpenAPI:
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/auth/login |
登录 |
| GET | /api/auth/me |
当前用户 |
| GET/POST/PATCH/DELETE | /api/admin/users |
管理员用户管理 |
| GET | /api/admin/audit-logs |
管理员审计日志 |
| POST | /api/admin/demo-factory-reset |
演示部署恢复出厂设置;请求体需 confirmation=RESET_DEMO_FACTORY;重置后保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目;同时按内置权威定义重建缺失的“腹腔镜胆囊切除术”“头颈部CT分割”系统模板,并覆盖恢复被修改或删减的默认语义分类树;响应包含兼容单个 project 和完整 projects 列表 |
| 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 点/框/interactive 推理 |
| POST | /api/ai/propagate |
当前启用 SAM 2 单 seed 同步视频片段传播并保存标注 |
| POST | /api/ai/propagate/task |
创建 SAM 2 自动传播后台任务;payload 可包含多个 seed/direction step |
| POST | /api/ai/analyze-mask |
分析前端选中 mask 的后端几何属性和拓扑锚点 |
| 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 | /api/export/{project_id}/results |
统一导出分割结果 ZIP,包含 annotations_coco.json、maskid_GT像素值_类别映射.json、原始图片/ 和按参数选择的 分开Mask分割结果/、GT_label图/、Pro_label彩色分割结果/、Mix_label重叠覆盖彩色分割结果/;GT_label 固定输出 8-bit uint8 PNG,背景为 0,类别值使用模板中的真实 maskid,maskid:0 待分类和背景同为 0,缺失 maskid 的旧标注才补下一个可用正整数;正整数 maskid 超出 1-255 时拒绝导出 |
| 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,
"maskId": 1,
"category": "腹腔镜胆囊切除术"
}
],
"rules": []
}
AI 推理请求体
前端 predictMask() 当前已适配后端 PredictRequest:
{
"image_id": 123,
"model": "sam2.1_hiera_tiny",
"prompt_type": "point",
"prompt_data": {
"points": [[0.5, 0.5]],
"labels": [1]
}
}
prompt_type 支持:
pointboxinteractive,用于 SAM 2 交互式细化,prompt_data同时携带box、累计points和labels。semantic当前被禁用;由于产品不提供文本提示,前端不会显示语义文本入口,后端收到 semantic 会返回 400。
SAM 2 点提示和 auto fallback 当前只采用最高分候选 mask,避免同一提示下多个备选 mask 被前端叠加显示。
工作区 SAM 2 请求包含反向点时,CanvasArea 会发送 options.auto_filter_background=true 和 options.min_score=0.05;如果负向点过滤后没有可用 polygon,前端会移除当前旧候选 mask 并要求重新框选或添加正向点。
当前 registry 暴露 sam2.1_hiera_tiny、sam2.1_hiera_small、sam2.1_hiera_base_plus、sam2.1_hiera_large,并兼容 sam2 作为 tiny 别名;发送 model=sam3 会返回 400 Unsupported model。SAM 3 源码文件保留在仓库中,但没有接入当前运行时模型列表。
可选 options 字段:
crop_to_prompt:对 point/box/interactive prompt 按锚点或框附近区域裁剪后推理,再把 polygon 回映射到原图坐标。auto_filter_background:过滤低分结果,并移除包含负向点的 polygon。min_score:配合auto_filter_background使用的最低置信度阈值。
后端响应:
{
"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]
}
]
}
视频片段传播请求体
POST /api/ai/propagate 仍是单 seed 同步接口。工作区实际使用 POST /api/ai/propagate/task:当前打开帧作为参考帧,该帧全部 mask 作为 seed;用户设置传播起始帧和传播结束帧后,前端会在本地把多个 seed 或前后双向范围拆成 steps,一次提交为 propagate_masks 后台任务,避免长 HTTP 请求和多个视频 tracker 并发抢占 GPU。
单次调用示例:
{
"project_id": 1,
"frame_id": 123,
"model": "sam2.1_hiera_tiny",
"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, "maskId": 1},
"template_id": 2,
"source_instance_id": "instance-123"
}
}
后台任务调用示例:
{
"project_id": 1,
"frame_id": 123,
"model": "sam2.1_hiera_tiny",
"include_source": false,
"save_annotations": true,
"steps": [
{
"direction": "forward",
"max_frames": 30,
"seed": {
"polygons": [[[0.1, 0.1], [0.3, 0.1], [0.3, 0.3]]],
"label": "胆囊",
"color": "#ff0000",
"source_instance_id": "instance-123"
}
}
]
}
SAM 2.1 变体使用对应 video predictor 的 mask seed 传播;model=sam2 会兼容归一化为 tiny,model=sam3 当前不支持。响应会返回已创建的 annotations,保存的 mask_data.source 为 <model_id>_propagation,前端回显时会把该字段保留到 Mask.metadata,用于在视频处理进度条上把自动传播帧显示为蓝色区段。
后台任务入队接口会先规范化/校验 model 字段中的 SAM 2.1 权重 id,再把规范化后的权重 id 写入 processing_tasks.payload.model;前端提交传播前会先保存当前项目中的 draft/dirty mask,使 seed 尽量携带稳定的 source_instance_id、source_annotation_id 和 source_mask_id。如果参考 mask 本身来自自动传播且未被编辑,前端会继承其 source_instance_id 和 propagation_seed_signature,让后端识别它仍是原始 seed 的同一条传播链;如果该 mask 被编辑,保存时只保留 lineage,不继承旧签名,从而触发旧结果清理和重传。worker 保存传播结果时会写入 instance_id、source_instance_id、propagation_seed_key、propagation_seed_signature 和 propagation_direction。同一目标帧段内,同一 seed、同一权重、同一方向再次传播时,如果所有目标帧已有同签名结果,worker 会跳过该 seed;如果签名变化、目标帧段只部分覆盖或本次改用其他 SAM 2.1 权重,worker 会先删除本次目标帧段内的旧自动传播标注再保存新结果。同一参考帧多个同类别 seed 会优先按 source_instance_id/instance_id 区分实例,再兼容 source_annotation_id、source_mask_id 和 propagation_seed_key,避免 label/color/class/maskid 相同的不同 mask 互相清理;旧版本缺少稳定来源 id 的传播结果才走 label/color/class 兼容清理,避免保存后的稳定 id 无法替换旧结果。任务运行中/完成后会写入 processing_tasks.result.model、completed_steps、processed_frame_count、created_annotation_count、deleted_annotation_count、skipped_seed_count 和每个 step 的权重/方向/数量结果;前端通过 GET /api/tasks/{task_id} 轮询,Dashboard 同时可通过 Redis/WebSocket 进度流显示该任务。
已完成的接口对齐
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};工作区批量删除前会先用GET /api/ai/annotations预检存在的 id,跳过本地陈旧 id。importGtMask()已接入POST /api/ai/import-gt-mask,导入后端生成的高精度 polygon 标注、原始gt_label_value、原图尺寸/是否拉伸信息。导入端使用cv2.IMREAD_UNCHANGED读取后校验 dtype,仅接受 8-bit 灰度图和 8-bit RGB 三通道相等图,并按模板maskId匹配类别;16-bit/uint16 GT_label、全背景 0 图和普通彩色 RGB 类别图都会返回格式错误,全背景图保留“GT Mask 图片中没有非背景 maskid 区域。”提示;超出现有类别时由unknown_color_policy决定舍弃或写为gt_unknown_class未定义类别。导入 mask 与普通 mask 共用拓扑统计、边缘平滑和保存更新接口,中空导入结果通过mask_data.holes和metadata.polygonRingCounts回显为可编辑内洞,前端不显示黄色 seed point。exportMasks()已接入GET /api/export/{projectId}/masks。parseMedia()已改为创建 Celery 后台任务,并返回ProcessingTask。queuePropagationTask()已接入/api/ai/propagate/task,自动传播不再依赖长时间同步 HTTP 请求;传播 seed 可携带与polygons对齐的holes和source_instance_id,后端 seed 签名、SAM 2 seed mask 栅格化和传播结果保存都会保留内洞,并用实例 id 区分同语义多 mask。getTask()已接入GET /api/tasks/{taskId}。cancelTask()已接入POST /api/tasks/{taskId}/cancel。retryTask()已接入POST /api/tasks/{taskId}/retry。getDashboardOverview()已从processing_tasks聚合解析队列。- Dashboard 任务列表已展示 queued/running/success/failed/cancelled 任务,并可通过
getTask()查看失败详情;summary.parsing_task_count仍只统计 queued/running。 - 工作区“分割结果导出”已调用
exportSegmentationResults(),并会先保存未归档 mask;旧的exportCoco()/exportMasks()仍保留为兼容接口。 - PNG mask ZIP 已包含每帧
semantic_frame_*.png和semantic_classes.json,重叠区域按 zIndex 裁决。 - 统一导出 ZIP 下载文件名为
{项目库项目名}_seg_T_{起始时间戳}-{结束时间戳}_P_{起始项目帧序号}-{结束项目帧序号}.zip;项目名来自Project.name并会替换文件系统不安全字符,时间戳来自帧timestamp_ms并格式化为0h00m00s000ms,帧号使用项目抽帧后的 1-basedframe_index + 1,不使用原视频source_frame_number。ZIP 内包含annotations_coco.json、maskid_GT像素值_类别映射.json和原始图片/。原始图片按视频名称_时间戳_项目帧序号命名;选择分开 mask 时写入分开Mask分割结果/{视频名称_时间戳_项目帧序号}_分别导出/{视频名称_时间戳_项目帧序号}_{类别名称}_maskid{maskid}.png,同一帧同一类别会合并为一张二值 mask;选择 GT_label 图时写入GT_label图/{视频名称_时间戳_项目帧序号}.png,固定为 8-bit uint8 PNG;选择 Pro_label 彩色图时写入Pro_label彩色分割结果/{视频名称_时间戳_项目帧序号}.png;选择 Mix_label 叠加图时写入Mix_label重叠覆盖彩色分割结果/{视频名称_时间戳_项目帧序号}.png,透明度由mix_opacity控制,默认 0.3。导出时 maskid 与 GT_label 像素值相同;有模板 maskid 的类别保留真实 maskid,其中maskid:0的“待分类”和背景同为 0,缺失 maskid 的旧标注补下一个可用正整数并写入映射 JSON,跨图一致;正整数 maskid 必须在 1-255 内,超出时拒绝导出;maskid 不参与覆盖排序,覆盖顺序仍使用内部拖拽排序字段。
仍需处理的接口问题
- WebSocket 地址已从
VITE_WS_PROGRESS_URL读取,未配置时从API_BASE_URL推导;部署时仍要确认浏览器能访问该地址。 - Celery worker 进度会写 PostgreSQL 任务表,同时发布到 Redis
seg:progress;FastAPI 订阅后广播到/ws/progress。 - 已保存标注目前支持分类级更新、polygon 顶点拖动、顶点删除、边中点插入、多 polygon 子区域选择、中空 mask 内洞 ring 编辑后的 PATCH 更新和整帧清空删除;
mask_data.polygons保存外圈,mask_data.holes保存与外圈对齐的内洞,metadata.polygonRingCounts支撑前端把外圈/内洞重新组合成可编辑结构。