From e0326c2d80538643169872cdd3df0af088dab71c Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Thu, 23 Apr 2026 07:14:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=BB=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=EF=BC=9B=E5=A2=9E=E5=8A=A0=E6=8A=A5=E5=91=8A=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- XplorePlane/Doc/CNC多检测结果归档.md | 505 +++++++++++++++++++++++++ XplorePlane/Views/Main/MainWindow.xaml | 4 +- 3 files changed, 509 insertions(+), 4 deletions(-) create mode 100644 XplorePlane/Doc/CNC多检测结果归档.md diff --git a/README.md b/README.md index ca86f50..6d8de7a 100644 --- a/README.md +++ b/README.md @@ -106,5 +106,5 @@ dotnet build XplorePlane.sln -c Release - [x] 相机实时影像集成(连接、采集、Live View、像素坐标显示) - [x] 相机参数设置对话框(曝光、增益、分辨率、像素格式) - [x] 主界面硬件栏相机设置按钮 -- [ ] 打通与硬件层的调用流程 -- [ ] 打通与图像层的调用流程 +- [x] 打通与硬件层的调用流程 +- [x] 打通与图像层的调用流程 diff --git a/XplorePlane/Doc/CNC多检测结果归档.md b/XplorePlane/Doc/CNC多检测结果归档.md new file mode 100644 index 0000000..fd167f3 --- /dev/null +++ b/XplorePlane/Doc/CNC多检测结果归档.md @@ -0,0 +1,505 @@ +# CNC 多检测结果归档与报告取数说明 + +## 1. 目标 + +为 CNC 执行结果提供一套适合报告模块直接取数的归档结构。 + +设计目标: + +- 以“一次工件检测实例”作为主归档单位 +- 同时保留到“检测节点级别”的明细 +- 支持保存: + - CNC 程序名 + - 工件号 / 序列号 + - 检测节点信息 + - 节点使用的 Pipeline / 配方快照 + - 原图、节点输入图、节点最终结果图 + - 节点输出的多个数值结果 + - 节点判定和整次实例判定 +- 方便后续报告模块直接读取,不依赖运行时最新配方 + +当前实现采用: + +- SQLite 保存结构化索引数据 +- 文件系统保存图片资产和 `manifest.json` + +--- + +## 2. 总体设计 + +一次检测实例会生成: + +1. 一组数据库记录 +2. 一组文件目录和图像文件 +3. 一份 `manifest.json` 快照文件 + +归档核心对象: + +- `InspectionRunRecord` + - 一次完整检测实例 +- `InspectionNodeResult` + - 一个 CNC 检测节点的结果 +- `InspectionMetricResult` + - 节点输出的数值结果 +- `InspectionAssetRecord` + - 图像或附件索引 +- `PipelineExecutionSnapshot` + - 节点执行时使用的 Pipeline 快照 + +默认图片保留策略: + +- 整次实例原图 +- 每个节点的输入图 +- 每个节点的最终结果图 + +不默认保存每个算法步骤的中间图。 + +--- + +## 3. 文件存储结构 + +### 3.1 根目录 + +默认根目录: + +```text +%AppData%\XplorePlane\InspectionResults +``` + +每次检测实例按日期和 `RunId` 分层: + +```text +Results/{yyyy}/{MM}/{dd}/{RunId}/ +``` + +### 3.2 示例目录 + +假设: + +- `ProgramName = NewCncProgram` +- `WorkpieceId = QFN_1` +- `SerialNumber = SN-001` +- `RunId = 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` +- 检测节点共 2 个 + +则文件结构大致为: + +```text +%AppData%\XplorePlane\InspectionResults\ +└─ Results\ + └─ 2026\ + └─ 04\ + └─ 21\ + └─ 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a\ + ├─ manifest.json + ├─ run\ + │ └─ source.bmp + └─ nodes\ + ├─ 001_检测节点1\ + │ ├─ input.bmp + │ └─ result_overlay.bmp + └─ 002_检测节点2\ + └─ result_overlay.bmp +``` + +### 3.3 文件说明 + +- `run/source.bmp` + - 本次工件检测实例的原始输入图 +- `nodes/001_检测节点1/input.bmp` + - 节点 1 输入图 +- `nodes/001_检测节点1/result_overlay.bmp` + - 节点 1 最终结果图 +- `nodes/002_检测节点2/result_overlay.bmp` + - 节点 2 最终结果图 +- `manifest.json` + - 本次检测完整快照,便于离线查看、调试和导出 + +--- + +## 4. 数据库表设计 + +当前实现包含 5 张主表。 + +### 4.1 `inspection_runs` + +用途:保存一次完整检测实例的主记录。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `run_id` | `TEXT` | 主键,检测实例 ID,GUID | +| `program_name` | `TEXT` | CNC 程序名 | +| `workpiece_id` | `TEXT` | 工件号 | +| `serial_number` | `TEXT` | 序列号 | +| `started_at` | `TEXT` | 开始时间,ISO 8601 | +| `completed_at` | `TEXT` | 结束时间,ISO 8601,可空 | +| `overall_pass` | `INTEGER` | 整体判定,`0/1` | +| `source_image_path` | `TEXT` | 原图相对路径 | +| `result_root_path` | `TEXT` | 本次结果包根目录相对路径 | +| `node_count` | `INTEGER` | 节点数量 | + +样例数据: + +| run_id | program_name | workpiece_id | serial_number | started_at | completed_at | overall_pass | source_image_path | result_root_path | node_count | +|---|---|---|---|---|---|---:|---|---|---:| +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `NewCncProgram` | `QFN_1` | `SN-001` | `2026-04-21T10:00:00.0000000Z` | `2026-04-21T10:00:03.2000000Z` | `0` | `Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp` | `Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `2` | + +### 4.2 `inspection_node_results` + +用途:保存一次检测实例中的节点级结果。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `run_id` | `TEXT` | 所属检测实例 ID | +| `node_id` | `TEXT` | 节点 ID,GUID | +| `node_index` | `INTEGER` | 节点序号 | +| `node_name` | `TEXT` | 节点名称 | +| `pipeline_id` | `TEXT` | Pipeline ID,GUID | +| `pipeline_name` | `TEXT` | Pipeline 名称 | +| `pipeline_version_hash` | `TEXT` | Pipeline 快照 hash | +| `node_pass` | `INTEGER` | 节点判定,`0/1` | +| `source_image_path` | `TEXT` | 节点输入图相对路径 | +| `result_image_path` | `TEXT` | 节点结果图相对路径 | +| `status` | `TEXT` | 节点状态:`Succeeded / Failed / PartialSuccess / AssetMissing` | +| `duration_ms` | `INTEGER` | 节点耗时 | + +样例数据: + +| run_id | node_id | node_index | node_name | pipeline_name | pipeline_version_hash | node_pass | source_image_path | result_image_path | status | duration_ms | +|---|---|---:|---|---|---|---:|---|---|---|---:| +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `1` | `检测节点1` | `Recipe-A` | `A1B2C3...` | `1` | `Results/2026/04/21/.../nodes/001_检测节点1/input.bmp` | `Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp` | `Succeeded` | `135` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `2` | `检测节点2` | `Recipe-B` | `D4E5F6...` | `0` | `` | `Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp` | `Failed` | `240` | + +### 4.3 `inspection_metric_results` + +用途:保存节点输出的结构化数值结果。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `run_id` | `TEXT` | 所属检测实例 ID | +| `node_id` | `TEXT` | 所属节点 ID | +| `metric_key` | `TEXT` | 指标 key | +| `metric_name` | `TEXT` | 指标名称 | +| `metric_value` | `REAL` | 指标值 | +| `unit` | `TEXT` | 单位 | +| `lower_limit` | `REAL` | 下限,可空 | +| `upper_limit` | `REAL` | 上限,可空 | +| `is_pass` | `INTEGER` | 单指标判定,`0/1` | +| `display_order` | `INTEGER` | 展示顺序 | + +样例数据: + +| run_id | node_id | metric_key | metric_name | metric_value | unit | lower_limit | upper_limit | is_pass | display_order | +|---|---|---|---|---:|---|---:|---:|---:|---:| +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `bridge.rate` | `Bridge Rate` | `0.12` | `%` | | `0.2` | `1` | `1` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `void.area` | `Void Area` | `5.6` | `px` | | `8` | `1` | `2` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `solder.height` | `Solder Height` | `1.7` | `mm` | `1.8` | | `0` | `1` | + +### 4.4 `inspection_assets` + +用途:保存文件资产索引。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `run_id` | `TEXT` | 所属检测实例 ID | +| `node_id` | `TEXT` | 所属节点 ID,可空 | +| `asset_type` | `TEXT` | 资产类型 | +| `relative_path` | `TEXT` | 相对路径 | +| `file_format` | `TEXT` | 文件格式 | +| `width` | `INTEGER` | 宽度 | +| `height` | `INTEGER` | 高度 | + +约定的 `asset_type`: + +- `RunSourceImage` +- `NodeInputImage` +- `NodeResultImage` + +样例数据: + +| run_id | node_id | asset_type | relative_path | file_format | width | height | +|---|---|---|---|---|---:|---:| +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | | `RunSourceImage` | `Results/2026/04/21/.../run/source.bmp` | `bmp` | `0` | `0` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `NodeInputImage` | `Results/2026/04/21/.../nodes/001_检测节点1/input.bmp` | `bmp` | `0` | `0` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `NodeResultImage` | `Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp` | `bmp` | `0` | `0` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `NodeResultImage` | `Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp` | `bmp` | `0` | `0` | + +### 4.5 `pipeline_execution_snapshots` + +用途:保存节点执行时的 Pipeline 快照,避免后续配方修改影响历史报告。 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `run_id` | `TEXT` | 所属检测实例 ID | +| `node_id` | `TEXT` | 所属节点 ID | +| `pipeline_name` | `TEXT` | Pipeline 名称 | +| `pipeline_definition_json` | `TEXT` | Pipeline 序列化 JSON | +| `pipeline_hash` | `TEXT` | Pipeline JSON 的 SHA-256 | + +样例数据: + +| run_id | node_id | pipeline_name | pipeline_hash | +|---|---|---|---| +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `Recipe-A` | `A1B2C3...` | +| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `Recipe-B` | `D4E5F6...` | + +--- + +## 5. `manifest.json` 示例 + +每次 `CompleteRunAsync` 后,会在结果包目录下生成 `manifest.json`。 + +示例: + +```json +{ + "Run": { + "RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "ProgramName": "NewCncProgram", + "WorkpieceId": "QFN_1", + "SerialNumber": "SN-001", + "StartedAt": "2026-04-21T10:00:00.0000000Z", + "CompletedAt": "2026-04-21T10:00:03.2000000Z", + "OverallPass": false, + "SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp", + "ResultRootPath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "NodeCount": 2 + }, + "Nodes": [ + { + "RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "NodeId": "11111111-1111-1111-1111-111111111111", + "NodeIndex": 1, + "NodeName": "检测节点1", + "PipelineName": "Recipe-A", + "PipelineVersionHash": "A1B2C3...", + "NodePass": true, + "SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/input.bmp", + "ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/result_overlay.bmp", + "Status": 0, + "DurationMs": 135 + }, + { + "RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "NodeId": "22222222-2222-2222-2222-222222222222", + "NodeIndex": 2, + "NodeName": "检测节点2", + "PipelineName": "Recipe-B", + "PipelineVersionHash": "D4E5F6...", + "NodePass": false, + "SourceImagePath": "", + "ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/002_检测节点2/result_overlay.bmp", + "Status": 1, + "DurationMs": 240 + } + ], + "Metrics": [ + { + "RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "NodeId": "11111111-1111-1111-1111-111111111111", + "MetricKey": "bridge.rate", + "MetricName": "Bridge Rate", + "MetricValue": 0.12, + "Unit": "%", + "LowerLimit": null, + "UpperLimit": 0.2, + "IsPass": true, + "DisplayOrder": 1 + }, + { + "RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a", + "NodeId": "22222222-2222-2222-2222-222222222222", + "MetricKey": "solder.height", + "MetricName": "Solder Height", + "MetricValue": 1.7, + "Unit": "mm", + "LowerLimit": 1.8, + "UpperLimit": null, + "IsPass": false, + "DisplayOrder": 1 + } + ] +} +``` + +说明: + +- `manifest.json` 是文件侧的完整快照 +- SQLite 是主索引 +- 报告模块可以优先查库,再按需读取文件 + +--- + +## 6. 报告模块如何取数 + +### 6.1 首页列表 / 历史查询 + +建议通过: + +- `inspection_runs` +- 必要时联查 `inspection_node_results` + +可支持筛选条件: + +- 时间范围 +- 程序名 +- 工件号 +- 序列号 +- Pipeline 名称 + +### 6.2 单份报告生成 + +建议按 `RunId` 调用: + +- `GetRunDetailAsync(runId)` + +得到: + +- `Run` +- `Nodes` +- `Metrics` +- `Assets` +- `PipelineSnapshots` + +即可直接组装报告: + +- 报告首页 + - 工件号、序列号、程序名、开始/结束时间、整体判定 +- 节点章节 + - 节点名称 + - 配方名 + - 结果图 + - 关键指标值 + - 节点判定 +- 追溯信息 + - Pipeline 快照 hash + - 原图路径 + - 结果图路径 + +### 6.3 离线导出 + +若后续需要将单个检测实例导出给第三方或留档,可直接复制: + +- 整个 `Results/{yyyy}/{MM}/{dd}/{RunId}` 目录 + +这样会同时带走: + +- `manifest.json` +- 原图 +- 节点图 + +--- + +## 7. 当前实现接口 + +当前服务接口: + +- `BeginRunAsync(...)` + - 创建实例记录和结果目录 +- `AppendNodeResultAsync(...)` + - 写入节点结果、指标、图片索引、Pipeline 快照 +- `CompleteRunAsync(...)` + - 回填结束时间、整体判定,并写出 `manifest.json` +- `QueryRunsAsync(...)` + - 查询检测实例列表 +- `GetRunDetailAsync(...)` + - 查询单个实例的完整报告数据 + +当前 DI 注册: + +- `IInspectionResultStore -> InspectionResultStore` + +--- + +## 8. 设计约束与说明 + +### 8.1 为什么不直接扩展 `MeasurementDataService` + +因为原有 `MeasurementRecord` 只适合: + +- 单值统计 +- 简单的 pass/fail 汇总 + +它不适合承载: + +- 多节点 +- 多图像 +- 多指标 +- Pipeline 快照 +- 报告导出 + +所以当前设计中: + +- `MeasurementDataService` 继续保留给旧统计用途 +- `InspectionResultStore` 作为报告归档主通道 + +### 8.2 为什么图片不直接进 SQLite + +因为图片数据量大,直接入库会带来: + +- 数据库膨胀 +- 查询性能下降 +- 迁移和备份成本变高 + +因此使用: + +- SQLite 存索引 +- 文件系统存图片 + +这是更适合报告场景的折中方案。 + +### 8.3 为什么要保存 Pipeline 快照 + +因为报告要可追溯。 + +如果只保存 `PipelineName`,后续配方被修改后,历史报告就无法复原当时的真实算法链。 +因此需要保存: + +- `pipeline_definition_json` +- `pipeline_hash` + +--- + +## 9. 后续可扩展方向 + +后续可继续扩展: + +1. 增加 `manifest.json` 中的设备上下文 + - 运动位置 + - 射线源状态 + - 探测器状态 + +2. 增加中间图可选保留策略 + - 默认关闭 + - 调试模式开启 + +3. 增加结果导出包 + - ZIP 打包 + - 单份报告 PDF + +4. 增加报告模板字段映射 + - 将 `MetricKey` 映射到报告模板占位符 + +5. 增加数据清理策略 + - 保留天数 + - 自动清理旧图片 + - 保留数据库索引或同时删除 + +--- + +## 10. 结论 + +当前这套归档设计的核心特点是: + +- 以“检测实例”为主组织数据 +- 以“检测节点”为明细展开 +- 以“结构化指标 + 图片文件 + Pipeline 快照”支撑报告 +- 通过 SQLite 和文件系统混合存储兼顾查询效率和图片落盘 + +对于后续报告模块,这套结构已经可以直接支持: + +- 历史列表查询 +- 单次检测报告生成 +- 结果图展示 +- 节点级指标展示 +- 历史结果可追溯 diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index 33b6ec6..2e0f475 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -472,7 +472,7 @@ - + @@ -500,7 +500,7 @@ - +