14 KiB
三维雷达 DS 接入设计:规范化格式 → 体渲染 + 切片 + 异常 — 2026-06-29
负责人范围:仅三维雷达的体渲染 + 切片 + 异常标注(不含 2D 波列图详情页、不含二维雷达)。 本 spec 基于精读现有架构(HEAD
main)+ Explore 代理交叉验证 + 用真实 Mala 数据实测确诊。 所有"数据事实"均已用D:/Downloads/MALA南同大道_rSlicer6 条测线核对,无未验证假设。
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 主序核对)。
- 数据体主序 = position-major:磁盘扫描顺序 =
(道0:通道0..M-1)(道1:通道0..M-1)…,每 sweep 内 N 采样连续。 即flat.reshape(K, M, N)[道][通道][采样]。probe 确诊:position-major 的 B-scan 出现连续直达波 + 地层 + 双曲线绕射;channel-major 为竖条乱码(已排除)。 - 直接对应 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/整块拷贝。"无需转置"指轴语义,不是字节布局。 - 维度推导:
M=NUMBER_OF_CH、N=SAMPLES、K=LAST_TRACE/NUMBER_OF_CH(.head的LAST_TRACE是总扫描数=K×M,不是道数)。 - 数据类型:int16 小端(
.rd3/bits=16) / int32 小端(.rd7/bits=32)。-32768是直达波饱和真实值,非空值哨兵。 BITS公式 bug:客户文档 §3.3BITS = 文件大小/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_pocCLI 的核外 LOD 多线路径,不是 app 内createGprVolume→builtI16ToVolumeGrid→loadVolume→addVolume的稠密单体路径——后者的 app 导入 UI 触发从未接 (HANDOFF-2026-06-27 §6 第 3 条:"数据层createGprVolume已就绪,但 UI 触发未接")。故 app 内稠密渲染对两套数据都是首次。
决策:本期不建 Impulse→规范化 转换器。 理由:
- Impulse
.iprb原始二进制远比 Mala "改名+字段映射"复杂(14 个独立通道文件、需逆向二进制布局);且 P1 链含处理 (gain/filter),而规范化.data是原始——raw vs processed 语义需先厘清。高成本、低边际价值。 - 明星路 的
.iprb已有现成 in-app 数据层入口(createGprVolume,只差接 UI 触发),复用成本低。
双数据集测试(校正后表述):
- Mala(16ch,规范化 reader 新路径) + 明星路(14ch,现有
.iprbP1 路径) 在 app 内首次稠密渲染, 二者喂同一条下游 render/slice/anomaly 链 → 互证下游对两种通道数/几何无关。前置任务:明星路 in-app 导入入口 同样需接(复用现成createGprVolume,与清单⑤同形)。 - 以
gpr_pocCLI 的明星路成像作为离线对照基线(那才是已验证的),不声称 in-app 已验证。 - ⚠️ 注意:明星路是处理后数据、Mala 是原始数据 → 双测只验"管路通 + 几何/通道数无关",不验"成像一致"(二者有无增益滤波,视觉不可比)。
- 把
RADAR_TYPE_IMPLUSE转换器列为规划的第二插件,待规范化管线需吞并 Impulse(多半与服务端插件同步)时再做, 届时一并解决 raw/processed 取舍。当前"插件产规范化→reader 吃规范化"契约仅对 Mala 一家成立,属过渡态。
8. 风险 / 待确认
.cor坐标语义:.pos是本地投影坐标(米),按文档直映入经纬度列、N/E/M 占位、解状态=4。 真实 CRS 未知;单线渲染不依赖.cor配准,多线阶段需后端给CRS_CODE。- 波速默认值:Mala
.rad无DIELECTRIC→ Z 深度 spacing 用默认波速(建议介电常数≈9 / v≈0.1 m·ns⁻¹), 仅影响深度标尺,不影响体结构。需确认默认值。 - 采集模式:本期只支持距离模式(X 用
DISTANCE_INTERVAL);时间模式(SCAN_SECOND)推迟。 - 大体量 / 内存实算: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