184 lines
14 KiB
Markdown
184 lines
14 KiB
Markdown
# 三维雷达 DS 接入设计:规范化格式 → 体渲染 + 切片 + 异常 — 2026-06-29
|
||
|
||
> 负责人范围:**仅三维雷达的体渲染 + 切片 + 异常标注**(不含 2D 波列图详情页、不含二维雷达)。
|
||
> 本 spec 基于精读现有架构(HEAD `main`)+ Explore 代理交叉验证 + 用真实 Mala 数据实测确诊。
|
||
> 所有"数据事实"均已用 `D:/Downloads/MALA南同大道_rSlicer` 6 条测线核对,**无未验证假设**。
|
||
|
||
---
|
||
|
||
## 1. 背景与锁定决策
|
||
|
||
客户新增雷达数据类型,模型层级沿用 `项目 → GS → TM(TmObject) → DS列表`。三维雷达 TM 携带
|
||
`头信息(.head)` + DS 列表 `= 雷达RTK轨迹(.cor,备份+快照) + 每频率 雷达3D-频率N(打标.index + 通道1..N data.data)`。
|
||
数据体 = **K 切片(沿运动) × M 通道(sweep) × N 采样(深度)**(与 IDS/OpenGPR 手册一致)。
|
||
|
||
四项锁定决策(用户 2026-06-29 确认):
|
||
|
||
| 维度 | 决策 |
|
||
|---|---|
|
||
| 职责范围 | 仅三维体 + 切片 + 异常;不做 2D 波列图详情、不做二维雷达 |
|
||
| 数据来源 | 后端未就绪 → 先读**本地已转换好的目录**(后端就绪后切按 DS 下载) |
|
||
| 输入格式 | 客户端**规范化 `.head/.data/.cor/.index`**(新写 reader) |
|
||
| 渲染规模 | 先**单线 / 下采样稠密体**打通 |
|
||
|
||
---
|
||
|
||
## 2. 已验证的数据事实(★C++ reader 必须遵守,零假设)
|
||
|
||
实测工具:`tools/radar_convert/malamira.py probe`(B-scan/C-scan 主序核对)。
|
||
|
||
1. **数据体主序 = position-major**:磁盘扫描顺序 = `(道0:通道0..M-1)(道1:通道0..M-1)…`,每 sweep 内 N 采样连续。
|
||
即 `flat.reshape(K, M, N)[道][通道][采样]`。probe 确诊:position-major 的 B-scan 出现连续直达波 +
|
||
地层 + 双曲线绕射;channel-major 为竖条乱码(已排除)。
|
||
2. **直接对应 geopro 体轴** `X=道(nx=K)` / `Y=通道(ny=M)` / `Z=采样(nz=N)`,**无需轴置换**——与现有
|
||
`Gpr3dvVolumeBridge` 轴映射完全一致。⚠️ 但**磁盘主序(采样最快→通道→道)与体内存布局(道最快,`((k·ny+j)·nx+i)`,
|
||
`ScalarVolumeI16.hpp:58`)相反**,新 reader 必须**逐体素散射重排填值**(照 bridge 的 `vol.at(to,j,s)=`),
|
||
**严禁 memcpy/整块拷贝**。"无需转置"指轴语义,不是字节布局。
|
||
3. **维度推导**:`M=NUMBER_OF_CH`、`N=SAMPLES`、`K=LAST_TRACE/NUMBER_OF_CH`(`.head` 的 `LAST_TRACE`
|
||
是**总扫描数**=K×M,不是道数)。
|
||
4. **数据类型**:int16 小端(`.rd3`/`bits=16`) / int32 小端(`.rd7`/`bits=32`)。`-32768` 是直达波饱和真实值,**非空值哨兵**。
|
||
5. **`BITS` 公式 bug**:客户文档 §3.3 `BITS = 文件大小/LAST_TRACE/NUMBER_OF_CH×8` **漏了 SAMPLES 维、量纲错**。
|
||
正确:`bytes = 文件大小/(LAST_TRACE×SAMPLES)`,并与扩展名交叉校验。**须同步后端**(见 `tools/radar_convert/README.md`)。
|
||
|
||
---
|
||
|
||
## 3. 架构论点:只换最内层 reader,下游 100% 复用
|
||
|
||
现有真实雷达体链路是分层的,**只有最内层 reader 绑定厂商格式**:
|
||
|
||
```
|
||
Api3dRepository::createGprVolume [api/Api3dRepository.cpp:128] 注册 dd_voxel/灰度色阶/预填 cachedGrid
|
||
└ data::createGprVolumeGrid [data/GprVolumeRepository.cpp:38]
|
||
├ io::gpr::buildLineVolumeFromGpr3dv ← 仅此层绑定厂商 .iprb/.ord(读 Impulse 原始)
|
||
└ data::builtI16ToVolumeGrid [data/GprVolumeRepository.cpp:11] int16→float 稠密体
|
||
→ 勾选 → VtkSceneController.loadVolume → VtkSceneView.addVolume → render::buildVoxel(GPU RayCast)
|
||
→ SliceTool / InteractionManager / AnomalyDrawTool (切片 B/C-scan + 点线面异常,全现成)
|
||
```
|
||
|
||
**新增一个产 `core::BuiltI16`(同轴映射 X=道/Y=通道/Z=采样)的规范化 reader**,则 `builtI16ToVolumeGrid`
|
||
往下的渲染/切片/异常链**零改动**。这正是 `GprVolumeRepository.cpp:14` 注释写明的"数据层方案 A"。
|
||
|
||
### 完全复用、不碰的部分
|
||
`render::buildVoxel`(GPU 体绘制)、三级树 voxel 段(`CategoryConfig.hpp:23` `dd_voxel`)、勾选→`loadVolume`→`addVolume`、
|
||
切片(`SliceTool` updown 深度 C-scan / leftright·frontback B-scan + `InteractionManager`)、
|
||
异常(`AnomalyDrawTool` 点/线/面 → `dd_anomaly` 挂体)、跨视图色阶联动、`builtI16ToVolumeGrid` 适配器。
|
||
|
||
---
|
||
|
||
## 4. 插件化转换层(已落地 Mala)
|
||
|
||
转换 = "按雷达型号的插件",本地 Python 工具 = 未来服务端下发插件的原型,契约不变:
|
||
|
||
```
|
||
plugin_id : RADAR_TYPE_MALAMIRA # 对齐文档 §2.1 型号列表
|
||
supports(fileset) -> bool
|
||
convert(lineDir, prefix, outDir) -> {head, data, cor} # 厂商原始 → 规范化
|
||
```
|
||
|
||
- **`RADAR_TYPE_MALAMIRA`(已完成)**:`tools/radar_convert/malamira.py`。`.rad→.head`(§3.3)、
|
||
`.rd3→.data`(§3.5 原样拷贝)、`.pos→.cor`(§2.2.2)。`info/convert/probe` 三命令。6 条线全部转换 + 校验通过。
|
||
与文档偏差 5 处见 `tools/radar_convert/README.md`(含 BITS 公式修正)。
|
||
- **`RADAR_TYPE_IMPLUSE`(规划,暂不做)**:见 §7。
|
||
|
||
客户端职责(未来):设备对接/原始导入 → 按型号拉插件 → `convert` → 喂规范化 reader。
|
||
|
||
**契约两点待补(登记)**:(a) `convert` 返回 `{head,data,cor}` **未含 `.index`(打标)**,有打标的型号需扩约定;
|
||
(b) 规范化 `.data` 无扩展名,C++ reader 解析字节宽(2/4) **单点依赖 `.head` 的 `BITS` 字段** → 保证转换器 BITS 永远正确是隐性前提(`malamira.py` 的 `filesize==LAST_TRACE×SAMPLES×bytes` 断言已守住,见 §2.5)。
|
||
|
||
---
|
||
|
||
## 5. 新增 / 改动清单(C++ 侧,待实现)
|
||
|
||
| # | 文件 | 动作 |
|
||
|---|---|---|
|
||
| ① | `src/io/gpr/NormalizedRadarReader.{hpp,cpp}` **新** | 纯函数:`parseHead(.head)→RadarHeader`;`readDataCube(.data,header)→cube`(按 `BITS`+`ENDIAN_TYPE` 解二进制,`reshape(K,M,N)` 主序已定);`parseCor(.cor)→vector<TracePos>`(先解析,世界配准后置) |
|
||
| ② | `src/io/gpr/NormalizedRadarVolumeBridge.{hpp,cpp}` **新** | `buildLineVolumeFromNormalized(lineDir,prefix,metrics,coarse,targetDy)→core::BuiltI16`。⚠️ **DRY**:`Gpr3dvVolumeBridge.cpp:133-197` 的值域扫描/`Quant`构造/逐体素填值/spacing 当前内联且耦合 Impulse 模型——动工时**先把这段抽成共享 helper**(签名接受抽象立方体访问器 `(c,t,s)→short` + 通道偏移/几何),两个 bridge 同调;`planChannelInterpolation`/`depthOfSample` 已是共享纯函数。**不要复制填体循环**(否则两份独立漂移) |
|
||
| ③ | `src/data/GprVolumeRepository.{hpp,cpp}` 改 | 加 `createRadarVolumeGrid(lineDir,prefix,coarse,targetDy)`(调②,复用 `builtI16ToVolumeGrid`)。**不动**现有 `createGprVolumeGrid` |
|
||
| ④ | `src/data/api/Api3dRepository.{cpp,hpp}` 改 | 加 `createRadarVolume(...)`(≈`createGprVolume` 复制换调③),仍注册 `dd_voxel` |
|
||
| ⑤ | `src/app/main.cpp` 改 | 本地导入入口「导入三维雷达测线目录」:选目录+前缀 → `createRadarVolume` → `refreshAnalysis` → 自动勾选渲染(后端就绪后换按 DS 下载) |
|
||
| ⑥ | 测试 | `tests/io/gpr/test_normalized_radar_reader.cpp`(喂 `tests/data/radar/` 截断样例,断言维度/字节序/通道插值/主序) |
|
||
|
||
### 几何映射(关键算法,纯函数易测)
|
||
- **X(沿线 spacing)** = `DISTANCE_INTERVAL`(距离模式,本期唯一支持)。
|
||
- **Y(通道横向)** = 复用 `GprGeometry::planChannelInterpolation(chXOffsets, targetDy)`(`GprGeometry.hpp:27`)——
|
||
通道偏移取自 `.head` 的 `CH_X_OFFSETS`(不读 `.ord`),线内插值、**绝不跨线**(默认 2.5cm)。
|
||
- **Z(深度 spacing)** = `TIMEWINDOW/(SAMPLES-1) × 波速/2`;波速由 `DIELECTRIC` 求,`.rad` 无该字段时用默认波速。
|
||
- **量化** → `core::BuiltI16`(复用 `core/algo/GprVolumeBuilder.hpp`)。
|
||
- **`.cor`/`.index`** 本期只解析不深用:单线放原点沿 +X,切片/异常即可工作。逐道 GPS 世界配准 + 打标 → 多线阶段。
|
||
|
||
---
|
||
|
||
## 6. 分期
|
||
|
||
- **P0(本期)**:①–⑥,单线规范化体打通。**验收**:导入 `samples/radar/malamira_南同大道`(如 000 线)→
|
||
树出现 `dd_voxel` → 勾选渲染 → 切 updown/leftright/frontback → 画点/线/面异常并挂体下。
|
||
- **P1**:`.cor` 逐道 GPS 世界配准 + 高程、`.index` 打标、多线合一场景。
|
||
- **P2**:大体量 → 把 `gpr_poc` 的 `ChunkedVolumeStore`+`*VolumeSource` 核外/LOD 接进 app(碰 controller/view,单独立项)。
|
||
|
||
---
|
||
|
||
## 7. 双数据集测试策略 + Impulse 转换器决策
|
||
|
||
已有完整 POC 基于**另一套雷达数据 明星路(Impulse)**,交接见 `.superpowers/sdd/HANDOFF-2026-06-27-gpr-lod-slice.md`。
|
||
明星路 数据在本地(`D:/Downloads/明星路`,14G,20 测线)。
|
||
|
||
**关键差异**:明星路 是 `RADAR_TYPE_IMPLUSE`——每线 **14 通道为 14 个独立 `.iprb` 二进制**(`_A01.._A14`,各 ~74MB)+
|
||
逐通道 `.iprh` + `.ord` + `.gps/.time/.cor/.mrk`。Mala 则把 16 通道打进**一个** position-major `.rd3`。
|
||
现有 C++ P1 链(`buildLineVolumeFromGpr3dv`)**能直读 `.iprb`**。
|
||
|
||
> ⚠️ **校正(评审 HIGH-2)**:明星路真正"经验证"的渲染是 **`gpr_poc` CLI 的核外 LOD 多线**路径,**不是** app 内
|
||
> `createGprVolume→builtI16ToVolumeGrid→loadVolume→addVolume` 的**稠密单体**路径——后者的 **app 导入 UI 触发从未接**
|
||
> (HANDOFF-2026-06-27 §6 第 3 条:"数据层 `createGprVolume` 已就绪,但 UI 触发未接")。故 app 内稠密渲染对**两套数据都是首次**。
|
||
|
||
**决策:本期不建 Impulse→规范化 转换器。** 理由:
|
||
1. Impulse `.iprb` 原始二进制远比 Mala "改名+字段映射"复杂(14 个独立通道文件、需逆向二进制布局);且 P1 链含**处理**
|
||
(gain/filter),而规范化 `.data` 是**原始**——raw vs processed 语义需先厘清。高成本、低边际价值。
|
||
2. 明星路 的 `.iprb` 已有现成 in-app 数据层入口(`createGprVolume`,只差接 UI 触发),复用成本低。
|
||
|
||
**双数据集测试(校正后表述)**:
|
||
- **Mala(16ch,规范化 reader 新路径)** + **明星路(14ch,现有 `.iprb` P1 路径)** 在 app 内**首次**稠密渲染,
|
||
二者喂**同一条下游 render/slice/anomaly 链** → 互证下游对两种通道数/几何无关。**前置任务**:明星路 in-app 导入入口
|
||
同样需接(复用现成 `createGprVolume`,与清单⑤同形)。
|
||
- 以 **`gpr_poc` CLI 的明星路成像作为离线对照基线**(那才是已验证的),不声称 in-app 已验证。
|
||
- ⚠️ 注意:明星路是**处理后**数据、Mala 是**原始**数据 → 双测只验"管路通 + 几何/通道数无关",**不验"成像一致"**(二者有无增益滤波,视觉不可比)。
|
||
- 把 **`RADAR_TYPE_IMPLUSE` 转换器列为规划的第二插件**,待规范化管线需吞并 Impulse(多半与服务端插件同步)时再做,
|
||
届时一并解决 raw/processed 取舍。当前"插件产规范化→reader 吃规范化"契约**仅对 Mala 一家成立,属过渡态**。
|
||
|
||
---
|
||
|
||
## 8. 风险 / 待确认
|
||
|
||
1. **`.cor` 坐标语义**:`.pos` 是本地投影坐标(米),按文档直映入经纬度列、N/E/M 占位、解状态=4。
|
||
真实 CRS 未知;单线渲染不依赖 `.cor` 配准,多线阶段需后端给 `CRS_CODE`。
|
||
2. **波速默认值**:Mala `.rad` 无 `DIELECTRIC` → Z 深度 spacing 用默认波速(建议介电常数≈9 / v≈0.1 m·ns⁻¹),
|
||
仅影响深度标尺,不影响体结构。需确认默认值。
|
||
3. **采集模式**:本期只支持**距离模式**(X 用 `DISTANCE_INTERVAL`);时间模式(`SCAN_SECOND`)推迟。
|
||
4. **大体量 / 内存实算**:app 侧 `ScalarVolume` 是 `std::vector<double>`(8 字节/体素,**比 int16 体放大 4×**,是真正天花板)。
|
||
单线最大线(K=3778, ny≈49@2.5cm, N=516):coarse=4 → nx=945 → 体素 23.9M → **≈182 MiB**(+中间 BuiltI16 48MB +VTK 副本,单线峰值 <0.5GB,**P0 安全**);
|
||
coarse=1 全分辨率 → **≈764 MB** 仅体(单线勉强,非 P0 默认)。**多线/全分辨率必走 P2 核外**(`ChunkedVolumeStore`,已有 gpr_poc 实现)。
|
||
|
||
---
|
||
|
||
## 9. 测试计划
|
||
|
||
- **转换器(已验证)**:`info` 维度校验 6/6 通过;`convert` 产物 `.data` 与源 `.rd3` 字节一致;`probe` 主序确诊。
|
||
- **C++ reader 单测**:`tests/data/radar/` 放截断样例(position-major 可按字节前截 K' 道得合法小体);
|
||
断言 K/M/N、字节序、`planChannelInterpolation` 行数、主序填值正确。
|
||
- **bridge/repo 单测**:`createRadarVolumeGrid` 产 `VolumeGrid` 维度/spacing/vmin-vmax 合理;NaN 空值语义。
|
||
- **联调(人工)**:`build.bat app` → 导入样例 → 渲染 + 三向切片 + 三类异常;对照 明星路 现有路径同场景。
|
||
- 失败排查看桌面日志 `%LOCALAPPDATA%/Geomative/Geopro3/logs/geopro_*.log`。
|
||
|
||
---
|
||
|
||
## 10. 文件地图(现有锚点)
|
||
|
||
- 数据体模型 `src/data/repo/I3dSceneRepository.hpp:19`(`VolumeGrid` 稠密 float)
|
||
- 渲染入口 `src/controller/I3dSceneView.hpp:45`(`addVolume`)/ `src/app/VtkSceneView.cpp:173`
|
||
- 体素工厂 `src/render/actors/VoxelActor.hpp:29/47`(`buildVoxel`/`buildVoxelI16`)
|
||
- GPR IO `src/io/gpr/Gpr3dvVolumeBridge.hpp:47`(轴映射范本)/ `GprGeometry.hpp:27,32`(通道插值/深度)
|
||
- 体进 app `src/data/api/Api3dRepository.cpp:128`(`createGprVolume`)/ `src/data/GprVolumeRepository.cpp:11,38`
|
||
- 切片/异常 `src/render/interact/SliceTool.hpp` / `InteractionManager.hpp:52,57` / `AnomalyDrawTool.hpp:33`
|
||
- 三级树 `src/app/panels/columns/CategoryAnalysisTab.hpp:28` / `CategoryConfig.hpp:23`
|
||
- 转换器 `tools/radar_convert/malamira.py` + `README.md`;样例 `samples/radar/malamira_南同大道/`
|
||
- 详情视图扩展(如未来做 2D 波列图,非本期)`docs/superpowers/specs/2026-06-28-dataset-detail-view-architecture-and-extension-guide.md`
|