- 接入 SAM2 视频传播能力:新增 /api/ai/propagate,支持用当前帧 mask/polygon/bbox 作为 seed,通过 SAM2 video predictor 向前、向后或双向传播,并可保存为真实 annotation。 - 接入 SAM3 video tracker:通过独立 Python 3.12 external worker 调用 SAM3 video predictor/tracker,使用本地 checkpoint 与 bbox seed 执行视频级跟踪,并在模型状态中标记 video_track 能力。 - 完善 SAM 模型分发:sam_registry 按 model_id 明确区分 sam2 propagation 与 sam3 video_track,避免两个模型链路混用。 - 打通前端“传播片段”:VideoWorkspace 使用当前选中 mask 和当前 AI 模型调用后端传播接口,传播结果回写并刷新工作区已保存标注。 - 增强 SAM3 本地 checkpoint 配置:新增 sam3_checkpoint_path 配置和 .env.example 示例,状态检查改为基于本地 checkpoint/独立环境/模型包可用性。 - 完善视频拆帧参数:/api/media/parse 支持 parse_fps、max_frames、target_width,后端任务保存帧时间戳、源帧号和 frame_sequence 元数据。 - 增加运行时 schema 兼容处理:启动时为旧 frames 表补充 timestamp_ms 和 source_frame_number 列,避免旧库升级后缺字段。 - 强化 Canvas 标注编辑:补齐多边形闭合、点工具、顶点拖拽、边中点插入、Delete/Backspace 删除、区域合并和重叠去除等交互。 - 增强语义分类联动:选中 mask 后可通过右侧语义分类树更新标签、颜色和 class metadata,并同步到保存/导出链路。 - 增加关键帧时间轴体验:FrameTimeline 显示具体时间信息,并支持键盘左右方向键切换关键帧。 - 完善 AI 交互分割参数:前端保留正向点、反向点、框选和 interactive prompt 的调用状态,支持 SAM2 细化候选区域与 SAM3 bbox 入口。 - 扩展后端/前端 API 类型:新增 propagateMasks、传播请求/响应 schema,并补齐 annotation、导出、模型状态和任务接口的测试覆盖。 - 更新项目文档:同步 README、AGENTS、接口契约、需求冻结、设计冻结、前端元素审计、实施计划和测试计划,标明真实功能边界与剩余风险。 - 增加测试覆盖:补充 SAM2/SAM3 传播、SAM3 状态、媒体拆帧参数、Canvas 编辑、语义标签切换、时间轴、工作区传播和 API 合约测试。 - 加强仓库安全边界:将 sam3权重/ 加入 .gitignore,避免本地模型权重被误提交。 验证:npm run test:run;pytest backend/tests;npm run lint;npm run build;python -m py_compile;git diff --check。
17 KiB
17 KiB
当前设计冻结文档
冻结日期:2026-05-01
本文档描述当前代码结构、数据流、接口契约和测试边界。后续实现如果改变这些设计,应同步更新本文档和测试。
总体架构
当前系统由三层组成:
- React + TypeScript 前端 SPA。
- FastAPI 后端 API。
- PostgreSQL、MinIO、Redis、SAM 2 / SAM 3 等外部基础设施。
开发时前端通过 server.ts 启动 Express + Vite middleware;后端通过 backend/main.py 启动 FastAPI。前端业务接口主要访问 FastAPI,不依赖 server.ts 中保留的旧 mock API。
前端模块
| 模块 | 文件 | 设计职责 |
|---|---|---|
| 应用入口 | src/App.tsx |
根据登录状态和 activeModule 切换页面 |
| 全局状态 | 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 |
进度流连接、订阅和重连 |
| 模型状态 | 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/VideoWorkspace.tsx |
加载帧和模板,组织工具栏、Canvas、本体面板、时间轴 |
| Canvas | src/components/CanvasArea.tsx |
显示帧、缩放平移、点/框提示、渲染 mask |
| 工具栏 | src/components/ToolsPalette.tsx |
切换工具、跳转 AI 页面、触发 mask 撤销/重做 |
| 时间轴 | src/components/FrameTimeline.tsx |
帧导航、左右方向键切帧、播放和当前/总时长显示 |
| 本体面板 | src/components/OntologyInspector.tsx |
模板选择、分类树、本地自定义分类 |
| AI 页面 | src/components/AISegmentation.tsx |
独立 AI 推理视图,使用当前项目帧 |
| 模板库 | src/components/TemplateRegistry.tsx |
模板 CRUD、分类编辑、导入、排序 |
后端模块
| 模块 | 文件 | 设计职责 |
|---|---|---|
| 应用入口 | backend/main.py |
FastAPI app、CORS、路由注册、健康检查、WebSocket |
| 配置 | backend/config.py |
Pydantic settings |
| 数据库 | backend/database.py |
SQLAlchemy engine、session、Base |
| 模型 | backend/models.py |
Project、Frame、Template、Annotation、Mask、ProcessingTask |
| Schema | backend/schemas.py |
Pydantic 请求/响应模型 |
| Auth | backend/routers/auth.py |
开发登录 |
| Projects | backend/routers/projects.py |
项目与帧 CRUD |
| Templates | backend/routers/templates.py |
模板 CRUD 和 mapping_rules 打包/解包 |
| Media | backend/routers/media.py |
上传媒体和拆帧 |
| AI | backend/routers/ai.py |
SAM 2 / SAM 3 可选推理、视频传播、模型状态和标注保存 |
| Export | backend/routers/export.py |
COCO 和 PNG mask 导出 |
| SAM 2 | backend/services/sam2_engine.py |
SAM 2 懒加载、状态检测、点/框/自动推理和视频 mask 传播 |
| SAM 3 | backend/services/sam3_engine.py, backend/services/sam3_external_worker.py, backend/setup_sam3_env.sh |
SAM 3 状态检测、独立 Python 3.12 环境桥接、本地 checkpoint 加载、文本语义推理、正框几何提示和 video tracker 适配 |
| SAM Registry | backend/services/sam_registry.py |
模型选择、GPU 状态和推理分发 |
状态模型
前端 store 的核心对象:
Project:项目基本信息、状态、帧数、fps、媒体路径。Frame:帧 ID、项目 ID、索引、图片 URL、宽高、序列时间戳和原视频源帧号。Template/TemplateClass:模板和分类定义。Mask:前端渲染用 mask,包含pathData、segmentation、bbox、area。selectedMaskIds:Canvas 当前选中的 mask id 列表,供右侧本体面板对已选区域直接换标签。maskHistory/maskFuture:mask 编辑历史栈,用于撤销和重做。activeModule:当前页面。activeTool:当前工具。aiModel:当前选择的 AI 模型,取值为sam2或sam3。
关键数据流
登录
Login收集用户名和密码。login()调用POST /api/auth/login。- 成功后 store 写入 token,App 渲染主界面。
项目导入
ProjectLibrary创建项目。- 上传视频或 DICOM 到
/api/media/upload或/api/media/upload/dicom。 - 调用
/api/media/parse创建异步拆帧任务;可通过parse_fps、max_frames和target_width指定标准帧序列参数。 - Celery worker 执行 FFmpeg/OpenCV/pydicom 拆帧,视频帧按
frame_%06d.jpg从frame_000000.jpg连续命名,并按目标宽度缩放。 - worker 写入
frames.timestamp_ms和frames.source_frame_number,并在任务result.frame_sequence中记录 FPS、帧数、时长、尺寸和对象存储前缀。 - worker 持续更新
processing_tasks,并发布 Redisseg:progress。 - 刷新项目列表。
任务控制
- Dashboard 从
GET /api/dashboard/overview读取 queued/running/failed/cancelled 任务。 - 用户取消任务时,前端调用
POST /api/tasks/{task_id}/cancel;后端写入cancelled、设置finished_at,并尝试celery_app.control.revoke(..., terminate=True)。 - worker 在下载、解析、上传、写帧等关键阶段刷新任务状态;如果发现
cancelled,停止后续写入并发布 cancelled 事件。 - 用户重试任务时,前端调用
POST /api/tasks/{task_id}/retry;后端基于原任务payload创建新任务,记录retry_of并重新投递 Celery。 - 用户打开详情时,前端调用
GET /api/tasks/{task_id},弹窗展示 error、payload、result、Celery ID 和时间。
工作区加载
VideoWorkspace根据currentProject.id调用getProjectFrames()。- 若无帧但项目有
video_path,触发parseMedia(),通过getTask()轮询任务完成后重新取帧。 - 帧数据映射为 store
Frame[],包含timestampMs和sourceFrameNumber,供时间轴和后续视频传播使用。 - 当前帧传入
CanvasArea。
AI 点/框推理
- 用户在 Canvas 选择正向点、反向点或框选。
CanvasArea读取当前帧 ID 和宽高。- SAM 2 框选会创建一个候选 mask,并记录原始框;后续正向点/反向点会累计到同一候选上。
predictMask()归一化坐标并携带当前model调用/api/ai/predict;同时有框和点时发送interactiveprompt。- 后端加载帧图片并通过 SAM registry 分发到 SAM 2 或 SAM 3。
- 前端把
polygons转为 mask;交互式细化会替换同一个候选 mask,而不是新增多个 mask。 - Canvas 按当前帧过滤并渲染 mask。
- 新 mask 会带上当前选择的模板分类元数据,包括
classId、className、classZIndex和保存状态draft。 - 用户点击“结构化归档保存”后,前端将像素
segmentation转成 normalizedmask_data.polygons;未保存 mask 调用POST /api/ai/annotate,dirty mask 调用PATCH /api/ai/annotations/{annotation_id}。 - 工作区加载项目帧后通过
GET /api/ai/annotations取回已保存标注并转成前端 mask。 - 工作区“清空遮罩”删除当前帧已保存标注,并清除当前帧本地 mask。
视频片段传播
- 用户在工作区选中一个当前帧 mask;如果未显式选中,前端使用当前帧第一个 mask。
VideoWorkspace用buildAnnotationPayload()把 seed mask 转成 normalized polygon、bbox、label、color 和 class 元数据。- 前端调用
POST /api/ai/propagate,默认direction=forward、max_frames=30、include_source=false。 - 后端按项目帧序列截取片段,下载对应帧到临时
frame_%06d.jpg目录,保持当前帧在片段中的相对索引。 model=sam2时,sam2_engine使用SAM2VideoPredictor.add_new_mask()注入 seed mask,再用propagate_in_video()传播。model=sam3时,sam3_engine将请求交给sam3_external_worker.py,由独立 Python 3.12 环境调用官方build_sam3_video_predictor(),以 seed bbox 走 video tracker。- 后端把传播返回的 normalized polygon 保存为后续帧
Annotation,跳过源帧,mask_data.source记录模型传播来源。 - 前端传播完成后重新调用
GET /api/ai/annotations并回显新标注。
手工绘制与历史栈
- 用户在
ToolsPalette选择多边形、矩形、圆、点或线工具。 CanvasArea将交互坐标转换成像素 polygon。- 多边形工具逐次记录节点,三点后点击首节点或按 Enter 时生成闭合 polygon。
- mask path 只在
move、area_merge和area_remove工具下拦截点击;绘制和 AI prompt 工具点击已有 mask 时继续冒泡给 Stage。 - 新 mask 写入
pathData、像素segmentation、bbox、area和当前模板分类元数据。 addMask()、setMasks()、updateMask()、clearMasks()会维护maskHistory/maskFuture。- 工具栏按钮、AI 页按钮和 Canvas Ctrl+Z/Ctrl+Y 调用
undoMasks()/redoMasks()。
Polygon 逐点编辑
- 用户点击 Canvas 上的 mask path 后,
CanvasArea记录selectedMaskId并显示该 mask 第一条 polygon 的顶点控制点。 - 拖动顶点后,前端重算
pathData、像素segmentation、bbox、area。 - 如果 mask 已有
annotationId,编辑会把saveStatus标成dirty且saved=false。 - 归档保存时复用现有
PATCH /api/ai/annotations/{annotation_id}链路,把更新后的 normalized polygon 写回后端。 - 选中顶点后 Delete/Backspace 可删除顶点;前端保持 polygon 至少三点。
- 未选中具体顶点但选中了 mask 时,Delete/Backspace 从前端 store 删除该 mask;如果包含
annotationId,通过工作区回调调用后端删除接口。
区域合并与去除
- 用户选择
area_merge或area_remove后,点击多个当前帧 mask 组成选择集。 - 合并/去除模式隐藏 polygon 顶点和边中点编辑手柄,并在右下角显示已选数量;少于两个 mask 时操作按钮禁用。
CanvasArea把Mask.segmentation转为polygon-clipping的 MultiPolygon。area_merge使用 union,更新第一个选中的主 mask,并从前端 store 移除后续被合并 mask;如果被移除 mask 已保存,会调用工作区传入的删除回调删除后端标注。area_remove使用 difference,从第一个选中的主 mask 中扣除后续选中 mask,扣除对象本身保留;如果 difference 产生内洞,segmentation保留外圈和 hole ring,渲染时使用 even-odd fill。- 结果会重算
pathData、segmentation、bbox、area,已保存主 mask 会进入 dirty 状态并复用归档 PATCH 链路;带洞结果的面积按外圈减内洞计算。
GT Mask 导入
- 工作区“导入 GT Mask”选择图片文件。
- 前端
importGtMask()以 multipart form-data 调用POST /api/ai/import-gt-mask,携带project_id和frame_id。 - 后端验证项目、帧、模板后使用 OpenCV 读取灰度 mask。
- 后端按非零像素值拆分多类别标签。
- 后端对每个类别的前景做 contour 提取,每个连通域保存为一个
Annotation。 points字段保存距离变换中心 seed point,mask_data.polygons保存 normalized polygon,mask_data.gt_label_value保存原始像素类别值。- 前端重新读取项目标注并回显。
annotationToMask()会把 normalized seed point 转成像素坐标,Canvas 以可拖拽点显示;拖动后buildAnnotationPayload()会把点再归一化写回后端。
模板管理
TemplateRegistry从后端读取模板。- 编辑态在组件本地维护分类列表。
- 保存时调用
createTemplate()或updateTemplate()。 - 后端把
classes、rules打包进mapping_rules。 - 返回时再解包给前端。
CanvasArea把当前选中的 mask id 同步到全局selectedMaskIds;切换工具、切换帧或卸载 Canvas 时会清空选择。OntologyInspector可以选择具体分类;选择结果进入全局 store,供CanvasArea和AISegmentation新建/更新 mask 时使用。- 如果
selectedMaskIds中存在当前 store 的 mask,点击分类时会立即更新这些 mask 的templateId、classId、className、classZIndex、label和color。 - 已保存 mask 被重新分类后进入
dirty且saved=false,继续复用工作区归档保存的 PATCH 链路。
导出
- 后端根据项目、帧、标注和模板生成 COCO JSON。
- PNG mask 导出会把 normalized polygon 渲染为单标注二值 mask。
- PNG mask 导出还会按
mask_data.class.zIndex或模板z_index从低到高覆盖,生成每帧语义融合 mask。 - ZIP 内写入
semantic_classes.json,记录语义值到类别、颜色和 zIndex 的映射。 - 前端“导出 JSON 标注集”和“导出 PNG Mask ZIP”按钮都会在导出前保存待归档标注,然后下载对应文件。
接口契约
接口详情见 doc/04-api-contracts.md。测试中重点固定以下契约:
updateProject()使用PATCH /api/projects/{id}。exportCoco()使用GET /api/export/{projectId}/coco。exportMasks()使用GET /api/export/{projectId}/masks。cancelTask()使用POST /api/tasks/{taskId}/cancel。retryTask()使用POST /api/tasks/{taskId}/retry。predictMask()使用POST /api/ai/predict,请求体为image_id、prompt_type、prompt_data、model。propagateMasks()使用POST /api/ai/propagate,请求体为project_id、frame_id、model、seed、direction、max_frames。saveAnnotation()使用POST /api/ai/annotate。importGtMask()使用POST /api/ai/import-gt-maskmultipart form-data。getProjectAnnotations()使用GET /api/ai/annotations。updateAnnotation()使用PATCH /api/ai/annotations/{annotationId}。deleteAnnotation()使用DELETE /api/ai/annotations/{annotationId}。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 3 暴露 semantic 文本语义推理和 box 几何提示;工作区 Canvas 的点交互会在选择 SAM 3 时显示提示,不再静默失败。
- SAM 3 box prompt 复用后端
/api/ai/predict的boxprompt_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/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 }对象形式。
外部依赖边界
测试不直接依赖以下真实服务:
- PostgreSQL:后端测试使用内存 SQLite。
- MinIO:上传、下载、预签名 URL 使用 monkeypatch。
- Redis:单测使用 monkeypatch 验证进度事件发布,不依赖真实 Redis 服务。
- SAM:AI 推理测试使用 fake registry。
- 浏览器 Canvas/Konva 图片加载:前端测试 mock
react-konva和use-image。
已知占位设计
以下能力属于当前冻结版本的占位或半可用功能:
- Dashboard 初始快照来自
GET /api/dashboard/overview;解析队列由processing_tasksqueued/running/failed/cancelled 任务生成。 - 已保存标注支持通过“应用分类”、polygon 顶点拖动/删除、边中点插入、多 polygon 子区域编辑和区域合并/去除进入 dirty 状态并归档更新;选中整块 mask 可用 Delete/Backspace 删除并同步后端;复杂洞结构编辑尚未实现。
- SAM 3 文本语义分割取决于官方依赖、GPU 运行环境和本地 checkpoint;状态接口会暴露真实可用性,运行时缺失时
available=false。 - 自定义分类只存在本地组件状态。
- GT mask 导入已完成多类别像素值拆分、contour、distance transform seed point 和前端 seed point 拖拽编辑;骨架提取、HDBSCAN 聚类和模板自动映射尚未实现。