12 KiB
算法集成规范(Route B · 通用接口接入)
本文是宏量点云处理平台对多类算法批量接入的对外规范,面向算法团队 + 验收方。一句话总结:
算法侧只交付
一个 CLI+一份 result.json,平台用统一插件机制 接入、并发执行、按 manifest 渲染表单、按 result.json 渲染结果。
满足工信部验收"算法在软件中集成运行"且 20 分钟内出结果的要求。
1. 总体架构
前端 (React)
└── AlgorithmRunPanel ← 由 manifest 自动生成插件列表 + 多选并发
└── GenericResultView ← 按 result.json 统一渲染(误差/测量/图片/3D 叠加)
└── PointCloudViewer ← pluginOverlay prop 接收插件生成的热力 / PLY
后端 (Node + tRPC)
└── algorithm.list ← 列出已注册插件
└── algorithm.run ← 单插件执行(入并发池)
└── algorithm.runBatch ← 多插件并发执行(入并发池)
└── /api/files/read/analysis/:taskId/:resultId/:filename ← 通用产物路由
算法层(按算法各自交付)
└── manifest/<id>.json ← 元数据 + UI 表单 + runtime
└── adapters/<id>_run.py ← 平台薄适配层
└── fixtures/<id>.json ← 未就绪时的 mock 预设
并发池:默认 4 路(环境变量 ALGORITHM_JOB_CONCURRENCY 可覆盖),
单算法超时默认 20 分钟(manifest 内 runtime.timeoutMs 可覆盖)。
2. Manifest JSON 协议
文件位置:server/plugins/manifests/<plugin-id>.json。完整示例见
coupler-panel.json 等。
{
"id": "coupler-panel", // 全局唯一 kebab-case ID
"name": "车钩面板检测", // UI 显示中文名
"description": "...",
"version": "1.0.0", // 算法版本
"schemaVersion": 1, // result.json schema 版本
"category": "几何测量", // UI 分组(可选)
"requiresPointCloud": true, // 默认 true
"requiresCad": false, // 默认 false;true 时需车型下有 CAD
"inputs": [
{
"name": "profile",
"label": "调参档位",
"type": "select", // number | integer | string | boolean | select
"default": "auto",
"options": [
{ "value": "auto", "label": "自动" },
{ "value": "high_density", "label": "高密度" }
],
"description": "辅助说明,渲染在字段下方"
}
],
"runtime": {
"kind": "process", // 真实算法
"cmd": ["{pythonBin}", "{adapterDir}/coupler_panel_run.py", "{inputJson}"],
"envExtra": {
"PYTHONPATH": "{algRoot}/挠度-偏转角/delivery/coupler_panel_release"
},
"timeoutMs": 1200000 // 默认 20 分钟
}
}
或 mock 模式(用于占位未就绪算法):
"runtime": {
"kind": "mock",
"fixture": "flatness-suite.json", // 相对 fixtures/ 的文件名
"simulateMs": 1500 // 模拟耗时,UI 演示进度条
}
2.1 cmd 占位符
| 占位符 | 含义 |
|---|---|
{pythonBin} |
容器内 Python 解释器($PYTHON3_PATH 或 python3) |
{inputJson} |
runner 写到 workDir 的 input.json 绝对路径 |
{workDir} |
任务工作目录(也是 outDir) |
{adapterDir} |
server/plugins/adapters/ 绝对路径 |
{algRoot} |
算法源码根目录($ALG_ROOT 或 /home/lipan/massive-cloud/alg) |
3. 输入契约 — input.json
runner 在 workDir 写出 input.json 后调用算法 CLI,固定路径:
/data/tasks/<taskId>/analysis/<resultId>/input.json。
{
"schemaVersion": 1,
"pluginId": "coupler-panel",
"pluginVersion": "1.0.0",
"resultId": 123,
"taskId": 88,
"workDir": "/data/tasks/88/analysis/123",
"outDir": "/data/tasks/88/analysis/123", // 与 workDir 相同
"inputs": {
"pointCloudPaths": ["/data/tasks/88/pointcloud/scan.ply"],
"cadPath": null, // 或 "/data/models/55/cad/panel.stl"
"config": { // 来自 UI 表单
"profile": "auto",
"rightAxisSign": "1"
}
}
}
约束:
- 算法不读全局环境,所有产物落
outDir; - 不修改 algorithm package 自身目录;
- CLI 必须非交互:禁用 GUI / 弹窗。
4. 输出契约 — result.json
算法在 outDir/result.json 写最终结果:
{
"schemaVersion": 1,
"status": "completed", // 或 "error"
"message": "可选简短说明",
"measurements": [
{ "name": "右偏转角", "value": 0.83, "unit": "°", "tolerance": 1.0, "status": "pass" },
{ "name": "最大 X 偏差", "value": 0.42, "unit": "mm", "tolerance": 1.0, "status": "pass" },
{ "name": "拟合内点率", "value": 0.92, "status": "info" }
],
"errorStats": { // 可选;用于 GenericResultView 误差卡片
"min": -1.83, "max": 1.28, "mean": 0.04, "stdDev": 0.32,
"totalPoints": 1500000,
"withinTolerance": 1450000, "outOfTolerance": 50000
},
"overlay3d": { // 可选;前端通过 PointCloudViewer.pluginOverlay 消费
"kind": "vertexColors", // 或 "ply" / "none"
"file": "heatmap.bin", // 相对 outDir
"vertexCount": 123456,
"dispMin": -0.5, "dispMax": 0.5
},
"images": ["plot.png", "images/diagnostic.png"], // 相对 outDir
"extra": { "...": "算法专属字段,前端折叠展示" }
}
measurements[*].status ∈ {pass | warning | fail | info},前端按 badge
颜色渲染。
5. 进度上报协议
子进程通过 stdout 单行 JSON 上报进度,runner 解析后写入数据库:
{"type":"progress","percent":0.12,"message":"去噪"}
{"type":"progress","percent":0.45,"message":"平面拟合"}
{"type":"progress","percent":0.80,"message":"轮廓度计算"}
字段说明:
percent:允许 01 或 0100,runner 自动归一化为 0~1;message:当前阶段名,会写入resultData.stage。
stderr 仅作日志,runner 会原样转发到平台日志。
6. 错误约定
| 情况 | 算法行为 | 平台行为 |
|---|---|---|
| 数据不合规 / 计算失败 | 写 result.json 且 status="error",message 中文+英文摘要 |
DB.status="error",前端红字提示 |
| 进程异常退出(无 result.json) | exit code 非 0 | runner 写一条带 exitCode 的 error 记录 |
| 超时 | runner 发 SIGKILL | DB.status="error",错误信息为「超时 N ms」 |
7. tRPC 接口
// 列出所有插件
trpc.algorithm.list.useQuery()
// → { plugins: PluginSummary[], scheduler: { running, queued, limit } }
// 单个算法
trpc.algorithm.run.useMutation()
// .mutate({ taskId, pluginId, params })
// → { id, pluginId, schemaVersion }
// 并发批量
trpc.algorithm.runBatch.useMutation()
// .mutate({ taskId, jobs: [{ pluginId, params }] })
// → { jobs: [{ id, pluginId }], scheduler }
每次 run 都会在 task_analysis_results 表插入一条 type='plugin' 的记录。
前端通过原有 trpc.analysisResult.list 轮询,按 pluginId 区分。
8. 数据库变更
迁移文件:drizzle/0007_task_analysis_plugin_columns.sql。
ALTER TABLE task_analysis_results
MODIFY COLUMN type ENUM(..., 'plugin') NOT NULL;
ALTER TABLE task_analysis_results
ADD COLUMN pluginId VARCHAR(64) NULL AFTER type,
ADD COLUMN schemaVersion INT NULL AFTER pluginId;
CREATE INDEX idx_task_analysis_plugin_id ON task_analysis_results (pluginId);
升级流程:
- 应用 SQL 迁移(
pnpm db:push或手动执行)。 - 重启 Node 服务。
- 新增算法只需新增 manifest + adapter,无需再变更数据库。
9. 文件路由(产物)
GET /api/files/read/analysis/:taskId/:resultId/:filename
返回 outDir 下任意相对路径的文件(含 images/foo.png 等)。前端通过
result.json.images / overlay3d.file 直接拼接 URL。
与现存
/read/analysis/heatmap/...、/read/analysis/assembly-vertex/...共存;具体路由优先匹配,最后由通用路由兜底。
10. 已接入的算法
页面右侧算法步骤与插件、平台适配器、算法源码包的对应关系如下。全部 5
个步骤都是真实 process 运行时,没有任何 mock。
| 页面步骤 | 插件 ID | 版本 | manifest | 平台适配器 | 算法源码包 / 工程 | 算法入口 | 算法负责人 |
|---|---|---|---|---|---|---|---|
| 整车配准 | vehicle-registration |
1.3.0 |
server/plugins/manifests/vehicle-registration.json |
server/plugins/adapters/vehicle_registration_run.py |
alg/整车配准v1.3 |
translate_align_pointcloud_to_cad.translate_align_by_front_tail |
待算法侧补充 |
| 车钩面板检测 | coupler-panel |
1.2.1 |
server/plugins/manifests/coupler-panel.json |
server/plugins/adapters/coupler_panel_run.py |
alg/delivery_v1.2.1/coupler_panel_release |
coupler_panel.pipeline.run_coupler_measurement_pipeline |
待算法侧补充 |
| 平整度 / 平面度 / 轮廓测量 | flatness-suite |
1.5.0 |
server/plugins/manifests/flatness-suite.json |
server/plugins/adapters/flatness_suite_run.py |
alg/平整度、平面度、轮廓测量v1.5 |
pipeline.MeasurementPipeline(contour / plane / curvature) |
待算法侧补充 |
| 列车点云测距 | train-measurement |
3.2.0 |
server/plugins/manifests/train-measurement.json |
server/plugins/adapters/train_measurement_run.py |
alg/train_measurement_system_v3.2_0629 |
task_manager.TaskManager |
待算法侧补充 |
| 构架挠度测量 | underframe-deflection |
1.2.1 |
server/plugins/manifests/underframe-deflection.json |
server/plugins/adapters/underframe_deflection_run.py |
alg/delivery_v1.2.1/underframe_deflection |
underframe_deflection.run_single_beam_pipeline |
待算法侧补充 |
说明:
- 负责人列用于区分各算法工程师归属;当前源码和 manifest 未记录个人姓名,需算法侧补充。
train-measurement即用户常说的train_measurement_system,当前接入版本为train_measurement_system_v3.2_0629。- 车钩面板检测和构架挠度测量同属
delivery_v1.2.1交付包,但分别是两个独立算法工程目录。 - flatness-suite 的 contour 子模块依赖较重(~2GB torch)。adapter 在 torch 缺失时会自动跳过 contour,plane / curvature 仍能正常出结果。
10.1 容器内依赖安装
镜像构建阶段:
# 通用部分 — 已经在 Dockerfile 里自动执行
python3 -m pip install --break-system-packages \
--default-timeout=300 --retries 10 \
-i https://pypi.tuna.tsinghua.edu.cn/simple \
-r requirements-geometry.txt
# 可选:启用 flatness-suite 的 contour 子模块(CPU 版,~2GB)
python3 -m pip install --break-system-packages \
-r requirements-flatness-contour.txt
10.2 算法源码挂载
docker-compose.yml 已配置:
app:
environment:
- ALG_ROOT=/alg
volumes:
- ${LOCAL_ALG_DIR:-./alg}:/alg:ro # 只读挂载,安全
宿主机上的 alg/ 目录(与本仓库 alg/ 结构一致)只读挂载到容器内 /alg。
manifest 里所有 {algRoot} 占位符在运行时都解析为该路径。算法源码通过卷挂载
而非打入镜像,避免 13.5GB 的构建上下文。
11. 验收检查清单
- 系统中存在「算法运行(多选并发)」面板,能列出 5 类算法
- 多选执行后所有算法并行调度(默认 4 路并发池)
- 单次运行有进度条,超时时间按各算法 manifest 配置
- 结果统一渲染:误差统计 / 测量值表 / 图片 / 3D 叠加按钮
- 数据库记录算法 ID + schemaVersion,可回放、可追溯
- 算法层与 UI 层完全解耦:新增算法不改前端 / 不改 tRPC / 不改数据库
12. 后续接入新算法的步骤
- 把算法封装成一个 Python 入口:
python -m my_algo.run <input.json>, 保证:读inputs.pointCloudPaths[0]→ 计算 → 写outDir/result.json。 - 复制一份 manifest(如
coupler-panel.json)改id、cmd、envExtra、inputs。 - (可选)写一个 adapter
server/plugins/adapters/my_algo_run.py做参数翻译, 或者算法 CLI 直接吃 input.json。 - 重启服务,前端自动刷出新条目,不需要任何前端 / SQL 改动。