- DEL 和清空遮罩在清空所有传播帧时检测人工/AI 标注帧并二次确认 - 按帧范围清空传播链时检测范围内人工/AI 标注帧,支持选择否后整帧保留 - 保留人工帧时只清其它自动传播帧,避免人工帧被局部掏空 - 补充清空所有传播帧和范围清空的人工帧保留回归测试 - 更新项目指南、实现地图、前端审计、需求冻结、设计冻结和测试计划文档
41 KiB
AGENTS.md — AI 编码助手项目指南
本文件面向 AI 编码助手。阅读者应假设对该项目一无所知。以下信息基于当前仓库实际文件、脚本和源码;不要把早期设计目标当作已实现事实。任何代码和功能修改都要落实到文档和测试上,如果生成git commit信息,要逐个列点把所有修改都列上,重要的、大的修改放前面,不重要的、小的修改列在后面。
项目概述
本项目是一个语义分割系统(Semantic Segmentation System),当前形态是 React 前端 + FastAPI 后端的全栈 Web 应用,用于视频/DICOM 医学影像上传、显式视频生成帧、交互式 Canvas 标注、视频片段传播、GT mask 导入、可选 SAM 2.1 tiny/small/base+/large 辅助分割、模板分类管理和标注导出。SAM 3 相关源码和安装脚本保留在仓库中,但由于当前产品不提供文本提示,前端入口已隐藏,后端 registry 也不暴露 sam3 模型。
- 项目名称:
react-example(package.json中的name) - 前端入口:
src/main.tsx→src/App.tsx - 前端服务入口:
server.ts(Express + Vite 中间件 / 生产静态服务;旧版 mock API 已清理) - 后端入口:
backend/main.py(FastAPI) - 默认前端地址:
http://localhost:3000 - 默认后端地址:
http://localhost:8000 - 前端 API 配置:
src/lib/config.ts,优先读取VITE_API_BASE_URL,未配置时按当前浏览器 hostname 推导http://<host>:8000 - 业务文档:
语义分割系统构建方案.docx(项目根目录)
技术栈
| 层级 | 技术 |
|---|---|
| 前端框架 | React 19 + TypeScript 5.8 |
| 构建工具 | Vite 6 |
| 前端样式 | TailwindCSS 4 + 自定义深色主题 |
| 前端状态 | Zustand(src/store/useStore.ts) |
| 前端请求 | Axios(src/lib/api.ts) |
| 实时通信 | WebSocket 客户端(src/lib/websocket.ts) |
| Canvas 渲染 | Konva + react-konva + use-image |
| 几何布尔运算 | polygon-clipping |
| 图标库 | lucide-react |
| 动画依赖 | motion(在 package.json 中声明) |
| AI SDK 依赖 | @google/genai(在 package.json 中声明;当前业务源码未直接调用) |
| 后端框架 | FastAPI + Uvicorn |
| ORM / 数据库 | SQLAlchemy + PostgreSQL |
| 缓存 / 队列 Broker | Redis |
| 后台任务 | Celery worker |
| 对象存储 | MinIO |
| AI 推理 | 当前启用 SAM 2.1 + PyTorch,可选 tiny/small/base+/large;GET /api/ai/models/status 返回真实 GPU 和各 SAM 2.1 变体状态;SAM 3 源码保留但产品入口禁用 |
| 视频 / 影像处理 | FFmpeg / OpenCV / pydicom |
| 运行时 | Node.js ES Modules;Python 3.11 后端环境;历史保留的 sam3 Python 3.12 conda 环境不是当前必需运行条件 |
项目结构
Seg_Server/
├── server.ts # Express + Vite 前端入口;不再提供旧版 /api mock
├── index.html # SPA HTML 入口
├── vite.config.ts # Vite 配置;含 @/* 路径别名与 DISABLE_HMR 逻辑
├── tsconfig.json # TypeScript 配置;@/* 映射到项目根目录
├── package.json # npm 依赖与脚本
├── .env.example # AI Studio/Gemini 前端环境变量模板
├── metadata.json # AI Studio 元数据
├── logo_square.png # Sidebar 与 favicon 使用的根目录方形 Logo
├── public/
│ └── logo.png # 旧版保留 Logo 静态资源
├── doc/ # 当前实现审计、接口契约和后续实施文档
├── start_services.sh # 本地一键启动 PostgreSQL/Redis/MinIO/FastAPI/Celery/前端
├── restart_dev_services.sh # 本地开发重启脚本;重启 FastAPI/Celery/前端并检查 3000/8000
├── backend/ # FastAPI 后端
│ ├── main.py # 应用入口、lifespan、CORS、路由注册、WebSocket
│ ├── config.py # Pydantic Settings;读取 backend/.env
│ ├── database.py # SQLAlchemy Engine / Session
│ ├── models.py # User/Project/Frame/Template/Annotation/Mask/AuditLog/ProcessingTask ORM
│ ├── schemas.py # Pydantic 请求/响应模型
│ ├── minio_client.py # MinIO 上传、下载、预签名 URL
│ ├── redis_client.py # Redis 连接封装
│ ├── celery_app.py # Celery app 配置
│ ├── worker_tasks.py # Celery 任务入口
│ ├── download_sam2.py # SAM 2 权重下载脚本
│ ├── setup_sam3_env.sh # 历史保留的 SAM 3 独立 Python 3.12 环境安装脚本;当前产品入口禁用
│ ├── requirements.txt # Python 依赖
│ ├── routers/
│ │ ├── auth.py # /api/auth/login、/api/auth/me、鉴权依赖
│ │ ├── admin.py # /api/admin/users、/api/admin/audit-logs、/api/admin/demo-factory-reset
│ │ ├── projects.py # /api/projects 与 /api/projects/{id}/frames
│ │ ├── templates.py # /api/templates
│ │ ├── media.py # /api/media/upload、/upload/dicom、/parse
│ │ ├── ai.py # /api/ai/predict、/propagate、/propagate/task、/models/status、/auto、/annotate
│ │ └── export.py # /api/export/{project_id}/coco、/masks
│ └── services/
│ ├── frame_parser.py # FFmpeg/OpenCV 拆帧、pydicom 读片、帧上传
│ ├── propagation_task_runner.py # Celery 自动传播任务 runner
│ ├── sam2_engine.py # SAM 2.1 变体选择、单帧推理和 video predictor 传播封装
│ ├── sam3_engine.py # 历史保留的 SAM 3 桥接实现;当前未接入 registry
│ ├── sam3_external_worker.py # 历史保留的独立 sam3 helper;当前未被产品入口调用
│ └── sam_registry.py # 当前暴露 SAM 2.1 变体、GPU 状态与推理分发
└── src/ # React 前端
├── main.tsx # React StrictMode 挂载
├── App.tsx # 登录拦截 + 模块切换
├── index.css # TailwindCSS 导入 + 全局样式
├── store/useStore.ts # Zustand 全局状态
├── lib/api.ts # Axios API 封装
├── lib/websocket.ts # 解析进度 WebSocket 客户端
├── lib/utils.ts # cn() 工具函数
└── components/ # 扁平化组件目录
├── Login.tsx
├── Sidebar.tsx
├── Dashboard.tsx
├── ProjectLibrary.tsx
├── VideoWorkspace.tsx
├── CanvasArea.tsx
├── ToolsPalette.tsx
├── OntologyInspector.tsx
├── FrameTimeline.tsx
├── AISegmentation.tsx
├── TransientNotice.tsx
└── TemplateRegistry.tsx
以下目录/文件通常是运行产物或本地数据,已在 .gitignore 中忽略:node_modules/、dist/、models/、uploads/、frames/、Data_*/、*.mp4、*.dcm、*.7z、backend/.env、日志文件等。
doc/ 目录是当前项目的事实文档入口。修改功能前优先查看:
doc/03-frontend-element-audit.md:哪些前端元素是真功能,哪些是 Mock/UI-only。doc/04-api-contracts.md:前后端接口契约,以及当前不一致点。doc/05-implementation-plan.md:建议的后续实施顺序。doc/10-installation.md:完整安装部署流程,覆盖 PostgreSQL、Redis、MinIO、FastAPI、Celery、前端和 SAM 2.1 权重。
构建与运行命令
前端 / Node 入口
npm install
# 开发模式:运行 tsx server.ts,Express 集成 Vite middleware,端口 3000
npm run dev
# 生产构建:输出 dist/
npm run build
# Vite 预览
npm run preview
# 生产模式运行 server.ts,服务 dist/
npm start
# TypeScript 类型检查
npm run lint
# 删除 dist/
npm run clean
FastAPI 后端
cd backend
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
一键启动
./start_services.sh
该脚本会依次检查/启动 PostgreSQL、Redis、MinIO、FastAPI 后端、Celery worker 和前端。
开发重启
./restart_dev_services.sh
本地代码改完后不要靠手工试进程。前端 src/ 通常由 Vite 热更新;凡是修改 FastAPI 后端、Celery worker、拆帧/SAM runner 或任务逻辑,完成验证后都要运行 ./restart_dev_services.sh,确保运行态加载最新代码。restart_dev_services.sh 会停止旧 FastAPI/Celery/前端进程,用独立后台进程重启三者,并检查 http://localhost:8000/health 和 http://localhost:3000。
运行时架构
前端
- 单页应用,无路由库;模块切换由
useStore().activeModule控制。 - 模块值包括:
dashboard、projects、ai、workspace、templates。 - 默认模块是
workspace。 - 未登录时渲染
Login。 - 登录成功后后端返回签名 JWT,token 写入
localStorage,Axios request interceptor 会附加Authorization: Bearer <token>。 App.tsx在已有 token 或登录成功后调用/api/auth/me恢复当前用户,再调用getProjects()初始化当前用户项目列表。
后端
- 主后端是
backend/main.py的 FastAPI 服务。 lifespan启动时会:- 创建数据库表;
- 检查/创建 MinIO bucket
seg-media; - 测试 Redis 连接;
- 后台 seed 默认模板,包括“腹腔镜胆囊切除术”和“头颈部CT分割”;
- 如果本地存在
demo_video_path和配置的demo_dicom_dirDICOM 序列,后台 seed 默认演示视频项目和演示 DICOM 项目,DICOM 会按文件名自然顺序生成帧。
- API 路由包括:
POST /api/auth/loginGET /api/auth/meGET/POST/PATCH/DELETE /api/admin/usersGET /api/admin/audit-logsPOST /api/admin/demo-factory-resetGET/POST/PATCH/DELETE /api/projectsGET/POST /api/projects/{project_id}/framesGET/POST/PATCH/DELETE /api/templatesPOST /api/media/uploadPOST /api/media/upload/dicomPOST /api/media/parseGET /api/tasksGET /api/tasks/{task_id}POST /api/tasks/{task_id}/cancelPOST /api/tasks/{task_id}/retryPOST /api/ai/predictPOST /api/ai/smooth-maskPOST /api/ai/propagatePOST /api/ai/propagate/taskGET /api/ai/models/statusPOST /api/ai/autoPOST /api/ai/annotatePOST /api/ai/import-gt-maskGET /api/ai/annotationsPATCH/DELETE /api/ai/annotations/{annotation_id}GET /api/dashboard/overviewGET /api/export/{project_id}/cocoGET /api/export/{project_id}/masksGET /healthWS /ws/progress
存储
- PostgreSQL 存储项目、帧、模板、标注、mask 和后台任务元数据。
- MinIO 存储上传视频、DICOM、拆出的帧、缩略图等对象;前端展示使用预签名 URL。
- Redis 当前作为 Celery broker/result backend,并用于连接检查。
主要业务流程
- 登录:
Login.tsx调用POST /api/auth/login,后端用users表和密码哈希校验凭证,默认启动时会种子化开发管理员admin / 123456;成功后返回签名 JWT,GET /api/auth/me可读取当前用户;角色包括admin、annotator、viewer,写入类业务接口要求admin/annotator,用户管理后台要求admin。 - 用户管理:
Sidebar仅对admin显示“用户管理”,UserAdmin.tsx调用/api/admin/users新增、停用/启用、改角色、改密码和删除无项目用户,并调用/api/admin/audit-logs展示登录和管理操作审计;演示部署可通过“恢复演示出厂设置”二次确认后调用/api/admin/demo-factory-reset,清空演示数据,只保留默认 admin、演示视频项目和一个已按文件名自然顺序生成帧的演示 DICOM 项目,同时按内置权威定义恢复“腹腔镜胆囊切除术”和“头颈部CT分割”系统模板,缺失的会重建,被修改或删减的语义分类树会覆盖回默认状态。 - 项目管理:
ProjectLibrary.tsx调用项目 API 创建项目、拉取列表、重命名项目、复制项目和删除项目;项目卡片删除按钮旁提供复制入口,复制时可选择“新项目重置”(复制项目媒体和已生成帧序列,但清空标注/mask)或“全内容复制”(复制项目、帧序列、标注和关联 mask 元数据),任务运行历史不复制;删除当前项目后会清空工作区当前项目、帧、mask 和选区。 - 上传资源:视频走
/api/media/upload,只上传源文件并关联项目,不自动拆帧;项目库在视频上传期间显示导入进度条、百分比和已上传字节。只有视频项目在尚未生成帧、未处于项目名称编辑状态且未解析中时显示“生成帧”,DICOM 项目不显示生成帧入口;DICOM 批量走/api/media/upload/dicom,前端和后端都会按文件名自然顺序排序.dcm文件,避免10.dcm排在2.dcm前导致切片错位;DICOM 上传期间显示导入进度条、本次有效文件数量和已上传字节,上传完成后轮询解析任务进度直到完成、失败或取消。 - 生成帧入队:用户在项目库点击“生成帧”,选择目标 FPS 后前端调用
/api/media/parse;后端创建ProcessingTask并投递 Celery,接口支持parse_fps、max_frames和target_width标准帧序列参数;项目库和模板库的成功/失败短反馈使用非阻塞TransientNotice,会自动消失。 - worker 执行:Celery worker 用 FFmpeg 优先拆视频帧,失败后用 OpenCV fallback,DICOM 使用 pydicom;worker 下载和读取 DICOM 时也按文件名自然顺序排序;视频/DICOM 解析完成后都按
frame_%06d.jpg连续生成项目帧序列,并记录timestamp_ms、source_frame_number和任务frame_sequence元数据,后续工作区、时间轴、AI 传播、标注和导出共用同一套帧序列逻辑。 - 帧展示:
VideoWorkspace.tsx调用/api/projects/{id}/frames,CanvasArea.tsx和FrameTimeline.tsx显示当前帧与时间轴缩略图;CanvasArea会按容器和帧尺寸默认居中放大底图并保留边距,右下角显示“当前帧:XX/XXX”;FrameTimeline会根据已保存标注回显到Mask.metadata的传播来源,把自动传播生成的帧在视频处理进度条显示为蓝色区段,人工/AI 标注帧显示红色竖线;每次自动传播成功处理帧后,VideoWorkspace会把本次传播范围作为当前会话历史片段传给FrameTimeline,在视频处理进度条上叠加同一蓝色系、最新传播最亮、旧传播逐次变暗且第 5 次及更早统一为阈值旧记录色的纯色条;传播历史条只显示当前仍有自动传播 mask 的帧,删除 mask 或清空范围后会按剩余传播 mask 自动裁剪,空帧不保留红/蓝颜色;视频处理进度条和红/蓝标识可点击跳转到对应帧;底部缩略图中人工/AI 标注帧用红色边框、自动传播/推理帧用蓝色边框,同一帧同时具备两种状态时红色标注边框优先保留,蓝色传播状态以内描边表达;当前帧仍以青色外框高亮优先;若当前帧同时是人工/AI 标注帧,则在青色外框内增加红色内描边,固定为外层当前帧、内层人工/AI 标注;进入自动传播、布尔操作或特定范围帧导出选择模式时,播放进度条和视频处理进度条会显示黄色范围框,并可点击/拖拽选择起止帧;前端Frame会保留后端返回的帧序列时间戳和源帧号。 - 手工标注:
CanvasArea.tsx支持多边形、矩形、圆、画笔和橡皮擦生成/编辑 polygon mask;多边形可按 Enter 或点击首节点闭合;多边形/矩形/圆在右侧语义分类树未选中类别时会自动归入黑色maskid:0的“待分类”;画笔/橡皮擦可在左侧工具栏调整大小,画笔要求右侧语义分类树已有选中类别,画出的圆形连续笔触会在鼠标松开时一次性 union 成 mask,若与当前选中 mask 连通则自动合并到该 mask,橡皮擦要求已选中 mask 并在松开时从该 mask 中 difference 扣除;普通 mask 和导入 mask 都不显示黄色 seed point,也不提供 seed point 拖动;未选中特定 mask 时,Canvas 会按右侧语义分类树拖拽得到的内部覆盖优先级从低到高渲染 mask,使高优先级类别显示在上层;Canvas 左上角工具上下文提示会在切换工具或操作状态变化时短暂显示,数秒后自动隐藏,避免长期遮挡底图;工具栏有“调整多边形”入口,左侧ToolsPalette使用紧凑垂直布局并在高度不足时自身滚动,基础绘制、画笔/橡皮擦/自动传播、布尔/删除、导入/AI 入口之间用浅灰分隔线区分;橡皮擦下方提供彩色 AI 图标“自动传播”入口,布尔/删除组包含区域合并、重叠区域去除、DEL和“清空遮罩”,其后通过data-testid="tool-group-separator"分隔紫色“导入 GT Mask”和 AI 智能分割入口;清空遮罩优先作用于当前帧选中 mask,没有选中时作用于当前帧全部 mask;无传播链结果时直接清当前帧,存在传播链结果时弹窗同一行选择取消、只清当前帧、按帧范围选择或清空所有传播帧,按帧范围选择会进入时间轴范围选择并二次确认;Canvas 右下角不再提供旧的“清空遮罩”或“应用分类”按钮,分类改由右侧语义分类树点击完成;工作区左侧工具栏不展示 AI 页的正向选点、反向选点和边界框选,也不重复放置撤销/重做;点击 mask 后可按住顶点直接拖动并实时更新 polygon,顶点拖拽结束不会触发 Stage 平移或重置 Canvas 视口;也可删除 polygon 顶点、通过边中点或双击边界插入新顶点;多 polygon/分离区域组成的同一 mask 进入编辑时所有子区域都会显示顶点和插点手柄,同帧同传播链的分散 mask 点选时会联动高亮;带中空洞的 mask 会用metadata.polygonRingCounts记录外圈与内圈的 ring 分组,调整多边形时外圈和内洞都显示可编辑顶点和插点手柄,保存时把内洞拆到mask_data.holes;选中整块 mask 可用 Delete/Backspace 或左侧DEL删除,已保存 mask 删除前会预检当前后端 annotation id,只对仍存在的 id 调用后端删除,避免陈旧本地 id 产生 DELETE 404;删除传播 seed 或任一传播结果时会扩展删除同一传播链上的自动传播 mask,但保留其他帧独立 AI 推理或人工标注 mask;区域合并/去除会隐藏编辑手柄并显示已选数量,第一个选中的主区域用黄色实线轮廓,后续参与合并/扣除的区域用红色虚线轮廓,使用polygon-clipping做 union/difference,若存在传播帧对应 mask 会先弹窗选择只处理当前帧、处理所有传播帧或按帧范围选择;按帧范围选择会进入时间轴范围选择并二次确认,只把同一布尔操作同步到所选范围内具备对应关系的传播帧;同步后的传播 mask 保留原source/lineage metadata,只进入 dirty 状态等待保存,不会在时间轴上变成人工/AI 标注帧;内含去除结果用 even-odd 规则渲染 hole;Zustand 维护maskHistory/maskFuture支持撤销/重做。 - AI 分割:侧栏和工作区工具栏的 AI 智能分割入口使用 Bot + Sparkles 组合图标强化 AI 识别;前端工具包括 SAM 2.1 变体选择、正向点、反向点和框选;AI 画布会按容器和当前帧尺寸默认居中放大底图并保留边距;工作区和 AI 页面都可点击已有提示点删除单点,AI 页面也可删除最近锚点、删除选中候选或清空本页锚点;这些删除入口会限制在当前提示点/本页 AI 候选范围内,避免误删工作区已有 mask。SAM 2.1 框选会建立候选 mask,后续正/反点通过
interactiveprompt 携带原始框和累计点细化同一个候选 mask;AI 页面框选会先固化promptBox,执行分割时只框选发送boxprompt,框选后继续加正/反点发送interactiveprompt;重复执行高精度分割会替换上一次 AI 页候选,只保留最新一个候选。包含反向点时工作区会传options.auto_filter_background=true和min_score=0.05,如果后端过滤为空则移除旧候选 mask。后端ai.py期望按image_id、prompt_type、prompt_data、model和可选options调用 SAM registry。当前 registry 暴露sam2.1_hiera_tiny、sam2.1_hiera_small、sam2.1_hiera_base_plus、sam2.1_hiera_large,并兼容sam2作为 tiny 别名;model=sam3会被拒绝,semantic文本提示也被禁用。SAM 2.1 支持点/框/interactive/自动分割和 video predictor 传播;多候选默认只采用最高分区域,避免重叠候选同时显示;AI 页面只渲染本页最新生成的候选 mask,不会把工作区已有 mask 带入 AI 画布;AI 页面生成的 mask 会写入全局masks并自动选中,右侧分类树可直接改标签,推送到工作区会切到“调整多边形”并保留选择和当前帧视角。options.crop_to_prompt可对点/框/interactive prompt 做局部裁剪推理并回映射,options.auto_filter_background可按分数和负向点过滤结果。 - 视频片段传播:工作区以当前打开帧作为参考帧,使用该帧全部 mask 作为 seed,并用传播起始帧和传播结束帧指定追踪范围;如果当前参考帧没有 mask,点击开始传播会提示“当前参考帧无遮罩”,不会提交任务或保存其它帧标注;用户点击左侧工具栏橡皮擦下方的彩色 AI 图标“自动传播”进入时间轴范围选择模式,在播放进度条或视频处理进度条上点击/拖拽选择范围,也可直接修改数字框,再点击顶栏“开始传播”。传播权重选择器只在进入自动传播选择/执行状态后显示,可为本次传播二次选择 SAM 2.1 tiny/small/base+/large 权重,不提供 SAM2/SAM3 家族切换,也不影响 AI 单帧分割权重;进入自动传播范围选择时,顶栏会显示当前传播权重以及相对参考帧的向前/向后帧数;前端提交传播前只保存当前参考帧中的 draft/dirty mask,使 seed 优先带稳定的后端
source_annotation_id,再按传播权重 id、seed mask、seed 来源 id 和前/后方向组装steps并调用POST /api/ai/propagate/task创建propagate_masks后台任务;同一参考帧多个同类别 mask 会各自作为独立 seed 传播,后端按source_annotation_id/source_mask_id/propagation_seed_key区分实例,避免同类不同实例互相删除;中空 seed 会携带和polygons对齐的holes,后端注入 SAM 2 video predictor 前会先填充外圈再扣除内洞,避免以实心 mask 传播;后端入队时会规范化/校验权重 id 并把规范化后的 id 写入任务 payload/result;Celery worker 顺序执行各 step,避免多个视频 tracker 并发抢占 GPU;每个 step 会根据 seed 来源 id、方向和包含holes的 seed 签名做幂等判断,同权重且未改变的 seed 直接跳过,已改变或换用其他权重的 seed 会先删除同源旧自动传播标注再重传;旧版本缺少稳定来源 id 的传播标注只在没有可靠 lineage 时走 label/color/class 兼容匹配,写入新结果前仍会通过空间重叠清理同一物体旧结果;中间帧人工新增/修改同一物体后重新传播时,后端会在写入目标帧新结果前按语义和空间重叠清理旧传播结果,且写入前清理不受旧结果传播方向限制;后端按项目帧序列下载片段帧,当前使用所选 SAM 2.1 权重变体的SAM2VideoPredictor.add_new_mask()+propagate_in_video(),并把后续帧结果保存为Annotation,传播结果轮廓用 CCOMP 层级提取并把内洞写入mask_data.holes;若历史或外部 seed 仍带geometry_smoothing,forward/backward 两个方向的传播结果保存前仍会应用同一参数;当前工作区平滑按钮应用后会直接改写实际 polygon,后续传播以新几何参与签名和追踪。工作区轮询GET /api/tasks/{task_id}展示进度并刷新标注,Dashboard 也能显示/取消/重试传播任务。 - GT 导入:工作区左侧工具栏“导入 GT Mask”调用
/api/ai/import-gt-mask;选择文件后前端会显示导入结果预览,并让用户决定未知 maskid 处理方式,可舍弃未知类别,也可导入为“未定义类别”等待重新命名。后端用cv2.IMREAD_UNCHANGED读取 mask 并校验 dtype;GT 图片必须是 8-bit 灰度 maskid 图,或 8-bit RGB 三通道完全相同的[X,X,X]maskid 图,0 为背景、X 为 1-255 的 maskid,16-bit/uint16 GT_label、普通彩色类别图和全背景 0 图都会返回明确错误;全背景图错误信息固定为“GT Mask 图片中没有非背景 maskid 区域。”;灰度/RGB 等通道图按模板maskId匹配类别,超出现有类别时按unknown_color_policy处理;如果 mask 图片尺寸和当前帧不同,会按当前帧长宽最近邻拉伸后再提取区域;每个连通域用高精度 contour 生成 polygon 标注,保留更多边界点并设置点数上限避免拖慢前端;导入结果与普通 mask 共用拓扑锚点统计、边缘平滑、顶点编辑、分类和保存链路;后端仍可写入 distance transform seed point 供数据兼容,但前端不显示或拖动 seed point。 - 模板管理:
TemplateRegistry.tsx管理分类、颜色、maskid 和内部覆盖顺序;所有新建、复制、批量导入和后端返回的模板都会归一化包含黑色[0,0,0]、maskid: 0的“待分类”保留类,该类固定在语义分类树最后,不能删除,也不能拖拽到更高层级;批量导入 JSON 支持[[colors], [names]]和{colors, names}两种格式,也兼容带“批量导入分类:”前缀、代码块、未加引号 keys、单引号、中文逗号/冒号和尾随逗号的粘贴内容,会先预览分类数量、maskid 分配起点和缺失颜色提示,语法或结构错误以内联错误展示;系统默认模板包括“腹腔镜胆囊切除术”和“头颈部CT分割”,头颈部 CT 默认分类名使用纯中文(肿瘤/结节、下颌骨、甲状腺、气管、颈椎、颈动脉、颈静脉、腮腺、下颌下腺、舌骨),恢复演示出厂设置只删除用户私有模板,并会重建缺失的系统默认模板、覆盖恢复被修改或删减的默认语义分类树;模板库“生效中模板架构清单”里的每个模板卡片支持鼠标点击复制,复制会创建当前用户私有副本并保留分类名称、颜色、maskid、内部层级和规则,同时重建类别内部 id;模板库详情页的分类区标题为“语义分类树(拖拽调层级)”,右上角提供带 Edit 图标的“编辑模板”按钮,每个分类行右侧用垃圾桶图标删除该 label,不再展示“未分类/批量导入/模板名”等来源标签;编辑模板弹窗点击分类后只编辑分类名称,不展示或编辑旧category来源元信息;如果项目中的已保存 mask 引用了当前模板里已被删除的类别,工作区打开项目回显时会把该 mask 降级为maskid: 0的“待分类”mask 并标记为待保存;项目已有任意 mask 时,用户在右侧本体面板修改激活模板必须先确认,确认后删除当前项目所有已有 mask/后端标注再切换;项目没有任何 mask 时可直接切换;模板库详情页和编辑弹窗都支持拖拽调整语义类别层级顺序,拖拽会重算zIndex并保存到后端,保存后当前详情页会立刻刷新;OntologyInspector.tsx在工作区显示当前模板分类树,也支持拖拽调整内部覆盖顺序。maskid 只作为 GT_label/类别 ID,不参与排序。 - 导出:工作区使用统一“分割结果导出”入口,导出前先保存待归档 mask;用户可选择整体视频、特定范围帧或当前图片,默认导出范围为当前图片,并勾选分开二值 mask、GT_label 黑白图、Pro_label 彩色图和 Mix_label 原图叠加图。选择特定范围帧时,可直接修改起止帧输入框,也可在播放进度条或视频处理进度条上点击/拖拽选择导出范围;选择 Mix_label 时可调透明度,默认 0.3,并显示当前/待导出第一帧预览。下载 ZIP 文件名使用
{项目库项目名}_seg_T_{起始时间戳}-{结束时间戳}_P_{起始项目帧序号}-{结束项目帧序号}.zip,项目名来自Project.name并替换文件系统不安全字符,时间戳格式为0h00m00s000ms,帧号使用项目抽帧后的 1-based 顺序而非原视频帧号。后端保留兼容的 COCO JSON 和 PNG mask ZIP 接口,同时新增统一结果 ZIP;统一 ZIP 固定包含annotations_coco.json、maskid_GT像素值_类别映射.json和原始图片/;导出时 GT_label 固定写 8-bit uint8 PNG,像素值使用类别真实maskid,其中maskid: 0的“待分类”与背景同为 0,Pro_label 中也与背景同为黑色[0,0,0],缺失maskid的旧标注才补下一个可用值,正整数 maskid 超出 1-255 会拒绝导出,保证导出的 GT_label 可按同一模板再导入;选择分开 mask 时输出分开Mask分割结果/{视频名称_时间戳_项目帧序号}_分别导出/{视频名称_时间戳_项目帧序号}_{类别名称}_maskid{maskid}.png,同一帧同一类别合并为一张图;选择 GT_label/Pro_label/Mix_label 时分别输出GT_label图/{视频名称_时间戳_项目帧序号}.png、Pro_label彩色分割结果/{视频名称_时间戳_项目帧序号}.png、Mix_label重叠覆盖彩色分割结果/{视频名称_时间戳_项目帧序号}.png。maskid 不参与覆盖排序,GT_label/Pro_label/Mix_label 重叠区域覆盖顺序由内部拖拽排序字段决定,并与未选中状态下的 Canvas 显示顺序一致。
当前实现注意事项
src/lib/config.ts会优先读取VITE_API_BASE_URL和VITE_WS_PROGRESS_URL;未配置时按当前浏览器 hostname 推导后端:8000地址。- 前端
predictMask()已按后端PredictRequest发送image_id、prompt_type、prompt_data、model,并将后端polygons转成 Konva 可渲染的pathData。 - 手工绘制工具会生成可保存的
Mask.segmentation;未选分类时的多边形/矩形/圆会自动归入maskid:0的“待分类”;撤销/重做通过maskHistory/maskFuture工作,工作区在 window capture 阶段处理Ctrl/Cmd+Z、Ctrl/Cmd+Shift+Z和Ctrl/Cmd+Y,并通过src/lib/keyboardShortcuts.ts兼容event.key与event.code=KeyZ/KeyY。 - Polygon 顶点编辑和新增顶点会重算
pathData/segmentation/bbox/area;多 polygon/分离区域和中空 mask 的外圈、内洞都可显示顶点与插点手柄,保存时通过mask_data.holes和metadata.polygonRingCounts保留 ring 分组;已保存 mask 进入 dirty 状态后复用归档 PATCH 链路。 - 区域合并/去除会重算主 mask 的几何;合并已保存的次级 mask 时会通过工作区回调删除对应后端标注;若主区域和参与区域存在传播链对应 mask,会先弹窗选择当前帧、所有传播帧或按帧范围选择;按帧范围选择复用工作区时间轴范围选择和确认弹窗,处理时同一布尔操作只同步应用到所选范围内的对应主区域和参与区域,保留传播来源 metadata,避免时间轴帧属性变色;用户在范围确认前重新点击合并/去除开始新的布尔操作时,会取消旧的顶栏范围请求,避免当前帧操作和旧范围操作被重复执行。
- 前端
importGtMask()已对齐后端/api/ai/import-gt-mask;工作区左侧工具栏“导入 GT Mask”会在上传前显示导入结果预览并选择未知 maskid 策略,后端仅支持 8-bit 二值/灰度 maskid 图和 8-bit RGB 三通道完全相同的[X,X,X]maskid 图,不再按彩色 RGB 类别图做颜色匹配,也不接受 16-bit/uint16 GT_label;尺寸不同的 mask 会最近邻拉伸到当前帧,导入后回显多类别高精度 polygon 标注,不显示黄色 seed point,并能直接使用普通 mask 的拓扑统计、边缘平滑、编辑和保存能力。 - 前端
exportCoco()已对齐后端/api/export/{project_id}/coco;前端exportMasks()已对齐后端/api/export/{project_id}/masks;前端exportSegmentationResults()已对齐后端/api/export/{project_id}/results;工作区“分割结果导出”按钮会先保存当前待归档 mask,再按所选范围、outputs 和 Mix_label 透明度下载统一 ZIP;特定范围帧导出可用帧号输入框或时间轴拖拽选择范围;下载文件名按项目库项目名、导出范围首尾时间戳和首尾项目帧序号生成;统一 ZIP 包含 maskid/GT 像素值映射 JSON、原始图片文件夹、按帧/类别合并的分开 Mask 文件夹、GT_label 图文件夹、Pro_label 彩色图文件夹和 Mix_label 叠加图文件夹;GT_label 固定为 uint8 PNG,像素值使用类别真实maskid并跨图一致。 - 右侧语义分类树点击分类会把分类变更同步到同一传播链前后帧对应 mask;识别依据为
source_annotation_id、source_mask_id和propagation_seed_key,被同步更新的已保存 mask 会进入 dirty 状态,等待工作区归档保存 PATCH 到后端;保存 dirty mask 时会保留source、传播 seed 和来源 id 等 metadata,避免传播帧在时间轴上变成人工/AI 标注帧。 - 工作区保存状态按钮会按当前项目待保存数量显示“保存 X 个改动”或“已全部保存”,并已接入
POST /api/ai/annotate和PATCH /api/ai/annotations/{id};dirty mask 更新前会预检后端标注 id,已知缺失的本地旧 annotationId 直接用POST重新创建;如果预检后PATCH仍返回 404,前端也会保留同一几何、分类和传播 lineage metadata,改用POST重新创建并在回显时替换本地旧 id,避免保存或开始传播被陈旧 annotationId 中断;加载工作区时会通过GET /api/ai/annotations回显已保存标注。 - 右侧实例属性面板“边缘平滑强度/应用边缘平滑”已接入
POST /api/ai/smooth-mask;滑杆会即时更新数值,但后端预览请求有短防抖,避免拖动时连续请求卡顿;预览不写入撤销历史也不标 dirty;点击应用后会把返回 polygon 作为新的实际 mask 几何写入当前 mask 和同传播链前后对应 mask,整次应用作为一个撤销/重做历史步骤,相关 mask 标记为 dirty/draft,平滑强度重置为 0,用户可继续用 polygon 编辑工具调整新多边形。 - 工作区“自动传播”按钮位于左侧工具栏橡皮擦下方,并已接入
POST /api/ai/propagate/task;若用户尚未显式设置范围,第一次点击会进入时间轴范围选择模式,顶栏才显示传播权重和向前/向后帧数,第二次点击“开始传播”才提交后台任务;当前启用所选 SAM 2.1 变体的视频 predictor 后台任务,运行中轮询任务进度,完成后刷新后端已保存标注;同一参考帧多个同类别 seed 会按来源 id 分开传播,不会因 label/color 相同互相覆盖;中空 seed 会把内洞传给后端,SAM 2 seed mask 栅格化时扣除内洞,传播结果保存时也会保留holes;GPU/CPU 模型状态只在左侧 Sidebar 底部用紧凑徽标展示,工作区顶栏不再重复显示,具体 SAM 2.1 传播权重由顶栏下拉选择;同步POST /api/ai/propagate仍作为单 seed 兼容接口保留。 - 工作区顶栏短状态会自动消失;保存、导出、导入 GT、传播进行中和无帧项目提示会保留到状态变化。
- 工作区“清空遮罩”和左侧
DEL删除只从左侧工具栏或键盘触发,会在删除已保存标注前预检当前项目仍存在的 annotation id,只对存在的 id 调用DELETE /api/ai/annotations/{id};如果当前帧有选中 mask 则优先清/删选中 mask,没有选中时清当前帧全部 mask;如果对象没关联其它传播帧则直接处理当前帧,如果存在传播链结果则弹窗在同一行选择取消、只处理当前帧、按帧范围选择或清空所有传播帧;按帧范围选择复用工作区时间轴范围选择和最终确认弹窗;按范围清空或清空所有传播帧时,如果目标帧范围内包含人工绘制或独立 AI 标注帧,会再提示是否删除人工/AI 标注帧,选择否时整帧保留,只清其它自动传播帧;工作区顶栏不再提供重复的“清空片段遮罩”。 - 项目状态已统一为
pending、parsing、ready、error;前端src/lib/api.ts会兼容归一化旧库中可能存在的Ready、Parsing、Error。 - 项目库的视频导入与生成帧是两个独立动作:导入视频只上传源文件,并通过 Axios
onUploadProgress在项目库显示导入进度;生成帧按钮才会带parse_fps调用/api/media/parse;DICOM 批量导入也会显示上传进度和文件数量,上传完成后创建解析任务并轮询显示解析进度。工作区不会再因“有视频但无帧”自动创建拆帧任务。 server.ts不再提供旧版/api/login、/api/projects、/api/templatesmock;当前前端真实 API 调用走 FastAPI 的/api/auth/*、/api/projects、/api/templates等接口。Dashboard.tsx初始统计、任务进度和活动日志来自GET /api/dashboard/overview;任务进度来自processing_tasksqueued/running/success/failed/cancelled,处理中统计只计算 queued/running,支持取消 queued/running 任务、重试 failed/cancelled 任务和查看失败详情。Celery worker 通过 Redis pub/sub 的seg:progress频道推送细粒度进度,再由 FastAPI 广播到/ws/progress;前端 WebSocket 客户端通过onopen/onclose/onerror更新连接状态,并定时发送ping心跳。
代码风格与约定
样式规范
- 深色主题为主,常见背景色包括
#0a0a0a、#111、#0d0d0d、#151515、#1e1e1e。 - 青色(如
cyan-400/cyan-500)用于激活状态、主按钮和关键指标。 - 前端样式主要使用 TailwindCSS 工具类,通过
cn()合并条件类名。 src/index.css使用 TailwindCSS 4 的@import "tailwindcss";。
组件规范
- 组件使用函数组件 + Hooks。
- 当前组件目录是扁平结构:
src/components/*.tsx,不是按模块子目录分层。 - Props 类型优先使用 TypeScript
interface。 - UI 文本保持中文。
- 代码与注释优先使用英文。
命名规范
- 组件文件使用 PascalCase,例如
AISegmentation.tsx。 - 工具文件使用 camelCase,例如
utils.ts。 - 类型和接口使用 PascalCase。
测试策略
当前仓库已配置前端 Vitest 测试和后端 pytest 测试。测试依据 doc/07-current-requirements-freeze.md、doc/08-current-design-freeze.md 和 doc/09-test-plan.md。
- 前端测试配置:
vitest.config.ts,共享 setup 在src/test/setup.tsx。 - 前端测试命令:
npm run test:run。 - 后端测试依赖:
backend/requirements-dev.txt。 - 后端测试命令:
pytest backend/tests,或在backend/目录执行pytest tests。 - 基础静态校验:
npm run lint、npm run build、python -m py_compile backend/routers/ai.py backend/routers/templates.py backend/schemas.py。 - 后端测试使用内存 SQLite、fake MinIO 和 fake SAM registry,不依赖真实 PostgreSQL、MinIO、Redis 或模型权重。
安全注意事项
- FastAPI 已有真实
users表、密码哈希和签名 JWT;默认admin / 123456只是开发种子用户,生产部署应通过环境变量或数据库改密。 - 业务路由会校验 Bearer token;项目、帧、标注、任务、Dashboard 和导出按当前用户拥有的项目过滤,模板支持系统模板(
owner_user_id IS NULL)和用户模板。 - 角色分为
admin、annotator、viewer:admin/annotator可调用写入类业务接口,viewer只能调用读接口;/api/admin/*仅允许admin。 - 管理员后台支持用户新增、停用/启用、改角色、站内弹窗改密码、站内确认删除无项目用户、查看登录/用户管理审计日志,以及站内二次确认后恢复演示出厂设置;禁止管理员删除、停用或降级自己。
- JWT 默认开发密钥在
backend/config.py,生产部署必须通过backend/.env覆盖JWT_SECRET_KEY。 backend/.env被.gitignore忽略;不要提交真实数据库、MinIO、Redis、模型路径等敏感配置。start_services.sh中包含本机路径和 sudo 启动逻辑,迁移机器时要审查。- Express
server.ts只负责前端开发/静态服务,不承担业务 API 或鉴权。
AI Studio / Vite 特定配置
.env.example包含GEMINI_API_KEY和APP_URL,说明这些值由 AI Studio 注入。vite.config.ts通过loadEnv把GEMINI_API_KEY注入到process.env.GEMINI_API_KEY。vite.config.ts中的DISABLE_HMR逻辑用于关闭 HMR,避免 AI Studio agent 编辑时闪烁。不要随意修改该逻辑。