feat: 打通全栈标注闭环、异步拆帧与模型状态
后端能力: - 新增 Celery app、worker task、ProcessingTask 模型、/api/tasks 查询接口和 media_task_runner,将 /api/media/parse 改为创建后台任务并由 worker 执行 FFmpeg/OpenCV/pydicom 拆帧。 - 新增 Redis 进度事件模块和 FastAPI Redis pub/sub 订阅,将 worker 任务进度广播到 /ws/progress;Dashboard 后端概览接口改为聚合 projects/frames/annotations/templates/processing_tasks。 - 统一项目状态为 pending/parsing/ready/error,新增共享 status 常量,并让前端兼容归一化旧状态值。 - 扩展 AI 后端:新增 SAM registry、SAM2 真实运行状态、SAM3 状态检测与文本语义推理适配入口,以及 /api/ai/models/status GPU/模型状态接口。 - 补齐标注保存/更新/删除、COCO/PNG mask 导出相关后端契约和模板 mapping_rules 打包/解包行为。 前端能力: - 新增运行时 API/WS 地址推导配置,前端 API 封装对齐 FastAPI 路由、字段映射、任务轮询、标注归档、导出下载和 AI 预测响应转换。 - Dashboard 改为读取 /api/dashboard/overview,并订阅 WebSocket progress/complete/error/status 更新解析队列和实时流转记录。 - 项目库导入视频/DICOM 后创建项目、上传媒体、触发异步解析并刷新真实项目列表。 - 工作区加载真实帧、无帧时触发解析任务、回显已保存标注、保存未归档 mask、更新 dirty mask、清空当前帧后端标注、导出 COCO JSON。 - Canvas 支持当前帧点/框提示调用后端 AI、渲染推理/已保存 mask、应用模板分类并维护保存状态计数;时间轴按项目 fps 播放。 - AI 页面新增 SAM2/SAM3 模型选择,预测请求携带 model;侧边栏和工作区新增真实 GPU/SAM 状态徽标。 - 模板库和本体面板接入真实模板 CRUD、分类编辑、拖拽排序、JSON 导入、默认腹腔镜分类和本地自定义分类选择。 测试与文档: - 新增 Vitest 配置、前端测试 setup、API/config/websocket/store/组件测试,覆盖登录、项目库、Dashboard、Canvas、工作区、模型状态、时间轴、本体和模板库。 - 新增 pytest 后端测试夹具和 auth/projects/templates/media/AI/export/dashboard/tasks/progress 测试,使用 SQLite、fake MinIO、fake SAM registry 和 Redis monkeypatch 隔离外部服务。 - 新增 doc/ 文档结构,冻结当前需求、设计、接口契约、测试计划、前端逐元素审计、实现地图和后续实施计划,并同步更新 README 与 AGENTS。 验证: - conda run -n seg_server pytest backend/tests:27 passed。 - npm run test:run:54 passed。 - npm run lint、npm run build、compileall、git diff --check 均通过;Vite 仅提示大 chunk 警告。
This commit is contained in:
@@ -4,7 +4,7 @@ export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
status: 'Ready' | 'Parsing' | 'Error';
|
||||
status: 'pending' | 'parsing' | 'ready' | 'error';
|
||||
fps?: string;
|
||||
frames?: number;
|
||||
thumbnail?: string;
|
||||
@@ -17,6 +17,8 @@ export interface Project {
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export type AiModelId = 'sam2' | 'sam3';
|
||||
|
||||
export interface Frame {
|
||||
id: string;
|
||||
projectId: string;
|
||||
@@ -42,6 +44,13 @@ export interface Annotation {
|
||||
export interface Mask {
|
||||
id: string;
|
||||
frameId: string;
|
||||
annotationId?: string;
|
||||
templateId?: string;
|
||||
classId?: string;
|
||||
className?: string;
|
||||
classZIndex?: number;
|
||||
saveStatus?: 'draft' | 'saved' | 'dirty' | 'saving' | 'error';
|
||||
saved?: boolean;
|
||||
pathData: string;
|
||||
label: string;
|
||||
color: string;
|
||||
@@ -96,24 +105,32 @@ export interface AppState {
|
||||
// Workspace
|
||||
activeModule: string;
|
||||
activeTool: string;
|
||||
aiModel: AiModelId;
|
||||
frames: Frame[];
|
||||
currentFrameIndex: number;
|
||||
annotations: Annotation[];
|
||||
masks: Mask[];
|
||||
setActiveModule: (module: string) => void;
|
||||
setActiveTool: (tool: string) => void;
|
||||
setAiModel: (model: AiModelId) => void;
|
||||
setFrames: (frames: Frame[]) => void;
|
||||
setCurrentFrame: (index: number) => void;
|
||||
addAnnotation: (annotation: Annotation) => void;
|
||||
addMask: (mask: Mask) => void;
|
||||
updateMask: (id: string, updates: Partial<Mask>) => void;
|
||||
setMasks: (masks: Mask[]) => void;
|
||||
clearMasks: () => void;
|
||||
removeAnnotation: (id: string) => void;
|
||||
|
||||
// Templates
|
||||
templates: Template[];
|
||||
activeTemplateId: string | null;
|
||||
activeClassId: string | null;
|
||||
activeClass: TemplateClass | null;
|
||||
setTemplates: (templates: Template[]) => void;
|
||||
setActiveTemplateId: (id: string | null) => void;
|
||||
setActiveClassId: (id: string | null) => void;
|
||||
setActiveClass: (templateClass: TemplateClass | null) => void;
|
||||
addTemplate: (template: Template) => void;
|
||||
updateTemplate: (template: Template) => void;
|
||||
removeTemplate: (id: string) => void;
|
||||
@@ -144,6 +161,9 @@ export const useStore = create<AppState>((set) => ({
|
||||
frames: [],
|
||||
annotations: [],
|
||||
masks: [],
|
||||
activeTemplateId: null,
|
||||
activeClassId: null,
|
||||
activeClass: null,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -162,18 +182,25 @@ export const useStore = create<AppState>((set) => ({
|
||||
// Workspace
|
||||
activeModule: 'workspace',
|
||||
activeTool: 'move',
|
||||
aiModel: 'sam2',
|
||||
frames: [],
|
||||
currentFrameIndex: 0,
|
||||
annotations: [],
|
||||
masks: [],
|
||||
setActiveModule: (activeModule: string) => set({ activeModule }),
|
||||
setActiveTool: (activeTool: string) => set({ activeTool }),
|
||||
setAiModel: (aiModel: AiModelId) => set({ aiModel }),
|
||||
setFrames: (frames: Frame[]) => set({ frames }),
|
||||
setCurrentFrame: (currentFrameIndex: number) => set({ currentFrameIndex }),
|
||||
addAnnotation: (annotation: Annotation) =>
|
||||
set((state) => ({ annotations: [...state.annotations, annotation] })),
|
||||
addMask: (mask: Mask) =>
|
||||
set((state) => ({ masks: [...state.masks, mask] })),
|
||||
updateMask: (id: string, updates: Partial<Mask>) =>
|
||||
set((state) => ({
|
||||
masks: state.masks.map((mask) => (mask.id === id ? { ...mask, ...updates } : mask)),
|
||||
})),
|
||||
setMasks: (masks: Mask[]) => set({ masks }),
|
||||
clearMasks: () => set({ masks: [] }),
|
||||
removeAnnotation: (id: string) =>
|
||||
set((state) => ({
|
||||
@@ -183,8 +210,15 @@ export const useStore = create<AppState>((set) => ({
|
||||
// Templates
|
||||
templates: [],
|
||||
activeTemplateId: null,
|
||||
activeClassId: null,
|
||||
activeClass: null,
|
||||
setTemplates: (templates: Template[]) => set({ templates }),
|
||||
setActiveTemplateId: (activeTemplateId: string | null) => set({ activeTemplateId }),
|
||||
setActiveClassId: (activeClassId: string | null) => set({ activeClassId }),
|
||||
setActiveClass: (activeClass: TemplateClass | null) => set({
|
||||
activeClass,
|
||||
activeClassId: activeClass?.id || null,
|
||||
}),
|
||||
addTemplate: (template: Template) =>
|
||||
set((state) => ({ templates: [...state.templates, template] })),
|
||||
updateTemplate: (template: Template) =>
|
||||
|
||||
Reference in New Issue
Block a user