⚠️ 自动镜像 · 此页由
docs-site/scripts/mirror-adr.mjs从docs/adr/0026-tool-unit-class-and-attribute-binding.md生成,请勿直接编辑此处;改源文件后pnpm docs:build会自动同步。
0026 — 类别与属性按工具单位 (tool_unit) 强隔离绑定
- Status: Accepted
- Date: 2026-05-19
- Deciders: core team
- Supersedes: —
Context
v0.10.16 之前,项目的类别 (classes + classes_config) 与属性 schema (attribute_schema) 是项目级扁平字段,所有工具共享同一份:bbox 工具下拉里看到的类、polygon 工具下拉里看到的类、AI 交互工具的类必须完全一致。客户反馈的现实场景需要更细颗粒度:
"我希望 bbox 工具标行人/车辆,polygon 工具标可行驶区/天空,AI 交互工具又有自己的类。一张大表混着塞不下,且现在的'类型' (image-det / image-seg) 枚举太死,新场景每来一次就要加 type_key。"
同期还有两条需求叠加:
- 新建项目向导的 type 枚举从 7 种(image-det / image-seg / image-kp / video-mm / video-track / mm / lidar)收敛 — 实际只有 image / video / lidar 三种数据载体,具体能做什么由"工具集"决定。
- TemplateEditModal 后端 schema 已支持
classes_config / attribute_schema / rendering_config,前端 Modal 没暴露;若工具维度生效,模板也得按工具单位携带。
| 选项 | 主要卖点 | 主要劣势 |
|---|---|---|
| A: 工具独占类别 (强隔离) | 不同工具的同名类是两条独立记录;可同名不同色;调色板 / 属性面板 / 导出 categories 都按工具单位天然分组 | bbox 与 polygon 同时想用「人」类要重复输入;客户跨工具复用类的场景需手工同步 |
| B: 工具选择类别子集 | 项目仍有一份扁平类别池,每工具勾选可用子集;复用方便 | UI 复杂度从「写一份」变成「写一份+按工具勾选」;后端导出仍需按工具分组,服务层逻辑跨字段 |
| C: 类别共享 + 属性绑定工具 | 类别保持项目级,只把 attribute_schema 按工具拆分 | "同一目标用不同工具时填不同属性"是少数场景;类别下拉无变化 → 客户原始诉求未解决 |
Decision
采用方案 A: 工具独占类别 (强隔离)。每个被启用的工具单位 (tool_unit_id) 独立持有 classes + attribute_schema,bbox 工具的「人」与 region 工具的「人」是两条独立记录。
工具单位枚举 (与后端 app/schemas/_jsonb_types.ToolUnitId Literal 严格对齐):
| tool_unit_id | 包含工具 (Workbench ToolId) | type 限制 |
|---|---|---|
bbox | BboxTool | image, video |
polyline | (v0.10.17 占位, 未实现) | image, video |
region | PolygonTool + MaskTool 打包 | image |
ai_interactive | SmartPointTool + SmartBoxTool + TextPromptTool + ExemplarTool + MagicBoxTool 打包 | image |
lidar_box_3d | (v0.10.17 占位, 未实现) | lidar |
实施要点
- 新增
Project.tool_bindings: JSONB,形状{ tool_unit_id: { enabled, classes: [...], attribute_schema: {...} } }。 Annotation.tool_unit_id/Prediction.tool_unit_id列必填,默认bbox;alembic 0072 按annotation_type(polygon/mask → region) 反向 backfill。- 旧
Project.classes_config与attribute_schema保留为派生只读字段,运行期由app/services/project.py的apply_tool_bindings_legacy_sync同步双写:- 写 tool_bindings → 派生覆盖 legacy
- 旧客户端只写 legacy →
coalesce_legacy_into_tool_bindings按 type_key 反推到对应 unit - 单源真值 = tool_bindings;legacy 字段在 v0.10.18 删除。
- COCO 导出 categories 按 tool_unit 分组,带
supercategory = tool_unit_id;cat_map改为(tool_unit_id, class_name) → category_id。 - AAP JSON
schema_version升1.1,envelope 加project.tool_bindings,annotations / predictions 数组每条加tool_unit_id(1.0 reader 走extra="ignore"仍兼容)。 - 工作台
useToolBindings(project, activeToolId)派生当前激活工具的 classes / classesConfig / attributeSchema;切工具时若 activeClass 不在新 unit 类别集自动切首个类。 - ProjectTemplate 同步加
tool_bindings字段 (alembic 0073) +CLONEABLE_PROJECT_FIELDS收入。
Consequences
正向
- 客户原始诉求 (同项目内不同工具用不同类) 直接解决,无需手工 hack。
- 工具维度的类别天然解耦:同名不同色合法 (强隔离),不存在"项目级类名重复"歧义。
- 导出 COCO/AAP JSON 的语义更清晰:
supercategory/tool_unit_id标注了类别来源工具,下游训练 pipeline 能区分。 - 新建向导只需问"启用哪些工具单位",不再依赖 type_key 列出 7 种排列组合;新增工具时不破坏 type 枚举。
- ProjectTemplate 与 Project 共享同一份 tool_bindings 结构,模板的工具集 / 类别 / 属性可独立编辑 (TemplateEditModal v0.10.17 已实现 3-tab UI)。
负向
- 跨工具复用同名类需重复输入 (bbox 加「人」 / region 加「人」是两次操作);若客户后续反馈"想共享类别名字 / 颜色",再加可选
alias_to: (tool_unit, class_name)链(本版不做)。 - 老项目数据迁移:alembic 0072 默认把所有 image-det / video-track 等项目类塞到
bboxunit;若客户实际混用 polygon 工具,需事后到 ProjectSettings 把类复制到regionunit(强隔离,不能共享)。这条已在 CHANGELOG / docs-site/user-guide 标注。 - API 增加一层概念:annotation 创建必须带
tool_unit_id,旧 SDK / 第三方调用者需升级 (本版默认bbox保兼容,但服务层 422 校验严格)。 - v0.10.17 期间 legacy 字段双写,有短暂"数据冗余",约 v0.10.18 删除派生字段后回归单源。
Alternatives Considered(详)
方案 B (项目共享类别池 + 工具勾选子集):更折中的形态,但需要新增"类别池"实体 + 每工具的"可用子集"映射两层数据结构,UI 上还得引导用户先建池再勾。客户的诉求是"独立",不是"共享后再勾",方案 B 中间多了一层抽象不必要。否决。
方案 C (类别共享 + 属性绑定工具):"同一目标用不同工具时填不同属性"是少数场景,不能覆盖原始诉求"bbox 标行人, polygon 标道路" — 道路根本不应该出现在 bbox 工具的类别下拉里。否决。
方案 D (彻底打散为 N 个独立项目):把 image-det 与 image-seg 拆成两个项目,分别配类。问题:同一份图像数据(dataset)要跑两次标注,任务调度 / 进度 / 成员管理双轨,客户更头疼。否决。
Notes
- 实现代码位置:
- 后端:
apps/api/app/db/models/project.py、annotation.py、prediction.py、project_template.py;apps/api/app/schemas/_jsonb_types.py;apps/api/app/schemas/project.py/annotation.py/prediction.py/project_template.py/aap_json.py;apps/api/app/services/project.py(新建,含derive_legacy_classes_config/coalesce_legacy_into_tool_bindings/apply_tool_bindings_legacy_sync);apps/api/app/services/annotation.py(class_name 软校验);apps/api/app/services/prediction.py(derive_tool_unit_from_ls_type派生);apps/api/app/services/export.py(COCO categories);apps/api/app/api/v1/projects.py(create/update/rename_class);apps/api/app/api/v1/tasks.py(create_annotation 透传)。 - 迁移:
alembic/versions/0072_project_tool_bindings.py、0073_template_tool_bindings.py。 - 前端:
apps/web/src/constants/toolUnits.ts、apps/web/src/components/projects/CreateProjectWizard.tsx、apps/web/src/pages/Projects/sections/{ClassesSection,AttributesSection,ToolUnitTabs,useProjectToolBindings}.{tsx,ts}、apps/web/src/pages/Workbench/state/useToolBindings.ts、apps/web/src/pages/Workbench/stage/tools/{MagicBoxTool,toolUnits}.ts、apps/web/src/pages/Workbench/stage/shared/geometry/bbox.ts、apps/web/src/pages/ProjectTemplates/TemplateEditModal.tsx。
- 后端:
- 相关 ROADMAP / ADR: ROADMAP §A「新建项目向导」「项目模板」、§C.3「Magic Box」、ADR-0023 (ProjectTemplate)、ADR-0024 (AAP JSON)。
- 触发后续工作: ROADMAP §A 加入 v0.10.18+ 「删除派生 classes_config / attribute_schema」「polyline / lidar_box_3d 工具实现」「跨 tool_unit 类别软关联 (alias_to)」「Snap-to-edge Canny/Sobel」「rendering_config 共享编辑器 (供 TemplateEditModal 复用)」等延伸项。