- 增强 DICOM/视频项目导入与演示数据:DICOM 按文件名自然顺序处理,导入后展示上传与解析任务进度,恢复演示出厂设置保留演示视频和演示 DICOM 项目,并补充 demo media seed 逻辑。 - 完善项目管理:项目支持重命名、删除、复制,删除使用站内确认弹窗,复制支持新项目重置和全内容复制,DICOM 项目不显示生成帧入口。 - 完善 GT Mask 与导出链路:只支持 8-bit maskid 图导入,非法/全背景图明确拒绝,尺寸自动适配,高精度 polygon 回显;统一导出默认当前帧,GT_label 使用 uint8 和真实 maskid,待分类 maskid 0 与背景一致。 - 完善分割工作区交互:新增画笔和橡皮擦并支持尺寸控制,移除创建点/线段入口,工具栏按类别分隔,AI 智能分割使用明确 AI 图标,取消黄色 seed point,清空/删除传播 mask 后同步清理空帧时间轴状态。 - 完善传播与时间轴:自动传播使用 SAM 2.1 权重任务,参考帧无遮罩时提示,传播历史按同一蓝色系递进变暗,删除/清空传播链时保留人工或独立 AI 标注来源。 - 完善模板库:新增头颈部 CT 分割默认模板,所有模板保留 maskid 0 待分类,支持鼠标复制模板、拖拽层级、JSON 批量导入预览、删除 label 和站内删除确认。 - 完善用户与高风险确认:用户改密码、删除用户、恢复演示出厂设置和清空人工/AI 标注帧均改为站内确认交互,避免浏览器原生 prompt/confirm。 - 补充前后端测试与文档:更新项目、模板、GT 导入、导出、传播、DICOM、用户管理等测试,并同步 README、AGENTS 和 doc 下实现/契约/测试计划文档。
384 lines
9.8 KiB
Python
384 lines
9.8 KiB
Python
"""Pydantic schemas for request/response validation."""
|
|
|
|
from datetime import datetime
|
|
from typing import Literal, Optional, Any
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Auth / user schemas
|
|
# ---------------------------------------------------------------------------
|
|
class UserOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
username: str
|
|
role: str
|
|
is_active: int
|
|
|
|
|
|
class LoginResponse(BaseModel):
|
|
token: str
|
|
token_type: str = "bearer"
|
|
username: str
|
|
user: UserOut
|
|
|
|
|
|
class AdminUserCreate(BaseModel):
|
|
username: str
|
|
password: str
|
|
role: str = "annotator"
|
|
is_active: bool = True
|
|
|
|
|
|
class AdminUserUpdate(BaseModel):
|
|
username: Optional[str] = None
|
|
password: Optional[str] = None
|
|
role: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
|
|
class AuditLogOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
actor_user_id: Optional[int] = None
|
|
action: str
|
|
target_type: Optional[str] = None
|
|
target_id: Optional[str] = None
|
|
detail: Optional[dict[str, Any]] = None
|
|
created_at: datetime
|
|
|
|
|
|
class DemoFactoryResetRequest(BaseModel):
|
|
confirmation: str
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Project schemas
|
|
# ---------------------------------------------------------------------------
|
|
class ProjectBase(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
video_path: Optional[str] = None
|
|
thumbnail_url: Optional[str] = None
|
|
status: Optional[str] = "pending"
|
|
source_type: Optional[str] = "video"
|
|
original_fps: Optional[float] = None
|
|
parse_fps: Optional[float] = 30.0
|
|
|
|
|
|
class ProjectCreate(ProjectBase):
|
|
pass
|
|
|
|
|
|
class ProjectUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
video_path: Optional[str] = None
|
|
thumbnail_url: Optional[str] = None
|
|
status: Optional[str] = None
|
|
source_type: Optional[str] = None
|
|
original_fps: Optional[float] = None
|
|
parse_fps: Optional[float] = None
|
|
|
|
|
|
class ProjectCopyRequest(BaseModel):
|
|
mode: Literal["reset", "full"] = "reset"
|
|
name: Optional[str] = None
|
|
|
|
|
|
class ProjectOut(ProjectBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
owner_user_id: Optional[int] = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
frame_count: int = 0
|
|
|
|
|
|
class DemoFactoryResetOut(BaseModel):
|
|
admin_user: UserOut
|
|
project: ProjectOut
|
|
projects: list[ProjectOut]
|
|
deleted_counts: dict[str, int]
|
|
message: str
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Frame schemas
|
|
# ---------------------------------------------------------------------------
|
|
class FrameBase(BaseModel):
|
|
frame_index: int
|
|
image_url: str
|
|
width: Optional[int] = None
|
|
height: Optional[int] = None
|
|
timestamp_ms: Optional[float] = None
|
|
source_frame_number: Optional[int] = None
|
|
|
|
|
|
class FrameCreate(FrameBase):
|
|
project_id: int
|
|
|
|
|
|
class FrameOut(FrameBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
project_id: int
|
|
created_at: datetime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Template schemas
|
|
# ---------------------------------------------------------------------------
|
|
class TemplateBase(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
color: str
|
|
z_index: int = 0
|
|
mapping_rules: Optional[dict[str, Any]] = None
|
|
classes: Optional[list[dict[str, Any]]] = None
|
|
rules: Optional[list[dict[str, Any]]] = None
|
|
|
|
|
|
class TemplateCreate(TemplateBase):
|
|
pass
|
|
|
|
|
|
class TemplateUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
color: Optional[str] = None
|
|
z_index: Optional[int] = None
|
|
mapping_rules: Optional[dict[str, Any]] = None
|
|
classes: Optional[list[dict[str, Any]]] = None
|
|
rules: Optional[list[dict[str, Any]]] = None
|
|
|
|
|
|
class TemplateOut(TemplateBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
owner_user_id: Optional[int] = None
|
|
created_at: datetime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Annotation schemas
|
|
# ---------------------------------------------------------------------------
|
|
class AnnotationBase(BaseModel):
|
|
project_id: int
|
|
frame_id: Optional[int] = None
|
|
template_id: Optional[int] = None
|
|
mask_data: Optional[dict[str, Any]] = None
|
|
points: Optional[list[list[float]]] = None
|
|
bbox: Optional[list[float]] = None
|
|
|
|
|
|
class AnnotationCreate(AnnotationBase):
|
|
pass
|
|
|
|
|
|
class AnnotationUpdate(BaseModel):
|
|
mask_data: Optional[dict[str, Any]] = None
|
|
points: Optional[list[list[float]]] = None
|
|
bbox: Optional[list[float]] = None
|
|
template_id: Optional[int] = None
|
|
|
|
|
|
class AnnotationOut(AnnotationBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mask schemas
|
|
# ---------------------------------------------------------------------------
|
|
class MaskBase(BaseModel):
|
|
annotation_id: int
|
|
mask_url: str
|
|
format: str = "png"
|
|
|
|
|
|
class MaskCreate(MaskBase):
|
|
pass
|
|
|
|
|
|
class MaskOut(MaskBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
created_at: datetime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Processing task schemas
|
|
# ---------------------------------------------------------------------------
|
|
class ProcessingTaskOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
task_type: str
|
|
status: str
|
|
progress: int
|
|
message: Optional[str] = None
|
|
project_id: Optional[int] = None
|
|
celery_task_id: Optional[str] = None
|
|
payload: Optional[dict[str, Any]] = None
|
|
result: Optional[dict[str, Any]] = None
|
|
error: Optional[str] = None
|
|
created_at: datetime
|
|
started_at: Optional[datetime] = None
|
|
finished_at: Optional[datetime] = None
|
|
updated_at: datetime
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# AI schemas
|
|
# ---------------------------------------------------------------------------
|
|
class PredictRequest(BaseModel):
|
|
image_id: int
|
|
prompt_type: str # point / box / semantic
|
|
prompt_data: Any
|
|
model: Optional[str] = None
|
|
options: Optional[dict[str, Any]] = None
|
|
|
|
|
|
class PredictResponse(BaseModel):
|
|
polygons: list[list[list[float]]]
|
|
scores: Optional[list[float]] = None
|
|
|
|
|
|
class MaskAnalysisRequest(BaseModel):
|
|
frame_id: Optional[int] = None
|
|
mask_data: dict[str, Any]
|
|
points: Optional[list[list[float]]] = None
|
|
bbox: Optional[list[float]] = None
|
|
extract_skeleton: bool = False
|
|
|
|
|
|
class MaskAnalysisResponse(BaseModel):
|
|
confidence: Optional[float] = None
|
|
confidence_source: str
|
|
topology_anchor_count: int
|
|
topology_anchors: list[list[float]]
|
|
area: float
|
|
bbox: Optional[list[float]] = None
|
|
source: Optional[str] = None
|
|
message: str
|
|
|
|
|
|
class SmoothMaskRequest(BaseModel):
|
|
frame_id: Optional[int] = None
|
|
mask_data: dict[str, Any]
|
|
points: Optional[list[list[float]]] = None
|
|
bbox: Optional[list[float]] = None
|
|
strength: float = 0.0
|
|
method: str = "chaikin"
|
|
|
|
|
|
class SmoothMaskResponse(BaseModel):
|
|
polygons: list[list[list[float]]]
|
|
topology_anchor_count: int
|
|
topology_anchors: list[list[float]]
|
|
area: float
|
|
bbox: Optional[list[float]] = None
|
|
smoothing: dict[str, Any]
|
|
message: str
|
|
|
|
|
|
class PropagationSeed(BaseModel):
|
|
polygons: Optional[list[list[list[float]]]] = None
|
|
bbox: Optional[list[float]] = None
|
|
points: Optional[list[list[float]]] = None
|
|
labels: Optional[list[int]] = None
|
|
label: Optional[str] = None
|
|
color: Optional[str] = None
|
|
class_metadata: Optional[dict[str, Any]] = None
|
|
template_id: Optional[int] = None
|
|
source_mask_id: Optional[str] = None
|
|
source_annotation_id: Optional[int] = None
|
|
propagation_seed_signature: Optional[str] = None
|
|
smoothing: Optional[dict[str, Any]] = None
|
|
|
|
|
|
class PropagateRequest(BaseModel):
|
|
project_id: int
|
|
frame_id: int
|
|
model: Optional[str] = "sam2.1_hiera_tiny"
|
|
seed: PropagationSeed
|
|
direction: str = "forward"
|
|
max_frames: int = 30
|
|
include_source: bool = False
|
|
save_annotations: bool = True
|
|
|
|
|
|
class PropagateResponse(BaseModel):
|
|
model: str
|
|
direction: str
|
|
source_frame_id: int
|
|
processed_frame_count: int
|
|
created_annotation_count: int
|
|
annotations: list[AnnotationOut]
|
|
|
|
|
|
class PropagateTaskStep(BaseModel):
|
|
seed: PropagationSeed
|
|
direction: str = "forward"
|
|
max_frames: int = 30
|
|
|
|
|
|
class PropagateTaskRequest(BaseModel):
|
|
project_id: int
|
|
frame_id: int
|
|
model: Optional[str] = "sam2.1_hiera_tiny"
|
|
steps: list[PropagateTaskStep]
|
|
include_source: bool = False
|
|
save_annotations: bool = True
|
|
|
|
|
|
class AiModelStatus(BaseModel):
|
|
id: str
|
|
label: str
|
|
available: bool
|
|
loaded: bool = False
|
|
device: str
|
|
supports: list[str]
|
|
message: str
|
|
package_available: bool = False
|
|
checkpoint_exists: bool = False
|
|
checkpoint_path: Optional[str] = None
|
|
python_ok: bool = True
|
|
torch_ok: bool = True
|
|
cuda_required: bool = False
|
|
external_available: bool = False
|
|
external_python: Optional[str] = None
|
|
|
|
|
|
class GpuStatus(BaseModel):
|
|
available: bool
|
|
device: str
|
|
name: Optional[str] = None
|
|
torch_available: bool
|
|
torch_version: Optional[str] = None
|
|
cuda_version: Optional[str] = None
|
|
|
|
|
|
class AiRuntimeStatus(BaseModel):
|
|
selected_model: str
|
|
gpu: GpuStatus
|
|
models: list[AiModelStatus]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Export schemas
|
|
# ---------------------------------------------------------------------------
|
|
class ExportStatus(BaseModel):
|
|
url: str
|
|
format: str
|