geopro/tools/validate_voxel.py

122 lines
5.0 KiB
Python
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.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dd_voxel 离线验证 / 渲染基准:把两条交叉剖面(剖面原数据1+2)三维空间插值(IDW + maxDist 裁剪)
成体素,切片可视化。用于:
1) 提前验证 IDW + 包络裁剪算法(与 Phase 1 core/algo/IdwInterpolator 同逻辑);
2) 把"可信体"问题看实——两条 1D 测线只在其所在竖直面附近有约束,体内大部分靠插值/留空;
3) 产出 VTK 体绘制/切片的地面真值对照图。
每个测点三维坐标 = (projX, projY, z=ylist[高程]),值 = vlist。
运行: python tools/validate_voxel.py 输出: docs/_validate/voxel_*.png
依赖: numpy, scipy, matplotlib
"""
import json
import os
import sys
import numpy as np
from scipy.spatial import cKDTree
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
DATA = os.path.join(os.path.dirname(__file__), "..", "docs", "剖面网格数据的色阶数据2等文件")
OUT = os.path.join(os.path.dirname(__file__), "..", "docs", "_validate")
POWER = 2.0 # IDW 幂
K = 8 # 取最近 K 个点
MAX_DIST = 4.0 # 超过此距离(米)→ 留空(NaN),约束插值域(设计 §10)
DXY = 1.0 # 水平网格步长(米)
DZ = 0.5 # 垂向步长(米)
def load_points(name):
d = json.load(open(os.path.join(DATA, name), encoding="utf-8"))["data"]
px = np.asarray(d["projectXList"], float)
py = np.asarray(d["projectYList"], float)
z = np.asarray(d["ylist"], float) # 高程
v = np.asarray(d["vlist"], float)
return np.column_stack([px, py, z]), v
def idw_voxel(pts, vals, spec):
ox, oy, oz, nx, ny, nz, dx, dy, dz = spec
tree = cKDTree(pts)
# 网格中心点
gx = ox + (np.arange(nx) + 0.5) * dx
gy = oy + (np.arange(ny) + 0.5) * dy
gz = oz + (np.arange(nz) + 0.5) * dz
GX, GY, GZ = np.meshgrid(gx, gy, gz, indexing="ij")
Q = np.column_stack([GX.ravel(), GY.ravel(), GZ.ravel()])
dist, idx = tree.query(Q, k=K)
dist = np.maximum(dist, 1e-9)
w = 1.0 / dist ** POWER
vol = (w * vals[idx]).sum(1) / w.sum(1)
vol[dist[:, 0] > MAX_DIST] = np.nan # 最近点都超距 → 留空
return vol.reshape(nx, ny, nz), (gx, gy, gz)
def main():
os.makedirs(OUT, exist_ok=True)
p1, v1 = load_points("剖面原数据1.txt")
p2, v2 = load_points("剖面原数据2.txt")
pts = np.vstack([p1, p2])
vals = np.concatenate([v1, v2])
# 局部化(减最小,规避大坐标;同 LocalFrame 思路)
origin = pts.min(0)
ptsL = pts - origin
ext = ptsL.max(0)
nx = int(ext[0] / DXY) + 1
ny = int(ext[1] / DXY) + 1
nz = int(ext[2] / DZ) + 1
spec = (0, 0, 0, nx, ny, nz, DXY, DXY, DZ)
print(f"points={len(vals)} grid={nx}x{ny}x{nz}={nx*ny*nz} cells "
f"horiz_extent={ext[0]:.1f}x{ext[1]:.1f}m depth={ext[2]:.1f}m")
vol, (gx, gy, gz) = idw_voxel(ptsL, vals, spec)
filled = np.isfinite(vol).sum()
print(f"filled cells={filled} ({100*filled/vol.size:.1f}%) "
f"-> 其余为留空(两测线包络外无约束) | v[{np.nanmin(vol):.0f},{np.nanmax(vol):.0f}]")
vmin, vmax = np.nanpercentile(vol, [2, 98])
# 1) 中深度水平切片:应显示两条交叉的带(十字支撑)
kz = nz // 2
plt.figure(figsize=(6, 5))
plt.imshow(vol[:, :, kz].T, origin="lower", extent=[0, ext[0], 0, ext[1]],
cmap="turbo", vmin=vmin, vmax=vmax)
plt.scatter(ptsL[:, 0], ptsL[:, 1], s=1, c="k", alpha=0.15)
plt.title(f"voxel 水平切片 z={gz[kz]:.1f} (应见交叉十字支撑)")
plt.xlabel("E(m)"); plt.ylabel("N(m)"); plt.colorbar(shrink=.8)
plt.tight_layout(); plt.savefig(f"{OUT}/voxel_hslice.png", dpi=90); plt.close()
# 2) 沿 E 的垂直切片(取 profile1 附近 N应重现剖面竖直结构
jy = ny // 2
plt.figure(figsize=(8, 3))
plt.imshow(vol[:, jy, :].T, origin="lower", extent=[0, ext[0], 0, ext[2]],
cmap="turbo", vmin=vmin, vmax=vmax, aspect="auto")
plt.title(f"voxel 垂直切片 N={gy[jy]:.1f}")
plt.xlabel("E(m)"); plt.ylabel("elev(m)"); plt.colorbar(shrink=.8)
plt.tight_layout(); plt.savefig(f"{OUT}/voxel_vslice.png", dpi=90); plt.close()
# 3) 非留空区的三维散点(直观看"十字片"支撑)
xi, yi, zi = np.where(np.isfinite(vol))
sub = slice(None, None, max(1, len(xi) // 6000))
fig = plt.figure(figsize=(7, 5)); ax = fig.add_subplot(111, projection="3d")
sc = ax.scatter(gx[xi[sub]], gy[yi[sub]], gz[zi[sub]],
c=vol[xi[sub], yi[sub], zi[sub]], cmap="turbo",
vmin=vmin, vmax=vmax, s=3)
ax.set_title("voxel 非留空区(两测线竖直面附近=十字片)")
ax.set_xlabel("E"); ax.set_ylabel("N"); ax.set_zlabel("elev")
fig.colorbar(sc, shrink=.6)
plt.tight_layout(); plt.savefig(f"{OUT}/voxel_support.png", dpi=90); plt.close()
print("written:", sorted(f for f in os.listdir(OUT) if f.startswith("voxel")))
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()