199 lines
17 KiB
Markdown
199 lines
17 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`)。
|
||
6. **ddCode(数据字典 DD0623 权威)**:三维雷达体 DS = **`dd_radar_3d`**(本次新增,形态=三维插值模型);
|
||
轨迹 DS = **`dd_trajectory_data`**(保留,复用现成 `TrajectoryStrategy`/`TrajectoryMapView`,零改动);
|
||
`dd_voxel` **仅物探反演体(电阻率/速度)、不含雷达**;`dd_slice` 切片共用;`dd_radar_rtk_trajectory` 已删除。
|
||
**雷达体不复用 `dd_voxel`。**
|
||
|
||
---
|
||
|
||
## 3. 架构论点:DS 优先 + 只换最内层 reader
|
||
|
||
### 3a. 数据层:只换最内层 reader(下游建体链复用)
|
||
|
||
现有真实雷达体链路是分层的,**只有最内层 reader 绑定厂商格式**:
|
||
|
||
```
|
||
data::createRadarVolumeGrid [data/GprVolumeRepository 新]
|
||
├ io::gpr::buildLineVolumeFromNormalized ← 新写:仅此层认规范化 .head/.data
|
||
└ data::builtI16ToVolumeGrid [data/GprVolumeRepository.cpp:11] int16→float 稠密体
|
||
```
|
||
(范本:现有 `createGprVolume`[`Api3dRepository.cpp:128`]→`createGprVolumeGrid`→`buildLineVolumeFromGpr3dv`(Impulse .iprb)。
|
||
我们换最内层 reader,复用 `builtI16ToVolumeGrid` 及其下游。)**产 `core::BuiltI16`(轴 X=道/Y=通道/Z=采样)即可**,
|
||
这正是 `GprVolumeRepository.cpp:14` 注释写明的"数据层方案 A"。
|
||
|
||
### 3b. DS 优先:运行期路由按 `volumes_` 成员,不看 ddCode(关键发现,已验真代码)
|
||
桌面端体渲染的运行期分流**只认 `Api3dRepository::volumes_` map 是否含此 dsId**,与 ddCode/"vol-" 前缀无关:
|
||
- `isVolumeDataset(dsId)` = `volumes_.count(dsId)`(`Api3dRepository.cpp:105`);
|
||
- `VtkSceneController::addDatasetAsync`(:182) 按 `isVolumeDataset` 分流体素/帘面;
|
||
- `loadVolume(dsId)`(:341) 唯一查找键 = `volumes_` 的 dsId;命中 `cachedGrid` 直渲(:347)。
|
||
|
||
**所以 DS 优先落地 = 把一条"真实 radar DS id"为 key 的 `StoredVolume` 写进 `volumes_`**(携 `ddCode=dd_radar_3d` + `lineDir/prefix`),
|
||
则 `isVolumeDataset/addDatasetAsync/loadVolume` **零改动**自动按体渲染。三维分析栏 voxel 段内容来自 `volumeRows()`→
|
||
`section("voxel")->setDatasets`(`main.cpp:602`,**绕过 `splitByCategory`**),故 `dimensionOf`/`CategoryConfig` **也无需改**——
|
||
唯一要改 `volumeRows()`(:170) 把硬编码 `dd_voxel` 改成输出该 DS 的真实 ddCode。
|
||
|
||
### 完全复用、不碰的部分
|
||
`render::buildVoxel`(GPU 体绘制)、勾选→`addDatasetAsync`→`loadVolume`→`addVolume` 路由、`isVolumeDataset`、
|
||
切片(`SliceTool` updown 深度 C-scan / leftright·frontback B-scan + `InteractionManager`)、
|
||
异常(`AnomalyDrawTool` 点/线/面 → `dd_anomaly` 挂体)、跨视图色阶联动、`builtI16ToVolumeGrid` 适配器、
|
||
轨迹 `dd_trajectory_data` 详情视图。
|
||
|
||
---
|
||
|
||
## 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}` 改 | **DS 优先 + 懒加载后台建体**(3 处):(a) `StoredVolume`(:139) 加 `lineDir/linePrefix/coarse/ddCode/structParentId` 字段(`ddCode` 默认 `"dd_voxel"`,存量行为不变);(b) 新 `registerRadarDataset(lineDir,prefix,name,structParentId,coarse)`——**只存元数据、不建体**,以 `ddCode="dd_radar_3d"` 写进 `volumes_`,id=`"radar-"+(++counter)`,瞬时返回;(c) `loadVolume`(:347 后) 加 radar 分支:未命中 cachedGrid 且有 `linePrefix` → **后台线程** `createRadarVolumeGrid`(仿 `finalizeVolume:268-335` 的 `std::thread`+`QMetaObject::invokeMethod(qApp,...)` 回主线程;无 `QCoreApplication` 时退化同步,可单测)→ 建体 + 灰度色阶 + 缓存 + async `onOk`;(d) `volumeRows()`(:170) 输出 `sv.ddCode` + `sv.structParentId`。运行期 `isVolumeDataset/addDatasetAsync` 按 `volumes_` 成员 → **零改动**。**懒加载+后台建体同时消除 UI 冻结(评审 HIGH)与 spinner 永久转圈(评审 MEDIUM)**。⚠️ `dimensionOf`/`CategoryConfig`/`splitByCategory` **不需改**(voxel 段走 `volumeRows` 注入、绕过分类,见 §3b) |
|
||
| ⑤ | `src/app/main.cpp` + `src/app/panels/columns/CategorySection.cpp` 改 | (1) 导入入口「三维雷达→导入测线目录」:选目录+前缀 → `registerRadarDataset` → `refreshAnalysis` → `setItemChecked(true)`+`setItemBusy(true)`+`scrollItemToTop`(同 `generateVolume:978-980`;渲染异步、spinner 由 `datasetRendered` 撤)。(2) **放开两处 ddCode gate**给 `dd_radar_3d`:`main.cpp:1011` `detailRequested`→体属性;**`CategorySection.cpp:397`** 列表右键 `if (ddCode=="dd_voxel")` 块(「生成切片」+「色阶」**同在此一个块内**,**只改 :397 这一处**即同时放开二者,无独立 :403 gate)——**不改则 radar 体无切片入口、P0 验收②不可达 — 评审 CRITICAL**|
|
||
| ⑥ | 测试 | `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_radar_3d`** DS → 勾选 → `loadVolume` 懒建体渲染 → 切 updown/leftright/frontback → 画点/线/面异常并挂体下。
|
||
- **P1**:`.cor` 逐道 GPS 世界配准 + 高程、`.index` 打标、多线合一场景、轨迹 DS(`dd_trajectory_data`) 一并登记、DS 进左侧数据集树(挂 TM)。
|
||
- **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`
|