# -*- coding: utf-8 -*-
"""
show_zgy_section.py
读取 ZGY(Schlumberger/OpenZGY)格式三维地震数据体,并显示
inline / crossline / 时间(深度)切片剖面。
依赖安装:
pip install openzgy numpy matplotlib
用法示例:
python show_zgy_section.py data.zgy # 打印数据体信息
python show_zgy_section.py data.zgy --inline 500 # 显示某条 inline 剖面
python show_zgy_section.py data.zgy --crossline 300 # 显示某条 crossline 剖面
python show_zgy_section.py data.zgy --timeslice 2000 # 显示某时间切片
python show_zgy_section.py data.zgy --inline 500 --cmap seismic --save inline500.png
"""
import argparse
import sys
import numpy as np
import matplotlib.pyplot as plt
def open_reader(zgy_file):
"""打开 ZGY 文件,返回 reader 对象(失败时给出中文提示)。"""
try:
from openzgy.api import ZgyReader
except ImportError:
sys.exit("缺少依赖库 openzgy,请先执行:pip install openzgy")
try:
return ZgyReader(zgy_file)
except Exception as e:
sys.exit(f"无法打开 ZGY 文件 {zgy_file}: {e}")
def _geometry(reader):
"""返回 (il_start, il_step, xl_start, xl_step, z_start, z_step, z_unit)。"""
il0, xl0 = reader.annotstart # 起始真实坐标 (inline, crossline)
dil, dxl = reader.annotinc # 步长
z0, dz, zunit = reader.zstart, reader.zinc, reader.zunitname or "ms"
return il0, dil, xl0, dxl, z0, dz, zunit
def print_info(reader):
"""打印数据体基本 geometry 信息。"""
il0, dil, xl0, dxl, z0, dz, zunit = _geometry(reader)
ni, nx, nz = reader.size
print("=" * 60)
print(f"数据体大小 (inline, crossline, samples): {ni} x {nx} x {nz}")
print(f"Inline 范围: {il0} ~ {il0 + (ni - 1) * dil} (step {dil})")
print(f"Crossline 范围: {xl0} ~ {xl0 + (nx - 1) * dxl} (step {dxl})")
print(f"Z 范围: {z0} ~ {z0 + (nz - 1) * dz} (step {dz}, 单位 {zunit})")
print("=" * 60)
def _real_to_index(real, start, step):
"""把真实道号/采样值换算成数组下标。"""
idx = int(round((real - start) / step))
return idx
def read_section(reader, kind, value):
"""
读取一个剖面,返回 (data, x轴标签, y轴标签)。
kind: 'inline' / 'crossline' / 'timeslice'
"""
il0, dil, xl0, dxl, z0, dz, zunit = _geometry(reader)
ni, nx, nz = reader.size
if kind == "inline":
idx = _real_to_index(value, il0, dil)
if not (0 <= idx < ni):
sys.exit(f"Inline {value} 超出范围 (数组下标 {idx},共 {ni} 条)")
# read(start, data): start 为起点下标,data 为预先分配的 float32 缓冲
buf = np.zeros((1, nx, nz), dtype=np.float32)
reader.read((idx, 0, 0), buf)
data = buf[0, :, :].T # (nz, nx)
x = xl0 + np.arange(nx) * dxl
y = z0 + np.arange(nz) * dz
return data, x, y, f"Inline {value}"
if kind == "crossline":
idx = _real_to_index(value, xl0, dxl)
if not (0 <= idx < nx):
sys.exit(f"Crossline {value} 超出范围 (数组下标 {idx},共 {nx} 条)")
buf = np.zeros((ni, 1, nz), dtype=np.float32)
reader.read((0, idx, 0), buf)
data = buf[:, 0, :].T # (nz, ni)
x = il0 + np.arange(ni) * dil
y = z0 + np.arange(nz) * dz
return data, x, y, f"Crossline {value}"
if kind == "timeslice":
idx = _real_to_index(value, z0, dz)
if not (0 <= idx < nz):
sys.exit(f"时间切片 {value} 超出范围 (数组下标 {idx},共 {nz} 个采样)")
buf = np.zeros((ni, nx, 1), dtype=np.float32)
reader.read((0, 0, idx), buf)
data = buf[:, :, 0] # (ni, nx)
x = xl0 + np.arange(nx) * dxl
y = il0 + np.arange(ni) * dil
return data, x, y, f"Time slice {value} {zunit}"
sys.exit(f"未知剖面类型: {kind}")
def plot_section(data, x, y, title, xlabel, ylabel, cmap, save=None, clip=99):
"""绘制二维剖面:imshow + 色标;clip 为振幅截断百分位,压制异常值。"""
vmax = np.percentile(np.abs(data[np.isfinite(data)]), clip) if np.isfinite(data).any() else 1
vmax = vmax if vmax > 0 else 1.0
fig, ax = plt.subplots(figsize=(12, 6))
extent = [x[0], x[-1], y[-1], y[0]] # y 轴默认上小下大(深度向下)
im = ax.imshow(data, cmap=cmap, aspect="auto", extent=extent,
vmin=-vmax, vmax=vmax, interpolation="bilinear")
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
fig.colorbar(im, ax=ax, label="Amplitude")
fig.tight_layout()
if save:
fig.savefig(save, dpi=200)
print(f"剖面已保存到: {save}")
else:
plt.show()
def main():
p = argparse.ArgumentParser(description="显示 ZGY 地震体剖面")
p.add_argument("zgy_file", help="ZGY 文件路径")
p.add_argument("--inline", type=float, default=None, help="要显示的 inline 号")
p.add_argument("--crossline", type=float, default=None, help="要显示的 crossline 号")
p.add_argument("--timeslice", type=float, default=None, help="要显示的时间/深度切片值")
p.add_argument("--cmap", default="seismic", help="colormap,默认 seismic")
p.add_argument("--save", default=None, help="保存为图片文件(如 out.png),不填则弹出窗口")
args = p.parse_args()
reader = open_reader(args.zgy_file)
print_info(reader)
_, _, _, _, _, _, zunit = _geometry(reader)
sections = [
("inline", args.inline, "Crossline", zunit),
("crossline", args.crossline, "Inline", zunit),
("timeslice", args.timeslice, "Crossline", "Inline"),
]
shown = False
n_sections = sum(1 for _, v, _, _ in sections if v is not None)
for kind, value, xlab, ylab in sections:
if value is None:
continue
data, x, y, title = read_section(reader, kind, value)
# 多个剖面同时保存时,自动在文件名中加剖面标识避免互相覆盖
save = args.save
if save and n_sections > 1:
base, dot, ext = save.rpartition(".")
base = base or save
ext = dot + ext if dot else ""
save = f"{base}_{kind}{int(value)}{ext}"
plot_section(data, x, y, title, xlab, ylab, args.cmap, save, clip=99)
shown = True
if not shown:
print("未指定剖面:使用 --inline / --crossline / --timeslice 之一来显示剖面。")
print("提示:可先只传文件名查看数据体范围,再选取合适的剖面号。")
if __name__ == "__main__":
main()