145 lines
11 KiB
Markdown
145 lines
11 KiB
Markdown
# 反演剖面三维体:客户目标方法(Surfer)对照与客户端差距 — 2026-06-27
|
||
|
||
> 来源:客户提供的演示视频 `ScreenShot/9f67e80cb823170bb9374d779ec4c0cb.mp4`(Golden Software Surfer,13min)
|
||
> 与参考成果图 `ScreenShot/Weixin Image_20260627080012_429_2117.png`。
|
||
> 结论一句话:**客户用的就是"合并点云 + 3D 反距离加权(IDW) + 边界裁剪(Blanking) + 体绘制/等值面"。**
|
||
> 客户端插值内核已与之一致,差距只在「搜索半径 / 边界裁剪 / 等值面」三点。
|
||
|
||
---
|
||
|
||
## 0. 背景与一次认知纠偏
|
||
|
||
用户反馈:"选多个 dd_inversion_data(江西理工四条井字相交测线)做三维体,得到的是四个剖面各自左右
|
||
拉伸的薄板,不连成完整体。"
|
||
|
||
排查中曾推荐"逐深度层各向异性插值"——看了客户演示视频后**确认是过度设计**。客户没有逐层做,
|
||
就是**朴素的三维 IDW**(对合并后的整团散点云一次性 3D 网格化)。本文以视频实证为准。
|
||
|
||
---
|
||
|
||
## 1. 客户目标方法(Surfer 实测,逐帧为证)
|
||
|
||
| 步骤 | 操作 | 视频帧(时间戳)证据 |
|
||
|---|---|---|
|
||
| 1. 合并点云 | 所有测线/剖面反演单元合并成**一个 XYZC 文件** `combine-e_m_m.xyz`,列= **X, Y, Elevation, Resistivity, Conductivity, Sensitivity** | t48.7:Grid Data 导入对话框,Data Type=**XYZC** |
|
||
| 2. 三维网格化 | Grid Data → 方法 **Inverse Distance to a Power(IDW,幂次)** → XYZC 直接生成 **3D 网格体**;注脚明示"does not extrapolate beyond the range of data" | t48.7:Gridding Method 选中 IDW;t97.5:Gridding 进度;t195:`.grd has been created` |
|
||
| 3. 边界裁剪 | 数字化**测区边界多边形**(Base vector / Polyline)→ **Blanking** 把体裁到测区真实足迹 | t292.5:Polygon 工具数字化边界 |
|
||
| 4. 三维渲染 | 3D View → 3D Grid Volume(`out-BAIHUA.vtk`):**Volume render**(Sliced / Slice count 500 / Tri-linear / Alpha blending / Opacity 80%) + **Isosurface**(阈值 isovalue) + **Image slice**(YZ/Z) | t585:Volume render 属性;t682:Isosurface(isovalue=1794.39);t633:Image slice(YZ) |
|
||
|
||
要点:
|
||
- **真三维 IDW**,对合并点云一次成体(非逐层 2D)。
|
||
- IDW **不外推到数据范围外**;测区足迹靠 **Blanking 多边形**裁出(参考图那个不规则边界即来源于此)。
|
||
- 红色异常体 = **等值面抽取**(Isosurface 按阈值)。
|
||
- 坐标为真实投影坐标 + 高程,可叠地形/影像底图。
|
||
|
||
---
|
||
|
||
## 2. 客户端现状(已实现部分)
|
||
|
||
生产路径:`Api3dRepository::createVolume`(`src/data/api/Api3dRepository.cpp`)
|
||
→ 把所有选中 ds 的反演单元按测线真实几何配准合并成 `PointSet`
|
||
→ `buildVolume(pts, cellXY, cellZ, power, maxDist)`(`src/core/algo/VolumeBuilder.cpp`)
|
||
→ **三维 IDW**(`src/core/algo/IdwInterpolator.cpp`):`maxDist` 外置 NaN 留空。
|
||
|
||
即:**「合并点云 + 3D IDW」内核与 Surfer 一致**。参数见 `src/data/repo/VolumeBuildParams.hpp`:
|
||
`cellXY=1.0, cellZ=0.5, power=2.0, maxDist=4.0`。
|
||
|
||
渲染:`VoxelActor`(`src/render/actors/VoxelActor.hpp`)仅 GPU 体绘制(NaN→透明),**无等值面**
|
||
(`GridContourActor`/`ContourBands` 是 2D 网格等值线,非 3D 等值面)。地形/影像/坐标轴/电极点已有
|
||
(`TerrainActor`/`TileBasemap`/`AxesActor`/`ElectrodeActor`)。
|
||
|
||
---
|
||
|
||
## 3. 差距与修复(共 3 点)
|
||
|
||
> 不需改插值算法(内核已对);改的是搜索域、裁剪、与等值面。
|
||
|
||
### G1. 搜索半径 maxDist 太小 → "四块板"
|
||
`maxDist=4m` 远小于井字测线间距 → IDW 只填测线 ±4m 管套,线间留空 → 四块薄板。
|
||
**修复**:把搜索半径放大到覆盖测区(或提供"覆盖全域"选项),对齐 Surfer 默认搜索域行为。
|
||
|
||
### G2. 缺边界裁剪(Blanking) → 单纯放大半径只会"变粗"
|
||
**这是用户观察到"调大 maxDist 只是让体看起来更粗"的真正原因**:没有足迹裁剪,放大半径会把体
|
||
鼓满整个外接盒 → 粗大臃肿。Surfer 不粗,是因为 Blanking 把体裁到了测区真实多边形足迹。
|
||
**修复**:加**足迹掩膜**——
|
||
- 自动:散点平面**凸包 / alpha-shape**(或沿测线 buffer 并集);
|
||
- 或手动:支持用户数字化/导入边界多边形(对齐 Surfer Blanking)。
|
||
掩膜外体素整列置空(NaN/透明)。
|
||
|
||
### G3. 缺 3D 等值面 → 出不来红色异常体
|
||
`VoxelActor` 只有体绘制。
|
||
**修复**:在体上加 **`vtkFlyingEdges3D` / `vtkContourFilter`** 抽等值面,阈值可调(对齐 Surfer Isosurface)。
|
||
|
||
---
|
||
|
||
## 4. 修复落地顺序
|
||
|
||
1. **G1+G2 一起做**(插值搜索域放大 + 足迹掩膜)→ 出满铺、裁到测区足迹的体。这是核心,先做。
|
||
2. **G3 等值面**(阈值可调)→ 出红色异常体。参考图第二主角,紧接着做。
|
||
3. 影像底图/坐标轴/电极点复用现有。
|
||
|
||
---
|
||
|
||
## 5. 必须先和客户对齐的预期(避免"又看起来不对")
|
||
|
||
参考图是**密集测网**(顶面可见很多条测线点阵);江西项目只有**四条井字线**。
|
||
- **形态可复刻**(满铺体 + 等值面 + 影像底图);
|
||
- 但**框内细节出不来**——参考图的细碎红异常源于密集采样,四条线之间只能给出平滑趋势,
|
||
等值面会是几个光滑大团。要那种精度需加密测线。
|
||
|
||
---
|
||
|
||
## 6. 非目标 / 说明
|
||
|
||
- 不改 IDW 内核算法本身(已与 Surfer 一致),不引入逐层各向异性(客户未用)。
|
||
- 各向异性搜索椭球可作为后续可选增强(Surfer 亦为可选项,非默认),本期不做。
|
||
- Kriging 仍为占位(`VolumeBuildParams::Model::Kriging`,core 未实现),本期不依赖。
|
||
|
||
---
|
||
|
||
## 7. 续:白化方式之争 + 体绘制边界「梯田」(2026-06-28,branch `fix/3d-volume-blanking-mask`)
|
||
|
||
> 本节记录 §1–6 之后这一轮的来龙去脉、技术取舍与权威佐证,供后续决策不再反复。
|
||
|
||
### 7.1 前因后果(时间线)
|
||
|
||
客户原话:"**我们这个白化确实有问题,填色了,填了蓝色**"——无数据区被填成蓝色,而非 Surfer 那种透明白化。排查分三层、逐个修:
|
||
|
||
1. **切片填蓝**:`vtkImagePlaneWidget` 会按【输入标量范围】(含哨兵)自动 window/level,把哨兵顶到 LUT 最低色格(蓝)且不透明。
|
||
修复:`SliceTool` 钉死 `SetWindowLevel([vmin,vmax])` + `ColorLutBuilder` 预留 0 号"白化槽"(全透明),哨兵(<vmin)钳到该槽即透明。实测证据:`tests/spike/slice_alpha_probe.cpp`(真 widget 离屏渲染 + 回读像素:背板透出=透明成功)。**这条同时纠正了切片颜色映射**(之前 colorbar 被错误拉伸到切片局部范围)。
|
||
2. **早期"满屏蓝"是误判**:实为 `maxDist=0`(自动覆盖测区,`kDefMaxDist=0.0`)把整个凸包**填实**=真实低值数据(蓝),**不是空值**(见 §3 G1)。日志实证请求体 `"maxDist":0`。
|
||
3. **三维体渗蓝**:无数据格设哨兵 `vmin-1`、不透明度 0;三线性插值在"哨兵↔真值"交界处插出低值(蓝)且不透明度非零 → 渗一圈蓝。为消除,给体绘制加**二值 mask**(`VoxelActor::makeMaskLike` + `assembleVolume` 用 `vtkGPUVolumeRayCastMapper`+`SetMaskInput`+`SetMaskTypeToBinary`,mask=0 体素被光线投射**硬跳过**)。
|
||
|
||
**mask 的副作用 = 用户 2026-06-28 截图的「竖条/梳齿/底边锯齿」**:硬跳过=边界不再三线性插值/羽化。`maxDist=0` 下体填满**凸包足迹**(带斜边多边形),斜边在 1m 规则网格上离散成**体素阶梯(staircase)**。以前(SmartVolumeMapper+哨兵→不透明度0 软消隐)斜边被羽化抹圆、看不出;加 mask 后变成逐体素硬台阶。**即使色阶不透明度=100% 也可见**——因 mask 在不透明度传函【之前】就把体素从光线上删了(两个并行子代理:渲染侧 + 数据/建体侧共同确诊)。
|
||
|
||
### 7.2 取舍的本质
|
||
|
||
| 方案 | 边界观感 | 数据诚实度 | 误判风险 |
|
||
|---|---|---|---|
|
||
| **二值 mask(当前)** | 体素梯田(难看) | ✅ 只画真数据、零假值 | 低(梯田明显是网格对齐的足迹边界,不像地质体) |
|
||
| 软消隐(哨兵→不透明度0,无 mask) | 平滑 | ❌ 边界是插值出的假值 | **高** |
|
||
|
||
### 7.3 「细蓝边」是什么 + 为何【不能】回退软消隐(权威佐证)
|
||
|
||
软消隐回退后的"细蓝边" = 真数据格与哨兵格(`vmin-1`)三线性插值出的、**数据里根本不存在的假低阻值**。低阻在物探通常指含水/导电体,这圈蓝出现在测区最外缘会被**误读成"边界存在真实低阻(导电)异常带"**——真有数据分析歧义。
|
||
|
||
多个权威来源一致认定"插值进 NoData 产生边缘伪影"是错误做法、应 mask 排除:
|
||
|
||
- **ESRI 官方**:双线性/三次插值 "**interpolate incorrectly into the NoData** and background areas... producing **artifacts or black ridges**" —— 我们的"蓝脊"与之同源(哨兵钳到最低色=蓝)。<https://support.esri.com/en-us/knowledge-base/faq-why-do-bilinear-interpolation-and-cubic-convolution-000003271>
|
||
- **GDAL 官方**:正确做法是把 masked NoData 的 "**weights of contributing source pixels are set to zero to ignore them**" / "will not be used in interpolation"。<https://gdal.org/en/stable/programs/gdalwarp.html>
|
||
- **rasterio**:bilinear 重采样在 NoData 边界产生 invalid 值(已知 issue #1721)。<https://github.com/rasterio/rasterio/issues/1721>
|
||
- **Golden Software Surfer**(客户参照工具):NoData "**removed from the neighborhood**",不跨它插值。<https://surferhelp.goldensoftware.com/gridmisc/Blanked_Nodes_Grid_Filter.htm>(定义 <https://surferhelp.goldensoftware.com/glossary/def_blanking.htm>)
|
||
- **凸包外 = 外推**:"**Extrapolated data is usually meaningless and misleading.**" <https://github.com/fatiando/fatiando/pull/44>、<https://www.spatialanalysisonline.com/HTML/gridding_and_interpolation_met.htm>
|
||
|
||
**结论:二值 mask = 业界标准的"把 NoData 排除出插值"做法,是对的**;梯田只是 mask 在斜足迹边界上的网格离散观感(诚实、不误导)。**不应回退软消隐**(=让哨兵参与插值=以上权威明确反对的造假值做法)。
|
||
|
||
### 7.4 决策与待办(截至 2026-06-28,本分支未提交)
|
||
|
||
- ✅ **保留二值 mask**(数据诚实/合规,符合 ESRI/GDAL/Surfer 标准)。
|
||
- 梯田若要压平,走**不造假值**的路(二选一,**待用户/客户拍板**):
|
||
- (a) **细化 XY 网格**:`cellXY` 1m→0.5m,阶梯缩到亚像素;代价:体素×4、耗时 ~3.5s→~14s、内存×4。
|
||
- (b) **接受梯田**:它诚实、且明显是足迹边界,不会被当成地质体。
|
||
- 渲染侧本轮其它已落地修复(排查"分层/稠密"时做、确认非主因但保留):GPU 探测+CPU 回退(`setVolumeGpuSupported`)、细采样距离+`UseJittering`、`ScalarOpacityUnitDistance=对角/10`、去 `kMaxOpacity`(改由色阶「不透明度」单一控制、100%=实心)、移除工具条「透」滑块。
|
||
- **附带缺陷(待修)**:`VoxelGenerateRequest::maxDist` 结构体默认 `4.0`(`src/data/dto/Vtk3dRequests.hpp:18`)与对话框 `kDefMaxDist=0.0`(`VolumeParamsDialog.cpp:34`)不一致——绕过对话框直建会拿到 4m → 退回"四块板/线间空隙"老问题,应统一为 0。
|
||
- 抽稀空间哈希 `(ix*p1)^(iy*p2)^(iz*p3)`(`VolumeBuilder.cpp` ~146-151)为 XOR 非单射、有碰撞风险(与本症无关,但宜换 `(iz*ny+iy)*nx+ix` 线性键)。
|