功能新增: - 新增 POST /api/ai/analyze-mask 后端接口,基于 mask polygon、bbox、points 和 score 返回置信度来源、面积、拓扑锚点和后端分析提示。 - 前端新增 analyzeMask API 封装,并在本体检查面板读取选中 mask 的后端几何属性和重新提取拓扑锚点结果。 - 右侧语义分类树点击分类时,会给当前选中 mask 换标签、更新 class 元数据,并将选中 mask 移到前端渲染最上层,方便继续编辑。 - 分割工作区画布新增上下文操作提示,覆盖多边形 Enter 完成、Esc 取消、首节点闭合、拖拽图形、点区域、SAM 点/框提示、区域合并/去除选择顺序和多边形编辑。 - AI 智能分割画布新增正向点、反向点、边界框选和视口控制的上下文提示。 - 自动传播交互收敛为参考帧加起止帧范围加单个“自动传播”按钮,默认使用当前参考帧全部 mask 作为 seed。 - 时间轴改为用浅蓝色进度条区段标记自动传播生成的帧,而不是已编辑帧竖线提示。 Bugfix: - AI 分割页无当前帧时移除外部演示背景图,改为明确空状态提示,避免误以为外部图片可参与真实推理。 - 工具栏魔法棒文案改为“打开 AI 智能分割”,避免误导为直接触发 SAM 推理。 - Canvas 底部当前图层信息改为显示真实选中 mask 标签和 annotation id,不再使用固定占位文本。 - 已保存标注回显时保留 mask metadata 中的传播来源、score 等字段,供时间轴和属性面板识别。 - 清理 server.ts 中遗留的 /api/login、/api/projects、/api/templates 内存 mock API,避免和 FastAPI 真实后端混淆。 测试: - 补充 analyze-mask 后端测试,覆盖后端几何属性和锚点返回。 - 补充 api.analyzeMask 前端契约测试,覆盖 normalized polygon、bbox、points 和 extract_skeleton payload。 - 补充本体面板测试,覆盖后端属性读取、自定义分类写回后端模板、选中 mask 换标签和置顶显示。 - 补充 Canvas 测试,覆盖上下文提示、多边形完成提示、布尔选择顺序提示、当前图层真实显示和编辑优先级。 - 补充 AI 分割测试,覆盖无帧空状态和提示工具上下文提示。 - 更新 Konva 测试 mock,支持拖动过程、stroke/dash/fillRule 等渲染断言。 文档: - 更新 README 和 AGENTS,说明 server.ts 不再保留业务 mock API。 - 更新 doc/02、doc/03、doc/04、doc/05、doc/07、doc/08、doc/09,记录后端属性分析、分类置顶显示、上下文提示、自动传播按钮、传播帧标记、测试覆盖和当前剩余限制。
286 lines
13 KiB
Markdown
286 lines
13 KiB
Markdown
# 接口契约清单
|
||
|
||
## 前端 API 基础配置
|
||
|
||
位置:`src/lib/config.ts`、`src/lib/api.ts`
|
||
|
||
```ts
|
||
API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://<current-browser-host>:8000'
|
||
timeout: 30000
|
||
```
|
||
|
||
前端 request interceptor 会从 localStorage 读取 `token`,附加:
|
||
|
||
```http
|
||
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.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_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 视频片段传播并保存标注 |
|
||
| 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` 作为心跳;后端收到任意文本心跳后返回:
|
||
|
||
```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`,不再依赖是否刚好收到任务进度事件。
|
||
|
||
## 关键请求体
|
||
|
||
### 登录
|
||
|
||
```json
|
||
{
|
||
"username": "admin",
|
||
"password": "123456"
|
||
}
|
||
```
|
||
|
||
### 创建项目
|
||
|
||
```json
|
||
{
|
||
"name": "example.mp4",
|
||
"description": "导入说明",
|
||
"parse_fps": 30
|
||
}
|
||
```
|
||
|
||
### 创建标准帧序列拆帧任务
|
||
|
||
```text
|
||
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` 包含:
|
||
|
||
```json
|
||
{
|
||
"frame_index": 0,
|
||
"image_url": "http://...",
|
||
"width": 960,
|
||
"height": 540,
|
||
"timestamp_ms": 0,
|
||
"source_frame_number": 0
|
||
}
|
||
```
|
||
|
||
### 创建/更新模板
|
||
|
||
```json
|
||
{
|
||
"name": "腹腔镜胆囊切除术",
|
||
"color": "#06b6d4",
|
||
"z_index": 0,
|
||
"classes": [
|
||
{
|
||
"id": "cls-1",
|
||
"name": "胆囊",
|
||
"color": "#ffae00",
|
||
"zIndex": 280,
|
||
"category": "腹腔镜胆囊切除术"
|
||
}
|
||
],
|
||
"rules": []
|
||
}
|
||
```
|
||
|
||
### AI 推理请求体
|
||
|
||
前端 `predictMask()` 当前已适配后端 `PredictRequest`:
|
||
|
||
```json
|
||
{
|
||
"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`、累计 `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` 使用的最低置信度阈值。
|
||
|
||
后端响应:
|
||
|
||
```json
|
||
{
|
||
"polygons": [
|
||
[[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75]]
|
||
],
|
||
"scores": [0.5]
|
||
}
|
||
```
|
||
|
||
前端会把上面的 `polygons` 转成:
|
||
|
||
```json
|
||
{
|
||
"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]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 视频片段传播请求体
|
||
|
||
后端接口仍以单个 seed 为单位。工作区前端当前只提供一个“自动传播”按钮:当前打开帧作为参考帧,该帧全部 mask 作为 seed;用户设置传播起始帧和传播结束帧后,前端会在本地把多个 seed 或前后双向范围拆成多次顺序调用,避免同时启动多个视频 tracker。
|
||
|
||
单次调用示例:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
}
|
||
}
|
||
```
|
||
|
||
SAM 2.1 变体使用对应 video predictor 的 mask seed 传播;`model=sam2` 会兼容归一化为 tiny,`model=sam3` 当前不支持。响应会返回已创建的 `annotations`,保存的 `mask_data.source` 为 `<model_id>_propagation`,前端回显时会把该字段保留到 `Mask.metadata`,用于把自动传播帧在时间进度条上标为浅蓝色。
|
||
|
||
## 已完成的接口对齐
|
||
|
||
- `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/success/failed/cancelled 任务,并可通过 `getTask()` 查看失败详情;`summary.parsing_task_count` 仍只统计 queued/running。
|
||
- 工作区导出按钮已调用 `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 更新和整帧清空删除;复杂洞结构的专业编辑仍未实现。
|