geopro/docs/superpowers/specs/2026-06-29-3d-radar-volume-...

199 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 三维雷达 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/明星路`14G20 测线)。
**关键差异**:明星路 是 `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 触发),复用成本低。
**双数据集测试(校正后表述)**
- **Mala16ch规范化 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=516coarse=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`