zgy地震数据体的显示

python 复制代码
# -*- 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()
相关推荐
happylifetree19 小时前
Python70:核心语法-函数进阶-案例2
python
王中阳Go20 小时前
两个入口答案不一致先别调模型:6 行入口口径对账表(附脚本)
人工智能·后端·python·大语言模型·1024程序员节
BK贝壳20 小时前
bool-hybrid-array 落盘我只认 .bha:用 Create_BHA / Ask_BHA 做布尔数组序列化
数据结构·python·numpy·1024程序员节
Sakura_j_h_20 小时前
Python 在定时任务里输出 emoji,UnicodeEncodeError 从哪来
开发语言·字符编码·windows·python·定时任务
supabc1231 天前
欧盟电子发票(XRechnung/Peppol/ZUGFeRD)踩坑记:用 Python 不装 Java 跑官方校验规则
xml·python·开源·电子发票·1024程序员节
ZhengEnCi1 天前
AI Agent工具调用防护层怎么写-一份能直接跑的Python四层拦截实现
python·llm·agent
ZhengEnCi1 天前
AI Agent工具误调用怎么解决-从参数校验到高危二次确认的四层工程防线
python·llm·agent
镜子AI1 天前
镜子AI工程教程:用Python实现关键词布局检查器与语义改写查重(附完整代码)
人工智能·python·microsoft
小oo呆1 天前
【学习心得】Python协程
python
信誓旦旦的程序猿1 天前
【Python量化实战宝典 #22】涨跌停、连板情绪手册:情绪指标构建(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口