Files
Pre_Seg_Server/doc/04-api-contracts.md
admin f365539ff2 fix: 避免自动传播重复叠加同源 mask
Bugfix:自动传播 worker 改为在本次目标帧段内按 seed 来源、方向、权重和签名查找旧传播结果;未修改且目标帧已覆盖时直接跳过,不再重复跑 SAM 造成 mask 堆叠。

Bugfix:同一 seed 被编辑、目标帧段只部分覆盖或切换 SAM 2.1 权重时,worker 会先删除本次目标帧段内同源旧自动传播标注,再重新传播。

Bugfix:未编辑的自动传播结果再次作为参考 seed 时会继承原始 propagation_seed_signature;编辑后的传播结果只保留 source_annotation_id/source_mask_id lineage,不继承旧签名,从而触发重传路径。

Bugfix:后端传播签名增加 canonical rounding,减少浮点精度细微变化导致未编辑 mask 被误判为已修改。

功能调整:清空片段遮罩改成与自动传播一致的时间轴范围选择流程,首次点击进入选区,拖拽选择起止帧后点击确认清空才执行。

接口契约:PropagationSeed 增加 propagation_seed_signature 字段,用于前端把未编辑传播结果绑定回原始 seed 传播链。

测试:补充前端 VideoWorkspace 范围清空、传播 lineage 传递测试;补充后端未编辑传播 seed 跳过重复传播、旧结果清理与换权重重传测试。

文档:同步更新 doc/03、doc/04、doc/07、doc/08、doc/09,明确 A/B 传播去重规则、清空片段范围选择和新增 seed signature 契约。
2026-05-02 07:11:03 +08:00

16 KiB
Raw Blame History

接口契约清单

前端 API 基础配置

位置:src/lib/config.tssrc/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_countthumbnail_url 等字段
createProject(payload) POST /api/projects 对齐 支持 namedescriptionparse_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_fpsmax_framestarget_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_mssource_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 对齐 工作区自动传播入口;创建 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
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_idsource_typeparse_fpsmax_framestarget_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 /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_fpsparse_fpsframe_countduration_mstarget_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.1_hiera_tiny",
  "prompt_type": "point",
  "prompt_data": {
    "points": [[0.5, 0.5]],
    "labels": [1]
  }
}

prompt_type 支持:

  • point
  • box
  • interactive,用于 SAM 2 交互式细化,prompt_data 同时携带 box、累计 pointslabels
  • semantic 当前被禁用;由于产品不提供文本提示,前端不会显示语义文本入口,后端收到 semantic 会返回 400。

SAM 2 点提示和 auto fallback 当前只采用最高分候选 mask避免同一提示下多个备选 mask 被前端叠加显示。

工作区 SAM 2 请求包含反向点时,CanvasArea 会发送 options.auto_filter_background=trueoptions.min_score=0.05;如果负向点过滤后没有可用 polygon前端会移除当前旧候选 mask 并要求重新框选或添加正向点。

当前 registry 暴露 sam2.1_hiera_tinysam2.1_hiera_smallsam2.1_hiera_base_plussam2.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},
    "template_id": 2
  }
}

后台任务调用示例:

{
  "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"
      }
    }
  ]
}

SAM 2.1 变体使用对应 video predictor 的 mask seed 传播;model=sam2 会兼容归一化为 tinymodel=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_annotation_id,同时仍会携带 source_mask_id。如果参考 mask 本身来自自动传播且未被编辑,前端会继承其 propagation_seed_signature,让后端识别它仍是原始 seed 的同一条传播链;如果该 mask 被编辑,保存时只保留 source_annotation_id/source_mask_id lineage不继承旧签名从而触发旧结果清理和重传。worker 保存传播结果时会写入 propagation_seed_keypropagation_seed_signaturepropagation_direction。同一目标帧段内,同一 seed、同一权重、同一方向再次传播时如果所有目标帧已有同签名结果worker 会跳过该 seed如果签名变化、目标帧段只部分覆盖或本次改用其他 SAM 2.1 权重worker 会先删除本次目标帧段内的旧自动传播标注再保存新结果。对于旧版本只记录前端临时 source_mask_id 的传播结果worker 会按方向和 label/color/class 做兼容清理,避免保存后的 source_annotation_id 无法替换旧结果。任务运行中/完成后会写入 processing_tasks.result.modelcompleted_stepsprocessed_frame_countcreated_annotation_countdeleted_annotation_countskipped_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}
  • importGtMask() 已接入 POST /api/ai/import-gt-mask,导入后端生成的 polygon 标注、原始 gt_label_value 和 seed point。
  • exportMasks() 已接入 GET /api/export/{projectId}/masks
  • parseMedia() 已改为创建 Celery 后台任务,并返回 ProcessingTask
  • queuePropagationTask() 已接入 /api/ai/propagate/task,自动传播不再依赖长时间同步 HTTP 请求。
  • 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。
  • 工作区导出按钮已调用 exportCoco() / exportMasks(),并会先保存未归档 mask。
  • PNG mask ZIP 已包含每帧 semantic_frame_*.pngsemantic_classes.json,重叠区域按 zIndex 裁决。

仍需处理的接口问题

  • WebSocket 地址已从 VITE_WS_PROGRESS_URL 读取,未配置时从 API_BASE_URL 推导;部署时仍要确认浏览器能访问该地址。
  • Celery worker 进度会写 PostgreSQL 任务表,同时发布到 Redis seg:progressFastAPI 订阅后广播到 /ws/progress
  • 已保存标注目前支持分类级更新、polygon 顶点拖动、顶点删除、边中点插入、多 polygon 子区域选择编辑后的 PATCH 更新和整帧清空删除;复杂洞结构的专业编辑仍未实现。